// Copyright 2012 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. #import "chrome/browser/ui/cocoa/browser_window_controller.h" #include #include #include #include "base/command_line.h" #include "base/mac/bundle_locations.h" #import "base/mac/foundation_util.h" #include "base/mac/mac_util.h" #import "base/mac/sdk_forward_declarations.h" #include "base/strings/sys_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "chrome/app/chrome_command_ids.h" // IDC_* #include "chrome/browser/bookmarks/bookmark_model_factory.h" #include "chrome/browser/bookmarks/managed_bookmark_service_factory.h" #include "chrome/browser/browser_process.h" #include "chrome/browser/devtools/devtools_window.h" #include "chrome/browser/extensions/extension_commands_global_registry.h" #include "chrome/browser/fullscreen.h" #include "chrome/browser/profiles/avatar_menu.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile_info_cache.h" #include "chrome/browser/profiles/profile_manager.h" #include "chrome/browser/profiles/profiles_state.h" #include "chrome/browser/themes/theme_service.h" #include "chrome/browser/themes/theme_service_factory.h" #include "chrome/browser/translate/chrome_translate_client.h" #include "chrome/browser/ui/bookmarks/bookmark_editor.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_command_controller.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_dialogs.h" #include "chrome/browser/ui/browser_instant_controller.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/browser/ui/browser_window_state.h" #import "chrome/browser/ui/cocoa/background_gradient_view.h" #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h" #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bubble_observer_cocoa.h" #import "chrome/browser/ui/cocoa/bookmarks/bookmark_editor_controller.h" #import "chrome/browser/ui/cocoa/browser_window_cocoa.h" #import "chrome/browser/ui/cocoa/browser_window_command_handler.h" #import "chrome/browser/ui/cocoa/browser_window_controller_private.h" #import "chrome/browser/ui/cocoa/browser_window_layout.h" #import "chrome/browser/ui/cocoa/browser_window_utils.h" #import "chrome/browser/ui/cocoa/dev_tools_controller.h" #import "chrome/browser/ui/cocoa/download/download_shelf_controller.h" #include "chrome/browser/ui/cocoa/extensions/extension_keybinding_registry_cocoa.h" #import "chrome/browser/ui/cocoa/fast_resize_view.h" #import "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h" #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h" #import "chrome/browser/ui/cocoa/framed_browser_window.h" #import "chrome/browser/ui/cocoa/fullscreen_window.h" #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h" #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" #import "chrome/browser/ui/cocoa/presentation_mode_controller.h" #import "chrome/browser/ui/cocoa/profiles/avatar_base_controller.h" #import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h" #import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h" #import "chrome/browser/ui/cocoa/status_bubble_mac.h" #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h" #import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h" #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h" #import "chrome/browser/ui/cocoa/tabs/tab_view.h" #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" #import "chrome/browser/ui/cocoa/translate/translate_bubble_controller.h" #include "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h" #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h" #include "chrome/browser/ui/location_bar/location_bar.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h" #include "chrome/browser/ui/translate/translate_bubble_model_impl.h" #include "chrome/browser/ui/website_settings/permission_bubble_manager.h" #include "chrome/browser/ui/window_sizer/window_sizer.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/command.h" #include "chrome/common/pref_names.h" #include "chrome/common/url_constants.h" #include "chrome/grit/generated_resources.h" #include "chrome/grit/locale_settings.h" #include "components/bookmarks/browser/bookmark_model.h" #include "components/bookmarks/managed/managed_bookmark_service.h" #include "components/signin/core/common/profile_management_switches.h" #include "components/translate/core/browser/translate_manager.h" #include "components/translate/core/browser/translate_ui_delegate.h" #include "components/web_modal/web_contents_modal_dialog_manager.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/render_widget_host.h" #include "content/public/browser/render_widget_host_view.h" #include "content/public/browser/web_contents.h" #import "ui/base/cocoa/cocoa_base_utils.h" #import "ui/base/cocoa/nsview_additions.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/l10n/l10n_util_mac.h" #import "ui/gfx/mac/coordinate_conversion.h" #include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h" #include "ui/gfx/screen.h" using bookmarks::BookmarkModel; using bookmarks::BookmarkNode; using l10n_util::GetStringUTF16; using l10n_util::GetNSStringWithFixup; using l10n_util::GetNSStringFWithFixup; // ORGANIZATION: This is a big file. It is (in principle) organized as follows // (in order): // 1. Interfaces. Very short, one-time-use classes may include an implementation // immediately after their interface. // 2. The general implementation section, ordered as follows: // i. Public methods and overrides. // ii. Overrides/implementations of undocumented methods. // iii. Delegate methods for various protocols, formal and informal, to which // |BrowserWindowController| conforms. // 3. (temporary) Implementation sections for various categories. // // Private methods are defined and implemented separately in // browser_window_controller_private.{h,mm}. // // Not all of the above guidelines are followed and more (re-)organization is // needed. BUT PLEASE TRY TO KEEP THIS FILE ORGANIZED. I'd rather re-organize as // little as possible, since doing so messes up the file's history. // // TODO(viettrungluu): [crbug.com/35543] on-going re-organization, splitting // things into multiple files -- the plan is as follows: // - in general, everything stays in browser_window_controller.h, but is split // off into categories (see below) // - core stuff stays in browser_window_controller.mm // - ... overrides also stay (without going into a category, in particular) // - private stuff which everyone needs goes into // browser_window_controller_private.{h,mm}; if no one else needs them, they // can go in individual files (see below) // - area/task-specific stuff go in browser_window_controller_.mm // - ... in categories called "()" or "()" // Plan of action: // - first re-organize into categories // - then split into files // Notes on self-inflicted (not user-inflicted) window resizing and moving: // // When the bookmark bar goes from hidden to shown (on a non-NTP) page, or when // the download shelf goes from hidden to shown, we grow the window downwards in // order to maintain a constant content area size. When either goes from shown // to hidden, we consequently shrink the window from the bottom, also to keep // the content area size constant. To keep things simple, if the window is not // entirely on-screen, we don't grow/shrink the window. // // The complications come in when there isn't enough room (on screen) below the // window to accomodate the growth. In this case, we grow the window first // downwards, and then upwards. So, when it comes to shrinking, we do the // opposite: shrink from the top by the amount by which we grew at the top, and // then from the bottom -- unless the user moved/resized/zoomed the window, in // which case we "reset state" and just shrink from the bottom. // // A further complication arises due to the way in which "zoom" ("maximize") // works on Mac OS X. Basically, for our purposes, a window is "zoomed" whenever // it occupies the full available vertical space. (Note that the green zoom // button does not track zoom/unzoomed state per se, but basically relies on // this heuristic.) We don't, in general, want to shrink the window if the // window is zoomed (scenario: window is zoomed, download shelf opens -- which // doesn't cause window growth, download shelf closes -- shouldn't cause the // window to become unzoomed!). However, if we grew the window // (upwards/downwards) to become zoomed in the first place, we *should* shrink // the window by the amounts by which we grew (scenario: window occupies *most* // of vertical space, download shelf opens causing growth so that window // occupies all of vertical space -- i.e., window is effectively zoomed, // download shelf closes -- should return the window to its previous state). // // A major complication is caused by the way grows/shrinks are handled and // animated. Basically, the BWC doesn't see the global picture, but it sees // grows and shrinks in small increments (as dictated by the animation). Thus // window growth/shrinkage (at the top/bottom) have to be tracked incrementally. // Allowing shrinking from the zoomed state also requires tracking: We check on // any shrink whether we're both zoomed and have previously grown -- if so, we // set a flag, and constrain any resize by the allowed amounts. On further // shrinks, we check the flag (since the size/position of the window will no // longer indicate that the window is shrinking from an apparent zoomed state) // and if it's set we continue to constrain the resize. using content::OpenURLParams; using content::Referrer; using content::RenderWidgetHostView; using content::WebContents; namespace { void SetUpBrowserWindowCommandHandler(NSWindow* window) { // Make the window handle browser window commands. [base::mac::ObjCCastStrict(window) setCommandHandler:[[[BrowserWindowCommandHandler alloc] init] autorelease]]; } } // namespace @interface NSWindow (NSPrivateApis) // Note: These functions are private, use -[NSObject respondsToSelector:] // before calling them. - (void)setBottomCornerRounded:(BOOL)rounded; - (NSRect)_growBoxRect; @end @implementation BrowserWindowController + (BrowserWindowController*)browserWindowControllerForWindow:(NSWindow*)window { while (window) { id controller = [window windowController]; if ([controller isKindOfClass:[BrowserWindowController class]]) return (BrowserWindowController*)controller; window = [window parentWindow]; } return nil; } + (BrowserWindowController*)browserWindowControllerForView:(NSView*)view { NSWindow* window = [view window]; return [BrowserWindowController browserWindowControllerForWindow:window]; } // Load the browser window nib and do any Cocoa-specific initialization. // Takes ownership of |browser|. Note that the nib also sets this controller // up as the window's delegate. - (id)initWithBrowser:(Browser*)browser { return [self initWithBrowser:browser takeOwnership:YES]; } // Private(TestingAPI) init routine with testing options. - (id)initWithBrowser:(Browser*)browser takeOwnership:(BOOL)ownIt { bool hasTabStrip = browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP); if ((self = [super initTabWindowControllerWithTabStrip:hasTabStrip])) { DCHECK(browser); initializing_ = YES; browser_.reset(browser); ownsBrowser_ = ownIt; NSWindow* window = [self window]; SetUpBrowserWindowCommandHandler(window); // Make the content view for the window have a layer. This will make all // sub-views have layers. This is necessary to ensure correct layer // ordering of all child views and their layers. [[window contentView] setWantsLayer:YES]; windowShim_.reset(new BrowserWindowCocoa(browser, self)); // Set different minimum sizes on tabbed windows vs non-tabbed, e.g. popups. // This has to happen before -enforceMinWindowSize: is called further down. NSSize minSize = [self isTabbedWindow] ? NSMakeSize(400, 272) : NSMakeSize(100, 122); [[self window] setMinSize:minSize]; // Create the bar visibility lock set; 10 is arbitrary, but should hopefully // be big enough to hold all locks that'll ever be needed. barVisibilityLocks_.reset([[NSMutableSet setWithCapacity:10] retain]); // Set the window to not have rounded corners, which prevents the resize // control from being inset slightly and looking ugly. Only bother to do // this on Snow Leopard; on Lion and later all windows have rounded bottom // corners, and this won't work anyway. if (base::mac::IsOSSnowLeopard() && [window respondsToSelector:@selector(setBottomCornerRounded:)]) [window setBottomCornerRounded:NO]; // Lion will attempt to automagically save and restore the UI. This // functionality appears to be leaky (or at least interacts badly with our // architecture) and thus BrowserWindowController never gets released. This // prevents the browser from being able to quit . if ([window respondsToSelector:@selector(setRestorable:)]) [window setRestorable:NO]; // Get the windows to swish in on Lion. if ([window respondsToSelector:@selector(setAnimationBehavior:)]) [window setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow]; // Get the most appropriate size for the window, then enforce the // minimum width and height. The window shim will handle flipping // the coordinates for us so we can use it to save some code. // Note that this may leave a significant portion of the window // offscreen, but there will always be enough window onscreen to // drag the whole window back into view. ui::WindowShowState show_state = ui::SHOW_STATE_DEFAULT; gfx::Rect desiredContentRect; chrome::GetSavedWindowBoundsAndShowState(browser_.get(), &desiredContentRect, &show_state); gfx::Rect windowRect = desiredContentRect; windowRect = [self enforceMinWindowSize:windowRect]; // When we are given x/y coordinates of 0 on a created popup window, assume // none were given by the window.open() command. if (browser_->is_type_popup() && windowRect.x() == 0 && windowRect.y() == 0) { gfx::Size size = windowRect.size(); windowRect.set_origin( WindowSizer::GetDefaultPopupOrigin(size, browser_->host_desktop_type())); } // Size and position the window. Note that it is not yet onscreen. Popup // windows may get resized later on in this function, once the actual size // of the toolbar/tabstrip is known. windowShim_->SetBounds(windowRect); // Puts the incognito badge on the window frame, if necessary. [self installAvatar]; // Create a sub-controller for the docked devTools and add its view to the // hierarchy. devToolsController_.reset([[DevToolsController alloc] init]); [[devToolsController_ view] setFrame:[[self tabContentArea] bounds]]; [[self tabContentArea] addSubview:[devToolsController_ view]]; // Create the overlayable contents controller. This provides the switch // view that TabStripController needs. overlayableContentsController_.reset( [[OverlayableContentsController alloc] init]); [[overlayableContentsController_ view] setFrame:[[devToolsController_ view] bounds]]; [[devToolsController_ view] addSubview:[overlayableContentsController_ view]]; // Create a controller for the tab strip, giving it the model object for // this window's Browser and the tab strip view. The controller will handle // registering for the appropriate tab notifications from the back-end and // managing the creation of new tabs. [self createTabStripController]; // Create a controller for the toolbar, giving it the toolbar model object // and the toolbar view from the nib. The controller will handle // registering for the appropriate command state changes from the back-end. // Adds the toolbar to the content area. toolbarController_.reset([[ToolbarController alloc] initWithCommands:browser->command_controller()->command_updater() profile:browser->profile() browser:browser resizeDelegate:self]); [toolbarController_ setHasToolbar:[self hasToolbar] hasLocationBar:[self hasLocationBar]]; // Create a sub-controller for the bookmark bar. bookmarkBarController_.reset( [[BookmarkBarController alloc] initWithBrowser:browser_.get() initialWidth:NSWidth([[[self window] contentView] frame]) delegate:self resizeDelegate:self]); [bookmarkBarController_ setBookmarkBarEnabled:[self supportsBookmarkBar]]; // Create the infobar container view, so we can pass it to the // ToolbarController. infoBarContainerController_.reset( [[InfoBarContainerController alloc] initWithResizeDelegate:self]); [self updateInfoBarTipVisibility]; // We don't want to try and show the bar before it gets placed in its parent // view, so this step shoudn't be inside the bookmark bar controller's // |-awakeFromNib|. windowShim_->BookmarkBarStateChanged( BookmarkBar::DONT_ANIMATE_STATE_CHANGE); // Allow bar visibility to be changed. [self enableBarVisibilityUpdates]; // Set the window to participate in Lion Fullscreen mode. Setting this flag // has no effect on Snow Leopard or earlier. Panels can share a fullscreen // space with a tabbed window, but they can not be primary fullscreen // windows. // This ensures the fullscreen button is appropriately positioned. It must // be done before calling layoutSubviews because the new avatar button's // position depends on the fullscreen button's position, as well as // TabStripController's rightIndentForControls. // The fullscreen button's position may depend on the old avatar button's // width, but that does not require calling layoutSubviews first. NSUInteger collectionBehavior = [window collectionBehavior]; collectionBehavior |= browser_->type() == Browser::TYPE_TABBED || browser_->type() == Browser::TYPE_POPUP ? NSWindowCollectionBehaviorFullScreenPrimary : NSWindowCollectionBehaviorFullScreenAuxiliary; [window setCollectionBehavior:collectionBehavior]; [self layoutSubviews]; // For non-trusted, non-app popup windows, |desiredContentRect| contains the // desired height of the content, not of the whole window. Now that all the // views are laid out, measure the current content area size and grow if // needed. The window has not been placed onscreen yet, so this extra resize // will not cause visible jank. if (chrome::SavedBoundsAreContentBounds(browser_.get())) { CGFloat deltaH = desiredContentRect.height() - NSHeight([[self tabContentArea] frame]); // Do not shrink the window, as that may break minimum size invariants. if (deltaH > 0) { // Convert from tabContentArea coordinates to window coordinates. NSSize convertedSize = [[self tabContentArea] convertSize:NSMakeSize(0, deltaH) toView:nil]; NSRect frame = [[self window] frame]; frame.size.height += convertedSize.height; frame.origin.y -= convertedSize.height; [[self window] setFrame:frame display:NO]; } } // Create the bridge for the status bubble. statusBubble_ = new StatusBubbleMac([self window], self); // Register for application hide/unhide notifications. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidHide:) name:NSApplicationDidHideNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidUnhide:) name:NSApplicationDidUnhideNotification object:nil]; // This must be done after the view is added to the window since it relies // on the window bounds to determine whether to show buttons or not. if ([self hasToolbar]) // Do not create the buttons in popups. [toolbarController_ createBrowserActionButtons]; extension_keybinding_registry_.reset( new ExtensionKeybindingRegistryCocoa(browser_->profile(), [self window], extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS, windowShim_.get())); PrefService* prefs = browser_->profile()->GetPrefs(); shouldHideFullscreenToolbar_ = prefs->GetBoolean(prefs::kHideFullscreenToolbar); blockLayoutSubviews_ = NO; // We are done initializing now. initializing_ = NO; } return self; } - (void)dealloc { browser_->tab_strip_model()->CloseAllTabs(); // Explicitly release |presentationModeController_| here, as it may call back // to this BWC in |-dealloc|. We are required to call |-exitPresentationMode| // before releasing the controller. [presentationModeController_ exitPresentationMode]; presentationModeController_.reset(); // Under certain testing configurations we may not actually own the browser. if (ownsBrowser_ == NO) ignore_result(browser_.release()); [[NSNotificationCenter defaultCenter] removeObserver:self]; // Inform reference counted objects that the Browser will be destroyed. This // ensures they invalidate their weak Browser* to prevent use-after-free. // These may outlive the Browser if they are retained by something else. For // example, since 10.10, the Nib loader internally creates an NSDictionary // that retains NSViewControllers and is autoreleased, so there is no way to // guarantee that the [super dealloc] call below will also call dealloc on the // controllers. [toolbarController_ browserWillBeDestroyed]; [tabStripController_ browserWillBeDestroyed]; [findBarCocoaController_ browserWillBeDestroyed]; [downloadShelfController_ browserWillBeDestroyed]; [bookmarkBarController_ browserWillBeDestroyed]; [avatarButtonController_ browserWillBeDestroyed]; [bookmarkBubbleController_ browserWillBeDestroyed]; [super dealloc]; } - (gfx::Rect)enforceMinWindowSize:(gfx::Rect)bounds { gfx::Rect checkedBounds = bounds; NSSize minSize = [[self window] minSize]; if (bounds.width() < minSize.width) checkedBounds.set_width(minSize.width); if (bounds.height() < minSize.height) checkedBounds.set_height(minSize.height); return checkedBounds; } - (BrowserWindow*)browserWindow { return windowShim_.get(); } - (ToolbarController*)toolbarController { return toolbarController_.get(); } - (TabStripController*)tabStripController { return tabStripController_.get(); } - (FindBarCocoaController*)findBarCocoaController { return findBarCocoaController_.get(); } - (InfoBarContainerController*)infoBarContainerController { return infoBarContainerController_.get(); } - (StatusBubbleMac*)statusBubble { return statusBubble_; } - (LocationBarViewMac*)locationBarBridge { return [toolbarController_ locationBarBridge]; } - (NSView*)floatingBarBackingView { return floatingBarBackingView_; } - (OverlayableContentsController*)overlayableContentsController { return overlayableContentsController_; } - (Profile*)profile { return browser_->profile(); } - (AvatarBaseController*)avatarButtonController { return avatarButtonController_.get(); } - (void)destroyBrowser { [NSApp removeWindowsItem:[self window]]; // We need the window to go away now. // We can't actually use |-autorelease| here because there's an embedded // run loop in the |-performClose:| which contains its own autorelease pool. // Instead call it after a zero-length delay, which gets us back to the main // event loop. [self performSelector:@selector(autorelease) withObject:nil afterDelay:0]; } // Called when the window meets the criteria to be closed (ie, // |-windowShouldClose:| returns YES). We must be careful to preserve the // semantics of BrowserWindow::Close() and not call the Browser's dtor directly // from this method. - (void)windowWillClose:(NSNotification*)notification { DCHECK_EQ([notification object], [self window]); DCHECK(browser_->tab_strip_model()->empty()); [savedRegularWindow_ close]; // We delete statusBubble here because we need to kill off the dependency // that its window has on our window before our window goes away. delete statusBubble_; statusBubble_ = NULL; // We can't actually use |-autorelease| here because there's an embedded // run loop in the |-performClose:| which contains its own autorelease pool. // Instead call it after a zero-length delay, which gets us back to the main // event loop. [self performSelector:@selector(autorelease) withObject:nil afterDelay:0]; } - (void)updateDevToolsForContents:(WebContents*)contents { BOOL layout_changed = [devToolsController_ updateDevToolsForWebContents:contents withProfile:browser_->profile()]; if (layout_changed && [findBarCocoaController_ isFindBarVisible]) [self layoutSubviews]; } // Called when the user wants to close a window or from the shutdown process. // The Browser object is in control of whether or not we're allowed to close. It // may defer closing due to several states, such as onUnload handlers needing to // be fired. If closing is deferred, the Browser will handle the processing // required to get us to the closing state and (by watching for all the tabs // going away) will again call to close the window when it's finally ready. - (BOOL)windowShouldClose:(id)sender { // Disable updates while closing all tabs to avoid flickering. gfx::ScopedCocoaDisableScreenUpdates disabler; // Give beforeunload handlers the chance to cancel the close before we hide // the window below. if (!browser_->ShouldCloseWindow()) return NO; // saveWindowPositionIfNeeded: only works if we are the last active // window, but orderOut: ends up activating another window, so we // have to save the window position before we call orderOut:. [self saveWindowPositionIfNeeded]; bool fast_tab_closing_enabled = base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kEnableFastUnload); if (!browser_->tab_strip_model()->empty()) { // Tab strip isn't empty. Hide the frame (so it appears to have closed // immediately) and close all the tabs, allowing the renderers to shut // down. When the tab strip is empty we'll be called back again. [[self window] orderOut:self]; browser_->OnWindowClosing(); if (fast_tab_closing_enabled) browser_->tab_strip_model()->CloseAllTabs(); return NO; } else if (fast_tab_closing_enabled && !browser_->HasCompletedUnloadProcessing()) { // The browser needs to finish running unload handlers. // Hide the window (so it appears to have closed immediately), and // the browser will call us back again when it is ready to close. [[self window] orderOut:self]; return NO; } // the tab strip is empty, it's ok to close the window return YES; } // Called right after our window became the main window. - (void)windowDidBecomeMain:(NSNotification*)notification { if (chrome::GetLastActiveBrowser() != browser_.get()) { BrowserList::SetLastActive(browser_.get()); } // Always saveWindowPositionIfNeeded when becoming main, not just // when |browser_| is not the last active browser. See crbug.com/536280 . [self saveWindowPositionIfNeeded]; NSView* rootView = [[[self window] contentView] superview]; [rootView cr_recursivelyInvokeBlock:^(id view) { if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)]) [view windowDidChangeActive]; }]; extensions::ExtensionCommandsGlobalRegistry::Get(browser_->profile()) ->set_registry_for_active_window(extension_keybinding_registry_.get()); } - (void)windowDidResignMain:(NSNotification*)notification { NSView* rootView = [[[self window] contentView] superview]; [rootView cr_recursivelyInvokeBlock:^(id view) { if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)]) [view windowDidChangeActive]; }]; extensions::ExtensionCommandsGlobalRegistry::Get(browser_->profile()) ->set_registry_for_active_window(nullptr); } // Called when we are activated (when we gain focus). - (void)windowDidBecomeKey:(NSNotification*)notification { // We need to activate the controls (in the "WebView"). To do this, get the // selected WebContents's RenderWidgetHostView and tell it to activate. if (WebContents* contents = [self webContents]) { WebContents* devtools = DevToolsWindow::GetInTabWebContents( contents, NULL); if (devtools) { RenderWidgetHostView* devtoolsView = devtools->GetRenderWidgetHostView(); if (devtoolsView && devtoolsView->HasFocus()) { devtoolsView->SetActive(true); return; } } if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) rwhv->SetActive(true); } } // Called when we are deactivated (when we lose focus). - (void)windowDidResignKey:(NSNotification*)notification { // If our app is still active and we're still the key window, ignore this // message, since it just means that a menu extra (on the "system status bar") // was activated; we'll get another |-windowDidResignKey| if we ever really // lose key window status. if ([NSApp isActive] && ([NSApp keyWindow] == [self window])) return; // We need to deactivate the controls (in the "WebView"). To do this, get the // selected WebContents's RenderWidgetHostView and tell it to deactivate. if (WebContents* contents = [self webContents]) { WebContents* devtools = DevToolsWindow::GetInTabWebContents( contents, NULL); if (devtools) { RenderWidgetHostView* devtoolsView = devtools->GetRenderWidgetHostView(); if (devtoolsView && devtoolsView->HasFocus()) { devtoolsView->SetActive(false); return; } } if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) rwhv->SetActive(false); } } // Called when we have been minimized. - (void)windowDidMiniaturize:(NSNotification *)notification { [self saveWindowPositionIfNeeded]; // Let the selected RenderWidgetHostView know, so that it can tell plugins. if (WebContents* contents = [self webContents]) { if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) rwhv->SetWindowVisibility(false); } } // Called when we have been unminimized. - (void)windowDidDeminiaturize:(NSNotification *)notification { // Make sure the window's show_state (which is now ui::SHOW_STATE_NORMAL) // gets saved. [self saveWindowPositionIfNeeded]; // Let the selected RenderWidgetHostView know, so that it can tell plugins. if (WebContents* contents = [self webContents]) { if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) rwhv->SetWindowVisibility(true); } } // Called when the application has been hidden. - (void)applicationDidHide:(NSNotification *)notification { // Let the selected RenderWidgetHostView know, so that it can tell plugins // (unless we are minimized, in which case nothing has really changed). if (![[self window] isMiniaturized]) { if (WebContents* contents = [self webContents]) { if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) rwhv->SetWindowVisibility(false); } } } // Called when the application has been unhidden. - (void)applicationDidUnhide:(NSNotification *)notification { // Let the selected RenderWidgetHostView know, so that it can tell plugins // (unless we are minimized, in which case nothing has really changed). if (![[self window] isMiniaturized]) { if (WebContents* contents = [self webContents]) { if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) rwhv->SetWindowVisibility(true); } } } // Called when the user clicks the zoom button (or selects it from the Window // menu) to determine the "standard size" of the window, based on the content // and other factors. If the current size/location differs nontrivally from the // standard size, Cocoa resizes the window to the standard size, and saves the // current size as the "user size". If the current size/location is the same (up // to a fudge factor) as the standard size, Cocoa resizes the window to the // saved user size. (It is possible for the two to coincide.) In this way, the // zoom button acts as a toggle. We determine the standard size based on the // content, but enforce a minimum width (calculated using the dimensions of the // screen) to ensure websites with small intrinsic width (such as google.com) // don't end up with a wee window. Moreover, we always declare the standard // width to be at least as big as the current width, i.e., we never want zooming // to the standard width to shrink the window. This is consistent with other // browsers' behaviour, and is desirable in multi-tab situations. Note, however, // that the "toggle" behaviour means that the window can still be "unzoomed" to // the user size. // Note: this method is also called from -isZoomed. If the returned zoomed rect // equals the current window's frame, -isZoomed returns YES. - (NSRect)windowWillUseStandardFrame:(NSWindow*)window defaultFrame:(NSRect)frame { // Forget that we grew the window up (if we in fact did). [self resetWindowGrowthState]; // |frame| already fills the current screen. Never touch y and height since we // always want to fill vertically. // If the shift key is down, maximize. Hopefully this should make the // "switchers" happy. if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) { return frame; } // To prevent strange results on portrait displays, the basic minimum zoomed // width is the larger of: 60% of available width, 60% of available height // (bounded by available width). const CGFloat kProportion = 0.6; CGFloat zoomedWidth = std::max(kProportion * NSWidth(frame), std::min(kProportion * NSHeight(frame), NSWidth(frame))); WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents(); if (contents) { // If the intrinsic width is bigger, then make it the zoomed width. const int kScrollbarWidth = 16; // TODO(viettrungluu): ugh. CGFloat intrinsicWidth = static_cast( contents->GetPreferredSize().width() + kScrollbarWidth); zoomedWidth = std::max(zoomedWidth, std::min(intrinsicWidth, NSWidth(frame))); } // Never shrink from the current size on zoom (see above). NSRect currentFrame = [[self window] frame]; zoomedWidth = std::max(zoomedWidth, NSWidth(currentFrame)); // |frame| determines our maximum extents. We need to set the origin of the // frame -- and only move it left if necessary. if (currentFrame.origin.x + zoomedWidth > NSMaxX(frame)) frame.origin.x = NSMaxX(frame) - zoomedWidth; else frame.origin.x = currentFrame.origin.x; // Set the width. Don't touch y or height. frame.size.width = zoomedWidth; return frame; } - (void)activate { [BrowserWindowUtils activateWindowForController:self]; } // Determine whether we should let a window zoom/unzoom to the given |newFrame|. // We avoid letting unzoom move windows between screens, because it's really // strange and unintuitive. - (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame { // Figure out which screen |newFrame| is on. NSScreen* newScreen = nil; CGFloat newScreenOverlapArea = 0.0; for (NSScreen* screen in [NSScreen screens]) { NSRect overlap = NSIntersectionRect(newFrame, [screen frame]); CGFloat overlapArea = NSWidth(overlap) * NSHeight(overlap); if (overlapArea > newScreenOverlapArea) { newScreen = screen; newScreenOverlapArea = overlapArea; } } // If we're somehow not on any screen, allow the zoom. if (!newScreen) return YES; // If the new screen is the current screen, we can return a definitive YES. // Note: This check is not strictly necessary, but just short-circuits in the // "no-brainer" case. To test the complicated logic below, comment this out! NSScreen* curScreen = [window screen]; if (newScreen == curScreen) return YES; // Worry a little: What happens when a window is on two (or more) screens? // E.g., what happens in a 50-50 scenario? Cocoa may reasonably elect to zoom // to the other screen rather than staying on the officially current one. So // we compare overlaps with the current window frame, and see if Cocoa's // choice was reasonable (allowing a small rounding error). This should // hopefully avoid us ever erroneously denying a zoom when a window is on // multiple screens. NSRect curFrame = [window frame]; NSRect newScrIntersectCurFr = NSIntersectionRect([newScreen frame], curFrame); NSRect curScrIntersectCurFr = NSIntersectionRect([curScreen frame], curFrame); if (NSWidth(newScrIntersectCurFr) * NSHeight(newScrIntersectCurFr) >= (NSWidth(curScrIntersectCurFr) * NSHeight(curScrIntersectCurFr) - 1.0)) { return YES; } // If it wasn't reasonable, return NO. return NO; } // Adjusts the window height by the given amount. - (BOOL)adjustWindowHeightBy:(CGFloat)deltaH { // By not adjusting the window height when initializing, we can ensure that // the window opens with the same size that was saved on close. if (initializing_ || [self isInAnyFullscreenMode] || deltaH == 0) return NO; NSWindow* window = [self window]; NSRect windowFrame = [window frame]; NSRect workarea = [[window screen] visibleFrame]; // Prevent the window from growing smaller than its minimum height: // http://crbug.com/230400 . if (deltaH < 0) { CGFloat minWindowHeight = [window minSize].height; if (windowFrame.size.height + deltaH < minWindowHeight) { // |deltaH| + |windowFrame.size.height| = |minWindowHeight|. deltaH = minWindowHeight - windowFrame.size.height; } if (deltaH == 0) { return NO; } } // If the window is not already fully in the workarea, do not adjust its frame // at all. if (!NSContainsRect(workarea, windowFrame)) return NO; // Record the position of the top/bottom of the window, so we can easily check // whether we grew the window upwards/downwards. CGFloat oldWindowMaxY = NSMaxY(windowFrame); CGFloat oldWindowMinY = NSMinY(windowFrame); // We are "zoomed" if we occupy the full vertical space. bool isZoomed = (windowFrame.origin.y == workarea.origin.y && NSHeight(windowFrame) == NSHeight(workarea)); // If we're shrinking the window.... if (deltaH < 0) { bool didChange = false; // Don't reset if not currently zoomed since shrinking can take several // steps! if (isZoomed) isShrinkingFromZoomed_ = YES; // If we previously grew at the top, shrink as much as allowed at the top // first. if (windowTopGrowth_ > 0) { CGFloat shrinkAtTopBy = MIN(-deltaH, windowTopGrowth_); windowFrame.size.height -= shrinkAtTopBy; // Shrink the window. deltaH += shrinkAtTopBy; // Update the amount left to shrink. windowTopGrowth_ -= shrinkAtTopBy; // Update the growth state. didChange = true; } // Similarly for the bottom (not an "else if" since we may have to // simultaneously shrink at both the top and at the bottom). Note that // |deltaH| may no longer be nonzero due to the above. if (deltaH < 0 && windowBottomGrowth_ > 0) { CGFloat shrinkAtBottomBy = MIN(-deltaH, windowBottomGrowth_); windowFrame.origin.y += shrinkAtBottomBy; // Move the window up. windowFrame.size.height -= shrinkAtBottomBy; // Shrink the window. deltaH += shrinkAtBottomBy; // Update the amount left.... windowBottomGrowth_ -= shrinkAtBottomBy; // Update the growth state. didChange = true; } // If we're shrinking from zoomed but we didn't change the top or bottom // (since we've reached the limits imposed by |window...Growth_|), then stop // here. Don't reset |isShrinkingFromZoomed_| since we might get called // again for the same shrink. if (isShrinkingFromZoomed_ && !didChange) return NO; } else { isShrinkingFromZoomed_ = NO; // Don't bother with anything else. if (isZoomed) return NO; } // Shrinking from zoomed is handled above (and is constrained by // |window...Growth_|). if (!isShrinkingFromZoomed_) { // Resize the window down until it hits the bottom of the workarea, then if // needed continue resizing upwards. Do not resize the window to be taller // than the current workarea. // Resize the window as requested, keeping the top left corner fixed. windowFrame.origin.y -= deltaH; windowFrame.size.height += deltaH; // If the bottom left corner is now outside the visible frame, move the // window up to make it fit, but make sure not to move the top left corner // out of the visible frame. if (windowFrame.origin.y < workarea.origin.y) { windowFrame.origin.y = workarea.origin.y; windowFrame.size.height = std::min(NSHeight(windowFrame), NSHeight(workarea)); } // Record (if applicable) how much we grew the window in either direction. // (N.B.: These only record growth, not shrinkage.) if (NSMaxY(windowFrame) > oldWindowMaxY) windowTopGrowth_ += NSMaxY(windowFrame) - oldWindowMaxY; if (NSMinY(windowFrame) < oldWindowMinY) windowBottomGrowth_ += oldWindowMinY - NSMinY(windowFrame); } // Disable subview resizing while resizing the window, or else we will get // unwanted renderer resizes. The calling code must call layoutSubviews to // make things right again. NSView* chromeContentView = [self chromeContentView]; BOOL autoresizesSubviews = [chromeContentView autoresizesSubviews]; [chromeContentView setAutoresizesSubviews:NO]; // On Yosemite the toolbar can flicker when hiding or showing the bookmarks // bar. Here, |chromeContentView| is set to not autoresize its subviews during // the window resize. Because |chromeContentView| is not flipped, if the // window is getting shorter, the toolbar will move up within the window. // Soon after, a call to layoutSubviews corrects its position. Passing NO to // setFrame:display: should keep the toolbarView's intermediate position // hidden, as should the prior call to disable screen updates. For some // reason, neither prevents the toolbarView's intermediate position from // becoming visible. Its subsequent appearance in its correct location causes // the flicker. It may be that the Appkit assumes that updating the window // immediately is not a big deal given that everything in it is layer-backed. // Indeed, turning off layer backing for all ancestors of the toolbarView // causes the flicker to go away. // // By shifting the toolbarView enough so that it's in its correct location // immediately after the call to setFrame:display:, the toolbar will be in // the right spot when the Appkit prematurely flushes the window contents to // the screen. http://crbug.com/444080 . if ([self hasToolbar]) { NSView* toolbarView = [toolbarController_ view]; NSRect currentWindowFrame = [window frame]; NSRect toolbarViewFrame = [toolbarView frame]; toolbarViewFrame.origin.y += windowFrame.size.height - currentWindowFrame.size.height; [toolbarView setFrame:toolbarViewFrame]; } [window setFrame:windowFrame display:NO]; [chromeContentView setAutoresizesSubviews:autoresizesSubviews]; return YES; } // Main method to resize browser window subviews. This method should be called // when resizing any child of the content view, rather than resizing the views // directly. If the view is already the correct height, does not force a // relayout. - (void)resizeView:(NSView*)view newHeight:(CGFloat)height { // We should only ever be called for one of the following four views. // |downloadShelfController_| may be nil. If we are asked to size the bookmark // bar directly, its superview must be this controller's content view. DCHECK(view); DCHECK(view == [toolbarController_ view] || view == [infoBarContainerController_ view] || view == [downloadShelfController_ view] || view == [bookmarkBarController_ view]); // The infobar has insufficient information to determine its new height. It // knows the total height of all of the info bars (which is what it passes // into this method), but knows nothing about the maximum arrow height, which // is determined by this class. if (view == [infoBarContainerController_ view]) { base::scoped_nsobject layout( [[BrowserWindowLayout alloc] init]); [self updateLayoutParameters:layout]; // Use the new height for the info bar. [layout setInfoBarHeight:height]; chrome::LayoutOutput output = [layout computeLayout]; height = NSHeight(output.infoBarFrame); } // Change the height of the view and call |-layoutSubViews|. We set the height // here without regard to where the view is on the screen or whether it needs // to "grow up" or "grow down." The below call to |-layoutSubviews| will // position each view correctly. NSRect frame = [view frame]; if (NSHeight(frame) == height) return; // Disable screen updates to prevent flickering. gfx::ScopedCocoaDisableScreenUpdates disabler; // Grow or shrink the window by the amount of the height change. We adjust // the window height only in two cases: // 1) We are adjusting the height of the bookmark bar and it is currently // animating either open or closed. // 2) We are adjusting the height of the download shelf. // // We do not adjust the window height for bookmark bar changes on the NTP. BOOL shouldAdjustBookmarkHeight = [bookmarkBarController_ isAnimatingBetweenState:BookmarkBar::HIDDEN andState:BookmarkBar::SHOW]; BOOL resizeRectDirty = NO; if ((shouldAdjustBookmarkHeight && view == [bookmarkBarController_ view]) || view == [downloadShelfController_ view]) { CGFloat deltaH = height - NSHeight(frame); if ([self adjustWindowHeightBy:deltaH] && view == [downloadShelfController_ view]) { // If the window height didn't change, the download shelf will change the // size of the contents. If the contents size doesn't change, send it // an explicit grow box invalidation (else, the resize message does that.) resizeRectDirty = YES; } } frame.size.height = height; // TODO(rohitrao): Determine if calling setFrame: twice is bad. [view setFrame:frame]; [self layoutSubviews]; if (resizeRectDirty) { // Send new resize rect to foreground tab. if (WebContents* contents = [self webContents]) { if (content::RenderViewHost* rvh = contents->GetRenderViewHost()) { rvh->GetWidget()->ResizeRectChanged( windowShim_->GetRootWindowResizerRect()); } } } } - (BOOL)handledByExtensionCommand:(NSEvent*)event priority:(ui::AcceleratorManager::HandlerPriority)priority { return extension_keybinding_registry_->ProcessKeyEvent( content::NativeWebKeyboardEvent(event), priority); } // StatusBubble delegate method: tell the status bubble the frame it should // position itself in. - (NSRect)statusBubbleBaseFrame { NSView* view = [overlayableContentsController_ view]; return [view convertRect:[view bounds] toView:nil]; } - (void)updateToolbarWithContents:(WebContents*)tab { [toolbarController_ updateToolbarWithContents:tab]; } - (void)resetTabState:(WebContents*)tab { [toolbarController_ resetTabState:tab]; } - (void)setStarredState:(BOOL)isStarred { [toolbarController_ setStarredState:isStarred]; } - (void)setCurrentPageIsTranslated:(BOOL)on { [toolbarController_ setTranslateIconLit:on]; } - (void)onActiveTabChanged:(content::WebContents*)oldContents to:(content::WebContents*)newContents { // No need to remove previous bubble. It will close itself. PermissionBubbleManager* manager(nullptr); if (oldContents) { manager = PermissionBubbleManager::FromWebContents(oldContents); if (manager) manager->HideBubble(); } if (newContents) { manager = PermissionBubbleManager::FromWebContents(newContents); if (manager) manager->DisplayPendingRequests(); } } - (void)zoomChangedForActiveTab:(BOOL)canShowBubble { [toolbarController_ zoomChangedForActiveTab:canShowBubble]; } // Return the rect, in WebKit coordinates (flipped), of the window's grow box // in the coordinate system of the content area of the currently selected tab. // |windowGrowBox| needs to be in the window's coordinate system. - (NSRect)selectedTabGrowBoxRect { NSWindow* window = [self window]; if (![window respondsToSelector:@selector(_growBoxRect)]) return NSZeroRect; // Before we return a rect, we need to convert it from window coordinates // to tab content area coordinates and flip the coordinate system. NSRect growBoxRect = [[self tabContentArea] convertRect:[window _growBoxRect] fromView:nil]; growBoxRect.origin.y = NSHeight([[self tabContentArea] frame]) - NSMaxY(growBoxRect); return growBoxRect; } // Accept tabs from a BrowserWindowController with the same Profile. - (BOOL)canReceiveFrom:(TabWindowController*)source { BrowserWindowController* realSource = base::mac::ObjCCast(source); if (!realSource || browser_->profile() != realSource->browser_->profile()) { return NO; } // Can't drag a tab from a normal browser to a pop-up if (browser_->type() != realSource->browser_->type()) { return NO; } return YES; } // Move a given tab view to the location of the current placeholder. If there is // no placeholder, it will go at the end. |controller| is the window controller // of a tab being dropped from a different window. It will be nil if the drag is // within the window, otherwise the tab is removed from that window before being // placed into this one. The implementation will call |-removePlaceholder| since // the drag is now complete. This also calls |-layoutTabs| internally so // clients do not need to call it again. - (void)moveTabViews:(NSArray*)views fromController:(TabWindowController*)dragController { if (dragController) { // Moving between windows. NSView* activeTabView = [dragController activeTabView]; BrowserWindowController* dragBWC = base::mac::ObjCCastStrict(dragController); // We will drop the tabs starting at indexOfPlaceholder, and increment from // there. We remove the placehoder before dropping the tabs, so that the // new tab animation's destination frame is correct. int tabIndex = [tabStripController_ indexOfPlaceholder]; [self removePlaceholder]; for (NSView* view in views) { // Figure out the WebContents to drop into our tab model from the source // window's model. int index = [dragBWC->tabStripController_ modelIndexForTabView:view]; WebContents* contents = dragBWC->browser_->tab_strip_model()->GetWebContentsAt(index); // The tab contents may have gone away if given a window.close() while it // is being dragged. If so, bail, we've got nothing to drop. if (!contents) continue; // Convert |view|'s frame (which starts in the source tab strip's // coordinate system) to the coordinate system of the destination tab // strip. This needs to be done before being detached so the window // transforms can be performed. NSRect destinationFrame = [view frame]; NSPoint tabOrigin = destinationFrame.origin; tabOrigin = [[dragController tabStripView] convertPoint:tabOrigin toView:nil]; tabOrigin = [[dragController window] convertBaseToScreen:tabOrigin]; tabOrigin = [[self window] convertScreenToBase:tabOrigin]; tabOrigin = [[self tabStripView] convertPoint:tabOrigin fromView:nil]; destinationFrame.origin = tabOrigin; // Before the tab is detached from its originating tab strip, store the // pinned state so that it can be maintained between the windows. bool isPinned = dragBWC->browser_->tab_strip_model()->IsTabPinned(index); // Now that we have enough information about the tab, we can remove it // from the dragging window. We need to do this *before* we add it to the // new window as this will remove the WebContents' delegate. [dragController detachTabView:view]; // Deposit it into our model at the appropriate location (it already knows // where it should go from tracking the drag). Doing this sets the tab's // delegate to be the Browser. [tabStripController_ dropWebContents:contents atIndex:tabIndex++ withFrame:destinationFrame asPinnedTab:isPinned activate:view == activeTabView]; } } else { // Moving within a window. for (NSView* view in views) { int index = [tabStripController_ modelIndexForTabView:view]; [tabStripController_ moveTabFromIndex:index]; } [self removePlaceholder]; } } // Tells the tab strip to forget about this tab in preparation for it being // put into a different tab strip, such as during a drop on another window. - (void)detachTabView:(NSView*)view { int index = [tabStripController_ modelIndexForTabView:view]; browser_->tab_strip_model()->DetachWebContentsAt(index); } - (NSArray*)tabViews { return [tabStripController_ tabViews]; } - (NSView*)activeTabView { return [tabStripController_ activeTabView]; } - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { [toolbarController_ setIsLoading:isLoading force:force]; } // Make the location bar the first responder, if possible. - (void)focusLocationBar:(BOOL)selectAll { [toolbarController_ focusLocationBar:selectAll]; } - (void)focusTabContents { content::WebContents* const activeWebContents = browser_->tab_strip_model()->GetActiveWebContents(); if (activeWebContents) activeWebContents->Focus(); } - (void)layoutTabs { [tabStripController_ layoutTabs]; } - (TabWindowController*)detachTabsToNewWindow:(NSArray*)tabViews draggedTab:(NSView*)draggedTab { DCHECK_GT([tabViews count], 0U); // Disable screen updates so that this appears as a single visual change. gfx::ScopedCocoaDisableScreenUpdates disabler; // Set the window size. Need to do this before we detach the tab so it's // still in the window. We have to flip the coordinates as that's what // is expected by the Browser code. NSWindow* sourceWindow = [draggedTab window]; NSRect windowRect = [sourceWindow frame]; NSScreen* screen = [sourceWindow screen]; windowRect.origin.y = NSHeight([screen frame]) - NSMaxY(windowRect); gfx::Rect browserRect(windowRect.origin.x, windowRect.origin.y, NSWidth(windowRect), NSHeight(windowRect)); std::vector contentses; TabStripModel* model = browser_->tab_strip_model(); for (TabView* tabView in tabViews) { // Fetch the tab contents for the tab being dragged. int index = [tabStripController_ modelIndexForTabView:tabView]; bool isPinned = model->IsTabPinned(index); bool isActive = (index == model->active_index()); TabStripModelDelegate::NewStripContents item; item.web_contents = model->GetWebContentsAt(index); item.add_types = (isActive ? TabStripModel::ADD_ACTIVE : TabStripModel::ADD_NONE) | (isPinned ? TabStripModel::ADD_PINNED : TabStripModel::ADD_NONE); contentses.push_back(item); } for (TabView* tabView in tabViews) { int index = [tabStripController_ modelIndexForTabView:tabView]; // Detach it from the source window, which just updates the model without // deleting the tab contents. This needs to come before creating the new // Browser because it clears the WebContents' delegate, which gets hooked // up during creation of the new window. model->DetachWebContentsAt(index); } // Create a new window with the dragged tabs in its model. Browser* newBrowser = browser_->tab_strip_model()->delegate()-> CreateNewStripWithContents(contentses, browserRect, false); // Get the new controller by asking the new window for its delegate. BrowserWindowController* controller = reinterpret_cast( [newBrowser->window()->GetNativeWindow() delegate]); DCHECK(controller && [controller isKindOfClass:[TabWindowController class]]); // And make sure we use the correct frame in the new view. TabStripController* tabStripController = [controller tabStripController]; NSView* tabStrip = [self tabStripView]; NSEnumerator* tabEnumerator = [tabViews objectEnumerator]; for (NSView* newView in [tabStripController tabViews]) { NSView* oldView = [tabEnumerator nextObject]; if (oldView) { // Pushes tabView's frame back inside the tabstrip. NSRect sourceTabRect = [oldView frame]; NSSize tabOverflow = [self overflowFrom:[tabStrip convertRect:sourceTabRect toView:nil] to:[tabStrip frame]]; NSRect tabRect = NSOffsetRect(sourceTabRect, -tabOverflow.width, -tabOverflow.height); // Force the added tab to the right size (remove stretching.) tabRect.size.height = [TabStripController defaultTabHeight]; [tabStripController setFrame:tabRect ofTabView:newView]; } } return controller; } - (void)insertPlaceholderForTab:(TabView*)tab frame:(NSRect)frame { [super insertPlaceholderForTab:tab frame:frame]; [tabStripController_ insertPlaceholderForTab:tab frame:frame]; } - (void)removePlaceholder { [super removePlaceholder]; [tabStripController_ insertPlaceholderForTab:nil frame:NSZeroRect]; } - (BOOL)isDragSessionActive { // The tab can be dragged within the existing tab strip or detached // into its own window (then the overlay window will be present). return [[self tabStripController] isDragSessionActive] || [self overlayWindow] != nil; } - (BOOL)tabDraggingAllowed { return [tabStripController_ tabDraggingAllowed]; } - (BOOL)tabTearingAllowed { return ![self isInAnyFullscreenMode]; } - (BOOL)windowMovementAllowed { return ![self isInAnyFullscreenMode]; } - (BOOL)isTabFullyVisible:(TabView*)tab { return [tabStripController_ isTabFullyVisible:tab]; } - (void)showNewTabButton:(BOOL)show { [tabStripController_ showNewTabButton:show]; } - (BOOL)shouldShowAvatar { if (![self hasTabStrip]) return NO; if (browser_->profile()->IsOffTheRecord()) return YES; ProfileInfoCache& cache = g_browser_process->profile_manager()->GetProfileInfoCache(); if (cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath()) == std::string::npos) { return NO; } return AvatarMenu::ShouldShowAvatarMenu(); } - (BOOL)shouldUseNewAvatarButton { return profiles::IsRegularOrGuestSession(browser_.get()); } - (BOOL)isBookmarkBarVisible { return [bookmarkBarController_ isVisible]; } - (BOOL)isBookmarkBarAnimating { return [bookmarkBarController_ isAnimationRunning]; } - (BookmarkBarController*)bookmarkBarController { return bookmarkBarController_; } - (DevToolsController*)devToolsController { return devToolsController_; } - (BOOL)isDownloadShelfVisible { return downloadShelfController_ != nil && [downloadShelfController_ isVisible]; } - (void)createAndAddDownloadShelf { if (!downloadShelfController_.get()) { downloadShelfController_.reset([[DownloadShelfController alloc] initWithBrowser:browser_.get() resizeDelegate:self]); [self.chromeContentView addSubview:[downloadShelfController_ view]]; } } - (DownloadShelfController*)downloadShelf { return downloadShelfController_; } - (void)addFindBar:(FindBarCocoaController*)findBarCocoaController { // Shouldn't call addFindBar twice. DCHECK(!findBarCocoaController_.get()); // Create a controller for the findbar. findBarCocoaController_.reset([findBarCocoaController retain]); [self layoutSubviews]; [self updateSubviewZOrder]; } - (NSWindow*)createFullscreenWindow { NSWindow* window = [[[FullscreenWindow alloc] initForScreen:[[self window] screen]] autorelease]; SetUpBrowserWindowCommandHandler(window); return window; } - (NSInteger)numberOfTabs { // count() includes pinned tabs. return browser_->tab_strip_model()->count(); } - (BOOL)hasLiveTabs { return !browser_->tab_strip_model()->empty(); } - (NSString*)activeTabTitle { WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents(); return base::SysUTF16ToNSString(contents->GetTitle()); } - (NSRect)regularWindowFrame { return [self isInAnyFullscreenMode] ? savedRegularWindowFrame_ : [[self window] frame]; } // (Override of |TabWindowController| method.) - (BOOL)hasTabStrip { return [self supportsWindowFeature:Browser::FEATURE_TABSTRIP]; } - (BOOL)isTabDraggable:(NSView*)tabView { // TODO(avi, thakis): ConstrainedWindowSheetController has no api to move // tabsheets between windows. Until then, we have to prevent having to move a // tabsheet between windows, e.g. no tearing off of tabs. int index = [tabStripController_ modelIndexForTabView:tabView]; WebContents* contents = browser_->tab_strip_model()->GetWebContentsAt(index); if (!contents) return NO; const web_modal::WebContentsModalDialogManager* manager = web_modal::WebContentsModalDialogManager::FromWebContents(contents); return !manager || !manager->IsDialogActive(); } // TabStripControllerDelegate protocol. - (void)onActivateTabWithContents:(WebContents*)contents { // Update various elements that are interested in knowing the current // WebContents. // Update all the UI bits. windowShim_->UpdateTitleBar(); // Update the bookmark bar. // TODO(viettrungluu): perhaps update to not terminate running animations (if // applicable)? windowShim_->BookmarkBarStateChanged( BookmarkBar::DONT_ANIMATE_STATE_CHANGE); [infoBarContainerController_ changeWebContents:contents]; // Must do this after bookmark and infobar updates to avoid // unnecesary resize in contents. [devToolsController_ updateDevToolsForWebContents:contents withProfile:browser_->profile()]; } - (void)onTabChanged:(TabStripModelObserver::TabChangeType)change withContents:(WebContents*)contents { // Update titles if this is the currently selected tab and if it isn't just // the loading state which changed. if (change != TabStripModelObserver::LOADING_ONLY) windowShim_->UpdateTitleBar(); // Update the bookmark bar if this is the currently selected tab and if it // isn't just the title which changed. This for transitions between the NTP // (showing its floating bookmark bar) and normal web pages (showing no // bookmark bar). // TODO(viettrungluu): perhaps update to not terminate running animations? if (change != TabStripModelObserver::TITLE_NOT_LOADING) { windowShim_->BookmarkBarStateChanged( BookmarkBar::DONT_ANIMATE_STATE_CHANGE); } } - (void)onTabDetachedWithContents:(WebContents*)contents { [infoBarContainerController_ tabDetachedWithContents:contents]; } - (void)userChangedTheme { NSView* rootView = [[[self window] contentView] superview]; [rootView cr_recursivelyInvokeBlock:^(id view) { if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)]) [view windowDidChangeTheme]; // TODO(andresantoso): Remove this once all themed views respond to // windowDidChangeTheme above. [view setNeedsDisplay:YES]; }]; } - (const ui::ThemeProvider*)themeProvider { return &ThemeService::GetThemeProviderForProfile(browser_->profile()); } - (ThemedWindowStyle)themedWindowStyle { ThemedWindowStyle style = 0; if (browser_->profile()->IsOffTheRecord()) style |= THEMED_INCOGNITO; if (browser_->is_devtools()) style |= THEMED_DEVTOOLS; if (browser_->is_type_popup()) style |= THEMED_POPUP; return style; } - (NSPoint)themeImagePositionForAlignment:(ThemeImageAlignment)alignment { NSView* windowChromeView = [[[self window] contentView] superview]; NSView* tabStripView = nil; if ([self hasTabStrip]) tabStripView = [self tabStripView]; return [BrowserWindowUtils themeImagePositionFor:windowChromeView withTabStrip:tabStripView alignment:alignment]; } - (NSPoint)bookmarkBubblePoint { return [toolbarController_ bookmarkBubblePoint]; } // Show the bookmark bubble (e.g. user just clicked on the STAR). - (void)showBookmarkBubbleForURL:(const GURL&)url alreadyBookmarked:(BOOL)alreadyMarked { if (bookmarkBubbleObserver_.get()) return; bookmarkBubbleObserver_.reset(new BookmarkBubbleObserverCocoa(self)); if (chrome::ToolkitViewsDialogsEnabled()) { chrome::ShowBookmarkBubbleViewsAtPoint( gfx::ScreenPointFromNSPoint( [[self window] convertBaseToScreen:[self bookmarkBubblePoint]]), [[self window] contentView], bookmarkBubbleObserver_.get(), browser_.get(), url, alreadyMarked); } else { BookmarkModel* model = BookmarkModelFactory::GetForProfile(browser_->profile()); bookmarks::ManagedBookmarkService* managed = ManagedBookmarkServiceFactory::GetForProfile(browser_->profile()); const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url); bookmarkBubbleController_ = [[BookmarkBubbleController alloc] initWithParentWindow:[self window] bubbleObserver:bookmarkBubbleObserver_.get() managed:managed model:model node:node alreadyBookmarked:alreadyMarked]; [bookmarkBubbleController_ showWindow:self]; } DCHECK(bookmarkBubbleObserver_); } - (void)bookmarkBubbleClosed { // Nil out the weak bookmark bubble controller reference. bookmarkBubbleController_ = nil; bookmarkBubbleObserver_.reset(); } // Handle the editBookmarkNode: action sent from bookmark bubble controllers. - (void)editBookmarkNode:(id)sender { BOOL responds = [sender respondsToSelector:@selector(node)]; DCHECK(responds); if (responds) { const BookmarkNode* node = [sender node]; if (node) BookmarkEditor::Show([self window], browser_->profile(), BookmarkEditor::EditDetails::EditNode(node), BookmarkEditor::SHOW_TREE); } } - (void)showTranslateBubbleForWebContents:(content::WebContents*)contents step:(translate::TranslateStep)step errorType:(translate::TranslateErrors::Type) errorType { // TODO(hajimehoshi): The similar logic exists at TranslateBubbleView:: // ShowBubble. This should be unified. if (translateBubbleController_) { // When the user reads the advanced setting panel, the bubble should not be // changed because they are focusing on the bubble. if (translateBubbleController_.webContents == contents && translateBubbleController_.model->GetViewState() == TranslateBubbleModel::VIEW_STATE_ADVANCED) { return; } if (step != translate::TRANSLATE_STEP_TRANSLATE_ERROR) { TranslateBubbleModel::ViewState viewState = TranslateBubbleModelImpl::TranslateStepToViewState(step); [translateBubbleController_ switchView:viewState]; } else { [translateBubbleController_ switchToErrorView:errorType]; } return; } std::string sourceLanguage; std::string targetLanguage; ChromeTranslateClient::GetTranslateLanguages( contents, &sourceLanguage, &targetLanguage); scoped_ptr uiDelegate( new translate::TranslateUIDelegate( ChromeTranslateClient::GetManagerFromWebContents(contents) ->GetWeakPtr(), sourceLanguage, targetLanguage)); scoped_ptr model( new TranslateBubbleModelImpl(step, std::move(uiDelegate))); translateBubbleController_ = [[TranslateBubbleController alloc] initWithParentWindow:self model:std::move(model) webContents:contents]; [translateBubbleController_ showWindow:nil]; NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; [center addObserver:self selector:@selector(translateBubbleWindowWillClose:) name:NSWindowWillCloseNotification object:[translateBubbleController_ window]]; } - (void)dismissPermissionBubble { PermissionBubbleManager* manager = [self permissionBubbleManager]; if (manager) manager->HideBubble(); } // Nil out the weak translate bubble controller reference. - (void)translateBubbleWindowWillClose:(NSNotification*)notification { DCHECK_EQ([notification object], [translateBubbleController_ window]); NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; [center removeObserver:self name:NSWindowWillCloseNotification object:[translateBubbleController_ window]]; translateBubbleController_ = nil; } // If the browser is in incognito mode or has multi-profiles, install the image // view to decorate the window at the upper right. Use the same base y // coordinate as the tab strip. - (void)installAvatar { // Install the image into the badge view. Hide it for now; positioning and // sizing will be done by the layout code. The AvatarIcon will choose which // image to display based on the browser. The AvatarButton will display // the browser profile's name unless the browser is incognito. NSView* view; if ([self shouldUseNewAvatarButton]) { avatarButtonController_.reset( [[AvatarButtonController alloc] initWithBrowser:browser_.get()]); } else { avatarButtonController_.reset( [[AvatarIconController alloc] initWithBrowser:browser_.get()]); } view = [avatarButtonController_ view]; [view setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin]; [view setHidden:![self shouldShowAvatar]]; // Install the view. [[[self window] contentView] addSubview:view]; } // Called when we get a three-finger swipe. - (void)swipeWithEvent:(NSEvent*)event { CGFloat deltaX = [event deltaX]; CGFloat deltaY = [event deltaY]; // Map forwards and backwards to history; left is positive, right is negative. unsigned int command = 0; if (deltaX > 0.5) { command = IDC_BACK; } else if (deltaX < -0.5) { command = IDC_FORWARD; } else if (deltaY > 0.5) { // TODO(pinkerton): figure out page-up, http://crbug.com/16305 } else if (deltaY < -0.5) { // TODO(pinkerton): figure out page-down, http://crbug.com/16305 } // Ensure the command is valid first (ExecuteCommand() won't do that) and // then make it so. if (chrome::IsCommandEnabled(browser_.get(), command)) { chrome::ExecuteCommandWithDisposition( browser_.get(), command, ui::WindowOpenDispositionFromNSEvent(event)); } } // Delegate method called when window is resized. - (void)windowDidResize:(NSNotification*)notification { [self saveWindowPositionIfNeeded]; // Resize (and possibly move) the status bubble. Note that we may get called // when the status bubble does not exist. if (statusBubble_) { statusBubble_->UpdateSizeAndPosition(); } // Let the selected RenderWidgetHostView know, so that it can tell plugins. if (WebContents* contents = [self webContents]) { if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) rwhv->WindowFrameChanged(); } // The FindBar needs to know its own position to properly detect overlaps // with find results. The position changes whenever the window is resized, // and |layoutSubviews| computes the FindBar's position. // TODO: calling |layoutSubviews| here is a waste, find a better way to // do this. if ([findBarCocoaController_ isFindBarVisible]) [self layoutSubviews]; } // Handle the openLearnMoreAboutCrashLink: action from SadTabView when // "Learn more" link in "Aw snap" page (i.e. crash page or sad tab) is // clicked. Decoupling the action from its target makes unit testing possible. - (void)openLearnMoreAboutCrashLink:(id)sender { if (WebContents* contents = [self webContents]) { OpenURLParams params(GURL(chrome::kCrashReasonURL), Referrer(), CURRENT_TAB, ui::PAGE_TRANSITION_LINK, false); contents->OpenURL(params); } } // Delegate method called when window did move. (See below for why we don't use // |-windowWillMove:|, which is called less frequently than |-windowDidMove| // instead.) - (void)windowDidMove:(NSNotification*)notification { [self saveWindowPositionIfNeeded]; NSWindow* window = [self window]; NSRect windowFrame = [window frame]; NSRect workarea = [[window screen] visibleFrame]; // We reset the window growth state whenever the window is moved out of the // work area or away (up or down) from the bottom or top of the work area. // Unfortunately, Cocoa sends |-windowWillMove:| too frequently (including // when clicking on the title bar to activate), and of course // |-windowWillMove| is called too early for us to apply our heuristic. (The // heuristic we use for detecting window movement is that if |windowTopGrowth_ // > 0|, then we should be at the bottom of the work area -- if we're not, // we've moved. Similarly for the other side.) if (!NSContainsRect(workarea, windowFrame) || (windowTopGrowth_ > 0 && NSMinY(windowFrame) != NSMinY(workarea)) || (windowBottomGrowth_ > 0 && NSMaxY(windowFrame) != NSMaxY(workarea))) [self resetWindowGrowthState]; // Let the selected RenderWidgetHostView know, so that it can tell plugins. if (WebContents* contents = [self webContents]) { if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) rwhv->WindowFrameChanged(); } } // Delegate method called when window will be resized; not called for // |-setFrame:display:|. - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize { [self resetWindowGrowthState]; return frameSize; } // Delegate method: see |NSWindowDelegate| protocol. - (id)windowWillReturnFieldEditor:(NSWindow*)sender toObject:(id)obj { // Ask the toolbar controller if it wants to return a custom field editor // for the specific object. return [toolbarController_ customFieldEditorForObject:obj]; } // (Needed for |BookmarkBarControllerDelegate| protocol.) - (void)bookmarkBar:(BookmarkBarController*)controller didChangeFromState:(BookmarkBar::State)oldState toState:(BookmarkBar::State)newState { [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]]; [self adjustToolbarAndBookmarkBarForCompression: [controller getDesiredToolbarHeightCompression]]; } // (Needed for |BookmarkBarControllerDelegate| protocol.) - (void)bookmarkBar:(BookmarkBarController*)controller willAnimateFromState:(BookmarkBar::State)oldState toState:(BookmarkBar::State)newState { [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]]; [self adjustToolbarAndBookmarkBarForCompression: [controller getDesiredToolbarHeightCompression]]; } // (Private/TestingAPI) - (void)resetWindowGrowthState { windowTopGrowth_ = 0; windowBottomGrowth_ = 0; isShrinkingFromZoomed_ = NO; } - (NSSize)overflowFrom:(NSRect)source to:(NSRect)target { // If |source|'s boundary is outside of |target|'s, set its distance // to |x|. Note that |source| can overflow to both side, but we // have nothing to do for such case. CGFloat x = 0; if (NSMaxX(target) < NSMaxX(source)) // |source| overflows to right x = NSMaxX(source) - NSMaxX(target); else if (NSMinX(source) < NSMinX(target)) // |source| overflows to left x = NSMinX(source) - NSMinX(target); // Same as |x| above. CGFloat y = 0; if (NSMaxY(target) < NSMaxY(source)) y = NSMaxY(source) - NSMaxY(target); else if (NSMinY(source) < NSMinY(target)) y = NSMinY(source) - NSMinY(target); return NSMakeSize(x, y); } // (Private/TestingAPI) - (ExclusiveAccessBubbleWindowController*) exclusiveAccessBubbleWindowController { return exclusiveAccessBubbleWindowController_.get(); } - (NSRect)omniboxPopupAnchorRect { // Start with toolbar rect. NSView* toolbarView = [toolbarController_ view]; NSRect anchorRect = [toolbarView frame]; // Adjust to account for height and possible bookmark bar. Compress by 1 // to account for the separator. anchorRect.origin.y = NSMaxY(anchorRect) - [toolbarController_ desiredHeightForCompression:1]; // Shift to window base coordinates. return [[toolbarView superview] convertRect:anchorRect toView:nil]; } - (BOOL)isLayoutSubviewsBlocked { return blockLayoutSubviews_; } - (BOOL)isActiveTabContentsControllerResizeBlocked { return [[tabStripController_ activeTabContentsController] blockFullscreenResize]; } - (void)sheetDidEnd:(NSWindow*)sheet returnCode:(NSInteger)code context:(void*)context { [sheet orderOut:self]; } - (PresentationModeController*)presentationModeController { return presentationModeController_.get(); } - (void)executeExtensionCommand:(const std::string&)extension_id command:(const extensions::Command&)command { // Global commands are handled by the ExtensionCommandsGlobalRegistry // instance. DCHECK(!command.global()); extension_keybinding_registry_->ExecuteCommand(extension_id, command.accelerator()); } - (void)setMediaState:(TabMediaState)mediaState { static_cast([self browserWindow]) ->UpdateMediaState(mediaState); } - (TabMediaState)mediaState { return static_cast([self browserWindow])->media_state(); } @end // @implementation BrowserWindowController @implementation BrowserWindowController(Fullscreen) - (void)handleLionToggleFullscreen { DCHECK(base::mac::IsOSLionOrLater()); chrome::ExecuteCommand(browser_.get(), IDC_FULLSCREEN); } - (void)enterBrowserFullscreenWithToolbar:(BOOL)withToolbar { if (!chrome::mac::SupportsSystemFullscreen()) { if (![self isInImmersiveFullscreen]) [self enterImmersiveFullscreen]; return; } if ([self isInAppKitFullscreen]) { [self updateFullscreenWithToolbar:withToolbar]; } else { // Need to invoke AppKit Fullscreen API. Presentation mode (if set) will // automatically be enabled in |-windowWillEnterFullScreen:|. enteringPresentationMode_ = !withToolbar; [self enterAppKitFullscreen]; } } - (void)updateFullscreenWithToolbar:(BOOL)withToolbar { [self adjustUIForSlidingFullscreenStyle: withToolbar ? fullscreen_mac::OMNIBOX_TABS_PRESENT : fullscreen_mac::OMNIBOX_TABS_HIDDEN]; } - (void)updateFullscreenExitBubbleURL:(const GURL&)url bubbleType:(ExclusiveAccessBubbleType)bubbleType { fullscreenUrl_ = url; exclusiveAccessBubbleType_ = bubbleType; [self layoutSubviews]; [self showFullscreenExitBubbleIfNecessary]; } - (void)setFullscreenToolbarHidden:(BOOL)shouldHide { if (shouldHideFullscreenToolbar_ == shouldHide) return; [presentationModeController_ setToolbarFraction:0.0]; shouldHideFullscreenToolbar_ = shouldHide; if ([self isInAppKitFullscreen]) [self updateFullscreenWithToolbar:!shouldHideFullscreenToolbar_]; } - (BOOL)isInAnyFullscreenMode { return [self isInImmersiveFullscreen] || [self isInAppKitFullscreen]; } - (BOOL)isInImmersiveFullscreen { return fullscreenWindow_.get() != nil || enteringImmersiveFullscreen_; } - (BOOL)isInAppKitFullscreen { return !exitingAppKitFullscreen_ && (([[self window] styleMask] & NSFullScreenWindowMask) == NSFullScreenWindowMask || enteringAppKitFullscreen_); } - (void)enterExtensionFullscreenForURL:(const GURL&)url bubbleType:(ExclusiveAccessBubbleType)bubbleType { if (chrome::mac::SupportsSystemFullscreen()) { fullscreenUrl_ = url; exclusiveAccessBubbleType_ = bubbleType; [self enterBrowserFullscreenWithToolbar:NO]; } else { [self enterImmersiveFullscreen]; DCHECK(!url.is_empty()); [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType]; } } - (void)enterWebContentFullscreenForURL:(const GURL&)url bubbleType:(ExclusiveAccessBubbleType)bubbleType { // HTML5 Fullscreen should only use AppKit fullscreen in 10.10+. // However, if the user is using multiple monitors and turned off // "Separate Space in Each Display", use Immersive Fullscreen so // that the other monitors won't blank out. gfx::Screen* screen = gfx::Screen::GetScreenFor([[self window] contentView]); BOOL hasMultipleMonitors = screen && screen->GetNumDisplays() > 1; if (chrome::mac::SupportsSystemFullscreen() && base::mac::IsOSYosemiteOrLater() && !(hasMultipleMonitors && ![NSScreen screensHaveSeparateSpaces])) { [self enterAppKitFullscreen]; } else { [self enterImmersiveFullscreen]; } if (!url.is_empty()) [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType]; } - (void)exitAnyFullscreen { // TODO(erikchen): Fullscreen modes should stack. Should be able to exit // Immersive Fullscreen and still be in AppKit Fullscreen. if ([self isInAppKitFullscreen]) [self exitAppKitFullscreen]; if ([self isInImmersiveFullscreen]) [self exitImmersiveFullscreen]; } - (BOOL)inPresentationMode { return presentationModeController_.get() && [presentationModeController_ inPresentationMode] && presentationModeController_.get().slidingStyle == fullscreen_mac::OMNIBOX_TABS_HIDDEN; } - (BOOL)shouldHideFullscreenToolbar { return shouldHideFullscreenToolbar_; } - (void)resizeFullscreenWindow { DCHECK([self isInAnyFullscreenMode]); if (![self isInAnyFullscreenMode]) return; NSWindow* window = [self window]; [window setFrame:[[window screen] frame] display:YES]; [self layoutSubviews]; } - (BOOL)isBarVisibilityLockedForOwner:(id)owner { DCHECK(owner); DCHECK(barVisibilityLocks_); return [barVisibilityLocks_ containsObject:owner]; } - (void)lockBarVisibilityForOwner:(id)owner withAnimation:(BOOL)animate delay:(BOOL)delay { if (![self isBarVisibilityLockedForOwner:owner]) { [barVisibilityLocks_ addObject:owner]; // If enabled, show the overlay if necessary (and if in presentation mode). if (barVisibilityUpdatesEnabled_) { [presentationModeController_ ensureOverlayShownWithAnimation:animate delay:delay]; } } } - (void)releaseBarVisibilityForOwner:(id)owner withAnimation:(BOOL)animate delay:(BOOL)delay { if ([self isBarVisibilityLockedForOwner:owner]) { [barVisibilityLocks_ removeObject:owner]; // If enabled, hide the overlay if necessary (and if in presentation mode). if (barVisibilityUpdatesEnabled_ && ![barVisibilityLocks_ count]) { [presentationModeController_ ensureOverlayHiddenWithAnimation:animate delay:delay]; } } } - (BOOL)floatingBarHasFocus { NSResponder* focused = [[self window] firstResponder]; return [focused isKindOfClass:[AutocompleteTextFieldEditor class]]; } @end // @implementation BrowserWindowController(Fullscreen) @implementation BrowserWindowController(WindowType) - (BOOL)supportsWindowFeature:(int)feature { return browser_->SupportsWindowFeature( static_cast(feature)); } - (BOOL)hasTitleBar { return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR]; } - (BOOL)hasToolbar { return [self supportsWindowFeature:Browser::FEATURE_TOOLBAR]; } - (BOOL)hasLocationBar { return [self supportsWindowFeature:Browser::FEATURE_LOCATIONBAR]; } - (BOOL)supportsBookmarkBar { return [self supportsWindowFeature:Browser::FEATURE_BOOKMARKBAR]; } - (BOOL)isTabbedWindow { return browser_->is_type_tabbed(); } @end // @implementation BrowserWindowController(WindowType)