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.
4
5 #include "chrome/browser/ui/views/profiles/user_manager_view.h"
6
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "base/memory/ptr_util.h"
10 #include "base/time/time.h"
11 #include "build/build_config.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/password_manager/chrome_password_manager_client.h"
14 #include "chrome/browser/platform_util.h"
15 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
16 #include "chrome/browser/profiles/profile_manager.h"
17 #include "chrome/browser/profiles/profile_metrics.h"
18 #include "chrome/browser/profiles/profile_window.h"
19 #include "chrome/browser/profiles/profiles_state.h"
20 #include "chrome/browser/signin/signin_promo.h"
21 #include "chrome/browser/signin/signin_util.h"
22 #include "chrome/browser/task_manager/web_contents_tags.h"
23 #include "chrome/browser/ui/autofill/chrome_autofill_client.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/browser/ui/browser_dialogs.h"
26 #include "chrome/browser/ui/browser_finder.h"
27 #include "chrome/browser/ui/browser_window.h"
28 #include "chrome/browser/ui/profile_picker.h"
29 #include "chrome/browser/ui/ui_features.h"
30 #include "chrome/browser/ui/user_manager.h"
31 #include "chrome/common/url_constants.h"
32 #include "chrome/grit/chromium_strings.h"
33 #include "chrome/grit/generated_resources.h"
34 #include "components/guest_view/browser/guest_view_manager.h"
35 #include "components/keep_alive_registry/keep_alive_types.h"
36 #include "components/keep_alive_registry/scoped_keep_alive.h"
37 #include "components/web_modal/web_contents_modal_dialog_manager.h"
38 #include "content/public/browser/navigation_details.h"
39 #include "content/public/browser/render_widget_host_view.h"
40 #include "content/public/browser/web_contents.h"
41 #include "content/public/browser/web_contents_user_data.h"
42 #include "google_apis/gaia/gaia_urls.h"
43 #include "ui/display/display.h"
44 #include "ui/display/screen.h"
45 #include "ui/views/controls/webview/webview.h"
46 #include "ui/views/layout/fill_layout.h"
47 #include "ui/views/view.h"
48 #include "ui/views/widget/widget.h"
49
50 #if defined(OS_WIN)
51 #include "chrome/browser/shell_integration_win.h"
52 #include "ui/base/win/shell.h"
53 #include "ui/views/win/hwnd_util.h"
54 #endif
55
56 #if defined(OS_MAC)
57 #include "chrome/browser/app_controller_mac.h"
58 #endif
59
60 namespace {
61
62 // An open User Manager window. There can only be one open at a time. This
63 // is reset to nullptr when the window is closed.
64 UserManagerView* g_user_manager_view = nullptr;
65 base::OnceClosure* g_user_manager_shown_callback_for_testing = nullptr;
66 bool g_is_user_manager_view_under_construction = false;
67 } // namespace
68
69 // Delegate---------------------------------------------------------------
70
UserManagerProfileDialogDelegate(UserManagerView * parent,views::WebView * web_view,const std::string & email_address,const GURL & url)71 UserManagerProfileDialogDelegate::UserManagerProfileDialogDelegate(
72 UserManagerView* parent,
73 views::WebView* web_view,
74 const std::string& email_address,
75 const GURL& url)
76 : parent_(parent), web_view_(web_view), email_address_(email_address) {
77 SetHasWindowSizeControls(true);
78 SetTitle(IDS_PROFILES_GAIA_SIGNIN_TITLE);
79 SetButtons(ui::DIALOG_BUTTON_NONE);
80 set_use_custom_frame(false);
81
82 AddChildView(web_view_);
83 SetLayoutManager(std::make_unique<views::FillLayout>());
84
85 web_view_->GetWebContents()->SetDelegate(this);
86
87 ChromePasswordManagerClient::CreateForWebContentsWithAutofillClient(
88 web_view_->GetWebContents(),
89 autofill::ChromeAutofillClient::FromWebContents(
90 web_view_->GetWebContents()));
91
92 web_modal::WebContentsModalDialogManager::CreateForWebContents(
93 web_view_->GetWebContents());
94 web_modal::WebContentsModalDialogManager::FromWebContents(
95 web_view_->GetWebContents())
96 ->SetDelegate(this);
97
98 web_view_->LoadInitialURL(url);
99
100 chrome::RecordDialogCreation(chrome::DialogIdentifier::USER_MANAGER_PROFILE);
101 }
102
~UserManagerProfileDialogDelegate()103 UserManagerProfileDialogDelegate::~UserManagerProfileDialogDelegate() {}
104
CalculatePreferredSize() const105 gfx::Size UserManagerProfileDialogDelegate::CalculatePreferredSize() const {
106 return gfx::Size(UserManagerProfileDialog::kDialogWidth,
107 UserManagerProfileDialog::kDialogHeight);
108 }
109
DisplayErrorMessage()110 void UserManagerProfileDialogDelegate::DisplayErrorMessage() {
111 web_view_->LoadInitialURL(GURL(chrome::kChromeUISigninErrorURL));
112 }
113
114 web_modal::WebContentsModalDialogHost*
GetWebContentsModalDialogHost()115 UserManagerProfileDialogDelegate::GetWebContentsModalDialogHost() {
116 return this;
117 }
118
GetHostView() const119 gfx::NativeView UserManagerProfileDialogDelegate::GetHostView() const {
120 return GetWidget()->GetNativeView();
121 }
122
GetDialogPosition(const gfx::Size & size)123 gfx::Point UserManagerProfileDialogDelegate::GetDialogPosition(
124 const gfx::Size& size) {
125 gfx::Size widget_size = GetWidget()->GetWindowBoundsInScreen().size();
126 return gfx::Point(std::max(0, (widget_size.width() - size.width()) / 2),
127 std::max(0, (widget_size.height() - size.height()) / 2));
128 }
129
GetMaximumDialogSize()130 gfx::Size UserManagerProfileDialogDelegate::GetMaximumDialogSize() {
131 return GetWidget()->GetWindowBoundsInScreen().size();
132 }
133
AddObserver(web_modal::ModalDialogHostObserver * observer)134 void UserManagerProfileDialogDelegate::AddObserver(
135 web_modal::ModalDialogHostObserver* observer) {}
136
RemoveObserver(web_modal::ModalDialogHostObserver * observer)137 void UserManagerProfileDialogDelegate::RemoveObserver(
138 web_modal::ModalDialogHostObserver* observer) {}
139
GetModalType() const140 ui::ModalType UserManagerProfileDialogDelegate::GetModalType() const {
141 return ui::MODAL_TYPE_WINDOW;
142 }
143
DeleteDelegate()144 void UserManagerProfileDialogDelegate::DeleteDelegate() {
145 OnDialogDestroyed();
146 delete this;
147 }
148
GetInitiallyFocusedView()149 views::View* UserManagerProfileDialogDelegate::GetInitiallyFocusedView() {
150 return static_cast<views::View*>(web_view_);
151 }
152
CloseDialog()153 void UserManagerProfileDialogDelegate::CloseDialog() {
154 OnDialogDestroyed();
155 GetWidget()->Close();
156 }
157
OnDialogDestroyed()158 void UserManagerProfileDialogDelegate::OnDialogDestroyed() {
159 if (parent_) {
160 parent_->OnDialogDestroyed();
161 parent_ = nullptr;
162 }
163 }
164
165 // UserManager -----------------------------------------------------------------
166
167 // static
Show(const base::FilePath & profile_path_to_focus,profiles::UserManagerAction user_manager_action)168 void UserManager::Show(
169 const base::FilePath& profile_path_to_focus,
170 profiles::UserManagerAction user_manager_action) {
171 DCHECK(profile_path_to_focus != ProfileManager::GetGuestProfilePath());
172
173 if (!signin_util::IsForceSigninEnabled() &&
174 (user_manager_action == profiles::USER_MANAGER_SELECT_PROFILE_NO_ACTION ||
175 user_manager_action == profiles::USER_MANAGER_OPEN_CREATE_USER_PAGE) &&
176 base::FeatureList::IsEnabled(features::kNewProfilePicker)) {
177 // Use the new profile picker instead.
178 ProfilePicker::Show(
179 user_manager_action == profiles::USER_MANAGER_OPEN_CREATE_USER_PAGE
180 ? ProfilePicker::EntryPoint::kProfileMenuAddNewProfile
181 : ProfilePicker::EntryPoint::kProfileMenuManageProfiles);
182 return;
183 }
184
185 if (g_user_manager_view) {
186 // If we are showing the User Manager after locking a profile, change the
187 // active profile to Guest.
188 profiles::SetActiveProfileToGuestIfLocked();
189
190 #if defined(OS_MAC)
191 app_controller_mac::CreateGuestProfileIfNeeded();
192 #endif
193
194 // Note the time we started opening the User Manager.
195 g_user_manager_view->set_user_manager_started_showing(base::Time::Now());
196
197 // If there's a user manager window open already, just activate it.
198 g_user_manager_view->GetWidget()->Activate();
199 return;
200 }
201
202 // Under some startup conditions, we can try twice to create the User Manager.
203 // Because creating the System profile is asynchronous, it's possible for
204 // there to then be multiple pending operations and eventually multiple
205 // User Managers.
206 if (g_is_user_manager_view_under_construction)
207 return;
208
209 // Create the system profile, if necessary, and open the user manager
210 // from the system profile.
211 UserManagerView* user_manager = new UserManagerView();
212 user_manager->set_user_manager_started_showing(base::Time::Now());
213 profiles::CreateSystemProfileForUserManager(
214 profile_path_to_focus, user_manager_action,
215 base::BindRepeating(
216 &UserManagerView::OnSystemProfileCreated,
217 base::Passed(base::WrapUnique(user_manager)),
218 base::Owned(new base::AutoReset<bool>(
219 &g_is_user_manager_view_under_construction, true))));
220 }
221
222 // static
Hide()223 void UserManager::Hide() {
224 // Hide the profile picker, in case it was opened by UserManager::Show().
225 ProfilePicker::Hide();
226
227 if (g_user_manager_view)
228 g_user_manager_view->GetWidget()->Close();
229 }
230
231 // static
IsShowing()232 bool UserManager::IsShowing() {
233 #if defined(OS_MAC)
234 // Widget activation works differently on Mac: the UserManager is a child
235 // widget, so it is not active in the IsActive() sense even when showing
236 // and interactable. Test for IsVisible instead - this is what the Cocoa
237 // UserManager::IsShowing() does as well.
238 return g_user_manager_view ? g_user_manager_view->GetWidget()->IsVisible()
239 : false;
240 #else
241 return g_user_manager_view ? g_user_manager_view->GetWidget()->IsActive()
242 : false;
243 #endif
244 }
245
246 // static
OnUserManagerShown()247 void UserManager::OnUserManagerShown() {
248 if (g_user_manager_view) {
249 g_user_manager_view->LogTimeToOpen();
250 if (g_user_manager_shown_callback_for_testing) {
251 if (!g_user_manager_shown_callback_for_testing->is_null())
252 std::move(*g_user_manager_shown_callback_for_testing).Run();
253
254 delete g_user_manager_shown_callback_for_testing;
255 g_user_manager_shown_callback_for_testing = nullptr;
256 }
257 }
258 }
259
260 // static
AddOnUserManagerShownCallbackForTesting(base::OnceClosure callback)261 void UserManager::AddOnUserManagerShownCallbackForTesting(
262 base::OnceClosure callback) {
263 DCHECK(!g_user_manager_shown_callback_for_testing);
264 g_user_manager_shown_callback_for_testing =
265 new base::OnceClosure(std::move(callback));
266 }
267
268 // static
GetSigninProfilePath()269 base::FilePath UserManager::GetSigninProfilePath() {
270 if (!g_user_manager_view)
271 return base::FilePath();
272
273 return g_user_manager_view->GetSigninProfilePath();
274 }
275
276 // UserManagerProfileDialog
277 // -------------------------------------------------------------
278
279 // static
ShowUnlockDialog(content::BrowserContext * browser_context,const std::string & email)280 void UserManagerProfileDialog::ShowUnlockDialog(
281 content::BrowserContext* browser_context,
282 const std::string& email) {
283 ShowUnlockDialogWithProfilePath(browser_context, email, base::FilePath());
284 }
285
286 // static
ShowUnlockDialogWithProfilePath(content::BrowserContext * browser_context,const std::string & email,const base::FilePath & profile_path)287 void UserManagerProfileDialog::ShowUnlockDialogWithProfilePath(
288 content::BrowserContext* browser_context,
289 const std::string& email,
290 const base::FilePath& profile_path) {
291 // This method should only be called if the user manager is already showing.
292 if (!UserManager::IsShowing())
293 return;
294 // Load the re-auth URL, prepopulated with the user's email address.
295 // Add the index of the profile to the URL so that the inline login page
296 // knows which profile to load and update the credentials.
297 GURL url = signin::GetEmbeddedReauthURLWithEmail(
298 signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER,
299 signin_metrics::Reason::REASON_UNLOCK, email);
300 g_user_manager_view->SetSigninProfilePath(profile_path);
301 g_user_manager_view->ShowDialog(browser_context, email, url);
302 }
303
304 // static
ShowForceSigninDialog(content::BrowserContext * browser_context,const base::FilePath & profile_path)305 void UserManagerProfileDialog::ShowForceSigninDialog(
306 content::BrowserContext* browser_context,
307 const base::FilePath& profile_path) {
308 if (!UserManager::IsShowing())
309 return;
310 g_user_manager_view->SetSigninProfilePath(profile_path);
311 GURL url = signin::GetEmbeddedPromoURL(
312 signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER,
313 signin_metrics::Reason::REASON_FORCED_SIGNIN_PRIMARY_ACCOUNT, true);
314 g_user_manager_view->ShowDialog(browser_context, std::string(), url);
315 }
316
ShowDialogAndDisplayErrorMessage(content::BrowserContext * browser_context)317 void UserManagerProfileDialog::ShowDialogAndDisplayErrorMessage(
318 content::BrowserContext* browser_context) {
319 if (!UserManager::IsShowing())
320 return;
321 // The error occurred before sign in happened, reset |signin_profile_path_|
322 // so that the error page will show the error message that is assoicated with
323 // the system profile.
324 g_user_manager_view->SetSigninProfilePath(base::FilePath());
325 g_user_manager_view->ShowDialog(browser_context, std::string(),
326 GURL(chrome::kChromeUISigninErrorURL));
327 }
328
329 // static
DisplayErrorMessage()330 void UserManagerProfileDialog::DisplayErrorMessage() {
331 // This method should only be called if the user manager is already showing.
332 DCHECK(g_user_manager_view);
333 g_user_manager_view->DisplayErrorMessage();
334 }
335
336 // static
HideDialog()337 void UserManagerProfileDialog::HideDialog() {
338 if (g_user_manager_view && g_user_manager_view->GetWidget()->IsVisible())
339 g_user_manager_view->HideDialog();
340 }
341
342 // UserManagerView -------------------------------------------------------------
343
UserManagerView()344 UserManagerView::UserManagerView()
345 : web_view_(nullptr),
346 delegate_(nullptr),
347 user_manager_started_showing_(base::Time()) {
348 SetButtons(ui::DIALOG_BUTTON_NONE);
349 SetHasWindowSizeControls(true);
350 SetTitle(IDS_PRODUCT_NAME);
351 set_use_custom_frame(false);
352 keep_alive_ = std::make_unique<ScopedKeepAlive>(
353 KeepAliveOrigin::USER_MANAGER_VIEW, KeepAliveRestartOption::DISABLED);
354 chrome::RecordDialogCreation(chrome::DialogIdentifier::USER_MANAGER);
355 }
356
~UserManagerView()357 UserManagerView::~UserManagerView() {
358 HideDialog();
359 }
360
361 // static
OnSystemProfileCreated(std::unique_ptr<UserManagerView> instance,base::AutoReset<bool> * pending,Profile * system_profile,const std::string & url)362 void UserManagerView::OnSystemProfileCreated(
363 std::unique_ptr<UserManagerView> instance,
364 base::AutoReset<bool>* pending,
365 Profile* system_profile,
366 const std::string& url) {
367 // If we are showing the User Manager after locking a profile, change the
368 // active profile to Guest.
369 profiles::SetActiveProfileToGuestIfLocked();
370
371 #if defined(OS_MAC)
372 app_controller_mac::CreateGuestProfileIfNeeded();
373 #endif
374
375 DCHECK(!g_user_manager_view);
376 g_user_manager_view =
377 instance.release(); // |g_user_manager_view| takes over ownership.
378 g_user_manager_view->Init(system_profile, GURL(url));
379 }
380
ShowDialog(content::BrowserContext * browser_context,const std::string & email,const GURL & url)381 void UserManagerView::ShowDialog(content::BrowserContext* browser_context,
382 const std::string& email,
383 const GURL& url) {
384 HideDialog();
385 // The dialog delegate will be deleted when the widget closes. The created
386 // WebView's lifetime is managed by the delegate.
387 delegate_ = new UserManagerProfileDialogDelegate(
388 this, new views::WebView(browser_context), email, url);
389 gfx::NativeView parent = g_user_manager_view->GetWidget()->GetNativeView();
390 views::DialogDelegate::CreateDialogWidget(delegate_, nullptr, parent);
391 delegate_->GetWidget()->Show();
392 }
393
HideDialog()394 void UserManagerView::HideDialog() {
395 if (delegate_) {
396 delegate_->CloseDialog();
397 DCHECK(!delegate_);
398 }
399 }
400
OnDialogDestroyed()401 void UserManagerView::OnDialogDestroyed() {
402 delegate_ = nullptr;
403 }
404
Init(Profile * system_profile,const GURL & url)405 void UserManagerView::Init(Profile* system_profile, const GURL& url) {
406 web_view_ = new views::WebView(system_profile);
407 web_view_->set_allow_accelerators(true);
408 AddChildView(web_view_);
409 SetLayoutManager(std::make_unique<views::FillLayout>());
410 AddAccelerator(ui::Accelerator(ui::VKEY_W, ui::EF_CONTROL_DOWN));
411 AddAccelerator(ui::Accelerator(ui::VKEY_F4, ui::EF_ALT_DOWN));
412
413 // Make the user manager WebContents show up in the task manager.
414 content::WebContents* web_contents = web_view_->GetWebContents();
415 task_manager::WebContentsTags::CreateForToolContents(
416 web_contents, IDS_PROFILES_MANAGE_USERS_BUTTON);
417
418 // If the user manager is being displayed from an existing profile, use
419 // its last active browser to determine where the user manager should be
420 // placed. This is used so that we can center the dialog on the correct
421 // monitor in a multiple-monitor setup.
422 //
423 // If the last active profile is empty (for example, starting up chrome
424 // when all existing profiles are locked), not loaded (for example, if guest
425 // was set after locking the only open profile) or we can't find an active
426 // browser, bounds will remain empty and the user manager will be centered on
427 // the default monitor by default.
428 //
429 // Note the profile is accessed via GetProfileByPath(GetLastUsedProfileDir())
430 // instead of GetLastUsedProfile(). If the last active profile isn't loaded,
431 // the latter may try to synchronously load it, which can only be done on a
432 // thread where disk IO is allowed.
433 gfx::Rect bounds;
434 ProfileManager* profile_manager = g_browser_process->profile_manager();
435 const base::FilePath& last_used_profile_path =
436 profile_manager->GetLastUsedProfileDir(profile_manager->user_data_dir());
437 Profile* profile = profile_manager->GetProfileByPath(last_used_profile_path);
438 if (profile) {
439 Browser* browser = chrome::FindLastActiveWithProfile(profile);
440 if (browser) {
441 gfx::NativeView native_view =
442 views::Widget::GetWidgetForNativeWindow(
443 browser->window()->GetNativeWindow())->GetNativeView();
444 bounds = display::Screen::GetScreen()
445 ->GetDisplayNearestView(native_view)
446 .work_area();
447 bounds.ClampToCenteredSize(gfx::Size(UserManager::kWindowWidth,
448 UserManager::kWindowHeight));
449 }
450 }
451
452 views::Widget::InitParams params =
453 GetDialogWidgetInitParams(this, nullptr, nullptr, bounds);
454 (new views::Widget)->Init(std::move(params));
455
456 #if defined(OS_WIN)
457 // Set the app id for the user manager to the app id of its parent.
458 ui::win::SetAppIdForWindow(
459 shell_integration::win::GetAppUserModelIdForBrowser(
460 system_profile->GetPath()),
461 views::HWNDForWidget(GetWidget()));
462 #endif
463
464 web_view_->LoadInitialURL(url);
465 content::RenderWidgetHostView* rwhv = web_contents->GetRenderWidgetHostView();
466 if (rwhv)
467 rwhv->SetBackgroundColor(profiles::kUserManagerBackgroundColor);
468
469 GetWidget()->Show();
470 web_view_->RequestFocus();
471 }
472
LogTimeToOpen()473 void UserManagerView::LogTimeToOpen() {
474 if (user_manager_started_showing_ == base::Time())
475 return;
476
477 ProfileMetrics::LogTimeToOpenUserManager(
478 base::Time::Now() - user_manager_started_showing_);
479 user_manager_started_showing_ = base::Time();
480 }
481
AcceleratorPressed(const ui::Accelerator & accelerator)482 bool UserManagerView::AcceleratorPressed(const ui::Accelerator& accelerator) {
483 int key = accelerator.key_code();
484 int modifier = accelerator.modifiers();
485
486 // Ignore presses of the Escape key. The user manager may be Chrome's only
487 // top-level window, in which case we don't want presses of Esc to maybe quit
488 // the entire browser. This has higher priority than the default dialog Esc
489 // accelerator (which would otherwise close the window).
490 if (key == ui::VKEY_ESCAPE && modifier == ui::EF_NONE)
491 return true;
492
493 DCHECK((key == ui::VKEY_W && modifier == ui::EF_CONTROL_DOWN) ||
494 (key == ui::VKEY_F4 && modifier == ui::EF_ALT_DOWN));
495 GetWidget()->Close();
496 return true;
497 }
498
CalculatePreferredSize() const499 gfx::Size UserManagerView::CalculatePreferredSize() const {
500 return gfx::Size(UserManager::kWindowWidth, UserManager::kWindowHeight);
501 }
502
WindowClosing()503 void UserManagerView::WindowClosing() {
504 // Now that the window is closed, we can allow a new one to be opened.
505 // (WindowClosing comes in asynchronously from the call to Close() and we
506 // may have already opened a new instance).
507 if (g_user_manager_view == this)
508 g_user_manager_view = nullptr;
509 }
510
DisplayErrorMessage()511 void UserManagerView::DisplayErrorMessage() {
512 if (delegate_)
513 delegate_->DisplayErrorMessage();
514 }
515
SetSigninProfilePath(const base::FilePath & profile_path)516 void UserManagerView::SetSigninProfilePath(const base::FilePath& profile_path) {
517 signin_profile_path_ = profile_path;
518 }
519
GetSigninProfilePath()520 base::FilePath UserManagerView::GetSigninProfilePath() {
521 return signin_profile_path_;
522 }
523