| // 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 <cmath> |
| #include <numeric> |
| #include <utility> |
| |
| #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 "build/buildflag.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/lifetime/browser_shutdown.h" |
| #include "chrome/browser/permissions/permission_request_manager.h" |
| #include "chrome/browser/profiles/avatar_menu.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/profiles/profile_attributes_storage.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_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/exclusive_access_controller_views.h" |
| #include "chrome/browser/ui/cocoa/browser_dialogs_views_mac.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/fullscreen_toolbar_controller.h" |
| #import "chrome/browser/ui/cocoa/fullscreen/fullscreen_toolbar_visibility_lock_controller.h" |
| #include "chrome/browser/ui/cocoa/fullscreen_placeholder_view.h" |
| #import "chrome/browser/ui/cocoa/fullscreen_window.h" |
| #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" |
| #include "chrome/browser/ui/cocoa/l10n_util.h" |
| #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h" |
| #import "chrome/browser/ui/cocoa/location_bar/location_bar_view_mac.h" |
| #import "chrome/browser/ui/cocoa/location_bar/star_decoration.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/app_toolbar_button.h" |
| #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" |
| #import "chrome/browser/ui/cocoa/touchbar/browser_window_touch_bar_controller.h" |
| #include "chrome/browser/ui/cocoa/translate/translate_bubble_bridge_views.h" |
| #import "chrome/browser/ui/cocoa/translate/translate_bubble_controller.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/window_sizer/window_sizer.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/extensions/command.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/bookmarks/browser/bookmark_model.h" |
| #include "components/bookmarks/managed/managed_bookmark_service.h" |
| #include "components/omnibox/browser/omnibox_edit_model.h" |
| #include "components/omnibox/browser/omnibox_popup_model.h" |
| #include "components/signin/core/browser/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" |
| #import "ui/base/cocoa/touch_bar_forward_declarations.h" |
| #include "ui/base/ui_features.h" |
| #include "ui/display/screen.h" |
| #import "ui/gfx/mac/coordinate_conversion.h" |
| #include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h" |
| #include "ui/gfx/scrollbar_size.h" |
| |
| using bookmarks::BookmarkModel; |
| using bookmarks::BookmarkNode; |
| |
| // 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_<area>.mm |
| // - ... in categories called "(<Area>)" or "(<PrivateArea>)" |
| // 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::RenderWidgetHostView; |
| using content::WebContents; |
| |
| namespace { |
| |
| // Make |window| able to handle Browser commands. |
| void SetUpBrowserWindowCommandHandler(NSWindow* window) { |
| [base::mac::ObjCCastStrict<ChromeEventProcessingWindow>(window) |
| setCommandHandler:[[[BrowserWindowCommandHandler alloc] init] |
| autorelease]]; |
| } |
| |
| // Decouples the command dispatcher associated with |window| from Browser |
| // command handling. This prevents handlers with a reference to |window| |
| // attempting to look up a Browser* for it. |
| void ClearCommandHandler(NSWindow* window) { |
| [base::mac::ObjCCastStrict<ChromeEventProcessingWindow>(window) |
| setCommandHandler:nil]; |
| } |
| |
| // Returns true if the Tab Detaching in Fullscreen is enabled. It's enabled by |
| // default. |
| bool IsTabDetachingInFullscreenEnabled() { |
| return !base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kDisableFullscreenTabDetaching); |
| } |
| |
| } // namespace |
| |
| @implementation BrowserWindowController |
| |
| + (BrowserWindowController*)browserWindowControllerForWindow:(NSWindow*)window { |
| return base::mac::ObjCCast<BrowserWindowController>( |
| [TabWindowController tabWindowControllerForWindow:window]); |
| } |
| |
| + (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); |
| bool hasTitleBar = browser->SupportsWindowFeature(Browser::FEATURE_TITLEBAR); |
| if ((self = [super initTabWindowControllerWithTabStrip:hasTabStrip |
| titleBar:hasTitleBar])) { |
| 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)); |
| |
| // This has to happen before -enforceMinWindowSize: is called further down. |
| [[self window] |
| setMinSize:(browser->is_type_tabbed() ? kMinCocoaTabbedWindowSize |
| : kMinCocoaPopupWindowSize) |
| .ToCGSize()]; |
| |
| // 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 <http://crbug.com/79113>. |
| [window setRestorable:NO]; |
| |
| // Get the windows to swish in on Lion. |
| [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)); |
| } |
| |
| // Creates the manager for fullscreen and fullscreen bubbles. |
| exclusiveAccessController_.reset( |
| new ExclusiveAccessController(self, browser_.get())); |
| |
| // 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() |
| profile:browser->profile() |
| browser:browser]); |
| [[toolbarController_ toolbarView] setResizeDelegate: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]); |
| // This call loads the view. |
| BookmarkBarToolbarView* bookmarkBarView = |
| [bookmarkBarController_ controlledView]; |
| [bookmarkBarView setResizeDelegate:self]; |
| |
| [bookmarkBarController_ setBookmarkBarEnabled:[self supportsBookmarkBar]]; |
| |
| // Create the infobar container view, so we can pass it to the |
| // ToolbarController. |
| infoBarContainerController_.reset( |
| [[InfoBarContainerController alloc] initWithResizeDelegate:self]); |
| |
| // 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); |
| |
| [self updateFullscreenCollectionBehavior]; |
| |
| [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); |
| |
| // 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]; |
| |
| extensionKeybindingRegistry_.reset( |
| new ExtensionKeybindingRegistryCocoa(browser_->profile(), |
| [self window], |
| extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS, |
| windowShim_.get())); |
| |
| blockLayoutSubviews_ = NO; |
| |
| // We are done initializing now. |
| initializing_ = NO; |
| } |
| return self; |
| } |
| |
| - (void)dealloc { |
| browser_->tab_strip_model()->CloseAllTabs(); |
| |
| DCHECK([self window]); |
| ClearCommandHandler([self window]); |
| |
| // Explicitly release |fullscreenToolbarController_| here, as it may call |
| // back to this BWC in |-dealloc|. |
| [fullscreenToolbarController_ exitFullscreenMode]; |
| fullscreenToolbarController_.reset(); |
| |
| // Explicitly release |fullscreenTransition_| here since it may call back to |
| // this BWC in |-dealloc|. Reset the fullscreen variables. |
| if (fullscreenTransition_) { |
| [fullscreenTransition_ browserWillBeDestroyed]; |
| [self resetCustomAppKitFullscreenVariables]; |
| } |
| |
| // 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]; |
| } |
| |
| // Hack to address crbug.com/667274 |
| // On TouchBar MacBooks, the touch bar machinery retains a reference |
| // to the browser window controller (which is an NSTouchBarProvider by |
| // default) but doesn't release it if Chrome quits before it takes the |
| // key window (for example, quitting from the Dock icon context menu.) |
| // |
| // If the window denies being a touch bar provider, it's never added |
| // to the set of providers and the reference is never taken. This |
| // prevents us from providing a touch bar from the window directly |
| // but descendant responders can still provide one. |
| // |
| // rdar://29467717 |
| - (BOOL)conformsToProtocol:(Protocol*)protocol { |
| if ([protocol isEqual:NSProtocolFromString(@"NSFunctionBarProvider")] || |
| [protocol isEqual:NSProtocolFromString(@"NSTouchBarProvider")]) { |
| return NO; |
| } |
| return [super conformsToProtocol:protocol]; |
| } |
| |
| - (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]]; |
| |
| // This is invoked from chrome::SessionEnding() which will terminate the |
| // process without spinning another RunLoop. So no need to perform an |
| // autorelease. Note this is currently controlled by an experiment. See |
| // features::kDesktopFastShutdown in chrome/browser/features.cc. |
| DCHECK_EQ(browser_shutdown::GetShutdownType(), browser_shutdown::END_SESSION); |
| } |
| |
| // 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 { |
| // Speculative fix for http://crbug.com/671213. It seems possible that AppKit |
| // may invoke -windowWillClose: twice under rare conditions. That would cause |
| // the logic below to post a second -autorelease, resulting in a double free. |
| // (Well, actually, a zombie access when the closure tries to call release on |
| // the strongly captured |self| pointer). |
| DCHECK(!didWindowWillClose_) << "If hit, please update crbug.com/671213."; |
| if (didWindowWillClose_) |
| return; |
| |
| didWindowWillClose_ = YES; |
| |
| 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 { |
| // Set this window as active even if the previously active window was the |
| // same one. This is needed for tracking visibility changes of a browser. |
| if (browser_->window()) |
| 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(extensionKeybindingRegistry_.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); |
| |
| if (browser_->window()) |
| BrowserList::NotifyBrowserNoLongerActive(browser_.get()); |
| } |
| |
| // Called when we have been minimized. |
| - (void)windowDidMiniaturize:(NSNotification *)notification { |
| [self saveWindowPositionIfNeeded]; |
| } |
| |
| // 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]; |
| } |
| |
| // 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) { |
| CGFloat intrinsicWidth = |
| static_cast<CGFloat>(contents->GetPreferredSize().width()); |
| // If the intrinsic width is bigger, then make it the zoomed width. |
| 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 nsWindowController]]; |
| } |
| |
| // 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<BrowserWindowLayout> 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]; |
| |
| if ((shouldAdjustBookmarkHeight && view == [bookmarkBarController_ view]) || |
| view == [downloadShelfController_ view]) { |
| CGFloat deltaH = height - NSHeight(frame); |
| [self adjustWindowHeightBy:deltaH]; |
| } |
| |
| frame.size.height = height; |
| // TODO(rohitrao): Determine if calling setFrame: twice is bad. |
| [view setFrame:frame]; |
| [self layoutSubviews]; |
| |
| } |
| |
| - (BOOL)handledByExtensionCommand:(NSEvent*)event |
| priority:(ui::AcceleratorManager::HandlerPriority)priority { |
| return extensionKeybindingRegistry_->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 { |
| if ([self isInAnyFullscreenMode]) { |
| [[self fullscreenToolbarController] revealToolbarForWebContents:newContents |
| inForeground:YES]; |
| } |
| } |
| |
| - (void)zoomChangedForActiveTab:(BOOL)canShowBubble { |
| [toolbarController_ zoomChangedForActiveTab:canShowBubble]; |
| } |
| |
| // Accept tabs from a BrowserWindowController with the same Profile. |
| - (BOOL)canReceiveFrom:(TabWindowController*)source { |
| BrowserWindowController* realSource = |
| base::mac::ObjCCast<BrowserWindowController>(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<BrowserWindowController>(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 = ui::ConvertPointFromWindowToScreen([dragController window], |
| tabOrigin); |
| tabOrigin = ui::ConvertPointFromScreenToWindow([self window], 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]; |
| |
| // TODO(erikchen): While it might be nice to fix ownership semantics here, |
| // realistically the code is going to be deleted in the not-too-distant |
| // future. |
| browser_->tab_strip_model()->DetachWebContentsAt(index).release(); |
| } |
| |
| - (NSArray*)tabViews { |
| return [tabStripController_ tabViews]; |
| } |
| |
| - (NSView*)activeTabView { |
| return [tabStripController_ activeTabView]; |
| } |
| |
| - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force { |
| [toolbarController_ setIsLoading:isLoading force:force]; |
| } |
| |
| - (void)firstResponderUpdated:(NSResponder*)responder { |
| if (![self isInAppKitFullscreen] || |
| [fullscreenToolbarController_ toolbarStyle] == |
| FullscreenToolbarStyle::TOOLBAR_NONE) { |
| return; |
| } |
| |
| if (!responder) { |
| [self releaseToolbarVisibilityForOwner:self withAnimation:YES]; |
| return; |
| } |
| |
| if (![responder isKindOfClass:[NSView class]]) |
| return; |
| |
| // If the view is in the download shelf or the tab content area, don't |
| // lock the toolbar. |
| NSView* view = base::mac::ObjCCastStrict<NSView>(responder); |
| if (![view isDescendantOf:[[self window] contentView]] || |
| [view isDescendantOf:[downloadShelfController_ view]] || |
| [view isDescendantOf:[self tabContentArea]]) { |
| [self releaseToolbarVisibilityForOwner:self withAnimation:YES]; |
| return; |
| } |
| |
| [self lockToolbarVisibilityForOwner:self withAnimation:YES]; |
| } |
| |
| // 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) + [self menubarOffset]; |
| gfx::Rect browserRect(windowRect.origin.x, windowRect.origin.y, |
| NSWidth(windowRect), NSHeight(windowRect)); |
| |
| std::vector<TabStripModelDelegate::NewStripContents> 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->DetachWebContentsAt(index); |
| item.add_types = |
| (isActive ? TabStripModel::ADD_ACTIVE : TabStripModel::ADD_NONE) | |
| (isPinned ? TabStripModel::ADD_PINNED : TabStripModel::ADD_NONE); |
| contentses.push_back(std::move(item)); |
| } |
| |
| // Create a new window with the dragged tabs in its model. |
| Browser* newBrowser = |
| browser_->tab_strip_model()->delegate()->CreateNewStripWithContents( |
| std::move(contentses), browserRect, false); |
| |
| // Get the new controller by asking the new window for its delegate. |
| BrowserWindowController* controller = [BrowserWindowController |
| browserWindowControllerForWindow:newBrowser->window()->GetNativeWindow()]; |
| DCHECK(controller && [controller isKindOfClass:[TabWindowController class]]); |
| |
| // Ensure that the window will appear on top of the source window in |
| // fullscreen mode. |
| if ([self isInAppKitFullscreen]) { |
| NSWindow* window = [controller window]; |
| NSUInteger collectionBehavior = [window collectionBehavior]; |
| collectionBehavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; |
| collectionBehavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; |
| [window setCollectionBehavior:collectionBehavior]; |
| [window setLevel:NSFloatingWindowLevel]; |
| |
| controller->savedRegularWindowFrame_ = savedRegularWindowFrame_; |
| } |
| |
| // 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)detachedWindowEnterFullscreenIfNeeded:(TabWindowController*)source { |
| // Ensure that this is only called when the tab is detached into its own |
| // window (in which the overlay window will be present). |
| DCHECK([self overlayWindow]); |
| |
| if (([[source window] styleMask] & NSFullScreenWindowMask) |
| == NSFullScreenWindowMask) { |
| [self updateFullscreenCollectionBehavior]; |
| |
| // Since the detached window in fullscreen will have the size of the |
| // screen, it will set |savedRegularWindowFrame_| to the screen size after |
| // it enters fullscreen. Make sure that we have the correct value for the |
| // |savedRegularWindowFrame_|. |
| NSRect regularWindowFrame = savedRegularWindowFrame_; |
| [[self window] toggleFullScreen:nil]; |
| savedRegularWindowFrame_ = regularWindowFrame; |
| } |
| } |
| |
| - (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] || IsTabDetachingInFullscreenEnabled(); |
| } |
| |
| - (BOOL)windowMovementAllowed { |
| return ![self isInAnyFullscreenMode] || [self overlayWindow]; |
| } |
| |
| - (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; |
| |
| ProfileAttributesEntry* entry; |
| return g_browser_process->profile_manager() |
| ->GetProfileAttributesStorage() |
| .GetProfileAttributesWithPath(browser_->profile()->GetPath(), &entry); |
| } |
| |
| - (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]]; |
| [self layoutSubviews]; |
| } |
| } |
| |
| - (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(); |
| } |
| |
| - (CGFloat)menubarOffset { |
| return [[self fullscreenToolbarController] computeLayout].menubarOffset; |
| } |
| |
| // 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:(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 != TabChangeType::kLoadingOnly) |
| windowShim_->UpdateTitleBar(); |
| |
| // Update the bookmark bar if this is the currently selected tab. 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? |
| windowShim_->BookmarkBarStateChanged(BookmarkBar::DONT_ANIMATE_STATE_CHANGE); |
| } |
| |
| - (void)onTabDetachedWithContents:(WebContents*)contents { |
| [infoBarContainerController_ tabDetachedWithContents:contents]; |
| } |
| |
| - (void)onTabInsertedWithContents:(content::WebContents*)contents |
| inForeground:(BOOL)inForeground { |
| if ([self isInAnyFullscreenMode] && !inForeground) |
| [[self fullscreenToolbarController] |
| revealToolbarForWebContents:contents |
| inForeground:inForeground]; |
| |
| if (inForeground) { |
| AppToolbarButton* appMenuButton = |
| static_cast<AppToolbarButton*>([toolbarController_ appMenuButton]); |
| [appMenuButton animateIfPossibleWithDelay:YES]; |
| } |
| } |
| |
| - (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::ShowPilotDialogsWithViewsToolkit()) { |
| chrome::ShowBookmarkBubbleViewsAtPoint( |
| gfx::ScreenPointFromNSPoint(ui::ConvertPointFromWindowToScreen( |
| [self window], [self bookmarkBubblePoint])), |
| [[self window] contentView], bookmarkBubbleObserver_.get(), |
| browser_.get(), url, alreadyMarked, |
| [self locationBarBridge]->star_decoration()); |
| } else { |
| #if BUILDFLAG(MAC_VIEWS_BROWSER) |
| NOTREACHED() << "MacViews Browser can't show cocoa dialogs"; |
| #else |
| BookmarkModel* model = |
| BookmarkModelFactory::GetForBrowserContext(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]; |
| #endif |
| } |
| 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 { |
| if (chrome::ShowAllDialogsWithViewsToolkit()) { |
| ShowTranslateBubbleViews([self window], [self locationBarBridge], contents, |
| step, errorType, true); |
| return; |
| } |
| // 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); |
| |
| std::unique_ptr<translate::TranslateUIDelegate> uiDelegate( |
| new translate::TranslateUIDelegate( |
| ChromeTranslateClient::GetManagerFromWebContents(contents) |
| ->GetWeakPtr(), |
| sourceLanguage, targetLanguage)); |
| std::unique_ptr<TranslateBubbleModel> 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 { |
| PermissionPrompt::Delegate* delegate = [self permissionRequestManager]; |
| if (delegate) |
| delegate->Closing(); |
| } |
| |
| // 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() |
| window:[self window]]); |
| } else { |
| avatarButtonController_.reset( |
| [[AvatarIconController alloc] initWithBrowser:browser_.get()]); |
| } |
| view = [avatarButtonController_ view]; |
| if (cocoa_l10n_util::ShouldFlipWindowControlsInRTL()) |
| [view setAutoresizingMask:NSViewMaxXMargin | NSViewMinYMargin]; |
| else |
| [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(); |
| } |
| |
| [self updatePermissionBubbleAnchor]; |
| |
| // 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]; |
| } |
| |
| // 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]; |
| |
| // When dragging tabs, the window is repositioned with direct setFrame: calls |
| // which don't automatically reposition child windows. Most dialogs block tab |
| // dragging or dismiss on focus loss. Permission bubbles do not, so ensure |
| // they are anchored correctly. |
| if ([self isDragSessionActive]) |
| [self updatePermissionBubbleAnchor]; |
| |
| 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]; |
| } |
| |
| // 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) |
| - (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]; |
| } |
| |
| - (FullscreenToolbarController*)fullscreenToolbarController { |
| return fullscreenToolbarController_.get(); |
| } |
| |
| - (void)setFullscreenToolbarController: |
| (FullscreenToolbarController*)controller { |
| fullscreenToolbarController_.reset([controller retain]); |
| } |
| |
| - (void)setBrowserWindowTouchBarController: |
| (BrowserWindowTouchBarController*)touchBarController { |
| touchBarController_.reset([touchBarController retain]); |
| } |
| |
| - (void)executeExtensionCommand:(const std::string&)extension_id |
| command:(const extensions::Command&)command { |
| // Global commands are handled by the ExtensionCommandsGlobalRegistry |
| // instance. |
| DCHECK(!command.global()); |
| extensionKeybindingRegistry_->ExecuteCommand(extension_id, |
| command.accelerator()); |
| } |
| |
| - (void)setAlertState:(TabAlertState)alertState { |
| static_cast<BrowserWindowCocoa*>([self browserWindow]) |
| ->UpdateAlertState(alertState); |
| } |
| |
| - (TabAlertState)alertState { |
| return static_cast<BrowserWindowCocoa*>([self browserWindow])->alert_state(); |
| } |
| |
| - (BrowserWindowTouchBarController*)browserWindowTouchBarController { |
| if (!touchBarController_) { |
| touchBarController_.reset([[BrowserWindowTouchBarController alloc] |
| initWithBrowser:browser_.get() |
| window:[self window]]); |
| } |
| |
| return touchBarController_.get(); |
| } |
| |
| - (BOOL)isToolbarShowing { |
| return [fullscreenToolbarController_ mustShowFullscreenToolbar]; |
| } |
| |
| @end // @implementation BrowserWindowController |
| |
| @implementation BrowserWindowController(Fullscreen) |
| |
| - (void)enterBrowserFullscreen { |
| [self enterAppKitFullscreen]; |
| } |
| |
| - (void)updateUIForTabFullscreen: |
| (ExclusiveAccessContext::TabFullscreenState)state { |
| // Only need to update and layout toolbar in fullscreen mode. |
| if (![self isInAnyFullscreenMode]) |
| return; |
| [fullscreenToolbarController_ |
| layoutToolbarStyleIsExitingTabFullscreen: |
| state == ExclusiveAccessContext::STATE_EXIT_TAB_FULLSCREEN]; |
| } |
| |
| - (void)updateFullscreenExitBubble { |
| [self showFullscreenExitBubbleIfNecessary]; |
| } |
| |
| - (BOOL)exitExtensionFullscreenIfPossible { |
| if (browser_->exclusive_access_manager() |
| ->fullscreen_controller() |
| ->IsExtensionFullscreenOrPending()) { |
| browser_->extension_window_controller()->SetFullscreenMode(NO, GURL()); |
| return YES; |
| } |
| return NO; |
| } |
| |
| - (BOOL)isInImmersiveFullscreen { |
| return fullscreenWindow_.get() != nil || enteringImmersiveFullscreen_; |
| } |
| |
| - (BOOL)isInAppKitFullscreen { |
| return !exitingAppKitFullscreen_ && |
| (([[self window] styleMask] & NSFullScreenWindowMask) == |
| NSFullScreenWindowMask || |
| enteringAppKitFullscreen_); |
| } |
| |
| - (BOOL)isInAnyFullscreenMode { |
| return [self isInImmersiveFullscreen] || [self isInAppKitFullscreen]; |
| } |
| |
| - (NSView*)avatarView { |
| return [avatarButtonController_ view]; |
| } |
| |
| - (void)enterWebContentFullscreen { |
| // 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. |
| display::Screen* screen = display::Screen::GetScreen(); |
| BOOL hasMultipleMonitors = screen && screen->GetNumDisplays() > 1; |
| |
| if (base::mac::IsAtLeastOS10_10() && |
| !(hasMultipleMonitors && ![NSScreen screensHaveSeparateSpaces])) { |
| [self enterAppKitFullscreen]; |
| } else { |
| [self enterImmersiveFullscreen]; |
| } |
| |
| if (!exclusiveAccessController_->url().is_empty()) |
| [self updateFullscreenExitBubble]; |
| } |
| |
| - (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 exitAppKitFullscreenAsync:NO]; |
| if ([self isInImmersiveFullscreen]) |
| [self exitImmersiveFullscreen]; |
| } |
| |
| - (void)exitFullscreenAnimationFinished { |
| if (appKitDidExitFullscreen_) { |
| [self windowDidExitFullScreen:nil]; |
| appKitDidExitFullscreen_ = NO; |
| } |
| } |
| |
| - (void)resizeFullscreenWindow { |
| DCHECK([self isInAnyFullscreenMode]); |
| if (![self isInAnyFullscreenMode]) |
| return; |
| |
| NSWindow* window = [self window]; |
| [window setFrame:[[window screen] frame] display:YES]; |
| [self layoutSubviews]; |
| } |
| |
| - (BOOL)isToolbarVisibilityLockedForOwner:(id)owner { |
| FullscreenToolbarVisibilityLockController* visibilityController = |
| [self fullscreenToolbarVisibilityLockController]; |
| return [visibilityController isToolbarVisibilityLockedForOwner:owner]; |
| } |
| |
| - (void)lockToolbarVisibilityForOwner:(id)owner withAnimation:(BOOL)animate { |
| FullscreenToolbarVisibilityLockController* visibilityController = |
| [self fullscreenToolbarVisibilityLockController]; |
| [visibilityController lockToolbarVisibilityForOwner:owner |
| withAnimation:animate]; |
| } |
| |
| - (void)releaseToolbarVisibilityForOwner:(id)owner withAnimation:(BOOL)animate { |
| FullscreenToolbarVisibilityLockController* visibilityController = |
| [self fullscreenToolbarVisibilityLockController]; |
| [visibilityController releaseToolbarVisibilityForOwner:owner |
| withAnimation:animate]; |
| } |
| |
| - (BOOL)floatingBarHasFocus { |
| NSResponder* focused = [[self window] firstResponder]; |
| return [focused isKindOfClass:[AutocompleteTextFieldEditor class]]; |
| } |
| |
| - (BOOL)isFullscreenForTabContentOrExtension { |
| FullscreenController* controller = |
| browser_->exclusive_access_manager()->fullscreen_controller(); |
| return controller->IsWindowFullscreenForTabOrPending() || |
| controller->IsExtensionFullscreenOrPending(); |
| } |
| |
| - (ExclusiveAccessController*)exclusiveAccessController { |
| return exclusiveAccessController_.get(); |
| } |
| |
| @end // @implementation BrowserWindowController(Fullscreen) |
| |
| |
| @implementation BrowserWindowController(WindowType) |
| |
| - (BOOL)supportsWindowFeature:(int)feature { |
| return browser_->SupportsWindowFeature( |
| static_cast<Browser::WindowFeature>(feature)); |
| } |
| |
| - (BOOL)hasTitleBar { |
| return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR]; |
| } |
| |
| - (BOOL)hasToolbar { |
| FullscreenToolbarLayout layout = |
| [[self fullscreenToolbarController] computeLayout]; |
| return layout.toolbarStyle != FullscreenToolbarStyle::TOOLBAR_NONE && |
| [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(); |
| } |
| |
| - (NSRect)savedRegularWindowFrame { |
| return savedRegularWindowFrame_; |
| } |
| |
| - (BOOL)isFullscreenTransitionInProgress { |
| return enteringAppKitFullscreen_ || exitingAppKitFullscreen_; |
| } |
| |
| @end // @implementation BrowserWindowController(WindowType) |