blob: fb6095915eff3d51a2fbcbf823a0478bf2f19f3a [file] [log] [blame]
// Copyright 2014 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/cocoa/profiles/user_manager_mac.h"
#include "base/callback.h"
#include "base/mac/foundation_util.h"
#include "chrome/app/chrome_command_ids.h"
#import "chrome/browser/app_controller_mac.h"
#include "chrome/browser/browser_process.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/profiles_state.h"
#include "chrome/browser/signin/signin_promo.h"
#include "chrome/browser/ui/browser_dialogs.h"
#import "chrome/browser/ui/cocoa/browser_window_utils.h"
#include "chrome/browser/ui/cocoa/chrome_event_processing_window.h"
#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_sheet.h"
#import "chrome/browser/ui/cocoa/constrained_window/constrained_window_custom_window.h"
#include "chrome/browser/ui/cocoa/constrained_window/constrained_window_mac.h"
#include "chrome/browser/ui/user_manager.h"
#include "chrome/common/url_constants.h"
#include "chrome/grit/chromium_strings.h"
#include "components/signin/core/common/profile_management_switches.h"
#include "components/web_modal/web_contents_modal_dialog_host.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_delegate.h"
#include "ui/base/l10n/l10n_util_mac.h"
#include "ui/events/keycodes/keyboard_codes.h"
namespace {
// Update the App Controller with a new Profile. Used when a Profile is locked
// to set the Controller to the Guest profile so the old Profile's bookmarks,
// etc... cannot be accessed.
void ChangeAppControllerForProfile(Profile* profile,
Profile::CreateStatus status) {
if (status == Profile::CREATE_STATUS_INITIALIZED) {
AppController* controller =
base::mac::ObjCCast<AppController>([NSApp delegate]);
[controller windowChangedToProfile:profile];
}
}
// An open User Manager window. There can only be one open at a time. This
// is reset to NULL when the window is closed.
UserManagerMac* instance_ = nullptr; // Weak.
std::vector<base::Closure>* user_manager_shown_callbacks_for_testing_ = nullptr;
BOOL instance_under_construction_ = NO;
void CloseInstanceReauthDialog() {
DCHECK(instance_);
instance_->CloseReauthDialog();
}
// The modal dialog host the User Manager uses to display the reauth dialog.
class UserManagerModalHost : public web_modal::WebContentsModalDialogHost {
public:
UserManagerModalHost(gfx::NativeView host_view)
: host_view_(host_view) {}
gfx::Size GetMaximumDialogSize() override {
return switches::UsePasswordSeparatedSigninFlow() ?
gfx::Size(UserManager::kReauthDialogWidth,
UserManager::kReauthDialogHeight) :
gfx::Size(UserManager::kPasswordCombinedReauthDialogWidth,
UserManager::kPasswordCombinedReauthDialogHeight);
}
~UserManagerModalHost() override {}
gfx::NativeView GetHostView() const override {
return host_view_;
}
gfx::Point GetDialogPosition(const gfx::Size& size) override {
return gfx::Point(0, 0);
}
void AddObserver(web_modal::ModalDialogHostObserver* observer) override {}
void RemoveObserver(web_modal::ModalDialogHostObserver* observer) override {}
private:
gfx::NativeView host_view_;
};
// The modal manager delegate allowing the display of constrained windows for
// the reauth dialog.
class UserManagerModalManagerDelegate :
public web_modal::WebContentsModalDialogManagerDelegate {
public:
UserManagerModalManagerDelegate(gfx::NativeView host_view) {
modal_host_.reset(new UserManagerModalHost(host_view));
}
web_modal::WebContentsModalDialogHost* GetWebContentsModalDialogHost()
override {
return modal_host_.get();
}
bool IsWebContentsVisible(content::WebContents* web_contents) override {
return true;
}
~UserManagerModalManagerDelegate() override {}
protected:
std::unique_ptr<UserManagerModalHost> modal_host_;
};
// Custom WebContentsDelegate that allows handling of hotkeys.
class UserManagerWebContentsDelegate : public content::WebContentsDelegate {
public:
UserManagerWebContentsDelegate() {}
// WebContentsDelegate implementation. Forwards all unhandled keyboard events
// to the current window.
void HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) override {
if (![BrowserWindowUtils shouldHandleKeyboardEvent:event])
return;
// -getCommandId returns -1 if the event isn't a chrome accelerator.
int chromeCommandId = [BrowserWindowUtils getCommandId:event];
// Check for Cmd+A and Cmd+V events that could come from a password field.
BOOL isTextEditingCommand = [BrowserWindowUtils isTextEditingEvent:event];
// Only handle close window Chrome accelerators and text editing ones.
if (chromeCommandId == IDC_CLOSE_WINDOW || chromeCommandId == IDC_EXIT ||
isTextEditingCommand) {
[[NSApp mainMenu] performKeyEquivalent:event.os_event];
}
}
};
class ReauthDialogDelegate : public UserManager::BaseReauthDialogDelegate,
public ConstrainedWindowMacDelegate {
public:
ReauthDialogDelegate() {
hotKeysWebContentsDelegate_.reset(new UserManagerWebContentsDelegate());
}
// UserManager::BaseReauthDialogDelegate:
void CloseReauthDialog() override {
CloseInstanceReauthDialog();
}
// WebContentsDelegate::HandleKeyboardEvent:
void HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) override {
hotKeysWebContentsDelegate_->HandleKeyboardEvent(source, event);
}
// ConstrainedWindowMacDelegate:
void OnConstrainedWindowClosed(ConstrainedWindowMac* window) override {
CloseReauthDialog();
}
private:
std::unique_ptr<UserManagerWebContentsDelegate> hotKeysWebContentsDelegate_;
DISALLOW_COPY_AND_ASSIGN(ReauthDialogDelegate);
};
} // namespace
// WindowController for the reauth dialog.
@interface ReauthDialogWindowController
: NSWindowController <NSWindowDelegate> {
@private
std::string emailAddress_;
content::WebContents* webContents_;
signin_metrics::Reason reason_;
std::unique_ptr<ReauthDialogDelegate> webContentsDelegate_;
std::unique_ptr<ConstrainedWindowMac> constrained_window_;
std::unique_ptr<content::WebContents> reauthWebContents_;
}
- (id)initWithProfile:(Profile*)profile
email:(std::string)email
reason:(signin_metrics::Reason)reason
webContents:(content::WebContents*)webContents;
- (void)showURL:(const GURL&)url;
- (void)close;
@end
@implementation ReauthDialogWindowController
- (id)initWithProfile:(Profile*)profile
email:(std::string)email
reason:(signin_metrics::Reason)reason
webContents:(content::WebContents*)webContents {
webContents_ = webContents;
emailAddress_ = email;
reason_ = reason;
NSRect frame = NSMakeRect(
0, 0, UserManager::kReauthDialogWidth, UserManager::kReauthDialogHeight);
base::scoped_nsobject<ConstrainedWindowCustomWindow> window(
[[ConstrainedWindowCustomWindow alloc]
initWithContentRect:frame
styleMask:NSTitledWindowMask | NSClosableWindowMask]);
if ((self = [super initWithWindow:window])) {
webContents_ = webContents;
reauthWebContents_.reset(content::WebContents::Create(
content::WebContents::CreateParams(profile)));
window.get().contentView = reauthWebContents_->GetNativeView();
webContentsDelegate_.reset(new ReauthDialogDelegate());
reauthWebContents_->SetDelegate(webContentsDelegate_.get());
base::scoped_nsobject<CustomConstrainedWindowSheet> sheet(
[[CustomConstrainedWindowSheet alloc]
initWithCustomWindow:[self window]]);
constrained_window_ =
CreateAndShowWebModalDialogMac(
webContentsDelegate_.get(), webContents_, sheet);
// The close button needs to call CloseWebContentsModalDialog() on the
// constrained window isntead of just [window close] so grab a reference to
// it in the title bar and change its action.
auto closeButton = [window standardWindowButton:NSWindowCloseButton];
[closeButton setTarget:self];
[closeButton setAction:@selector(closeButtonClicked:)];
[self show];
}
return self;
}
- (void)showURL:(const GURL&)url {
reauthWebContents_->GetController().LoadURL(url, content::Referrer(),
ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
std::string());
}
- (void)show {
GURL url = signin::GetReauthURLWithEmail(
signin_metrics::AccessPoint::ACCESS_POINT_USER_MANAGER, reason_,
emailAddress_);
[self showURL:url];
}
- (void)closeButtonClicked:(NSButton*)button {
[self close];
}
- (void)close {
constrained_window_->CloseWebContentsModalDialog();
}
- (void)dealloc {
constrained_window_->CloseWebContentsModalDialog();
[super dealloc];
}
@end
// Window controller for the User Manager view.
@interface UserManagerWindowController : NSWindowController <NSWindowDelegate> {
@private
std::unique_ptr<content::WebContents> webContents_;
std::unique_ptr<UserManagerWebContentsDelegate> webContentsDelegate_;
UserManagerMac* userManagerObserver_; // Weak.
std::unique_ptr<UserManagerModalManagerDelegate> modal_manager_delegate_;
base::scoped_nsobject<ReauthDialogWindowController> reauth_window_controller_;
}
- (void)windowWillClose:(NSNotification*)notification;
- (void)dealloc;
- (id)initWithProfile:(Profile*)profile
withObserver:(UserManagerMac*)userManagerObserver;
- (void)showURL:(const GURL&)url;
- (void)show;
- (void)close;
- (BOOL)isVisible;
- (void)showReauthDialogWithProfile:(Profile*)profile
email:(std::string)email
reason:(signin_metrics::Reason)reason;
- (void)displayErrorMessage;
- (void)closeReauthDialog;
@end
@implementation UserManagerWindowController
- (id)initWithProfile:(Profile*)profile
withObserver:(UserManagerMac*)userManagerObserver {
// Center the window on the screen that currently has focus.
NSScreen* mainScreen = [NSScreen mainScreen];
CGFloat screenHeight = [mainScreen frame].size.height;
CGFloat screenWidth = [mainScreen frame].size.width;
NSRect contentRect =
NSMakeRect((screenWidth - UserManager::kWindowWidth) / 2,
(screenHeight - UserManager::kWindowHeight) / 2,
UserManager::kWindowWidth, UserManager::kWindowHeight);
ChromeEventProcessingWindow* window = [[ChromeEventProcessingWindow alloc]
initWithContentRect:contentRect
styleMask:NSTitledWindowMask |
NSClosableWindowMask |
NSResizableWindowMask
backing:NSBackingStoreBuffered
defer:NO
screen:mainScreen];
[window setTitle:l10n_util::GetNSString(IDS_PRODUCT_NAME)];
[window setMinSize:NSMakeSize(UserManager::kWindowWidth,
UserManager::kWindowHeight)];
if ((self = [super initWithWindow:window])) {
userManagerObserver_ = userManagerObserver;
// Initialize the web view.
webContents_.reset(content::WebContents::Create(
content::WebContents::CreateParams(profile)));
window.contentView = webContents_->GetNativeView();
webContentsDelegate_.reset(new UserManagerWebContentsDelegate());
webContents_->SetDelegate(webContentsDelegate_.get());
web_modal::WebContentsModalDialogManager::CreateForWebContents(
webContents_.get());
modal_manager_delegate_.reset(
new UserManagerModalManagerDelegate([[self window] contentView]));
web_modal::WebContentsModalDialogManager::FromWebContents(
webContents_.get())->SetDelegate(modal_manager_delegate_.get());
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(windowWillClose:)
name:NSWindowWillCloseNotification
object:self.window];
}
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Remove the ModalDailogManager that's about to be destroyed.
auto* manager = web_modal::WebContentsModalDialogManager::FromWebContents(
webContents_.get());
if (manager)
manager->SetDelegate(nullptr);
[super dealloc];
}
- (void)showURL:(const GURL&)url {
webContents_->GetController().LoadURL(url, content::Referrer(),
ui::PAGE_TRANSITION_AUTO_TOPLEVEL,
std::string());
content::RenderWidgetHostView* rwhv = webContents_->GetRenderWidgetHostView();
if (rwhv)
rwhv->SetBackgroundColor(profiles::kUserManagerBackgroundColor);
[self show];
}
- (void)show {
// Because the User Manager isn't a BrowserWindowController, activating it
// will not trigger a -windowChangedToProfile and update the menu bar.
// This is only important if the active profile is Guest, which may have
// happened after locking a profile.
if (profiles::SetActiveProfileToGuestIfLocked()) {
g_browser_process->profile_manager()->CreateProfileAsync(
ProfileManager::GetGuestProfilePath(),
base::Bind(&ChangeAppControllerForProfile),
base::string16(),
std::string(),
std::string());
}
[[self window] makeKeyAndOrderFront:self];
}
- (void)close {
[[self window] close];
}
-(BOOL)isVisible {
return [[self window] isVisible];
}
- (void)windowWillClose:(NSNotification*)notification {
[[NSNotificationCenter defaultCenter] removeObserver:self];
DCHECK(userManagerObserver_);
userManagerObserver_->WindowWasClosed();
}
- (void)showReauthDialogWithProfile:(Profile*)profile
email:(std::string)email
reason:(signin_metrics::Reason)reason {
reauth_window_controller_.reset([[ReauthDialogWindowController alloc]
initWithProfile:profile
email:email
reason:reason
webContents:webContents_.get()]);
}
- (void)displayErrorMessage {
[reauth_window_controller_ showURL:GURL(chrome::kChromeUISigninErrorURL)];
}
- (void)closeReauthDialog {
[reauth_window_controller_ close];
}
@end
// static
void UserManager::Show(
const base::FilePath& profile_path_to_focus,
profiles::UserManagerTutorialMode tutorial_mode,
profiles::UserManagerAction user_manager_action) {
DCHECK(profile_path_to_focus != ProfileManager::GetGuestProfilePath());
ProfileMetrics::LogProfileOpenMethod(ProfileMetrics::OPEN_USER_MANAGER);
if (instance_) {
// If there's a user manager window open already, just activate it.
[instance_->window_controller() show];
instance_->set_user_manager_started_showing(base::Time::Now());
return;
}
// Under some startup conditions, we can try twice to create the User Manager.
// Because creating the System profile is asynchronous, it's possible for
// there to then be multiple pending operations and eventually multiple
// User Managers.
if (instance_under_construction_)
return;
instance_under_construction_ = YES;
// Create the guest profile, if necessary, and open the User Manager
// from the guest profile.
profiles::CreateSystemProfileForUserManager(
profile_path_to_focus,
tutorial_mode,
user_manager_action,
base::Bind(&UserManagerMac::OnSystemProfileCreated, base::Time::Now()));
}
// static
void UserManager::Hide() {
if (instance_)
[instance_->window_controller() close];
}
// static
bool UserManager::IsShowing() {
return instance_ ? [instance_->window_controller() isVisible]: false;
}
// static
void UserManager::OnUserManagerShown() {
if (instance_) {
instance_->LogTimeToOpen();
if (user_manager_shown_callbacks_for_testing_) {
for (const auto& callback : *user_manager_shown_callbacks_for_testing_) {
if (!callback.is_null())
callback.Run();
}
// Delete the callback list after calling.
delete user_manager_shown_callbacks_for_testing_;
user_manager_shown_callbacks_for_testing_ = nullptr;
}
}
}
// static
void UserManager::ShowReauthDialog(content::BrowserContext* browser_context,
const std::string& email,
signin_metrics::Reason reason) {
// This method should only be called if the user manager is already showing.
if (!IsShowing())
return;
instance_->ShowReauthDialog(browser_context, email, reason);
}
// static
void UserManager::HideReauthDialog() {
// This method should only be called if the user manager is already showing.
if (!IsShowing())
return;
instance_->CloseReauthDialog();
}
// static
void UserManager::AddOnUserManagerShownCallbackForTesting(
const base::Closure& callback) {
if (!user_manager_shown_callbacks_for_testing_)
user_manager_shown_callbacks_for_testing_ = new std::vector<base::Closure>;
user_manager_shown_callbacks_for_testing_->push_back(callback);
}
// static
void UserManager::ShowSigninDialog(content::BrowserContext* browser_context,
const base::FilePath& profile_path) {
if (!IsShowing())
return;
instance_->SetSigninProfilePath(profile_path);
ShowReauthDialog(browser_context, std::string(),
signin_metrics::Reason::REASON_SIGNIN_PRIMARY_ACCOUNT);
}
// static
void UserManager::DisplayErrorMessage() {
DCHECK(instance_);
instance_->DisplayErrorMessage();
}
// static
base::FilePath UserManager::GetSigninProfilePath() {
return instance_->GetSigninProfilePath();
}
void UserManagerMac::ShowReauthDialog(content::BrowserContext* browser_context,
const std::string& email,
signin_metrics::Reason reason) {
[window_controller_
showReauthDialogWithProfile:Profile::FromBrowserContext(browser_context)
email:email
reason:reason];
}
void UserManagerMac::CloseReauthDialog() {
[window_controller_ closeReauthDialog];
}
UserManagerMac::UserManagerMac(Profile* profile) {
window_controller_.reset([[UserManagerWindowController alloc]
initWithProfile:profile withObserver:this]);
}
UserManagerMac::~UserManagerMac() {
}
// static
void UserManagerMac::OnSystemProfileCreated(const base::Time& start_time,
Profile* system_profile,
const std::string& url) {
DCHECK(!instance_);
instance_ = new UserManagerMac(system_profile);
instance_->set_user_manager_started_showing(start_time);
[instance_->window_controller() showURL:GURL(url)];
instance_under_construction_ = NO;
}
void UserManagerMac::LogTimeToOpen() {
if (user_manager_started_showing_ == base::Time())
return;
ProfileMetrics::LogTimeToOpenUserManager(
base::Time::Now() - user_manager_started_showing_);
user_manager_started_showing_ = base::Time();
}
void UserManagerMac::WindowWasClosed() {
CloseReauthDialog();
instance_ = NULL;
delete this;
}
void UserManagerMac::DisplayErrorMessage() {
[window_controller_ displayErrorMessage];
}
void UserManagerMac::SetSigninProfilePath(const base::FilePath& profile_path) {
signin_profile_path_ = profile_path;
}
base::FilePath UserManagerMac::GetSigninProfilePath() {
return signin_profile_path_;
}