Chromium Code Reviews| Index: chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm |
| diff --git a/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm b/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm |
| index 2b8dbf2e385f24fcc263f5e6a2abb4d259376a46..305837f176096291be946312e55b20fd276495a8 100644 |
| --- a/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm |
| +++ b/chrome/browser/ui/cocoa/profiles/profile_chooser_controller.mm |
| @@ -32,6 +32,7 @@ |
| #include "chrome/browser/signin/signin_manager_factory.h" |
| #include "chrome/browser/signin/signin_promo.h" |
| #include "chrome/browser/signin/signin_ui_util.h" |
| +#include "chrome/browser/sync/profile_sync_service_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/browser_list.h" |
| @@ -51,11 +52,13 @@ |
| #include "chrome/common/url_constants.h" |
| #include "chrome/grit/chromium_strings.h" |
| #include "chrome/grit/generated_resources.h" |
| +#include "components/browser_sync/browser/profile_sync_service.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/signin/core/browser/profile_oauth2_token_service.h" |
| #include "components/signin/core/browser/signin_manager.h" |
| #include "components/signin/core/browser/signin_metrics.h" |
| #include "components/signin/core/common/profile_management_switches.h" |
| +#include "components/sync_driver/sync_error_controller.h" |
| #include "content/public/browser/native_web_keyboard_event.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| @@ -931,10 +934,11 @@ class ActiveProfileObserverBridge : public AvatarMenuObserver, |
| // Builds the regular profile chooser view. |
| - (void)buildProfileChooserViewWithProfileView:(NSView*)currentProfileView |
| tutorialView:(NSView*)tutorialView |
| + syncErrorView:(NSView*)syncErrorView |
| otherProfiles:(NSArray*)otherProfiles |
| atYOffset:(CGFloat)yOffset |
| inContainer:(NSView*)container |
| - showLock:(bool)showLock; |
| + showLock:(bool)showLock; |
| // Builds the profile chooser view. |
| - (NSView*)buildProfileChooserView; |
| @@ -957,6 +961,12 @@ class ActiveProfileObserverBridge : public AvatarMenuObserver, |
| linkAction:(SEL)linkAction |
| buttonAction:(SEL)buttonAction; |
| +// Builds a header for signin and sync error surfacing on the user menu. |
| +- (NSView*)buildSyncErrorViewIfNeeded; |
| +- (NSView*)buildSyncErrorViewWithContent:(int)contentStringId |
| + buttonStringId:(int)buttonStringId |
| + buttonAction:(SEL)buttonAction; |
| + |
| // Builds a tutorial card to introduce an upgrade user to the new avatar menu if |
| // needed. |tutorial_shown| indicates if the tutorial has already been shown in |
| // the previous active view. |avatar_item| refers to the current profile. |
| @@ -1165,11 +1175,32 @@ class ActiveProfileObserverBridge : public AvatarMenuObserver, |
| [self initMenuContentsWithView:profiles::BUBBLE_VIEW_MODE_ACCOUNT_REMOVAL]; |
| } |
| +- (IBAction)showSignoutView:(id)sender { |
| + chrome::ShowSettingsSubPage(browser_, chrome::kSignOutSubPage); |
| +} |
| + |
| +- (IBAction)showSignoutSigninView:(id)sender { |
| + if (ProfileSyncServiceFactory::GetForProfile(browser_->profile())) |
| + ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS); |
| + SigninManagerFactory::GetForProfile(browser_->profile()) |
| + ->SignOut(signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS, |
| + signin_metrics::SignoutDelete::IGNORE_METRIC); |
| + [self showSigninUIForMode:profiles::BUBBLE_VIEW_MODE_GAIA_SIGNIN]; |
| +} |
| + |
| - (IBAction)showAccountReauthenticationView:(id)sender { |
| DCHECK(!isGuestSession_); |
| [self showSigninUIForMode:profiles::BUBBLE_VIEW_MODE_GAIA_REAUTH]; |
| } |
| +- (IBAction)showUpdateChromeView:(id)sender { |
| + chrome::OpenUpdateChromeDialog(browser_); |
| +} |
| + |
| +- (IBAction)showSyncPassphraseSetupView:(id)sender { |
| + chrome::ShowSettingsSubPage(browser_, chrome::kSyncSetupSubPage); |
| +} |
| + |
| - (IBAction)removeAccount:(id)sender { |
| DCHECK(!accountIdToRemove_.empty()); |
| ProfileOAuth2TokenServiceFactory::GetForProfile(browser_->profile()) |
| @@ -1404,10 +1435,11 @@ class ActiveProfileObserverBridge : public AvatarMenuObserver, |
| - (void)buildProfileChooserViewWithProfileView:(NSView*)currentProfileView |
| tutorialView:(NSView*)tutorialView |
| + syncErrorView:(NSView*)syncErrorView |
| otherProfiles:(NSArray*)otherProfiles |
| atYOffset:(CGFloat)yOffset |
| inContainer:(NSView*)container |
| - showLock:(bool)showLock { |
| + showLock:(bool)showLock { |
| if (switches::IsMaterialDesignUserMenu()) |
| yOffset += kRelatedControllVerticalSpacing; |
| @@ -1450,14 +1482,23 @@ class ActiveProfileObserverBridge : public AvatarMenuObserver, |
| } |
| } |
| + const AvatarMenu::Item& item = |
| + avatarMenu_->GetItemAt(avatarMenu_->GetActiveProfileIndex()); |
| if (viewMode_ == profiles::BUBBLE_VIEW_MODE_ACCOUNT_MANAGEMENT) { |
| - NSView* currentProfileAccountsView = [self |
| - createCurrentProfileAccountsView:NSMakeRect(0, yOffset, |
| - GetFixedMenuWidth(), 0)]; |
| - [container addSubview:currentProfileAccountsView]; |
| - yOffset = NSMaxY([currentProfileAccountsView frame]); |
| + if (item.signed_in) { |
| + NSView* currentProfileAccountsView = [self |
| + createCurrentProfileAccountsView:NSMakeRect(0, yOffset, |
| + GetFixedMenuWidth(), 0)]; |
| + [container addSubview:currentProfileAccountsView]; |
| + yOffset = NSMaxY([currentProfileAccountsView frame]); |
| - yOffset = [self addSeparatorToContainer:container atYOffset: yOffset]; |
| + yOffset = [self addSeparatorToContainer:container atYOffset:yOffset]; |
| + } else { |
| + // This is the case when the user selects the sign out option in the user |
| + // menu upon encountering unrecoverable errors. Afterwards, the profile |
| + // chooser view is shown instead of the account management view. |
| + viewMode_ = profiles::BUBBLE_VIEW_MODE_FAST_PROFILE_CHOOSER; |
| + } |
| } |
| // Active profile card. |
| @@ -1471,6 +1512,13 @@ class ActiveProfileObserverBridge : public AvatarMenuObserver, |
| yOffset = NSMaxY([currentProfileView frame]) + verticalSpacing; |
| } |
| + if (syncErrorView) { |
| + yOffset = [self addSeparatorToContainer:container atYOffset:yOffset]; |
| + [syncErrorView setFrameOrigin:NSMakePoint(0, yOffset)]; |
| + [container addSubview:syncErrorView]; |
| + yOffset = NSMaxY([syncErrorView frame]); |
| + } |
| + |
| if (tutorialView) { |
| [tutorialView setFrameOrigin:NSMakePoint(0, yOffset)]; |
| [container addSubview:tutorialView]; |
| @@ -1488,6 +1536,7 @@ class ActiveProfileObserverBridge : public AvatarMenuObserver, |
| [[NSView alloc] initWithFrame:NSZeroRect]); |
| NSView* tutorialView = nil; |
| + NSView* syncErrorView = nil; |
| NSView* currentProfileView = nil; |
| base::scoped_nsobject<NSMutableArray> otherProfiles( |
| [[NSMutableArray alloc] init]); |
| @@ -1507,7 +1556,9 @@ class ActiveProfileObserverBridge : public AvatarMenuObserver, |
| for (int i = avatarMenu_->GetNumberOfItems() - 1; i >= 0; --i) { |
| const AvatarMenu::Item& item = avatarMenu_->GetItemAt(i); |
| if (item.active) { |
| - if (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) { |
| + if (switches::IsMaterialDesignUserMenu()) { |
| + syncErrorView = [self buildSyncErrorViewIfNeeded]; |
| + } else if (viewMode_ == profiles::BUBBLE_VIEW_MODE_PROFILE_CHOOSER) { |
| tutorialView = [self buildTutorialViewIfNeededForItem:item]; |
| } |
| currentProfileView = |
| @@ -1537,10 +1588,11 @@ class ActiveProfileObserverBridge : public AvatarMenuObserver, |
| } else { |
| [self buildProfileChooserViewWithProfileView:currentProfileView |
| tutorialView:tutorialView |
| + syncErrorView:syncErrorView |
| otherProfiles:otherProfiles.get() |
| atYOffset:yOffset |
| inContainer:container |
| - showLock:showLock]; |
| + showLock:showLock]; |
| } |
| return container.autorelease(); |
| @@ -1803,6 +1855,135 @@ class ActiveProfileObserverBridge : public AvatarMenuObserver, |
| return container.autorelease(); |
| } |
| +- (NSView*)buildSyncErrorViewIfNeeded { |
| + ProfileSyncService* service = |
| + ProfileSyncServiceFactory::GetForProfile(browser_->profile()); |
| + |
| + // The order or priority is going to be: 1. Unrecoverable errors. |
| + // 2. Auth errors. 3. Protocol errors. 4. Passphrase errors. |
| + if (service && service->HasUnrecoverableError()) { |
|
groby-ooo-7-16
2016/07/22 23:53:16
It seems a ton of this logic is shared with the Vi
|
| + // An unrecoverable error is sometimes accompanied by an actionable error. |
| + // If an actionable error is not set to be UPGRADE_CLIENT, then show a |
| + // generic unrecoverable error message. |
| + ProfileSyncService::Status status; |
| + service->QueryDetailedSyncStatus(&status); |
| + if (status.sync_protocol_error.action != syncer::UPGRADE_CLIENT) { |
| + // Display different messages and buttons for managed accounts. |
| + if (SigninManagerFactory::GetForProfile(browser_->profile()) |
| + ->IsSignoutProhibited()) { |
| + // For a managed user, the user is directed to the signout |
| + // confirmation dialogue in the settings page. |
| + return [self buildSyncErrorViewWithContent: |
| + IDS_SYNC_ERROR_USER_MENU_SIGNOUT_MESSAGE |
| + buttonStringId: |
| + IDS_SYNC_ERROR_USER_MENU_SIGNOUT_BUTTON |
| + buttonAction:@selector(showSignoutView:)]; |
| + } |
| + // For a non-managed user, we sign out on the user's behalf and prompt |
| + // the user to sign in again. |
| + return [self |
| + buildSyncErrorViewWithContent: |
| + IDS_SYNC_ERROR_USER_MENU_SIGNIN_AGAIN_MESSAGE |
| + buttonStringId: |
| + IDS_SYNC_ERROR_USER_MENU_SIGNIN_AGAIN_BUTTON |
| + buttonAction:@selector(showSignoutSigninView:)]; |
| + } |
| + } |
| + |
| + // Check for an auth error. |
| + if (HasAuthError(browser_->profile())) { |
| + return [self |
| + buildSyncErrorViewWithContent:IDS_SYNC_ERROR_USER_MENU_SIGNIN_MESSAGE |
| + buttonStringId:IDS_SYNC_ERROR_USER_MENU_SIGNIN_BUTTON |
| + buttonAction:@selector( |
| + showAccountReauthenticationView:)]; |
| + } |
| + |
| + // Check for sync errors if the sync service is enabled. |
| + if (service) { |
| + // Check for an actionable UPGRADE_CLIENT error. |
| + ProfileSyncService::Status status; |
| + service->QueryDetailedSyncStatus(&status); |
| + if (status.sync_protocol_error.action == syncer::UPGRADE_CLIENT) { |
| + return [self |
| + buildSyncErrorViewWithContent:IDS_SYNC_ERROR_USER_MENU_UPGRADE_MESSAGE |
| + buttonStringId:IDS_SYNC_ERROR_USER_MENU_UPGRADE_BUTTON |
| + buttonAction:@selector(showUpdateChromeView:)]; |
| + } |
| + |
| + // Check for a sync passphrase error. |
| + SyncErrorController* sync_error_controller = |
| + service->sync_error_controller(); |
| + if (sync_error_controller && sync_error_controller->HasError()) { |
| + return [self |
| + buildSyncErrorViewWithContent: |
| + IDS_SYNC_ERROR_USER_MENU_PASSPHRASE_MESSAGE |
| + buttonStringId: |
| + IDS_SYNC_ERROR_USER_MENU_PASSPHRASE_BUTTON |
| + buttonAction:@selector( |
| + showSyncPassphraseSetupView:)]; |
| + } |
| + } |
| + |
| + // There is no error. |
| + return nil; |
| +} |
| + |
| +- (NSView*)buildSyncErrorViewWithContent:(int)contentStringId |
| + buttonStringId:(int)buttonStringId |
| + buttonAction:(SEL)buttonAction { |
| + base::scoped_nsobject<NSView> container( |
| + [[NSView alloc] initWithFrame:NSMakeRect(0, 0, GetFixedMenuWidth(), 0)]); |
| + CGFloat iconSize = 20.0; |
| + CGFloat xOffset = kHorizontalSpacing + iconSize + 12.0; |
| + CGFloat availableWidth = GetFixedMenuWidth() - xOffset - kHorizontalSpacing; |
| + CGFloat yOffset = 20.0; |
| + |
| + // Adds an action button for resolving the error at the bottom. |
| + base::scoped_nsobject<NSButton> resolveErrorButton( |
| + [[BlueLabelButton alloc] initWithFrame:NSZeroRect]); |
| + [resolveErrorButton setTitle:l10n_util::GetNSString(buttonStringId)]; |
| + [resolveErrorButton setTarget:self]; |
| + [resolveErrorButton setAction:buttonAction]; |
| + [resolveErrorButton setAlignment:NSCenterTextAlignment]; |
| + [resolveErrorButton sizeToFit]; |
| + [resolveErrorButton setFrameOrigin:NSMakePoint(xOffset, yOffset)]; |
| + [container addSubview:resolveErrorButton]; |
| + yOffset = NSMaxY([resolveErrorButton frame]) + kVerticalSpacing; |
| + |
| + // Adds the error message content. |
| + NSTextField* contentLabel = |
| + BuildLabel(l10n_util::GetNSString(contentStringId), |
| + NSMakePoint(xOffset, yOffset), nil); |
| + [contentLabel setFrameSize:NSMakeSize(availableWidth, 0)]; |
| + [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:contentLabel]; |
| + [container addSubview:contentLabel]; |
| + yOffset = NSMaxY([contentLabel frame]) + 4; |
| + |
| + // Adds the title for the error card. |
| + NSTextField* titleLabel = |
| + BuildLabel(l10n_util::GetNSString(IDS_SYNC_ERROR_USER_MENU_TITLE), |
| + NSMakePoint(xOffset, yOffset), |
| + skia::SkColorToCalibratedNSColor(gfx::kGoogleRed700)); |
| + [titleLabel setFrameSize:NSMakeSize(availableWidth, 0)]; |
| + [GTMUILocalizerAndLayoutTweaker sizeToFitFixedWidthTextField:titleLabel]; |
| + [container addSubview:titleLabel]; |
| + yOffset = NSMaxY([titleLabel frame]); |
| + |
| + // Adds the sync problem icon. |
| + base::scoped_nsobject<NSImageView> syncProblemIcon([[NSImageView alloc] |
| + initWithFrame:NSMakeRect(kHorizontalSpacing, yOffset - iconSize, iconSize, |
| + iconSize)]); |
| + [syncProblemIcon setImage:NSImageFromImageSkia(gfx::CreateVectorIcon( |
| + gfx::VectorIconId::SYNC_PROBLEM, iconSize, |
| + gfx::kGoogleRed700))]; |
| + [container addSubview:syncProblemIcon]; |
| + |
| + [container |
| + setFrameSize:NSMakeSize(GetFixedMenuWidth(), yOffset + kVerticalSpacing)]; |
| + return container.autorelease(); |
| +} |
| + |
| - (NSView*)createCurrentProfileView:(const AvatarMenu::Item&)item { |
| base::scoped_nsobject<NSView> container([[NSView alloc] |
| initWithFrame:NSZeroRect]); |