| // 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/metrics/histogram_functions.h" |
| #include "base/optional.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_finder.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/user_manager.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/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 "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, |
| }; |
| |
| base::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 base::nullopt; |
| } |
| |
| void RecordProfilePickerAction(ProfilePickerAction action) { |
| base::UmaHistogramEnumeration("ProfilePicker.UserAction", action); |
| } |
| |
| void RecordAskOnStartupChanged(bool value) { |
| base::UmaHistogramBoolean("ProfilePicker.AskOnStartupChanged", value); |
| } |
| |
| void RecordNewProfileSpec(base::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, |
| base::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 = |
| ui::NativeTheme::GetInstanceForNativeUi()->ShouldUseDarkColors(); |
| 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, base::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); |
| } |
| |
| } // 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( |
| "createProfile", |
| base::BindRepeating(&ProfilePickerHandler::HandleCreateProfile, |
| 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))); |
| } |
| |
| void ProfilePickerHandler::OnJavascriptAllowed() { |
| g_browser_process->profile_manager() |
| ->GetProfileAttributesStorage() |
| .AddObserver(this); |
| } |
| void ProfilePickerHandler::OnJavascriptDisallowed() { |
| g_browser_process->profile_manager() |
| ->GetProfileAttributesStorage() |
| .RemoveObserver(this); |
| } |
| |
| 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; |
| |
| base::Optional<base::FilePath> profile_path = |
| util::ValueToFilePath(*profile_path_value); |
| if (!profile_path) |
| return; |
| |
| ProfileAttributesEntry* entry; |
| if (!g_browser_process->profile_manager() |
| ->GetProfileAttributesStorage() |
| .GetProfileAttributesWithPath(*profile_path, &entry)) { |
| NOTREACHED(); |
| return; |
| } |
| |
| if (entry->IsSigninRequired()) { |
| DCHECK(signin_util::IsForceSigninEnabled()); |
| 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(); |
| UserManagerProfileDialog::ShowDialogAndDisplayErrorMessage( |
| web_ui()->GetWebContents()->GetBrowserContext()); |
| } else { |
| // Fresh sign in via user manager without existing email address. |
| UserManagerProfileDialog::ShowForceSigninDialog( |
| web_ui()->GetWebContents()->GetBrowserContext(), *profile_path); |
| } |
| return; |
| } |
| |
| 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(); |
| base::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); |
| 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::HandleCreateProfile(const base::ListValue* args) { |
| // profileName, profileColor, avatarUrl, isGeneric, createShortcut |
| CHECK_EQ(5U, args->GetList().size()); |
| base::string16 profile_name = |
| base::UTF8ToUTF16(args->GetList()[0].GetString()); |
| // profileColor is undefined for the default theme. |
| base::Optional<SkColor> profile_color; |
| if (args->GetList()[1].is_int()) |
| profile_color = args->GetList()[1].GetInt(); |
| std::string avatar_url = args->GetList()[2].GetString(); |
| bool is_generic = args->GetList()[3].GetBool(); |
| bool create_shortcut = args->GetList()[4].GetBool(); |
| DCHECK(base::IsStringASCII(avatar_url)); |
| base::TrimWhitespace(profile_name, base::TRIM_ALL, &profile_name); |
| CHECK(!profile_name.empty()); |
| if (is_generic) { |
| avatar_url = profiles::GetDefaultAvatarIconUrl( |
| profiles::GetPlaceholderAvatarIndex()); |
| } |
| |
| #ifndef NDEBUG |
| size_t icon_index; |
| DCHECK(profiles::IsDefaultAvatarIconUrl(avatar_url, &icon_index)); |
| #endif |
| |
| ProfileMetrics::LogProfileAddNewUser( |
| ProfileMetrics::ADD_NEW_PROFILE_PICKER_LOCAL); |
| ProfileManager::CreateMultiProfileAsync( |
| profile_name, avatar_url, |
| base::BindRepeating(&ProfilePickerHandler::OnProfileCreated, |
| weak_factory_.GetWeakPtr(), profile_color, |
| create_shortcut)); |
| } |
| |
| void ProfilePickerHandler::OnProfileCreated( |
| base::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; |
| } |
| } |
| |
| if (IsJavascriptAllowed()) |
| FireWebUIListener("create-profile-finished", base::Value()); |
| } |
| |
| void ProfilePickerHandler::OnProfileCreationSuccess( |
| base::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 edded. |
| 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()); |
| } |
| |
| 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]; |
| base::Optional<base::FilePath> profile_path = |
| util::ValueToFilePath(profile_path_value); |
| |
| if (!profile_path) { |
| NOTREACHED(); |
| return; |
| } |
| base::string16 profile_name = |
| base::UTF8ToUTF16(args->GetList()[1].GetString()); |
| base::TrimWhitespace(profile_name, base::TRIM_ALL, &profile_name); |
| CHECK(!profile_name.empty()); |
| ProfileAttributesEntry* entry; |
| CHECK(g_browser_process->profile_manager() |
| ->GetProfileAttributesStorage() |
| .GetProfileAttributesWithPath(profile_path.value(), &entry)); |
| entry->SetLocalProfileName(profile_name); |
| } |
| |
| void ProfilePickerHandler::HandleRemoveProfile(const base::ListValue* args) { |
| CHECK_EQ(1U, args->GetSize()); |
| const base::Value& profile_path_value = args->GetList()[0]; |
| base::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]; |
| base::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) { |
| if (!IsJavascriptAllowed()) |
| return; |
| |
| 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) { |
| if (IsJavascriptAllowed()) |
| 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()); |
| if (open_settings) { |
| chrome::ShowSettingsSubPage(browser, chrome::kManageProfileSubPage); |
| } |
| |
| if (new_profile) { |
| RecordProfilePickerAction(ProfilePickerAction::kLaunchNewProfile); |
| } else if (profile->IsGuestSession() || profile->IsEphemeralGuestProfile()) { |
| RecordProfilePickerAction(ProfilePickerAction::kLaunchGuestProfile); |
| } else { |
| RecordProfilePickerAction( |
| open_settings |
| ? ProfilePickerAction::kLaunchExistingProfileCustomizeSettings |
| : ProfilePickerAction::kLaunchExistingProfile); |
| } |
| 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) { |
| if (entry->IsGuest()) |
| continue; |
| |
| profiles_order_[entry->GetPath()] = index++; |
| } |
| } |
| |
| std::vector<ProfileAttributesEntry*> |
| ProfilePickerHandler::GetProfileAttributes() { |
| size_t number_of_profiles = |
| g_browser_process->profile_manager()->GetNumberOfProfiles(); |
| std::vector<ProfileAttributesEntry*> ordered_entries = |
| g_browser_process->profile_manager() |
| ->GetProfileAttributesStorage() |
| .GetAllProfilesAttributesSortedByLocalProfilName(); |
| |
| 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::ListValue profiles_list; |
| std::vector<ProfileAttributesEntry*> entries = GetProfileAttributes(); |
| const int avatar_icon_size = |
| kProfileCardAvatarSize * web_ui()->GetDeviceScaleFactor(); |
| for (const ProfileAttributesEntry* entry : entries) { |
| auto profile_entry = std::make_unique<base::DictionaryValue>(); |
| profile_entry->SetKey("profilePath", |
| util::FilePathToValue(entry->GetPath())); |
| profile_entry->SetString("localProfileName", entry->GetLocalProfileName()); |
| profile_entry->SetBoolPath( |
| "isSyncing", entry->GetSigninState() == |
| SigninState::kSignedInWithConsentedPrimaryAccount); |
| profile_entry->SetBoolPath("needsSignin", entry->IsSigninRequired()); |
| // GAIA name/user name can be empty, if the profile is not signed in to |
| // chrome. |
| profile_entry->SetString("gaiaName", entry->GetGAIANameToDisplay()); |
| profile_entry->SetString("userName", entry->GetUserName()); |
| profile_entry->SetBoolPath( |
| "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->SetString("avatarIcon", icon_url); |
| profiles_list.Append(std::move(profile_entry)); |
| } |
| return std::move(profiles_list); |
| } |
| |
| void ProfilePickerHandler::OnProfileAdded(const base::FilePath& profile_path) { |
| size_t number_of_profiles = profiles_order_.size(); |
| profiles_order_[profile_path] = number_of_profiles; |
| PushProfilesList(); |
| } |
| |
| void ProfilePickerHandler::OnProfileWasRemoved( |
| const base::FilePath& profile_path, |
| const base::string16& profile_name) { |
| DCHECK(IsJavascriptAllowed()); |
| size_t index = profiles_order_[profile_path]; |
| profiles_order_.erase(profile_path); |
| for (auto it : profiles_order_) { |
| if (it.second > index) |
| profiles_order_[it.first] = it.second - 1; |
| } |
| FireWebUIListener("profile-removed", util::FilePathToValue(profile_path)); |
| } |
| |
| 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 base::string16& old_profile_name) { |
| PushProfilesList(); |
| } |
| |
| void ProfilePickerHandler::OnProfileHostedDomainChanged( |
| const base::FilePath& profile_path) { |
| PushProfilesList(); |
| } |
| |
| void ProfilePickerHandler::DidFirstVisuallyNonEmptyPaint() { |
| DCHECK(!creation_time_on_startup_.is_null()); |
| base::UmaHistogramTimes("ProfilePicker.StartupTime.FirstPaint", |
| base::TimeTicks::Now() - creation_time_on_startup_); |
| // 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); |
| } |