| // 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, ¶ms); |
| } |
| } |
| |
| 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); |
| } |