1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/ui/cocoa/profiles/user_manager_mac.h"
7 #include "base/mac/foundation_util.h"
8 #include "chrome/app/chrome_command_ids.h"
9 #import "chrome/browser/app_controller_mac.h"
10 #include "chrome/browser/browser_process.h"
11 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
12 #include "chrome/browser/profiles/profile_manager.h"
13 #include "chrome/browser/profiles/profile_metrics.h"
14 #include "chrome/browser/profiles/profiles_state.h"
15 #include "chrome/browser/signin/signin_promo.h"
16 #include "chrome/browser/ui/browser_dialogs.h"
17 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
18 #include "chrome/browser/ui/cocoa/chrome_event_processing_window.h"
19 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h"
20 #import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_window.h"
21 #include "chrome/browser/ui/cocoa/constrained_window/constrained_window_mac.h"
22 #include "chrome/browser/ui/user_manager.h"
23 #include "chrome/grit/chromium_strings.h"
24 #include "components/web_modal/web_contents_modal_dialog_host.h"
25 #include "components/web_modal/web_contents_modal_dialog_manager.h"
26 #include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
27 #include "content/public/browser/native_web_keyboard_event.h"
28 #include "content/public/browser/render_widget_host_view.h"
29 #include "content/public/browser/web_contents.h"
30 #include "content/public/browser/web_contents_delegate.h"
31 #include "ui/base/l10n/l10n_util_mac.h"
32 #include "ui/events/keycodes/keyboard_codes.h"
36 // Update the App Controller with a new Profile. Used when a Profile is locked
37 // to set the Controller to the Guest profile so the old Profile's bookmarks,
38 // etc... cannot be accessed.
39 void ChangeAppControllerForProfile(Profile* profile,
40 Profile::CreateStatus status) {
41 if (status == Profile::CREATE_STATUS_INITIALIZED) {
42 AppController* controller =
43 base::mac::ObjCCast<AppController>([NSApp delegate]);
44 [controller windowChangedToProfile:profile];
50 // An open User Manager window. There can only be one open at a time. This
51 // is reset to NULL when the window is closed.
52 UserManagerMac* instance_ = NULL; // Weak.
53 BOOL instance_under_construction_ = NO;
55 void CloseInstanceReauthDialog() {
57 instance_->CloseReauthDialog();
60 // The modal dialog host the User Manager uses to display the reauth dialog.
61 class UserManagerModalHost : public web_modal::WebContentsModalDialogHost {
63 UserManagerModalHost(gfx::NativeView host_view)
64 : host_view_(host_view) {}
66 gfx::Size GetMaximumDialogSize() override {
68 UserManager::kReauthDialogWidth, UserManager::kReauthDialogHeight);
71 ~UserManagerModalHost() override {}
73 gfx::NativeView GetHostView() const override {
77 gfx::Point GetDialogPosition(const gfx::Size& size) override {
78 return gfx::Point(0, 0);
81 void AddObserver(web_modal::ModalDialogHostObserver* observer) override {}
82 void RemoveObserver(web_modal::ModalDialogHostObserver* observer) override {}
85 gfx::NativeView host_view_;
88 // The modal manager delegate allowing the display of constrained windows for
90 class UserManagerModalManagerDelegate :
91 public web_modal::WebContentsModalDialogManagerDelegate {
93 UserManagerModalManagerDelegate(gfx::NativeView host_view) {
94 modal_host_.reset(new UserManagerModalHost(host_view));
97 web_modal::WebContentsModalDialogHost* GetWebContentsModalDialogHost()
99 return modal_host_.get();
102 bool IsWebContentsVisible(content::WebContents* web_contents) override {
106 ~UserManagerModalManagerDelegate() override {}
108 scoped_ptr<UserManagerModalHost> modal_host_;
111 // Custom WebContentsDelegate that allows handling of hotkeys.
112 class UserManagerWebContentsDelegate : public content::WebContentsDelegate {
114 UserManagerWebContentsDelegate() {}
116 // WebContentsDelegate implementation. Forwards all unhandled keyboard events
117 // to the current window.
118 void HandleKeyboardEvent(
119 content::WebContents* source,
120 const content::NativeWebKeyboardEvent& event) override {
121 if (![BrowserWindowUtils shouldHandleKeyboardEvent:event])
124 // -getCommandId returns -1 if the event isn't a chrome accelerator.
125 int chromeCommandId = [BrowserWindowUtils getCommandId:event];
127 // Check for Cmd+A and Cmd+V events that could come from a password field.
128 bool isTextEditingCommand =
129 (event.modifiers & blink::WebInputEvent::MetaKey) &&
130 (event.windowsKeyCode == ui::VKEY_A ||
131 event.windowsKeyCode == ui::VKEY_V);
133 // Only handle close window Chrome accelerators and text editing ones.
134 if (chromeCommandId == IDC_CLOSE_WINDOW || chromeCommandId == IDC_EXIT ||
135 isTextEditingCommand) {
136 [[NSApp mainMenu] performKeyEquivalent:event.os_event];
141 class ReauthDialogDelegate : public UserManager::ReauthDialogObserver,
142 public UserManagerWebContentsDelegate,
143 public ConstrainedWindowMacDelegate {
145 ReauthDialogDelegate(content::WebContents* web_contents, std::string email)
146 : UserManager::ReauthDialogObserver(web_contents, email) {}
148 // UserManager::ReauthDialogObserver:
149 void CloseReauthDialog() override {
150 CloseInstanceReauthDialog();
153 // ConstrainedWindowMacDelegate:
154 void OnConstrainedWindowClosed(ConstrainedWindowMac* window) override {
158 DISALLOW_COPY_AND_ASSIGN(ReauthDialogDelegate);
161 // WindowController for the reauth dialog.
162 @interface ReauthDialogWindowController
163 : NSWindowController <NSWindowDelegate> {
165 std::string emailAddress_;
166 content::WebContents* webContents_;
167 scoped_ptr<ReauthDialogDelegate> webContentsDelegate_;
168 scoped_ptr<ConstrainedWindowMac> constrained_window_;
169 scoped_ptr<content::WebContents> reauthWebContents_;
171 - (id)initWithProfile:(Profile*)profile
172 email:(std::string)email
173 webContents:(content::WebContents*)webContents;
177 @implementation ReauthDialogWindowController
179 - (id)initWithProfile:(Profile*)profile
180 email:(std::string)email
181 webContents:(content::WebContents*)webContents {
182 webContents_ = webContents;
183 emailAddress_ = email;
185 NSRect frame = NSMakeRect(
186 0, 0, UserManager::kReauthDialogWidth, UserManager::kReauthDialogHeight);
187 base::scoped_nsobject<ConstrainedWindowCustomWindow> window(
188 [[ConstrainedWindowCustomWindow alloc]
189 initWithContentRect:frame
190 styleMask:NSTitledWindowMask | NSClosableWindowMask]);
191 if ((self = [super initWithWindow:window])) {
192 webContents_ = webContents;
194 reauthWebContents_.reset(content::WebContents::Create(
195 content::WebContents::CreateParams(profile)));
196 window.get().contentView = reauthWebContents_->GetNativeView();
197 webContentsDelegate_.reset(
198 new ReauthDialogDelegate(reauthWebContents_.get(), emailAddress_));
199 reauthWebContents_->SetDelegate(webContentsDelegate_.get());
201 base::scoped_nsobject<CustomConstrainedWindowSheet> sheet(
202 [[CustomConstrainedWindowSheet alloc]
203 initWithCustomWindow:[self window]]);
204 constrained_window_.reset(
205 new ConstrainedWindowMac(
206 webContentsDelegate_.get(), webContents_, sheet));
208 // The close button needs to call CloseWebContentsModalDialog() on the
209 // constrained window isntead of just [window close] so grab a reference to
210 // it in the title bar and change its action.
211 auto closeButton = [window standardWindowButton:NSWindowCloseButton];
212 [closeButton setTarget:self];
213 [closeButton setAction:@selector(closeButtonClicked:)];
221 GURL url = signin::GetReauthURLWithEmail(emailAddress_);
222 reauthWebContents_->GetController().LoadURL(url, content::Referrer(),
223 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
227 - (void)closeButtonClicked:(NSButton*)button {
232 constrained_window_->CloseWebContentsModalDialog();
237 // Window controller for the User Manager view.
238 @interface UserManagerWindowController : NSWindowController <NSWindowDelegate> {
240 scoped_ptr<content::WebContents> webContents_;
241 scoped_ptr<UserManagerWebContentsDelegate> webContentsDelegate_;
242 UserManagerMac* userManagerObserver_; // Weak.
243 scoped_ptr<UserManagerModalManagerDelegate> modal_manager_delegate_;
244 base::scoped_nsobject<ReauthDialogWindowController> reauth_window_controller_;
246 - (void)windowWillClose:(NSNotification*)notification;
248 - (id)initWithProfile:(Profile*)profile
249 withObserver:(UserManagerMac*)userManagerObserver;
250 - (void)showURL:(const GURL&)url;
254 - (void)showReauthDialogWithProfile:(Profile*)profile email:(std::string)email;
255 - (void)closeReauthDialog;
258 @implementation UserManagerWindowController
260 - (id)initWithProfile:(Profile*)profile
261 withObserver:(UserManagerMac*)userManagerObserver {
262 // Center the window on the screen that currently has focus.
263 NSScreen* mainScreen = [NSScreen mainScreen];
264 CGFloat screenHeight = [mainScreen frame].size.height;
265 CGFloat screenWidth = [mainScreen frame].size.width;
268 NSMakeRect((screenWidth - UserManager::kWindowWidth) / 2,
269 (screenHeight - UserManager::kWindowHeight) / 2,
270 UserManager::kWindowWidth, UserManager::kWindowHeight);
271 ChromeEventProcessingWindow* window = [[ChromeEventProcessingWindow alloc]
272 initWithContentRect:contentRect
273 styleMask:NSTitledWindowMask |
274 NSClosableWindowMask |
275 NSResizableWindowMask
276 backing:NSBackingStoreBuffered
279 [window setTitle:l10n_util::GetNSString(IDS_PRODUCT_NAME)];
280 [window setMinSize:NSMakeSize(UserManager::kWindowWidth,
281 UserManager::kWindowHeight)];
283 if ((self = [super initWithWindow:window])) {
284 userManagerObserver_ = userManagerObserver;
286 // Initialize the web view.
287 webContents_.reset(content::WebContents::Create(
288 content::WebContents::CreateParams(profile)));
289 window.contentView = webContents_->GetNativeView();
290 webContentsDelegate_.reset(new UserManagerWebContentsDelegate());
291 webContents_->SetDelegate(webContentsDelegate_.get());
293 [[NSNotificationCenter defaultCenter]
295 selector:@selector(windowWillClose:)
296 name:NSWindowWillCloseNotification
303 [[NSNotificationCenter defaultCenter] removeObserver:self];
304 // Remove the ModalDailogManager that's about to be destroyed.
305 auto manager = web_modal::WebContentsModalDialogManager::FromWebContents(
308 manager->SetDelegate(nullptr);
313 - (void)showURL:(const GURL&)url {
314 webContents_->GetController().LoadURL(url, content::Referrer(),
315 ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
317 content::RenderWidgetHostView* rwhv = webContents_->GetRenderWidgetHostView();
319 rwhv->SetBackgroundColor(profiles::kUserManagerBackgroundColor);
324 // Because the User Manager isn't a BrowserWindowController, activating it
325 // will not trigger a -windowChangedToProfile and update the menu bar.
326 // This is only important if the active profile is Guest, which may have
327 // happened after locking a profile.
328 if (profiles::SetActiveProfileToGuestIfLocked()) {
329 g_browser_process->profile_manager()->CreateProfileAsync(
330 ProfileManager::GetGuestProfilePath(),
331 base::Bind(&ChangeAppControllerForProfile),
336 [[self window] makeKeyAndOrderFront:self];
340 [[self window] close];
344 return [[self window] isVisible];
347 - (void)windowWillClose:(NSNotification*)notification {
348 [[NSNotificationCenter defaultCenter] removeObserver:self];
349 DCHECK(userManagerObserver_);
350 userManagerObserver_->WindowWasClosed();
353 - (void)showReauthDialogWithProfile:(Profile*)profile email:(std::string)email {
354 // Make sure there's a WebContentsModalDialogManager for this UserManager's
356 web_modal::WebContentsModalDialogManager::CreateForWebContents(
358 modal_manager_delegate_.reset(
359 new UserManagerModalManagerDelegate([[self window] contentView]));
360 web_modal::WebContentsModalDialogManager::FromWebContents(
361 webContents_.get())->SetDelegate(modal_manager_delegate_.get());
362 reauth_window_controller_.reset(
363 [[ReauthDialogWindowController alloc]
364 initWithProfile:profile
366 webContents:webContents_.get()]);
369 - (void)closeReauthDialog {
370 [reauth_window_controller_ close];
377 void UserManager::Show(
378 const base::FilePath& profile_path_to_focus,
379 profiles::UserManagerTutorialMode tutorial_mode,
380 profiles::UserManagerProfileSelected profile_open_action) {
381 DCHECK(profile_path_to_focus != ProfileManager::GetGuestProfilePath());
383 ProfileMetrics::LogProfileOpenMethod(ProfileMetrics::OPEN_USER_MANAGER);
385 // If there's a user manager window open already, just activate it.
386 [instance_->window_controller() show];
387 instance_->set_user_manager_started_showing(base::Time::Now());
391 // Under some startup conditions, we can try twice to create the User Manager.
392 // Because creating the System profile is asynchronous, it's possible for
393 // there to then be multiple pending operations and eventually multiple
395 if (instance_under_construction_)
397 instance_under_construction_ = YES;
399 // Create the guest profile, if necessary, and open the User Manager
400 // from the guest profile.
401 profiles::CreateSystemProfileForUserManager(
402 profile_path_to_focus,
405 base::Bind(&UserManagerMac::OnSystemProfileCreated, base::Time::Now()));
409 void UserManager::Hide() {
411 [instance_->window_controller() close];
415 bool UserManager::IsShowing() {
416 return instance_ ? [instance_->window_controller() isVisible]: false;
420 void UserManager::OnUserManagerShown() {
422 instance_->LogTimeToOpen();
426 void UserManager::ShowReauthDialog(content::BrowserContext* browser_context,
427 const std::string& email) {
429 instance_->ShowReauthDialog(browser_context, email);
432 void UserManagerMac::ShowReauthDialog(content::BrowserContext* browser_context,
433 const std::string& email) {
435 showReauthDialogWithProfile:Profile::FromBrowserContext(browser_context)
439 void UserManagerMac::CloseReauthDialog() {
440 [window_controller_ closeReauthDialog];
443 UserManagerMac::UserManagerMac(Profile* profile) {
444 window_controller_.reset([[UserManagerWindowController alloc]
445 initWithProfile:profile withObserver:this]);
448 UserManagerMac::~UserManagerMac() {
452 void UserManagerMac::OnSystemProfileCreated(const base::Time& start_time,
453 Profile* system_profile,
454 const std::string& url) {
456 instance_ = new UserManagerMac(system_profile);
457 instance_->set_user_manager_started_showing(start_time);
458 [instance_->window_controller() showURL:GURL(url)];
459 instance_under_construction_ = NO;
462 void UserManagerMac::LogTimeToOpen() {
463 if (user_manager_started_showing_ == base::Time())
466 ProfileMetrics::LogTimeToOpenUserManager(
467 base::Time::Now() - user_manager_started_showing_);
468 user_manager_started_showing_ = base::Time();
471 void UserManagerMac::WindowWasClosed() {