blob: 3559fd59e6d214a9acf557ddfa167c1c0a29fa67 [file] [log] [blame]
// Copyright 2020 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/profile_picker_handler.h"
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/containers/cxx20_erase.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/util/values/values_util.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_statistics.h"
#include "chrome/browser/profiles/profile_statistics_factory.h"
#include "chrome/browser/profiles/profile_window.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/search/chrome_colors/chrome_colors_service.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/themes/theme_properties.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/browser/ui/profile_picker.h"
#include "chrome/browser/ui/signin/profile_colors_util.h"
#include "chrome/browser/ui/singleton_tabs.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/webui/profile_helper.h"
#include "chrome/browser/ui/webui/signin/login_ui_service.h"
#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
#include "chrome/browser/ui/webui/theme_source.h"
#include "chrome/browser/ui/webui/webui_util.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/search/generated_colors_info.h"
#include "chrome/common/themes/autogenerated_theme_util.h"
#include "chrome/common/webui_url_constants.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/startup_metric_utils/browser/startup_metric_utils.h"
#include "content/public/browser/url_data_source.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/image/image.h"
namespace {
const size_t kProfileCardAvatarSize = 74;
const size_t kProfileCreationAvatarSize = 100;
constexpr int kDefaultThemeColorId = -1;
constexpr int kManuallyPickedColorId = 0;
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class ProfilePickerAction {
kLaunchExistingProfile = 0,
kLaunchExistingProfileCustomizeSettings = 1,
kLaunchGuestProfile = 2,
kLaunchNewProfile = 3,
kDeleteProfile = 4,
kMaxValue = kDeleteProfile,
};
absl::optional<SkColor> GetChromeColorColorById(int color_id) {
for (chrome_colors::ColorInfo color_info :
chrome_colors::kGeneratedColorsInfo) {
if (color_id == color_info.id)
return color_info.color;
}
return absl::nullopt;
}
void RecordProfilePickerAction(ProfilePickerAction action) {
base::UmaHistogramEnumeration("ProfilePicker.UserAction", action);
}
void RecordAskOnStartupChanged(bool value) {
base::UmaHistogramBoolean("ProfilePicker.AskOnStartupChanged", value);
}
void RecordNewProfileSpec(absl::optional<SkColor> profile_color,
bool create_shortcut) {
int theme_id =
profile_color.has_value()
? chrome_colors::ChromeColorsService::GetColorId(*profile_color)
: chrome_colors::kDefaultColorId;
base::UmaHistogramSparse("ProfilePicker.NewProfileTheme", theme_id);
if (ProfileShortcutManager::IsFeatureEnabled()) {
base::UmaHistogramBoolean("ProfilePicker.NewProfileCreateShortcut",
create_shortcut);
}
}
base::Value GetAutogeneratedProfileThemeInfoValue(int color_id,
absl::optional<SkColor> color,
SkColor frame_color,
SkColor active_tab_color,
SkColor frame_text_color,
int avatar_icon_size) {
base::Value dict(base::Value::Type::DICTIONARY);
dict.SetIntKey("colorId", color_id);
if (color.has_value())
dict.SetIntKey("color", *color);
dict.SetStringKey("themeFrameColor",
color_utils::SkColorToRgbaString(frame_color));
dict.SetStringKey("themeShapeColor",
color_utils::SkColorToRgbaString(active_tab_color));
dict.SetStringKey("themeFrameTextColor",
color_utils::SkColorToRgbaString(frame_text_color));
gfx::Image icon = profiles::GetPlaceholderAvatarIconWithColors(
/*fill_color=*/frame_color,
/*stroke_color=*/GetAvatarStrokeColor(frame_color), avatar_icon_size);
dict.SetStringKey("themeGenericAvatar",
webui::GetBitmapDataUrl(icon.AsBitmap()));
return dict;
}
base::Value CreateDefaultProfileThemeInfo(int avatar_icon_size,
bool dark_mode) {
SkColor frame_color = ThemeProperties::GetDefaultColor(
ThemeProperties::COLOR_FRAME_ACTIVE, /*incognito=*/false, dark_mode);
SkColor active_tab_color = ThemeProperties::GetDefaultColor(
ThemeProperties::COLOR_TOOLBAR, /*incognito=*/false, dark_mode);
SkColor frame_text_color = ThemeProperties::GetDefaultColor(
ThemeProperties::COLOR_TAB_FOREGROUND_INACTIVE_FRAME_ACTIVE,
/*incognito=*/false, dark_mode);
return GetAutogeneratedProfileThemeInfoValue(
kDefaultThemeColorId, absl::nullopt, frame_color, active_tab_color,
frame_text_color, avatar_icon_size);
}
base::Value CreateAutogeneratedProfileThemeInfo(int color_id,
SkColor color,
int avatar_icon_size) {
auto theme_colors = GetAutogeneratedThemeColors(color);
SkColor frame_color = theme_colors.frame_color;
SkColor active_tab_color = theme_colors.active_tab_color;
SkColor frame_text_color = theme_colors.frame_text_color;
return GetAutogeneratedProfileThemeInfoValue(
color_id, color, frame_color, active_tab_color, frame_text_color,
avatar_icon_size);
}
void OpenOnSelectProfileTargetUrl(Browser* browser) {
GURL target_page_url = ProfilePicker::GetOnSelectProfileTargetUrl();
if (target_page_url.is_empty())
return;
if (target_page_url.spec() == chrome::kChromeUIHelpURL) {
chrome::ShowAboutChrome(browser);
} else if (target_page_url.spec() == chrome::kChromeUISettingsURL) {
chrome::ShowSettings(browser);
} else if (target_page_url.spec() == ProfilePicker::kTaskManagerUrl) {
chrome::OpenTaskManager(browser);
} else {
NavigateParams params(
GetSingletonTabNavigateParams(browser, target_page_url));
params.path_behavior = NavigateParams::RESPECT;
ShowSingletonTabOverwritingNTP(browser, &params);
}
}
base::Value CreateProfileEntry(const ProfileAttributesEntry* entry,
int avatar_icon_size) {
base::Value profile_entry(base::Value::Type::DICTIONARY);
profile_entry.SetKey("profilePath", util::FilePathToValue(entry->GetPath()));
profile_entry.SetStringKey("localProfileName", entry->GetLocalProfileName());
profile_entry.SetBoolKey(
"isSyncing", entry->GetSigninState() ==
SigninState::kSignedInWithConsentedPrimaryAccount);
profile_entry.SetBoolKey("needsSignin", entry->IsSigninRequired());
// GAIA full name/user name can be empty, if the profile is not signed in to
// chrome.
profile_entry.SetStringKey("gaiaName", entry->GetGAIAName());
profile_entry.SetStringKey("userName", entry->GetUserName());
profile_entry.SetBoolKey("isManaged",
AccountInfo::IsManaged(entry->GetHostedDomain()));
gfx::Image icon =
profiles::GetSizedAvatarIcon(entry->GetAvatarIcon(avatar_icon_size), true,
avatar_icon_size, avatar_icon_size);
std::string icon_url = webui::GetBitmapDataUrl(icon.AsBitmap());
profile_entry.SetStringKey("avatarIcon", icon_url);
return profile_entry;
}
#if BUILDFLAG(IS_CHROMEOS_LACROS)
Profile* FindPrimaryProfile() {
const auto profiles =
g_browser_process->profile_manager()->GetLoadedProfiles();
const auto primary_profile_iter = std::find_if(
profiles.cbegin(), profiles.cend(),
[](const Profile* const profile) { return profile->IsMainProfile(); });
if (primary_profile_iter == profiles.cend()) {
return nullptr;
}
return *primary_profile_iter;
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
} // namespace
ProfilePickerHandler::ProfilePickerHandler() = default;
ProfilePickerHandler::~ProfilePickerHandler() {
OnJavascriptDisallowed();
}
void ProfilePickerHandler::EnableStartupMetrics() {
DCHECK(creation_time_on_startup_.is_null());
content::WebContents* contents = web_ui()->GetWebContents();
if (contents->GetVisibility() == content::Visibility::VISIBLE) {
// Only record paint event if the window is visible.
creation_time_on_startup_ = base::TimeTicks::Now();
Observe(web_ui()->GetWebContents());
}
}
void ProfilePickerHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback(
"mainViewInitialize",
base::BindRepeating(&ProfilePickerHandler::HandleMainViewInitialize,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"launchSelectedProfile",
base::BindRepeating(&ProfilePickerHandler::HandleLaunchSelectedProfile,
base::Unretained(this), /*open_settings=*/false));
web_ui()->RegisterMessageCallback(
"openManageProfileSettingsSubPage",
base::BindRepeating(&ProfilePickerHandler::HandleLaunchSelectedProfile,
base::Unretained(this), /*open_settings=*/true));
web_ui()->RegisterMessageCallback(
"launchGuestProfile",
base::BindRepeating(&ProfilePickerHandler::HandleLaunchGuestProfile,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"askOnStartupChanged",
base::BindRepeating(&ProfilePickerHandler::HandleAskOnStartupChanged,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getNewProfileSuggestedThemeInfo",
base::BindRepeating(
&ProfilePickerHandler::HandleGetNewProfileSuggestedThemeInfo,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getProfileThemeInfo",
base::BindRepeating(&ProfilePickerHandler::HandleGetProfileThemeInfo,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"removeProfile",
base::BindRepeating(&ProfilePickerHandler::HandleRemoveProfile,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getProfileStatistics",
base::BindRepeating(&ProfilePickerHandler::HandleGetProfileStatistics,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"loadSignInProfileCreationFlow",
base::BindRepeating(
&ProfilePickerHandler::HandleLoadSignInProfileCreationFlow,
base::Unretained(this)));
// TODO(crbug.com/1115056): Consider renaming this message to
// 'createLocalProfile' as this is only used for local profiles.
web_ui()->RegisterMessageCallback(
"getAvailableIcons",
base::BindRepeating(&ProfilePickerHandler::HandleGetAvailableIcons,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"createProfile",
base::BindRepeating(&ProfilePickerHandler::HandleCreateProfile,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"getSwitchProfile",
base::BindRepeating(&ProfilePickerHandler::HandleGetSwitchProfile,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"confirmProfileSwitch",
base::BindRepeating(&ProfilePickerHandler::HandleConfirmProfileSwitch,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"cancelProfileSwitch",
base::BindRepeating(&ProfilePickerHandler::HandleCancelProfileSwitch,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"setProfileName",
base::BindRepeating(&ProfilePickerHandler::HandleSetProfileName,
base::Unretained(this)));
web_ui()->RegisterMessageCallback(
"recordSignInPromoImpression",
base::BindRepeating(
&ProfilePickerHandler::HandleRecordSignInPromoImpression,
base::Unretained(this)));
Profile* profile = Profile::FromWebUI(web_ui());
content::URLDataSource::Add(profile, std::make_unique<ThemeSource>(profile));
}
void ProfilePickerHandler::OnJavascriptAllowed() {
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.AddObserver(this);
}
void ProfilePickerHandler::OnJavascriptDisallowed() {
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.RemoveObserver(this);
weak_factory_.InvalidateWeakPtrs();
}
void ProfilePickerHandler::HandleMainViewInitialize(
const base::ListValue* args) {
if (!creation_time_on_startup_.is_null() && !main_view_initialized_) {
// This function can be called multiple times if the page is reloaded. The
// histogram is only recorded once.
main_view_initialized_ = true;
base::UmaHistogramTimes("ProfilePicker.StartupTime.MainViewInitialized",
base::TimeTicks::Now() - creation_time_on_startup_);
}
AllowJavascript();
PushProfilesList();
}
void ProfilePickerHandler::HandleLaunchSelectedProfile(
bool open_settings,
const base::ListValue* args) {
const base::Value* profile_path_value = nullptr;
if (!args->Get(0, &profile_path_value))
return;
absl::optional<base::FilePath> profile_path =
util::ValueToFilePath(*profile_path_value);
if (!profile_path)
return;
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(*profile_path);
if (!entry) {
NOTREACHED();
return;
}
if (entry->IsSigninRequired()) {
DCHECK(signin_util::IsForceSigninEnabled());
if (entry->IsAuthenticated() &&
base::FeatureList::IsEnabled(features::kForceSignInReauth)) {
ProfilePickerForceSigninDialog::ShowReauthDialog(
web_ui()->GetWebContents()->GetBrowserContext(),
base::UTF16ToUTF8(entry->GetUserName()), *profile_path);
} else if (entry->GetActiveTime() != base::Time()) {
// If force-sign-in is enabled, do not allow users to sign in to a
// pre-existing locked profile, as this may force unexpected profile data
// merge. We consider a profile as pre-existing if it has been actived
// previously. A pre-existed profile can still be used if it has been
// signed in with an email address matched RestrictSigninToPattern policy
// already.
LoginUIServiceFactory::GetForProfile(
Profile::FromWebUI(web_ui())->GetOriginalProfile())
->SetProfileBlockingErrorMessage();
ProfilePickerForceSigninDialog::ShowDialogAndDisplayErrorMessage(
web_ui()->GetWebContents()->GetBrowserContext());
} else {
// Fresh sign in via profile picker without existing email address.
ProfilePickerForceSigninDialog::ShowForceSigninDialog(
web_ui()->GetWebContents()->GetBrowserContext(), *profile_path);
}
return;
}
#if BUILDFLAG(IS_CHROMEOS_LACROS)
if (!profiles::AreSecondaryProfilesAllowed()) {
Profile* primary_profile = FindPrimaryProfile();
if (primary_profile && primary_profile->GetPath() != *profile_path) {
LoginUIServiceFactory::GetForProfile(
Profile::FromWebUI(web_ui())->GetOriginalProfile())
->SetProfileBlockingErrorMessage();
ProfilePickerForceSigninDialog::ShowDialogAndDisplayErrorMessage(
web_ui()->GetWebContents()->GetBrowserContext());
return;
}
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
profiles::SwitchToProfile(
*profile_path, /*always_create=*/false,
base::BindRepeating(&ProfilePickerHandler::OnSwitchToProfileComplete,
weak_factory_.GetWeakPtr(), false, open_settings));
}
void ProfilePickerHandler::HandleLaunchGuestProfile(
const base::ListValue* args) {
// TODO(crbug.com/1063856): Add check |IsGuestModeEnabled| once policy
// checking has been added to the UI.
profiles::SwitchToGuestProfile(
base::BindRepeating(&ProfilePickerHandler::OnSwitchToProfileComplete,
weak_factory_.GetWeakPtr(), false, false));
}
void ProfilePickerHandler::HandleAskOnStartupChanged(
const base::ListValue* args) {
bool show_on_startup;
if (!args->GetBoolean(0, &show_on_startup))
return;
PrefService* prefs = g_browser_process->local_state();
prefs->SetBoolean(prefs::kBrowserShowProfilePickerOnStartup, show_on_startup);
RecordAskOnStartupChanged(show_on_startup);
}
void ProfilePickerHandler::HandleGetNewProfileSuggestedThemeInfo(
const base::ListValue* args) {
AllowJavascript();
CHECK_EQ(1U, args->GetSize());
const base::Value& callback_id = args->GetList()[0];
chrome_colors::ColorInfo color_info = GenerateNewProfileColor();
int avatar_icon_size =
kProfileCreationAvatarSize * web_ui()->GetDeviceScaleFactor();
base::Value dict = CreateAutogeneratedProfileThemeInfo(
color_info.id, color_info.color, avatar_icon_size);
ResolveJavascriptCallback(callback_id, std::move(dict));
}
void ProfilePickerHandler::HandleGetProfileThemeInfo(
const base::ListValue* args) {
AllowJavascript();
CHECK_EQ(2U, args->GetList().size());
const base::Value& callback_id = args->GetList()[0];
const base::Value& user_theme_choice = args->GetList()[1];
int color_id = user_theme_choice.FindIntKey("colorId").value();
absl::optional<SkColor> color = user_theme_choice.FindDoubleKey("color");
int avatar_icon_size =
kProfileCreationAvatarSize * web_ui()->GetDeviceScaleFactor();
base::Value dict;
switch (color_id) {
case kDefaultThemeColorId:
dict = CreateDefaultProfileThemeInfo(
avatar_icon_size, webui::GetNativeTheme(web_ui()->GetWebContents())
->ShouldUseDarkColors());
break;
case kManuallyPickedColorId:
dict = CreateAutogeneratedProfileThemeInfo(color_id, *color,
avatar_icon_size);
break;
default:
dict = CreateAutogeneratedProfileThemeInfo(
color_id, *GetChromeColorColorById(color_id), avatar_icon_size);
break;
}
ResolveJavascriptCallback(callback_id, std::move(dict));
}
void ProfilePickerHandler::HandleGetAvailableIcons(
const base::ListValue* args) {
AllowJavascript();
CHECK_EQ(1U, args->GetSize());
const base::Value& callback_id = args->GetList()[0];
ResolveJavascriptCallback(
callback_id,
base::Value(profiles::GetCustomProfileAvatarIconsAndLabels()));
}
void ProfilePickerHandler::HandleCreateProfile(const base::ListValue* args) {
CHECK_EQ(4U, args->GetList().size());
std::u16string profile_name =
base::UTF8ToUTF16(args->GetList()[0].GetString());
// profileColor is undefined for the default theme.
absl::optional<SkColor> profile_color;
if (args->GetList()[1].is_int())
profile_color = args->GetList()[1].GetInt();
size_t avatar_index = args->GetList()[2].GetInt();
bool create_shortcut = args->GetList()[3].GetBool();
base::TrimWhitespace(profile_name, base::TRIM_ALL, &profile_name);
CHECK(!profile_name.empty());
#ifndef NDEBUG
DCHECK(profiles::IsDefaultAvatarIconIndex(avatar_index));
#endif
ProfileMetrics::LogProfileAddNewUser(
ProfileMetrics::ADD_NEW_PROFILE_PICKER_LOCAL);
ProfileManager::CreateMultiProfileAsync(
profile_name, avatar_index, /*is_hidden=*/false,
base::BindRepeating(&ProfilePickerHandler::OnProfileCreated,
weak_factory_.GetWeakPtr(), profile_color,
create_shortcut));
}
void ProfilePickerHandler::HandleGetSwitchProfile(const base::ListValue* args) {
AllowJavascript();
CHECK_EQ(1U, args->GetSize());
const base::Value& callback_id = args->GetList()[0];
int avatar_icon_size =
kProfileCardAvatarSize * web_ui()->GetDeviceScaleFactor();
base::FilePath profile_path = ProfilePicker::GetSwitchProfilePath();
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile_path);
CHECK(entry);
base::Value dict = CreateProfileEntry(entry, avatar_icon_size);
ResolveJavascriptCallback(callback_id, std::move(dict));
}
void ProfilePickerHandler::HandleConfirmProfileSwitch(
const base::ListValue* args) {
const base::Value* profile_path_value = nullptr;
if (!args->Get(0, &profile_path_value))
return;
absl::optional<base::FilePath> profile_path =
util::ValueToFilePath(*profile_path_value);
if (!profile_path)
return;
// TODO(https://crbug.com/1182206): remove the profile used for the sign-in
// flow.
profiles::SwitchToProfile(
*profile_path, /*always_create=*/false,
base::BindRepeating(&ProfilePickerHandler::OnSwitchToProfileComplete,
weak_factory_.GetWeakPtr(), false, false));
}
void ProfilePickerHandler::HandleCancelProfileSwitch(
const base::ListValue* args) {
ProfilePicker::CancelSignIn();
}
void ProfilePickerHandler::OnProfileCreated(
absl::optional<SkColor> profile_color,
bool create_shortcut,
Profile* profile,
Profile::CreateStatus status) {
switch (status) {
case Profile::CREATE_STATUS_LOCAL_FAIL: {
NOTREACHED() << "Local fail in creating new profile";
break;
}
case Profile::CREATE_STATUS_CREATED:
// Do nothing for an intermediate status.
return;
case Profile::CREATE_STATUS_INITIALIZED: {
OnProfileCreationSuccess(profile_color, create_shortcut, profile);
break;
}
// User-initiated cancellation is handled in CancelProfileRegistration and
// does not call this callback.
case Profile::CREATE_STATUS_CANCELED:
case Profile::CREATE_STATUS_REMOTE_FAIL:
case Profile::MAX_CREATE_STATUS: {
NOTREACHED();
break;
}
}
FireWebUIListener("create-profile-finished", base::Value());
}
void ProfilePickerHandler::OnProfileCreationSuccess(
absl::optional<SkColor> profile_color,
bool create_shortcut,
Profile* profile) {
DCHECK(profile);
DCHECK(!signin_util::IsForceSigninEnabled());
// Apply a new color to the profile or use the default theme.
auto* theme_service = ThemeServiceFactory::GetForProfile(profile);
if (profile_color.has_value())
theme_service->BuildAutogeneratedThemeFromColor(*profile_color);
else
theme_service->UseDefaultTheme();
// Create shortcut if needed.
if (create_shortcut) {
DCHECK(ProfileShortcutManager::IsFeatureEnabled());
ProfileShortcutManager* shortcut_manager =
g_browser_process->profile_manager()->profile_shortcut_manager();
DCHECK(shortcut_manager);
if (shortcut_manager)
shortcut_manager->CreateProfileShortcut(profile->GetPath());
}
if (base::FeatureList::IsEnabled(features::kSignInProfileCreation)) {
// Skip the FRE for this profile if sign-in was offered as part of the flow.
profile->GetPrefs()->SetBoolean(prefs::kHasSeenWelcomePage, true);
}
RecordNewProfileSpec(profile_color, create_shortcut);
// Launch profile and close the picker.
profiles::OpenBrowserWindowForProfile(
base::BindRepeating(&ProfilePickerHandler::OnSwitchToProfileComplete,
weak_factory_.GetWeakPtr(), true, false),
false, // Don't create a window if one already exists.
true, // Create a first run window.
false, // There is no need to unblock all extensions because we only open
// browser window if the Profile is not locked. Hence there is no
// extension blocked.
profile, Profile::CREATE_STATUS_INITIALIZED);
}
void ProfilePickerHandler::HandleRecordSignInPromoImpression(
const base::ListValue* /*args*/) {
signin_metrics::RecordSigninImpressionUserActionForAccessPoint(
signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER);
}
void ProfilePickerHandler::HandleSetProfileName(const base::ListValue* args) {
CHECK_EQ(2U, args->GetSize());
const base::Value& profile_path_value = args->GetList()[0];
absl::optional<base::FilePath> profile_path =
util::ValueToFilePath(profile_path_value);
if (!profile_path) {
NOTREACHED();
return;
}
std::u16string profile_name =
base::UTF8ToUTF16(args->GetList()[1].GetString());
base::TrimWhitespace(profile_name, base::TRIM_ALL, &profile_name);
CHECK(!profile_name.empty());
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile_path.value());
CHECK(entry);
entry->SetLocalProfileName(profile_name, /*is_default_name=*/false);
}
void ProfilePickerHandler::HandleRemoveProfile(const base::ListValue* args) {
CHECK_EQ(1U, args->GetSize());
const base::Value& profile_path_value = args->GetList()[0];
absl::optional<base::FilePath> profile_path =
util::ValueToFilePath(profile_path_value);
if (!profile_path) {
NOTREACHED();
return;
}
RecordProfilePickerAction(ProfilePickerAction::kDeleteProfile);
webui::DeleteProfileAtPath(*profile_path,
ProfileMetrics::DELETE_PROFILE_USER_MANAGER);
}
void ProfilePickerHandler::HandleGetProfileStatistics(
const base::ListValue* args) {
AllowJavascript();
CHECK_EQ(1U, args->GetSize());
const base::Value& profile_path_value = args->GetList()[0];
absl::optional<base::FilePath> profile_path =
util::ValueToFilePath(profile_path_value);
if (!profile_path)
return;
Profile* profile =
g_browser_process->profile_manager()->GetProfileByPath(*profile_path);
if (profile) {
GatherProfileStatistics(profile);
} else {
g_browser_process->profile_manager()->LoadProfileByPath(
*profile_path, false,
base::BindOnce(&ProfilePickerHandler::GatherProfileStatistics,
weak_factory_.GetWeakPtr()));
}
}
void ProfilePickerHandler::GatherProfileStatistics(Profile* profile) {
if (!profile) {
return;
}
ProfileStatisticsFactory::GetForProfile(profile)->GatherStatistics(
base::BindRepeating(&ProfilePickerHandler::OnProfileStatisticsReceived,
weak_factory_.GetWeakPtr(), profile->GetPath()));
}
void ProfilePickerHandler::OnProfileStatisticsReceived(
base::FilePath profile_path,
profiles::ProfileCategoryStats result) {
base::Value dict(base::Value::Type::DICTIONARY);
dict.SetKey("profilePath", util::FilePathToValue(profile_path));
base::Value stats(base::Value::Type::DICTIONARY);
// Categories are defined in |kProfileStatisticsCategories|
// {"BrowsingHistory", "Passwords", "Bookmarks", "Autofill"}.
for (const auto& item : result) {
stats.SetIntKey(item.category, item.count);
}
dict.SetKey("statistics", std::move(stats));
FireWebUIListener("profile-statistics-received", std::move(dict));
}
void ProfilePickerHandler::HandleLoadSignInProfileCreationFlow(
const base::ListValue* args) {
CHECK_EQ(1U, args->GetSize());
SkColor profile_color;
if (args->GetList()[0].is_int()) {
profile_color = args->GetList()[0].GetInt();
} else {
// The profile color must provided in `args` unless the force signin is
// enabled.
DCHECK(signin_util::IsForceSigninEnabled());
profile_color = GenerateNewProfileColor().color;
}
ProfilePicker::SwitchToSignIn(
profile_color, base::BindOnce(&ProfilePickerHandler::OnLoadSigninFinished,
weak_factory_.GetWeakPtr()));
}
void ProfilePickerHandler::OnLoadSigninFinished(bool success) {
FireWebUIListener("load-signin-finished", base::Value(success));
}
void ProfilePickerHandler::OnSwitchToProfileComplete(
bool new_profile,
bool open_settings,
Profile* profile,
Profile::CreateStatus profile_create_status) {
Browser* browser = chrome::FindAnyBrowser(profile, false);
DCHECK(browser);
DCHECK(browser->window());
// Only show the profile switch IPH when the user clicked the card, and there
// are multiple profiles.
std::vector<ProfileAttributesEntry*> entries =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetAllProfilesAttributes();
int profile_count = std::count_if(
entries.begin(), entries.end(),
[](ProfileAttributesEntry* entry) { return !entry->IsOmitted(); });
if (profile_count > 1 && !open_settings &&
ProfilePicker::GetOnSelectProfileTargetUrl().is_empty()) {
browser->window()->MaybeShowProfileSwitchIPH();
}
if (new_profile) {
RecordProfilePickerAction(ProfilePickerAction::kLaunchNewProfile);
ProfilePicker::Hide();
return;
}
if (profile->IsGuestSession()) {
RecordProfilePickerAction(ProfilePickerAction::kLaunchGuestProfile);
} else {
RecordProfilePickerAction(
open_settings
? ProfilePickerAction::kLaunchExistingProfileCustomizeSettings
: ProfilePickerAction::kLaunchExistingProfile);
}
if (open_settings) {
// User clicked 'Edit' from the profile card menu.
chrome::ShowSettingsSubPage(browser, chrome::kManageProfileSubPage);
} else {
// Opens a target url upon user selecting a pre-existing profile. For
// new profiles the chrome welcome page is displayed.
OpenOnSelectProfileTargetUrl(browser);
}
ProfilePicker::Hide();
}
void ProfilePickerHandler::PushProfilesList() {
DCHECK(IsJavascriptAllowed());
FireWebUIListener("profiles-list-changed", GetProfilesList());
}
void ProfilePickerHandler::SetProfilesOrder(
const std::vector<ProfileAttributesEntry*>& entries) {
profiles_order_.clear();
size_t index = 0;
for (const ProfileAttributesEntry* entry : entries) {
profiles_order_[entry->GetPath()] = index++;
}
}
std::vector<ProfileAttributesEntry*>
ProfilePickerHandler::GetProfileAttributes() {
std::vector<ProfileAttributesEntry*> ordered_entries =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetAllProfilesAttributesSortedByLocalProfilName();
base::EraseIf(ordered_entries, [](const ProfileAttributesEntry* entry) {
return entry->IsGuest() || entry->IsOmitted();
});
size_t number_of_profiles = ordered_entries.size();
if (profiles_order_.size() != number_of_profiles) {
// Should only happen the first time the function is called.
// Profile creation and deletion are handled at
// 'OnProfileAdded', 'OnProfileWasRemoved'.
DCHECK(!profiles_order_.size());
SetProfilesOrder(ordered_entries);
return ordered_entries;
}
// Vector of nullptr entries.
std::vector<ProfileAttributesEntry*> entries(number_of_profiles);
for (ProfileAttributesEntry* entry : ordered_entries) {
DCHECK(profiles_order_.find(entry->GetPath()) != profiles_order_.end());
size_t index = profiles_order_[entry->GetPath()];
DCHECK_LT(index, number_of_profiles);
DCHECK(!entries[index]);
entries[index] = entry;
}
return entries;
}
base::Value ProfilePickerHandler::GetProfilesList() {
base::Value profiles_list(base::Value::Type::LIST);
std::vector<ProfileAttributesEntry*> entries = GetProfileAttributes();
const int avatar_icon_size =
kProfileCardAvatarSize * web_ui()->GetDeviceScaleFactor();
for (const ProfileAttributesEntry* entry : entries) {
profiles_list.Append(CreateProfileEntry(entry, avatar_icon_size));
}
return profiles_list;
}
void ProfilePickerHandler::AddProfileToList(
const base::FilePath& profile_path) {
size_t number_of_profiles = profiles_order_.size();
auto it_and_whether_inserted =
profiles_order_.insert({profile_path, number_of_profiles});
// We shouldn't add the same profile to the list more than once. Use
// `insert()` to not corrput the map in case this happens.
// https://crbug.com/1195784
DCHECK(it_and_whether_inserted.second);
}
bool ProfilePickerHandler::RemoveProfileFromList(
const base::FilePath& profile_path) {
auto remove_it = profiles_order_.find(profile_path);
// Guest and omitted profiles aren't added to the list.
// It's possible that a profile gets marked as guest or as omitted after it
// had been added to the list. In that case, the profile gets removed from the
// list once in `OnProfileIsOmittedChanged()` but not the second time when
// `OnProfileWasRemoved()` is called.
if (remove_it == profiles_order_.end())
return false;
size_t index = remove_it->second;
profiles_order_.erase(remove_it);
for (auto& it : profiles_order_) {
if (it.second > index)
--it.second;
}
return true;
}
void ProfilePickerHandler::OnProfileAdded(const base::FilePath& profile_path) {
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile_path);
CHECK(entry);
if (entry->IsGuest() || entry->IsOmitted())
return;
AddProfileToList(profile_path);
PushProfilesList();
}
void ProfilePickerHandler::OnProfileWasRemoved(
const base::FilePath& profile_path,
const std::u16string& profile_name) {
DCHECK(IsJavascriptAllowed());
if (RemoveProfileFromList(profile_path))
FireWebUIListener("profile-removed", util::FilePathToValue(profile_path));
}
void ProfilePickerHandler::OnProfileIsOmittedChanged(
const base::FilePath& profile_path) {
ProfileAttributesEntry* entry =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile_path);
CHECK(entry);
if (entry->IsOmitted()) {
if (RemoveProfileFromList(profile_path))
PushProfilesList();
} else {
AddProfileToList(profile_path);
PushProfilesList();
}
}
void ProfilePickerHandler::OnProfileAvatarChanged(
const base::FilePath& profile_path) {
PushProfilesList();
}
void ProfilePickerHandler::OnProfileHighResAvatarLoaded(
const base::FilePath& profile_path) {
PushProfilesList();
}
void ProfilePickerHandler::OnProfileNameChanged(
const base::FilePath& profile_path,
const std::u16string& old_profile_name) {
PushProfilesList();
}
void ProfilePickerHandler::OnProfileHostedDomainChanged(
const base::FilePath& profile_path) {
PushProfilesList();
}
void ProfilePickerHandler::DidFirstVisuallyNonEmptyPaint() {
DCHECK(!creation_time_on_startup_.is_null());
auto now = base::TimeTicks::Now();
base::UmaHistogramTimes("ProfilePicker.StartupTime.FirstPaint",
now - creation_time_on_startup_);
startup_metric_utils::RecordExternalStartupMetric(
"ProfilePicker.StartupTime.FirstPaint.FromApplicationStart", now,
/*set_non_browser_ui_displayed=*/true);
// Stop observing so that the histogram is only recorded once.
Observe(nullptr);
}
void ProfilePickerHandler::OnVisibilityChanged(content::Visibility visibility) {
// If the profile picker is hidden, the first paint will be delayed until the
// picker is visible again. Stop monitoring the first paint to avoid polluting
// the metrics.
if (visibility != content::Visibility::VISIBLE)
Observe(nullptr);
}