blob: 06d478de1503c8b112e7b48cdbb1300c25598ea4 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/webui/signin/sync_confirmation_handler.h"
#include <vector>
#include "base/bind.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/consent_auditor/consent_auditor_factory.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/signin/account_tracker_service_factory.h"
#include "chrome/browser/signin/signin_manager_factory.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/signin_view_controller_delegate.h"
#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
#include "chrome/browser/ui/webui/signin/signin_utils.h"
#include "chrome/browser/ui/webui/signin/sync_confirmation_ui.h"
#include "chrome/common/webui_url_constants.h"
#include "components/consent_auditor/consent_auditor.h"
#include "components/signin/core/browser/account_tracker_service.h"
#include "components/signin/core/browser/avatar_icon_util.h"
#include "components/signin/core/browser/signin_manager.h"
#include "components/unified_consent/feature.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "url/gurl.h"
namespace {
// Used for UMA. Do not reorder, append new values at the end.
enum class UnifiedConsentBumpAction {
kOptIn = 0,
kMoreOptionsOptIn = 1,
kMoreOptionsSettings = 2,
kMoreOptionsNoChanges = 3,
kAbort = 4,
kMaxValue = kAbort
};
} // namespace
const int kProfileImageSize = 128;
SyncConfirmationHandler::SyncConfirmationHandler(
Browser* browser,
const std::unordered_map<std::string, int>& string_to_grd_id_map,
consent_auditor::Feature consent_feature)
: profile_(browser->profile()),
browser_(browser),
did_user_explicitly_interact(false),
string_to_grd_id_map_(string_to_grd_id_map),
consent_feature_(consent_feature) {
DCHECK(profile_);
DCHECK(browser_);
BrowserList::AddObserver(this);
}
SyncConfirmationHandler::~SyncConfirmationHandler() {
BrowserList::RemoveObserver(this);
AccountTrackerServiceFactory::GetForProfile(profile_)->RemoveObserver(this);
// Abort signin and prevent sync from starting if none of the actions on the
// sync confirmation dialog are taken by the user.
if (!did_user_explicitly_interact) {
HandleUndo(nullptr);
if (IsUnifiedConsentBumpDialog()) {
UMA_HISTOGRAM_ENUMERATION("UnifiedConsent.ConsentBump.Action",
UnifiedConsentBumpAction::kAbort);
} else {
base::RecordAction(base::UserMetricsAction("Signin_Abort_Signin"));
}
}
}
void SyncConfirmationHandler::OnBrowserRemoved(Browser* browser) {
if (browser_ == browser)
browser_ = nullptr;
}
void SyncConfirmationHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"confirm", base::BindRepeating(&SyncConfirmationHandler::HandleConfirm,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"undo", base::BindRepeating(&SyncConfirmationHandler::HandleUndo,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"goToSettings",
base::BindRepeating(&SyncConfirmationHandler::HandleGoToSettings,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"initializedWithSize",
base::BindRepeating(&SyncConfirmationHandler::HandleInitializedWithSize,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"accountImageRequest",
base::BindRepeating(&SyncConfirmationHandler::HandleAccountImageRequest,
base::Unretained(this)));
}
void SyncConfirmationHandler::HandleConfirm(const base::ListValue* args) {
did_user_explicitly_interact = true;
RecordConsent(args);
CloseModalSigninWindow(LoginUIService::SYNC_WITH_DEFAULT_SETTINGS);
}
void SyncConfirmationHandler::HandleGoToSettings(const base::ListValue* args) {
did_user_explicitly_interact = true;
RecordConsent(args);
CloseModalSigninWindow(LoginUIService::CONFIGURE_SYNC_FIRST);
}
void SyncConfirmationHandler::HandleUndo(const base::ListValue* args) {
did_user_explicitly_interact = true;
CloseModalSigninWindow(LoginUIService::ABORT_SIGNIN);
}
void SyncConfirmationHandler::HandleAccountImageRequest(
const base::ListValue* args) {
std::string account_id = SigninManagerFactory::GetForProfile(profile_)
->GetAuthenticatedAccountId();
AccountInfo account_info =
AccountTrackerServiceFactory::GetForProfile(profile_)->GetAccountInfo(
account_id);
// Fire the "account-image-changed" listener from |SetUserImageURL()|.
// Note: If the account info is not available yet in the
// AccountTrackerService, i.e. account_info is empty, the listener will be
// fired again through |OnAccountUpdated()|.
SetUserImageURL(account_info.picture_url);
}
void SyncConfirmationHandler::RecordConsent(const base::ListValue* args) {
CHECK_EQ(2U, args->GetSize());
const std::vector<base::Value>& consent_description =
args->GetList()[0].GetList();
const std::string& consent_confirmation = args->GetList()[1].GetString();
std::vector<int> consent_text_ids;
// The strings returned by the WebUI are not free-form, they must belong into
// a pre-determined set of strings (stored in |string_to_grd_id_map_|). As
// this has privacy and legal implications, CHECK the integrity of the strings
// received from the renderer process before recording the consent.
for (const base::Value& text : consent_description) {
auto iter = string_to_grd_id_map_.find(text.GetString());
CHECK(iter != string_to_grd_id_map_.end()) << "Unexpected string:\n"
<< text.GetString();
consent_text_ids.push_back(iter->second);
}
auto iter = string_to_grd_id_map_.find(consent_confirmation);
CHECK(iter != string_to_grd_id_map_.end()) << "Unexpected string:\n"
<< consent_confirmation;
int consent_confirmation_id = iter->second;
consent_auditor::ConsentAuditor* consent_auditor =
ConsentAuditorFactory::GetForProfile(profile_);
const std::string& account_id = SigninManagerFactory::GetForProfile(profile_)
->GetAuthenticatedAccountId();
// TODO(markusheintz): Use a bool unified_consent_enabled instead of a
// consent_auditor::Feature type variable.
if (consent_feature_ == consent_auditor::Feature::CHROME_UNIFIED_CONSENT) {
sync_pb::UserConsentTypes::UnifiedConsent unified_consent;
unified_consent.set_confirmation_grd_id(consent_confirmation_id);
for (int id : consent_text_ids) {
unified_consent.add_description_grd_ids(id);
}
unified_consent.set_status(sync_pb::UserConsentTypes::ConsentStatus::
UserConsentTypes_ConsentStatus_GIVEN);
consent_auditor->RecordUnifiedConsent(account_id, unified_consent);
} else {
sync_pb::UserConsentTypes::SyncConsent sync_consent;
sync_consent.set_confirmation_grd_id(consent_confirmation_id);
for (int id : consent_text_ids) {
sync_consent.add_description_grd_ids(id);
}
sync_consent.set_status(sync_pb::UserConsentTypes::ConsentStatus::
UserConsentTypes_ConsentStatus_GIVEN);
consent_auditor->RecordSyncConsent(account_id, sync_consent);
}
}
void SyncConfirmationHandler::SetUserImageURL(const std::string& picture_url) {
std::string picture_url_to_load;
GURL picture_gurl(picture_url);
if (picture_gurl.is_valid()) {
picture_url_to_load =
signin::GetAvatarImageURLWithOptions(picture_gurl, kProfileImageSize,
false /* no_silhouette */)
.spec();
} else {
// Use the placeholder avatar icon until the account picture URL is fetched.
picture_url_to_load = profiles::GetPlaceholderAvatarIconUrl();
}
base::Value picture_url_value(picture_url_to_load);
web_ui()->CallJavascriptFunctionUnsafe("sync.confirmation.setUserImageURL",
picture_url_value);
if (unified_consent::IsUnifiedConsentFeatureEnabled()) {
AllowJavascript();
FireWebUIListener("account-image-changed", picture_url_value);
}
}
void SyncConfirmationHandler::OnAccountUpdated(const AccountInfo& info) {
if (!info.IsValid())
return;
SigninManager* signin_manager = SigninManagerFactory::GetForProfile(profile_);
if (info.account_id != signin_manager->GetAuthenticatedAccountId())
return;
AccountTrackerServiceFactory::GetForProfile(profile_)->RemoveObserver(this);
SetUserImageURL(info.picture_url);
}
void SyncConfirmationHandler::CloseModalSigninWindow(
LoginUIService::SyncConfirmationUIClosedResult result) {
if (!IsUnifiedConsentBumpDialog()) {
// Metrics for the unified consent bump are recorded directly from
// javascript.
switch (result) {
case LoginUIService::CONFIGURE_SYNC_FIRST:
base::RecordAction(
base::UserMetricsAction("Signin_Signin_WithAdvancedSyncSettings"));
break;
case LoginUIService::SYNC_WITH_DEFAULT_SETTINGS:
base::RecordAction(
base::UserMetricsAction("Signin_Signin_WithDefaultSyncSettings"));
break;
case LoginUIService::ABORT_SIGNIN:
base::RecordAction(base::UserMetricsAction("Signin_Undo_Signin"));
break;
}
}
LoginUIServiceFactory::GetForProfile(profile_)->SyncConfirmationUIClosed(
result);
if (browser_)
browser_->signin_view_controller()->CloseModalSignin();
}
void SyncConfirmationHandler::HandleInitializedWithSize(
const base::ListValue* args) {
if (!browser_)
return;
std::string account_id = SigninManagerFactory::GetForProfile(profile_)
->GetAuthenticatedAccountId();
if (account_id.empty()) {
// No account is signed in, so there is nothing to be displayed in the sync
// confirmation dialog.
return;
}
AccountTrackerService* account_tracker =
AccountTrackerServiceFactory::GetForProfile(profile_);
AccountInfo account_info = account_tracker->GetAccountInfo(account_id);
if (!account_info.IsValid()) {
SetUserImageURL(AccountTrackerService::kNoPictureURLFound);
account_tracker->AddObserver(this);
} else {
SetUserImageURL(account_info.picture_url);
}
signin::SetInitializedModalHeight(browser_, web_ui(), args);
// After the dialog is shown, some platforms might have an element focused.
// To be consistent, clear the focused element on all platforms.
// TODO(anthonyvd): Figure out why this is needed on Mac and not other
// platforms and if there's a way to start unfocused while avoiding this
// workaround.
web_ui()->CallJavascriptFunctionUnsafe("sync.confirmation.clearFocus");
}
bool SyncConfirmationHandler::IsUnifiedConsentBumpDialog() {
return web_ui()->GetWebContents()->GetVisibleURL() ==
chrome::kChromeUISyncConsentBumpURL;
}