blob: dbc7b22d98572492352553f117703162875549e1 [file] [log] [blame]
// 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 "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/chrome_bookmark_client.h"
#include "chrome/browser/bookmarks/chrome_bookmark_client_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/signin/signin_ui_util.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/bookmarks/bookmark_utils.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_editor_controller.h"
#import "chrome/browser/ui/cocoa/browser_window_cocoa.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"
#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/sad_tab_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/toolbar/encoding_menu_controller.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/url_constants.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/grit/locale_settings.h"
#include "components/bookmarks/browser/bookmark_model.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/popup_manager.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_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"
#include "ui/gfx/mac/scoped_ns_disable_screen_updates.h"
using bookmarks::BookmarkModel;
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_<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::OpenURLParams;
using content::Referrer;
using content::RenderWidgetHostView;
using content::WebContents;
@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];
}
+ (void)updateSigninItem:(id)signinItem
shouldShow:(BOOL)showSigninMenuItem
currentProfile:(Profile*)profile {
DCHECK([signinItem isKindOfClass:[NSMenuItem class]]);
NSMenuItem* signinMenuItem = static_cast<NSMenuItem*>(signinItem);
// Look for a separator immediately after the menu item so it can be hidden
// or shown appropriately along with the signin menu item.
NSMenuItem* followingSeparator = nil;
NSMenu* menu = [signinItem menu];
if (menu) {
NSInteger signinItemIndex = [menu indexOfItem:signinMenuItem];
DCHECK_NE(signinItemIndex, -1);
if ((signinItemIndex + 1) < [menu numberOfItems]) {
NSMenuItem* menuItem = [menu itemAtIndex:(signinItemIndex + 1)];
if ([menuItem isSeparatorItem]) {
followingSeparator = menuItem;
}
}
}
base::string16 label = signin_ui_util::GetSigninMenuLabel(profile);
[signinMenuItem setTitle:l10n_util::FixUpWindowsStyleLabel(label)];
[signinMenuItem setHidden:!showSigninMenuItem];
[followingSeparator setHidden:!showSigninMenuItem];
}
// 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];
// 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 <http://crbug.com/79113>.
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] initWithBrowser:browser]);
[[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 a popup window, |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 (browser_->is_type_popup()) {
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);
// Create the permissions bubble.
permissionBubbleCocoa_.reset(new PermissionBubbleCocoa([self window]));
// 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()));
// We are done initializing now.
initializing_ = NO;
}
return self;
}
- (void)dealloc {
browser_->tab_strip_model()->CloseAllTabs();
[downloadShelfController_ exiting];
// 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];
[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::ScopedNSDisableScreenUpdates 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 {
BrowserList::SetLastActive(browser_.get());
[self saveWindowPositionIfNeeded];
// TODO(dmaclach): Instead of redrawing the whole window, views that care
// about the active window state should be registering for notifications.
[[self window] setViewsNeedDisplay:YES];
// TODO(viettrungluu): For some reason, the above doesn't suffice.
if ([self isInAnyFullscreenMode])
[floatingBarBackingView_ setNeedsDisplay:YES]; // Okay even if nil.
extensions::ExtensionCommandsGlobalRegistry::Get(browser_->profile())
->set_registry_for_active_window(extension_keybinding_registry_.get());
}
- (void)windowDidResignMain:(NSNotification*)notification {
// TODO(dmaclach): Instead of redrawing the whole window, views that care
// about the active window state should be registering for notifications.
[[self window] setViewsNeedDisplay:YES];
// TODO(viettrungluu): For some reason, the above doesn't suffice.
if ([self isInAnyFullscreenMode])
[floatingBarBackingView_ setNeedsDisplay:YES]; // Okay even if nil.
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 =
browser_->tab_strip_model()->GetActiveWebContents()) {
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 =
browser_->tab_strip_model()->GetActiveWebContents()) {
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 =
browser_->tab_strip_model()->GetActiveWebContents()) {
if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
rwhv->SetWindowVisibility(false);
}
}
// Called when we have been unminimized.
- (void)windowDidDeminiaturize:(NSNotification *)notification {
// Let the selected RenderWidgetHostView know, so that it can tell plugins.
if (WebContents* contents =
browser_->tab_strip_model()->GetActiveWebContents()) {
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 =
browser_->tab_strip_model()->GetActiveWebContents()) {
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 =
browser_->tab_strip_model()->GetActiveWebContents()) {
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.
- (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<CGFloat>(
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];
// 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];
[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::ScopedNSDisableScreenUpdates 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 (content::WebContents* contents =
browser_->tab_strip_model()->GetActiveWebContents()) {
if (content::RenderViewHost* rvh = contents->GetRenderViewHost()) {
rvh->ResizeRectChanged(windowShim_->GetRootWindowResizerRect());
}
}
}
}
// Update a toggle state for an NSMenuItem if modified.
// Take care to ensure |item| looks like a NSMenuItem.
// Called by validateUserInterfaceItem:.
- (void)updateToggleStateWithTag:(NSInteger)tag forItem:(id)item {
if (![item respondsToSelector:@selector(state)] ||
![item respondsToSelector:@selector(setState:)])
return;
// On Windows this logic happens in bookmark_bar_view.cc. On the
// Mac we're a lot more MVC happy so we've moved it into a
// controller. To be clear, this simply updates the menu item; it
// does not display the bookmark bar itself.
if (tag == IDC_SHOW_BOOKMARK_BAR) {
bool toggled = windowShim_->IsBookmarkBarVisible();
NSInteger oldState = [(NSMenuItem*)item state];
NSInteger newState = toggled ? NSOnState : NSOffState;
if (oldState != newState)
[item setState:newState];
}
// Update the checked/Unchecked state of items in the encoding menu.
// On Windows, this logic is part of |EncodingMenuModel| in
// browser/ui/views/toolbar_view.h.
EncodingMenuController encoding_controller;
if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) {
DCHECK(browser_.get());
Profile* profile = browser_->profile();
DCHECK(profile);
WebContents* current_tab =
browser_->tab_strip_model()->GetActiveWebContents();
if (!current_tab)
return;
const std::string encoding = current_tab->GetEncoding();
bool toggled = encoding_controller.IsItemChecked(profile, encoding, tag);
NSInteger oldState = [(NSMenuItem*)item state];
NSInteger newState = toggled ? NSOnState : NSOffState;
if (oldState != newState)
[item setState:newState];
}
}
// Called to validate menu and toolbar items when this window is key. All the
// items we care about have been set with the |-commandDispatch:| or
// |-commandDispatchUsingKeyModifiers:| actions and a target of FirstResponder
// in IB. If it's not one of those, let it continue up the responder chain to be
// handled elsewhere. We pull out the tag as the cross-platform constant to
// differentiate and dispatch the various commands.
// NOTE: we might have to handle state for app-wide menu items,
// although we could cheat and directly ask the app controller if our
// command_updater doesn't support the command. This may or may not be an issue,
// too early to tell.
- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
SEL action = [item action];
BOOL enable = NO;
if (action == @selector(commandDispatch:) ||
action == @selector(commandDispatchUsingKeyModifiers:)) {
NSInteger tag = [item tag];
if (chrome::SupportsCommand(browser_.get(), tag)) {
// Generate return value (enabled state)
enable = chrome::IsCommandEnabled(browser_.get(), tag);
switch (tag) {
case IDC_CLOSE_TAB:
// Disable "close tab" if the receiving window is not tabbed.
// We simply check whether the item has a keyboard shortcut set here;
// app_controller_mac.mm actually determines whether the item should
// be enabled.
if (NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item))
enable &= !![[menuItem keyEquivalent] length];
break;
case IDC_FULLSCREEN: {
if (NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item)) {
NSString* menuTitle = l10n_util::GetNSString(
[self isInAppKitFullscreen] && ![self inPresentationMode]
? IDS_EXIT_FULLSCREEN_MAC
: IDS_ENTER_FULLSCREEN_MAC);
[menuItem setTitle:menuTitle];
if (!chrome::mac::SupportsSystemFullscreen())
[menuItem setHidden:YES];
}
break;
}
case IDC_PRESENTATION_MODE: {
if (NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item)) {
NSString* menuTitle = l10n_util::GetNSString(
[self inPresentationMode] ? IDS_EXIT_PRESENTATION_MAC :
IDS_ENTER_PRESENTATION_MAC);
[menuItem setTitle:menuTitle];
}
break;
}
case IDC_SHOW_SIGNIN: {
Profile* original_profile =
browser_->profile()->GetOriginalProfile();
[BrowserWindowController updateSigninItem:item
shouldShow:enable
currentProfile:original_profile];
break;
}
case IDC_BOOKMARK_PAGE: {
// Extensions have the ability to hide the bookmark page menu item.
// This only affects the bookmark page menu item under the main menu.
// The bookmark page menu item under the wrench menu has its
// visibility controlled by WrenchMenuModel.
bool shouldHide =
chrome::ShouldRemoveBookmarkThisPageUI(browser_->profile());
NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item);
[menuItem setHidden:shouldHide];
break;
}
case IDC_BOOKMARK_ALL_TABS: {
// Extensions have the ability to hide the bookmark all tabs menu
// item. This only affects the bookmark page menu item under the main
// menu. The bookmark page menu item under the wrench menu has its
// visibility controlled by WrenchMenuModel.
bool shouldHide =
chrome::ShouldRemoveBookmarkOpenPagesUI(browser_->profile());
NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item);
[menuItem setHidden:shouldHide];
break;
}
default:
// Special handling for the contents of the Text Encoding submenu. On
// Mac OS, instead of enabling/disabling the top-level menu item, we
// enable/disable the submenu's contents (per Apple's HIG).
EncodingMenuController encoding_controller;
if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) {
enable &= chrome::IsCommandEnabled(browser_.get(),
IDC_ENCODING_MENU) ? YES : NO;
}
}
// If the item is toggleable, find its toggle state and
// try to update it. This is a little awkward, but the alternative is
// to check after a commandDispatch, which seems worse.
[self updateToggleStateWithTag:tag forItem:item];
}
}
return enable;
}
// Called when the user picks a menu or toolbar item when this window is key.
// Calls through to the browser object to execute the command. This assumes that
// the command is supported and doesn't check, otherwise it would have been
// disabled in the UI in validateUserInterfaceItem:.
- (void)commandDispatch:(id)sender {
DCHECK(sender);
// Identify the actual BWC to which the command should be dispatched. It might
// belong to a background window, yet this controller gets it because it is
// the foreground window's controller and thus in the responder chain. Some
// senders don't have this problem (for example, menus only operate on the
// foreground window), so this is only an issue for senders that are part of
// windows.
BrowserWindowController* targetController = self;
if ([sender respondsToSelector:@selector(window)])
targetController = [[sender window] windowController];
DCHECK([targetController isKindOfClass:[BrowserWindowController class]]);
DCHECK(targetController->browser_.get());
chrome::ExecuteCommand(targetController->browser_.get(), [sender tag]);
}
// Same as |-commandDispatch:|, but executes commands using a disposition
// determined by the key flags. If the window is in the background and the
// command key is down, ignore the command key, but process any other modifiers.
- (void)commandDispatchUsingKeyModifiers:(id)sender {
DCHECK(sender);
if (![sender isEnabled]) {
// This code is reachable e.g. if the user mashes the back button, queuing
// up a bunch of events before the button's enabled state is updated:
// http://crbug.com/63254
return;
}
// See comment above for why we do this.
BrowserWindowController* targetController = self;
if ([sender respondsToSelector:@selector(window)])
targetController = [[sender window] windowController];
DCHECK([targetController isKindOfClass:[BrowserWindowController class]]);
DCHECK(targetController->browser_.get());
NSInteger command = [sender tag];
NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
if ((command == IDC_RELOAD) &&
(modifierFlags & (NSShiftKeyMask | NSControlKeyMask))) {
command = IDC_RELOAD_IGNORING_CACHE;
// Mask off Shift and Control so they don't affect the disposition below.
modifierFlags &= ~(NSShiftKeyMask | NSControlKeyMask);
}
if (![[sender window] isMainWindow]) {
// Remove the command key from the flags, it means "keep the window in
// the background" in this case.
modifierFlags &= ~NSCommandKeyMask;
}
chrome::ExecuteCommandWithDisposition(
targetController->browser_.get(), command,
ui::WindowOpenDispositionFromNSEventWithFlags(
[NSApp currentEvent], modifierFlags));
}
// Called when another part of the internal codebase needs to execute a
// command.
- (void)executeCommand:(int)command {
chrome::ExecuteCommand(browser_.get(), command);
}
- (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)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<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 = [[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::ScopedNSDisableScreenUpdates 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<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->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<BrowserWindowController*>(
[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 switches::IsNewAvatarMenu() &&
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 {
return [[[FullscreenWindow alloc] initForScreen:[[self window] screen]]
autorelease];
}
- (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;
return !web_modal::PopupManager::FromWebContents(contents)->
IsWebModalDialogActive(contents);
}
// 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];
// No need to remove previous bubble. It will close itself.
// TODO(leng): The PermissionBubbleManager for the previous contents should
// have SetView(NULL) called. Fix this when the previous contents are
// available here or move to BrowserWindowCocoa::OnActiveTabChanged().
// crbug.com/340720
PermissionBubbleManager::FromWebContents(contents)->SetView(
permissionBubbleCocoa_.get());
// 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 {
[[[[self window] contentView] superview] cr_recursivelySetNeedsDisplay:YES];
}
- (ui::ThemeProvider*)themeProvider {
return ThemeServiceFactory::GetForProfile(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 (!bookmarkBubbleController_) {
BookmarkModel* model =
BookmarkModelFactory::GetForProfile(browser_->profile());
ChromeBookmarkClient* client =
ChromeBookmarkClientFactory::GetForProfile(browser_->profile());
const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url);
bookmarkBubbleController_ =
[[BookmarkBubbleController alloc] initWithParentWindow:[self window]
client:client
model:model
node:node
alreadyBookmarked:alreadyMarked];
[bookmarkBubbleController_ showWindow:self];
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(bookmarkBubbleWindowWillClose:)
name:NSWindowWillCloseNotification
object:[bookmarkBubbleController_ window]];
}
}
// Nil out the weak bookmark bubble controller reference.
- (void)bookmarkBubbleWindowWillClose:(NSNotification*)notification {
DCHECK_EQ([notification object], [bookmarkBubbleController_ window]);
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center removeObserver:self
name:NSWindowWillCloseNotification
object:[bookmarkBubbleController_ window]];
bookmarkBubbleController_ = nil;
}
// 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 he/she is 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<translate::TranslateUIDelegate> uiDelegate(
new translate::TranslateUIDelegate(
ChromeTranslateClient::GetManagerFromWebContents(contents)
->GetWeakPtr(),
sourceLanguage,
targetLanguage));
scoped_ptr<TranslateBubbleModel> model(
new TranslateBubbleModelImpl(step, uiDelegate.Pass()));
translateBubbleController_ = [[TranslateBubbleController alloc]
initWithParentWindow:self
model:model.Pass()
webContents:contents];
[translateBubbleController_ showWindow:nil];
NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:@selector(translateBubbleWindowWillClose:)
name:NSWindowWillCloseNotification
object:[translateBubbleController_ window]];
}
// 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 =
browser_->tab_strip_model()->GetActiveWebContents()) {
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 SadTabController 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 (SadTabController* sadTab =
base::mac::ObjCCast<SadTabController>(sender)) {
WebContents* webContents = [sadTab webContents];
if (webContents) {
OpenURLParams params(
GURL(chrome::kCrashReasonURL), Referrer(), CURRENT_TAB,
ui::PAGE_TRANSITION_LINK, false);
webContents->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 =
browser_->tab_strip_model()->GetActiveWebContents()) {
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];
}
- (void)layoutInfoBars {
[self layoutSubviews];
}
- (void)sheetDidEnd:(NSWindow*)sheet
returnCode:(NSInteger)code
context:(void*)context {
[sheet orderOut:self];
}
- (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());
}
@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];
}
- (BOOL)isInAnyFullscreenMode {
return [self isInImmersiveFullscreen] || [self isInAppKitFullscreen];
}
- (BOOL)isInImmersiveFullscreen {
return fullscreenWindow_.get() != nil || enteringImmersiveFullscreen_;
}
- (BOOL)isInAppKitFullscreen {
return ([[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 {
[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;
}
- (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<Browser::WindowFeature>(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)