blob: 16e553932114d101f969f2b1d95682042dcb8d68 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/profiles/profile_menu_view.h"
#include <algorithm>
#include <string>
#include <utility>
#include "base/feature_list.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/user_metrics.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/enterprise/browser_management/browser_management_service.h"
#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
#include "chrome/browser/enterprise/util/managed_browser_utils.h"
#include "chrome/browser/feature_engagement/tracker_factory.h"
#include "chrome/browser/lifetime/application_lifetime.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_avatar_icon_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_metrics.h"
#include "chrome/browser/profiles/profile_window.h"
#include "chrome/browser/profiles/profiles_state.h"
#include "chrome/browser/signin/account_consistency_mode_manager.h"
#include "chrome/browser/signin/chrome_signin_client_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/signin/signin_error_controller_factory.h"
#include "chrome/browser/signin/signin_ui_util.h"
#include "chrome/browser/signin/signin_util.h"
#include "chrome/browser/sync/sync_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "chrome/browser/ui/browser_navigator_params.h"
#include "chrome/browser/ui/color/chrome_color_id.h"
#include "chrome/browser/ui/managed_ui.h"
#include "chrome/browser/ui/passwords/ui_utils.h"
#include "chrome/browser/ui/profiles/profile_colors_util.h"
#include "chrome/browser/ui/profiles/profile_picker.h"
#include "chrome/browser/ui/profiles/profile_view_utils.h"
#include "chrome/browser/ui/sync/sync_promo_ui.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/accessibility/non_accessible_image_view.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/browser/ui/views/chrome_typography.h"
#include "chrome/browser/ui/views/controls/hover_button.h"
#include "chrome/browser/ui/views/profiles/badged_profile_photo.h"
#include "chrome/browser/ui/web_applications/app_browser_controller.h"
#include "chrome/browser/web_applications/web_app_id_constants.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/branded_strings.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/theme_resources.h"
#include "components/signin/core/browser/signin_error_controller.h"
#include "components/signin/public/base/consent_level.h"
#include "components/signin/public/base/signin_metrics.h"
#include "components/signin/public/base/signin_pref_names.h"
#include "components/signin/public/base/signin_switches.h"
#include "components/signin/public/identity_manager/account_info.h"
#include "components/signin/public/identity_manager/primary_account_mutator.h"
#include "components/strings/grit/components_strings.h"
#include "components/vector_icons/vector_icons.h"
#include "net/base/url_util.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/models/image_model.h"
#include "ui/base/resource/resource_bundle.h"
#include "ui/base/ui_base_features.h"
#include "ui/color/color_id.h"
#include "ui/color/color_provider.h"
#include "ui/gfx/image/canvas_image_source.h"
#include "ui/gfx/image/image.h"
#include "ui/gfx/image/image_skia_operations.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/accessibility/view_accessibility.h"
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "components/trusted_vault/features.h"
#endif
#if BUILDFLAG(IS_MAC)
#include "chrome/browser/apps/app_shim/app_shim_manager_mac.h"
#include "chrome/browser/web_applications/app_shim_registry_mac.h"
#endif
namespace {
// Helpers --------------------------------------------------------------------
std::u16string GetSyncErrorButtonText(AvatarSyncErrorType error) {
switch (error) {
case AvatarSyncErrorType::kSyncPaused:
case AvatarSyncErrorType::kUnrecoverableError:
// The user was signed out. Offer them to sign in again.
return l10n_util::GetStringUTF16(IDS_SYNC_ERROR_USER_MENU_SIGNIN_BUTTON);
case AvatarSyncErrorType::kManagedUserUnrecoverableError:
// As opposed to the corresponding error in an unmanaged account
// (AvatarSyncErrorType::kUnrecoverableError), sign-out hasn't happened
// here yet. The button directs to the sign-out confirmation dialog in
// settings.
return l10n_util::GetStringUTF16(IDS_SYNC_ERROR_USER_MENU_SIGNOUT_BUTTON);
case AvatarSyncErrorType::kUpgradeClientError:
return l10n_util::GetStringUTF16(IDS_SYNC_ERROR_USER_MENU_UPGRADE_BUTTON);
case AvatarSyncErrorType::kPassphraseError:
return l10n_util::GetStringUTF16(
IDS_SYNC_ERROR_USER_MENU_PASSPHRASE_BUTTON);
case AvatarSyncErrorType::kTrustedVaultKeyMissingForEverythingError:
case AvatarSyncErrorType::kTrustedVaultKeyMissingForPasswordsError:
return l10n_util::GetStringUTF16(
IDS_SYNC_ERROR_USER_MENU_RETRIEVE_KEYS_BUTTON);
case AvatarSyncErrorType::
kTrustedVaultRecoverabilityDegradedForEverythingError:
case AvatarSyncErrorType::
kTrustedVaultRecoverabilityDegradedForPasswordsError:
return l10n_util::GetStringUTF16(
IDS_SYNC_ERROR_USER_MENU_RECOVERABILITY_BUTTON);
case AvatarSyncErrorType::kSettingsUnconfirmedError:
return l10n_util::GetStringUTF16(
IDS_SYNC_ERROR_USER_MENU_CONFIRM_SYNC_SETTINGS_BUTTON);
}
}
} // namespace
// ProfileMenuView ---------------------------------------------------------
// static
bool ProfileMenuView::close_on_deactivate_for_testing_ = true;
ProfileMenuView::ProfileMenuView(views::Button* anchor_button, Browser* browser)
: ProfileMenuViewBase(anchor_button, browser) {
set_close_on_deactivate(close_on_deactivate_for_testing_);
}
ProfileMenuView::~ProfileMenuView() = default;
void ProfileMenuView::BuildMenu() {
Profile* profile = browser()->profile();
if (profile->IsGuestSession()) {
BuildGuestIdentity();
} else {
CHECK(!profile->IsOffTheRecord());
BuildIdentity();
// Users should not be able to open chrome settings from WebApps.
if (!web_app::AppBrowserController::IsWebApp(browser())) {
BuildSyncInfo();
BuildAutofillButtons();
}
}
// Users should not be able to use features from WebApps.
if (!web_app::AppBrowserController::IsWebApp(browser())) {
BuildFeatureButtons();
}
// ChromeOS doesn't support multi-profile.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
if (!(profile->IsGuestSession())) {
SetProfileManagementHeading(l10n_util::GetStringUTF16(
switches::IsExplicitBrowserSigninUIOnDesktopEnabled()
? IDS_PROFILE_MENU_PROFILES_LIST_TITLE
: IDS_PROFILES_LIST_PROFILES_TITLE));
BuildAvailableProfiles();
// Users should not be able to manage profiles from WebApps.
if (!web_app::AppBrowserController::IsWebApp(browser())) {
BuildProfileManagementFeatureButtons();
}
}
#endif
if (web_app::AppBrowserController::IsWebApp(browser())) {
browser()->window()->NotifyFeatureEngagementEvent(
"web_app_profile_menu_shown");
}
}
gfx::ImageSkia ProfileMenuView::GetSyncIcon() const {
Profile* profile = browser()->profile();
if (profile->IsOffTheRecord() || profile->IsGuestSession())
return gfx::ImageSkia();
bool is_sync_feature_enabled =
IdentityManagerFactory::GetForProfile(profile)->HasPrimaryAccount(
signin::ConsentLevel::kSync);
if (!is_sync_feature_enabled) {
// This is done regardless of GetAvatarSyncErrorType() because the icon
// should reflect that sync-the-feature is off. The error will still be
// highlighted by other parts of the UI.
return features::IsChromeRefresh2023()
? ColoredImageForMenu(kSyncDisabledChromeRefreshIcon,
kColorProfileMenuSyncOffIcon)
: ColoredImageForMenu(kSyncPausedCircleIcon, ui::kColorIcon);
}
std::optional<AvatarSyncErrorType> error = GetAvatarSyncErrorType(profile);
if (!error) {
return features::IsChromeRefresh2023()
? ColoredImageForMenu(kSyncChromeRefreshIcon,
kColorProfileMenuSyncIcon)
: ColoredImageForMenu(kSyncCircleIcon,
ui::kColorAlertLowSeverity);
}
ui::ColorId color_id = error == AvatarSyncErrorType::kSyncPaused
? ui::kColorButtonBackgroundProminent
: ui::kColorAlertHighSeverity;
ui::ColorId refreshed_color_id = error == AvatarSyncErrorType::kSyncPaused
? kColorProfileMenuSyncPausedIcon
: kColorProfileMenuSyncErrorIcon;
return features::IsChromeRefresh2023()
? ColoredImageForMenu(kSyncDisabledChromeRefreshIcon,
refreshed_color_id)
: ColoredImageForMenu(kSyncPausedCircleIcon, color_id);
}
std::u16string ProfileMenuView::GetAccessibleWindowTitle() const {
std::u16string title =
l10n_util::GetStringUTF16(IDS_PROFILES_PROFILE_BUBBLE_ACCESSIBLE_TITLE);
if (!menu_title_.empty()) {
title = l10n_util::GetStringFUTF16(IDS_CONCAT_TWO_STRINGS_WITH_COMMA, title,
menu_title_);
}
if (!menu_subtitle_.empty()) {
title = l10n_util::GetStringFUTF16(IDS_CONCAT_TWO_STRINGS_WITH_COMMA, title,
menu_subtitle_);
}
return title;
}
void ProfileMenuView::OnManageGoogleAccountButtonClicked() {
RecordClick(ActionableItem::kManageGoogleAccountButton);
if (!perform_menu_actions())
return;
Profile* profile = browser()->profile();
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
DCHECK(identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSignin));
NavigateToGoogleAccountPage(
profile,
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin)
.email);
}
void ProfileMenuView::OnPasswordsButtonClicked() {
RecordClick(ActionableItem::kPasswordsButton);
if (!perform_menu_actions())
return;
NavigateToManagePasswordsPage(
browser(), password_manager::ManagePasswordsReferrer::kProfileChooser);
}
void ProfileMenuView::OnCreditCardsButtonClicked() {
RecordClick(ActionableItem::kCreditCardsButton);
if (!perform_menu_actions())
return;
chrome::ShowSettingsSubPage(browser(), chrome::kPaymentsSubPage);
}
void ProfileMenuView::OnAddressesButtonClicked() {
RecordClick(ActionableItem::kAddressesButton);
if (!perform_menu_actions())
return;
chrome::ShowSettingsSubPage(browser(), chrome::kAddressesSubPage);
}
void ProfileMenuView::OnGuestProfileButtonClicked() {
RecordClick(ActionableItem::kGuestProfileButton);
if (!perform_menu_actions())
return;
DCHECK(profiles::IsGuestModeEnabled());
profiles::SwitchToGuestProfile();
}
void ProfileMenuView::OnExitProfileButtonClicked() {
RecordClick(ActionableItem::kExitProfileButton);
if (!perform_menu_actions())
return;
profiles::CloseProfileWindows(browser()->profile());
}
void ProfileMenuView::OnSyncSettingsButtonClicked() {
RecordClick(ActionableItem::kSyncSettingsButton);
if (!perform_menu_actions())
return;
chrome::ShowSettingsSubPage(browser(), chrome::kSyncSetupSubPage);
}
void ProfileMenuView::OnSyncErrorButtonClicked(AvatarSyncErrorType error) {
#if BUILDFLAG(IS_CHROMEOS_ASH)
// On ChromeOS, sync errors are fixed by re-signing into the OS.
chrome::AttemptUserExit();
#else
RecordClick(ActionableItem::kSyncErrorButton);
if (!perform_menu_actions())
return;
// The logic below must be consistent with GetSyncInfoForAvatarErrorType().
switch (error) {
case AvatarSyncErrorType::kManagedUserUnrecoverableError:
chrome::ShowSettingsSubPage(browser(), chrome::kSignOutSubPage);
break;
case AvatarSyncErrorType::kUnrecoverableError: {
Profile* profile = browser()->profile();
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
// This error means that the Sync engine failed to initialize. Shutdown
// Sync engine by revoking sync consent.
identity_manager->GetPrimaryAccountMutator()->RevokeSyncConsent(
signin_metrics::ProfileSignout::kUserClickedSignoutSettings);
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
// Re-enable sync with the same primary account.
signin_ui_util::EnableSyncFromSingleAccountPromo(
profile,
identity_manager->GetPrimaryAccountInfo(
signin::ConsentLevel::kSignin),
signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN);
break;
}
case AvatarSyncErrorType::kSyncPaused:
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
signin_ui_util::ShowReauthForPrimaryAccountWithAuthError(
browser()->profile(),
signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN);
break;
case AvatarSyncErrorType::kUpgradeClientError:
chrome::OpenUpdateChromeDialog(browser());
break;
case AvatarSyncErrorType::kTrustedVaultKeyMissingForEverythingError:
case AvatarSyncErrorType::kTrustedVaultKeyMissingForPasswordsError:
#if BUILDFLAG(IS_CHROMEOS_LACROS)
if (base::FeatureList::IsEnabled(
trusted_vault::kChromeOSTrustedVaultUseWebUIDialog)) {
OpenDialogForSyncKeyRetrieval(
browser()->profile(),
syncer::TrustedVaultUserActionTriggerForUMA::kProfileMenu);
break;
}
#endif
OpenTabForSyncKeyRetrieval(
browser(), syncer::TrustedVaultUserActionTriggerForUMA::kProfileMenu);
break;
case AvatarSyncErrorType::
kTrustedVaultRecoverabilityDegradedForEverythingError:
case AvatarSyncErrorType::
kTrustedVaultRecoverabilityDegradedForPasswordsError:
#if BUILDFLAG(IS_CHROMEOS_LACROS)
if (base::FeatureList::IsEnabled(
trusted_vault::kChromeOSTrustedVaultUseWebUIDialog)) {
OpenDialogForSyncKeyRecoverabilityDegraded(
browser()->profile(),
syncer::TrustedVaultUserActionTriggerForUMA::kProfileMenu);
break;
}
#endif
OpenTabForSyncKeyRecoverabilityDegraded(
browser(), syncer::TrustedVaultUserActionTriggerForUMA::kProfileMenu);
break;
case AvatarSyncErrorType::kPassphraseError:
case AvatarSyncErrorType::kSettingsUnconfirmedError:
chrome::ShowSettingsSubPage(browser(), chrome::kSyncSetupSubPage);
break;
}
#endif
}
void ProfileMenuView::OnSigninButtonClicked(CoreAccountInfo account,
ActionableItem button_type) {
RecordClick(button_type);
if (!perform_menu_actions())
return;
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
signin_ui_util::EnableSyncFromSingleAccountPromo(
browser()->profile(), account,
signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN);
}
#if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
void ProfileMenuView::OnSignoutButtonClicked() {
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(browser()->profile());
DCHECK(ChromeSigninClientFactory::GetForProfile(browser()->profile())
->IsClearPrimaryAccountAllowed(identity_manager->HasPrimaryAccount(
signin::ConsentLevel::kSync)))
<< "Clear primary account is not allowed. Signout should not be offered "
"in the UI.";
RecordClick(ActionableItem::kSignoutButton);
if (!perform_menu_actions())
return;
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
#if BUILDFLAG(ENABLE_DICE_SUPPORT)
browser()->signin_view_controller()->SignoutOrReauthWithPrompt(
signin_metrics::AccessPoint::
ACCESS_POINT_PROFILE_MENU_SIGNOUT_CONFIRMATION_PROMPT,
signin_metrics::ProfileSignout::kUserClickedSignoutProfileMenu,
signin_metrics::SourceForRefreshTokenOperation::
kUserMenu_SignOutAllAccounts);
#else
CHECK(!browser()->profile()->IsMainProfile());
identity_manager->GetPrimaryAccountMutator()->ClearPrimaryAccount(
signin_metrics::ProfileSignout::kUserClickedSignoutProfileMenu);
#endif
}
#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
#if !BUILDFLAG(IS_CHROMEOS_ASH)
void ProfileMenuView::OnOtherProfileSelected(
const base::FilePath& profile_path) {
RecordClick(ActionableItem::kOtherProfileButton);
if (!perform_menu_actions())
return;
if (!web_app::AppBrowserController::IsWebApp(browser())) {
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
profiles::SwitchToProfile(profile_path, /*always_create=*/false);
} else {
#if !BUILDFLAG(IS_CHROMEOS)
// Open the same web app for another profile.
// On non-macOS the only allowlisted case is PasswordManager WebApp, which
// uses a different code path from other PWAs as it needs to not only
// support switching profiles, but also possibly installing the app into a
// different profile. Regular PWAs can only switch to profiles where the app
// is already installed.
const webapps::AppId& app_id = browser()->app_controller()->app_id();
#if BUILDFLAG(IS_MAC)
if (app_id != web_app::kPasswordManagerAppId) {
apps::AppShimManager::Get()->LaunchAppInProfile(app_id, profile_path);
return;
}
#endif
CHECK_EQ(app_id, web_app::kPasswordManagerAppId);
app_profile_switcher_.emplace(
app_id, *browser()->profile(),
base::BindOnce(
[](views::Widget* widget) {
widget->CloseWithReason(
views::Widget::ClosedReason::kUnspecified);
},
// It's safe to use base::Unretained, because the profile
// switcher is owned by ProfileMenuView and is destroyed
// before the widget is destroyed.
base::Unretained(GetWidget())));
app_profile_switcher_->SwitchToProfile(profile_path);
#else
// WebApps can only be installed for the main profile on ChromeOS.
NOTREACHED();
#endif
}
}
void ProfileMenuView::OnAddNewProfileButtonClicked() {
RecordClick(ActionableItem::kAddNewProfileButton);
if (!perform_menu_actions())
return;
ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
ProfilePicker::EntryPoint::kProfileMenuAddNewProfile));
}
void ProfileMenuView::OnManageProfilesButtonClicked() {
RecordClick(ActionableItem::kManageProfilesButton);
if (!perform_menu_actions())
return;
ProfilePicker::Show(ProfilePicker::Params::FromEntryPoint(
ProfilePicker::EntryPoint::kProfileMenuManageProfiles));
}
void ProfileMenuView::OnEditProfileButtonClicked() {
RecordClick(ActionableItem::kEditProfileButton);
if (!perform_menu_actions())
return;
chrome::ShowSettingsSubPage(browser(), chrome::kManageProfileSubPage);
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
void ProfileMenuView::OnCookiesClearedOnExitLinkClicked() {
RecordClick(ActionableItem::kCookiesClearedOnExitLink);
if (!perform_menu_actions())
return;
chrome::ShowSettingsSubPage(browser(), chrome::kContentSettingsSubPage +
std::string("/") +
chrome::kCookieSettingsSubPage);
}
void ProfileMenuView::BuildIdentity() {
Profile* profile = browser()->profile();
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
CoreAccountInfo account =
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
AccountInfo account_info = identity_manager->FindExtendedAccountInfo(account);
ProfileAttributesEntry* profile_attributes =
g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetProfileAttributesWithPath(profile->GetPath());
if (!profile_attributes) {
// May happen if the profile is being deleted. https://crbug.com/1040079
return;
}
std::u16string profile_name;
std::optional<EditButtonParams> edit_button_params;
// Profile names are not supported on ChromeOS.
#if !BUILDFLAG(IS_CHROMEOS_ASH)
profile_name = profile_attributes->GetLocalProfileName();
if (!web_app::AppBrowserController::IsWebApp(browser()) &&
!switches::IsExplicitBrowserSigninUIOnDesktopEnabled()) {
edit_button_params = EditButtonParams(
features::IsChromeRefresh2023() ? &kEditChromeRefreshIcon
: &vector_icons::kEditIcon,
l10n_util::GetStringUTF16(
IDS_PROFILES_CUSTOMIZE_PROFILE_BUTTON_TOOLTIP),
base::BindRepeating(&ProfileMenuView::OnEditProfileButtonClicked,
base::Unretained(this)));
}
#endif
SkColor background_color =
profile_attributes->GetProfileThemeColors().profile_highlight_color;
if (!account_info.IsEmpty()) {
menu_title_ = base::UTF8ToUTF16(account_info.full_name);
menu_subtitle_ =
IsSyncPaused(profile)
? l10n_util::GetStringUTF16(IDS_PROFILES_LOCAL_PROFILE_STATE)
: base::UTF8ToUTF16(account_info.email);
auto account_manager = chrome::GetAccountManagerIdentity(profile);
std::u16string management_label;
ui::ImageModel badge_image_model;
if (chrome::enterprise_util::CanShowEnterpriseBadging(
browser()->profile())) {
management_label =
account_manager
? l10n_util::GetStringFUTF16(IDS_PROFILES_MANAGED_BY,
base::UTF8ToUTF16(*account_manager))
: std::u16string();
auto management_environment =
chrome::enterprise_util::GetManagementEnvironment(
profile, identity_manager->FindExtendedAccountInfoByAccountId(
identity_manager->GetPrimaryAccountId(
signin::ConsentLevel::kSignin)));
if (management_environment !=
chrome::enterprise_util::ManagementEnvironment::kNone) {
policy::BrowserManagementService* management_service =
static_cast<policy::BrowserManagementService*>(
policy::ManagementServiceFactory::GetForProfile(
browser()->profile()));
if (management_service->GetMetadata().GetManagementLogo().IsEmpty()) {
badge_image_model = ui::ImageModel::FromVectorIcon(
vector_icons::kBusinessIcon, ui::kColorMenuIcon, 16);
} else {
badge_image_model = ui::ImageModel::FromImage(
management_service->GetMetadata().GetManagementLogo());
}
}
}
SetProfileIdentityInfo(
profile_name, background_color, edit_button_params,
ui::ImageModel::FromImage(account_info.account_image),
badge_image_model, menu_title_, menu_subtitle_, management_label);
} else {
menu_title_ = l10n_util::GetStringUTF16(IDS_PROFILES_LOCAL_PROFILE_STATE);
// The email may be empty.
menu_subtitle_ = base::UTF8ToUTF16(account_info.email);
std::u16string management_label;
SetProfileIdentityInfo(
profile_name, background_color, edit_button_params,
ui::ImageModel::FromImage(
// If the user is in the web-only signed-in state in the
// experimental version of the UNO model, use the account image in
// the profile menu header.
// If the account does not have an image or it's not available yet,
// a grey silhouette will be used.
// If UNO is disabled or enabled in the full version, or there is no
// account, use the profile icon.
!account_info.IsEmpty()
? account_info.account_image
: profile_attributes->GetAvatarIcon(kIdentityImageSize)),
ui::ImageModel(), menu_title_, menu_subtitle_, management_label);
}
}
void ProfileMenuView::BuildGuestIdentity() {
int guest_window_count = BrowserList::GetGuestBrowserCount();
menu_title_ = l10n_util::GetStringUTF16(IDS_GUEST_PROFILE_NAME);
menu_subtitle_ = std::u16string();
std::u16string management_label;
if (guest_window_count > 1) {
menu_subtitle_ = l10n_util::GetPluralStringFUTF16(
IDS_GUEST_WINDOW_COUNT_MESSAGE, guest_window_count);
}
ui::ThemedVectorIcon header_art_icon(&kGuestMenuArtIcon,
ui::kColorAvatarHeaderArt);
SetProfileIdentityInfo(
/*profile_name=*/std::u16string(),
/*background_color=*/SK_ColorTRANSPARENT,
/*edit_button=*/std::nullopt, profiles::GetGuestAvatar(),
ui::ImageModel(), menu_title_, menu_subtitle_, management_label,
header_art_icon);
}
void ProfileMenuView::BuildAutofillButtons() {
AddShortcutFeatureButton(
features::IsChromeRefresh2023() ? vector_icons::kPasswordManagerIcon
: kKeyIcon,
l10n_util::GetStringUTF16(
IDS_PASSWORD_BUBBLES_PASSWORD_MANAGER_LINK_TEXT_SAVING_ON_DEVICE),
base::BindRepeating(&ProfileMenuView::OnPasswordsButtonClicked,
base::Unretained(this)));
AddShortcutFeatureButton(
features::IsChromeRefresh2023() ? kCreditCardChromeRefreshIcon
: kCreditCardIcon,
l10n_util::GetStringUTF16(IDS_PROFILES_CREDIT_CARDS_LINK),
base::BindRepeating(&ProfileMenuView::OnCreditCardsButtonClicked,
base::Unretained(this)));
AddShortcutFeatureButton(
features::IsChromeRefresh2023()
? vector_icons::kLocationOnChromeRefreshIcon
: vector_icons::kLocationOnIcon,
l10n_util::GetStringUTF16(IDS_PROFILES_ADDRESSES_LINK),
base::BindRepeating(&ProfileMenuView::OnAddressesButtonClicked,
base::Unretained(this)));
}
void ProfileMenuView::BuildSyncInfo() {
Profile* profile = browser()->profile();
if (!profile->GetPrefs()->GetBoolean(prefs::kSigninAllowed))
return;
if (!SyncServiceFactory::IsSyncAllowed(profile)) {
return;
}
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
bool is_sync_feature_enabled =
identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync);
// First, check for sync errors. They may exist even if sync-the-feature is
// disabled and only sync-the-transport is running.
const std::optional<AvatarSyncErrorType> error =
GetAvatarSyncErrorType(profile);
if (error) {
BuildSyncInfoWithCallToAction(
GetAvatarSyncErrorDescription(*error, is_sync_feature_enabled),
GetSyncErrorButtonText(*error),
error == AvatarSyncErrorType::kSyncPaused
? ui::kColorSyncInfoBackgroundPaused
: ui::kColorSyncInfoBackgroundError,
base::BindRepeating(&ProfileMenuView::OnSyncErrorButtonClicked,
base::Unretained(this), *error),
/*show_sync_badge=*/is_sync_feature_enabled);
return;
}
// If there's no error and sync-the-feature is enabled, the text says
// everything is fine and the button simply opens sync settings.
if (is_sync_feature_enabled) {
BuildSyncInfoWithoutCallToAction(
l10n_util::GetStringUTF16(IDS_PROFILES_OPEN_SYNC_SETTINGS_BUTTON),
base::BindRepeating(&ProfileMenuView::OnSyncSettingsButtonClicked,
base::Unretained(this)));
return;
}
// If there's no error and sync-the-feature is disabled, show a sync promo.
// For a signed-in user, the promo just opens the "turn on sync" dialog.
// For a web-only signed-in user in the UNO model, the promo signs the user on
// Chrome and opens the "turn on sync" dialog.
// For a signed-out user, it prompts for sign-in first.
CoreAccountInfo account_info =
identity_manager->GetPrimaryAccountInfo(signin::ConsentLevel::kSignin);
AccountInfo account_info_for_promos =
signin_ui_util::GetSingleAccountForPromos(identity_manager);
std::u16string description;
std::u16string button_text;
ActionableItem button_type = ActionableItem::kSigninAccountButton;
bool show_sync_badge = false;
bool show_account_card = false;
if (!account_info.IsEmpty()) {
if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled() &&
identity_manager->HasAccountWithRefreshTokenInPersistentErrorState(
account_info.account_id)) {
// Sign-in pending state.
description =
l10n_util::GetStringUTF16(IDS_SIGNIN_PAUSED_USER_MENU_VERIFY_MESSAGE);
// TODO(b/331902320): Create a new string id for the button text instead
// of reusing the one from sync.
button_text = l10n_util::GetStringUTF16(
IDS_SYNC_ERROR_USER_MENU_RECOVERABILITY_BUTTON);
} else {
// Signed-in not-syncing state.
description = l10n_util::GetStringUTF16(
switches::IsExplicitBrowserSigninUIOnDesktopEnabled()
? IDS_PROFILES_DICE_SYNC_PROMO
: IDS_PROFILES_DICE_NOT_SYNCING_TITLE);
button_text = l10n_util::GetStringUTF16(IDS_PROFILES_DICE_SIGNIN_BUTTON);
show_sync_badge = !switches::IsExplicitBrowserSigninUIOnDesktopEnabled();
}
} else if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled() &&
!account_info_for_promos.IsEmpty()) {
// Web-only signed-in state.
account_info = account_info_for_promos;
description = l10n_util::GetStringUTF16(
switches::IsExplicitBrowserSigninUIOnDesktopEnabled()
? IDS_PROFILE_MENU_SIGNIN_PROMO_DESCRIPTION
: IDS_PROFILES_DICE_SYNC_PROMO);
button_text = l10n_util::GetStringFUTF16(
IDS_PROFILES_DICE_WEB_ONLY_SIGNIN_BUTTON,
base::UTF8ToUTF16(!account_info_for_promos.given_name.empty()
? account_info_for_promos.given_name
: account_info_for_promos.email));
button_type = ActionableItem::kEnableSyncForWebOnlyAccountButton;
show_account_card = true;
} else {
#if BUILDFLAG(IS_CHROMEOS_ASH)
// There is always an account on ChromeOS.
NOTREACHED_NORETURN();
#else
// Not signed in state.
if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled()) {
description =
l10n_util::GetStringUTF16(IDS_PROFILE_MENU_SIGNIN_PROMO_DESCRIPTION);
button_text =
l10n_util::GetStringUTF16(IDS_PROFILE_MENU_SIGNIN_PROMO_BUTTON);
} else {
description = l10n_util::GetStringUTF16(IDS_PROFILES_DICE_SYNC_PROMO);
button_text = l10n_util::GetStringUTF16(IDS_PROFILES_DICE_SIGNIN_BUTTON);
}
button_type = ActionableItem::kSigninButton;
#endif
}
CHECK(!description.empty());
CHECK(!button_text.empty());
BuildSyncInfoWithCallToAction(
description, button_text, ui::kColorSyncInfoBackground,
base::BindRepeating(&ProfileMenuView::OnSigninButtonClicked,
base::Unretained(this), account_info, button_type),
show_sync_badge,
show_account_card ? account_info_for_promos : AccountInfo());
}
void ProfileMenuView::BuildFeatureButtons() {
Profile* profile = browser()->profile();
if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled() &&
!profile->IsGuestSession()) {
AddFeatureButton(
l10n_util::GetStringUTF16(IDS_PROFILE_MENU_CUSTOMIZE_PROFILE_BUTTON),
base::BindRepeating(&ProfileMenuView::OnEditProfileButtonClicked,
base::Unretained(this)),
vector_icons::kEditChromeRefreshIcon);
}
bool has_unconsented_account = HasUnconstentedProfile(profile);
if (has_unconsented_account && !IsSyncPaused(profile)) {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
// The Google G icon needs to be shrunk, so it won't look too big compared
// to the other icons.
AddFeatureButton(
l10n_util::GetStringUTF16(IDS_SETTINGS_MANAGE_GOOGLE_ACCOUNT),
base::BindRepeating(
&ProfileMenuView::OnManageGoogleAccountButtonClicked,
base::Unretained(this)),
vector_icons::kGoogleGLogoIcon,
/*icon_to_image_ratio=*/0.75f);
#else
AddFeatureButton(
l10n_util::GetStringUTF16(IDS_SETTINGS_MANAGE_GOOGLE_ACCOUNT),
base::BindRepeating(
&ProfileMenuView::OnManageGoogleAccountButtonClicked,
base::Unretained(this)));
#endif
}
int window_count = CountBrowsersFor(profile);
if (profile->IsGuestSession()) {
AddFeatureButton(
l10n_util::GetPluralStringFUTF16(IDS_GUEST_PROFILE_MENU_CLOSE_BUTTON,
window_count),
base::BindRepeating(&ProfileMenuView::OnExitProfileButtonClicked,
base::Unretained(this)),
features::IsChromeRefresh2023() ? vector_icons::kCloseChromeRefreshIcon
: vector_icons::kCloseIcon);
} else if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled() &&
window_count > 0) {
AddFeatureButton(
l10n_util::GetPluralStringFUTF16(
IDS_PROFILE_MENU_CLOSE_PROFILE_X_WINDOWS_BUTTON, window_count),
base::BindRepeating(&ProfileMenuView::OnExitProfileButtonClicked,
base::Unretained(this)),
features::IsChromeRefresh2023() ? vector_icons::kCloseChromeRefreshIcon
: vector_icons::kCloseIcon);
} else if (window_count > 1) {
AddFeatureButton(
l10n_util::GetPluralStringFUTF16(IDS_PROFILES_CLOSE_X_WINDOWS_BUTTON,
window_count),
base::BindRepeating(&ProfileMenuView::OnExitProfileButtonClicked,
base::Unretained(this)),
features::IsChromeRefresh2023() ? vector_icons::kCloseChromeRefreshIcon
: vector_icons::kCloseIcon);
}
#if BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile);
const bool has_primary_account =
!profile->IsGuestSession() &&
identity_manager->HasPrimaryAccount(signin::ConsentLevel::kSync);
bool hide_signout_button_for_managed_profiles =
chrome::enterprise_util::UserAcceptedAccountManagement(profile) &&
base::FeatureList::IsEnabled(kDisallowManagedProfileSignout);
bool add_sign_out_button = has_unconsented_account && !has_primary_account &&
!hide_signout_button_for_managed_profiles;
#if BUILDFLAG(IS_CHROMEOS_LACROS)
// Clearing the primary account is not allowed in the main profile.
add_sign_out_button &= !profile->IsMainProfile();
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
// The sign-out button is always at the bottom.
if (add_sign_out_button) {
AddFeatureButton(
l10n_util::GetStringUTF16(
switches::IsExplicitBrowserSigninUIOnDesktopEnabled()
? IDS_PROFILE_MENU_SIGN_OUT
: IDS_SCREEN_LOCK_SIGN_OUT),
base::BindRepeating(&ProfileMenuView::OnSignoutButtonClicked,
base::Unretained(this)),
kSignOutIcon);
}
#endif // BUILDFLAG(ENABLE_DICE_SUPPORT) || BUILDFLAG(IS_CHROMEOS_LACROS)
}
#if !BUILDFLAG(IS_CHROMEOS_ASH)
void ProfileMenuView::BuildAvailableProfiles() {
bool profiles_selectable = true;
#if BUILDFLAG(IS_CHROMEOS_LACROS)
profiles_selectable = profiles::AreSecondaryProfilesAllowed();
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
#if BUILDFLAG(IS_MAC)
const bool is_regular_web_app =
web_app::AppBrowserController::IsWebApp(browser()) &&
(browser()->app_controller()->app_id() != web_app::kPasswordManagerAppId);
std::set<base::FilePath> available_profiles;
if (is_regular_web_app) {
available_profiles = AppShimRegistry::Get()->GetInstalledProfilesForApp(
browser()->app_controller()->app_id());
}
#endif
auto profile_entries = g_browser_process->profile_manager()
->GetProfileAttributesStorage()
.GetAllProfilesAttributesSortedByNameWithCheck();
for (ProfileAttributesEntry* profile_entry : profile_entries) {
// The current profile is excluded.
if (profile_entry->GetPath() == browser()->profile()->GetPath()) {
continue;
}
if (profile_entry->IsOmitted()) {
continue;
}
#if BUILDFLAG(IS_MAC)
if (is_regular_web_app &&
!available_profiles.contains(profile_entry->GetPath())) {
continue;
}
#endif
AddAvailableProfile(
ui::ImageModel::FromImage(
profile_entry->GetAvatarIcon(profiles::kMenuAvatarIconSize)),
profile_entry->GetName(),
/*is_guest=*/false,
/*is_enabled=*/profiles_selectable,
base::BindRepeating(&ProfileMenuView::OnOtherProfileSelected,
base::Unretained(this), profile_entry->GetPath()));
}
UMA_HISTOGRAM_BOOLEAN("ProfileChooser.HasProfilesShown",
profile_entries.size() > 1);
if (!browser()->profile()->IsGuestSession() &&
profiles::IsGuestModeEnabled() &&
!web_app::AppBrowserController::IsWebApp(browser())) {
AddAvailableProfile(
profiles::GetGuestAvatar(),
l10n_util::GetStringUTF16(
switches::IsExplicitBrowserSigninUIOnDesktopEnabled()
? IDS_PROFILE_MENU_OPEN_GUEST_PROFILE
: IDS_GUEST_PROFILE_NAME),
/*is_guest=*/true,
/*is_enabled=*/true,
base::BindRepeating(&ProfileMenuView::OnGuestProfileButtonClicked,
base::Unretained(this)));
}
}
void ProfileMenuView::BuildProfileManagementFeatureButtons() {
bool profiles_selectable = true;
#if BUILDFLAG(IS_CHROMEOS_LACROS)
profiles_selectable = profiles::AreSecondaryProfilesAllowed();
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
if (switches::IsExplicitBrowserSigninUIOnDesktopEnabled()) {
if (profiles_selectable || profiles::IsProfileCreationAllowed()) {
AddProfileManagementFeaturesSeparator();
}
if (profiles::IsProfileCreationAllowed()) {
AddProfileManagementFeatureButton(
kAccountAddChromeRefreshIcon,
l10n_util::GetStringUTF16(IDS_PROFILE_MENU_ADD_NEW_PROFILE),
base::BindRepeating(&ProfileMenuView::OnAddNewProfileButtonClicked,
base::Unretained(this)));
}
if (profiles_selectable) {
AddProfileManagementFeatureButton(
kAccountManageChromeRefreshIcon,
l10n_util::GetStringUTF16(IDS_PROFILE_MENU_MANAGE_PROFILES),
base::BindRepeating(&ProfileMenuView::OnManageProfilesButtonClicked,
base::Unretained(this)));
} else {
AddProfileManagementManagedHint(
vector_icons::kBusinessIcon,
l10n_util::GetStringUTF16(
IDS_PROFILES_MANAGE_PROFILES_MANAGED_TOOLTIP));
}
} else {
if (profiles_selectable) {
AddProfileManagementShortcutFeatureButton(
features::IsChromeRefresh2023()
? vector_icons::kSettingsChromeRefreshIcon
: vector_icons::kSettingsIcon,
l10n_util::GetStringUTF16(
IDS_PROFILES_MANAGE_PROFILES_BUTTON_TOOLTIP),
base::BindRepeating(&ProfileMenuView::OnManageProfilesButtonClicked,
base::Unretained(this)));
} else {
AddProfileManagementManagedHint(
vector_icons::kBusinessIcon,
l10n_util::GetStringUTF16(
IDS_PROFILES_MANAGE_PROFILES_MANAGED_TOOLTIP));
}
if (profiles::IsProfileCreationAllowed()) {
AddProfileManagementFeatureButton(
features::IsChromeRefresh2023() ? vector_icons::kAddChromeRefreshIcon
: kAddIcon,
l10n_util::GetStringUTF16(IDS_ADD),
base::BindRepeating(&ProfileMenuView::OnAddNewProfileButtonClicked,
base::Unretained(this)));
}
}
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)