Logo Search packages:      
Sourcecode: chromium-browser version File versions  Download package


// Copyright (c) 2010 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/cocoa/browser_window_controller.h"

#include <Carbon/Carbon.h>

#include "app/l10n_util_mac.h"
#include "base/mac_util.h"
#include "base/nsimage_cache_mac.h"
#include "base/scoped_nsdisable_screen_updates.h"
#import "base/scoped_nsobject.h"
#include "base/sys_string_conversions.h"
#include "chrome/app/chrome_dll_resource.h"  // IDC_*
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_list.h"
#include "chrome/browser/browser_theme_provider.h"
#include "chrome/browser/dock_info.h"
#include "chrome/browser/encoding_menu_controller.h"
#include "chrome/browser/location_bar.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/window_sizer.h"
#include "chrome/browser/bookmarks/bookmark_editor.h"
#import "chrome/browser/cocoa/autocomplete_text_field_editor.h"
#import "chrome/browser/cocoa/background_gradient_view.h"
#import "chrome/browser/cocoa/bookmark_bar_controller.h"
#import "chrome/browser/cocoa/bookmark_editor_controller.h"
#import "chrome/browser/cocoa/browser_window_cocoa.h"
#import "chrome/browser/cocoa/browser_window_controller_private.h"
#import "chrome/browser/cocoa/download_shelf_controller.h"
#import "chrome/browser/cocoa/event_utils.h"
#import "chrome/browser/cocoa/fast_resize_view.h"
#import "chrome/browser/cocoa/find_bar_cocoa_controller.h"
#import "chrome/browser/cocoa/find_bar_bridge.h"
#import "chrome/browser/cocoa/focus_tracker.h"
#import "chrome/browser/cocoa/fullscreen_controller.h"
#import "chrome/browser/cocoa/fullscreen_window.h"
#import "chrome/browser/cocoa/infobar_container_controller.h"
#import "chrome/browser/cocoa/sad_tab_controller.h"
#import "chrome/browser/cocoa/status_bubble_mac.h"
#import "chrome/browser/cocoa/tab_contents_controller.h"
#import "chrome/browser/cocoa/tab_strip_model_observer_bridge.h"
#import "chrome/browser/cocoa/tab_strip_controller.h"
#import "chrome/browser/cocoa/tab_strip_view.h"
#import "chrome/browser/cocoa/tab_view.h"
#import "chrome/browser/cocoa/toolbar_controller.h"
#include "chrome/browser/renderer_host/render_widget_host_view.h"
#include "chrome/browser/sync/profile_sync_service.h"
#include "chrome/browser/sync/sync_ui_util_mac.h"
#include "chrome/browser/tab_contents/tab_contents.h"
#include "chrome/browser/tab_contents/tab_contents_view.h"
#include "chrome/browser/tabs/tab_strip_model.h"
#include "grit/generated_resources.h"
#include "grit/locale_settings.h"

// 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.

@interface NSWindow(NSPrivateApis)
// Note: These functions are private, use -[NSObject respondsToSelector:]
// before calling them.

- (void)setBottomCornerRounded:(BOOL)rounded;

- (NSRect)_growBoxRect;


// IncognitoImageView subclasses NSImageView to allow mouse events to pass
// through it so you can drag the window by dragging on the spy guy
@interface IncognitoImageView : NSImageView

@implementation IncognitoImageView
- (BOOL)mouseDownCanMoveWindow {
  return YES;

@implementation BrowserWindowController

+ (BrowserWindowController*)browserWindowControllerForWindow:(NSWindow*)window {
  while (window) {
    id controller = [window windowController];
    if ([controller isKindOfClass:[BrowserWindowController class]])
      return (BrowserWindowController*)controller;
    window = [window parentWindow];
  return nil;

+ (BrowserWindowController*)browserWindowControllerForView:(NSView*)view {
  NSWindow* window = [view window];
  return [BrowserWindowController browserWindowControllerForWindow:window];

// Load the browser window nib and do any Cocoa-specific initialization.
// Takes ownership of |browser|. Note that the nib also sets this controller
// up as the window's delegate.
- (id)initWithBrowser:(Browser*)browser {
  return [self initWithBrowser:browser takeOwnership:YES];

// Private(TestingAPI) init routine with testing options.
- (id)initWithBrowser:(Browser*)browser takeOwnership:(BOOL)ownIt {
  // Use initWithWindowNibPath:: instead of initWithWindowNibName: so we
  // can override it in a unit test.
  NSString* nibpath = [mac_util::MainAppBundle()
  if ((self = [super initWithWindowNibPath:nibpath owner:self])) {
    initializing_ = YES;
    ownsBrowser_ = ownIt;
        new TabStripModelObserverBridge(browser->tabstrip_model(), self));
    NSWindow* window = [self window];
    windowShim_.reset(new BrowserWindowCocoa(browser, self, window));

    // 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]);

    // Sets the window to not have rounded corners, which prevents
    // the resize control from being inset slightly and looking ugly.
    if ([window respondsToSelector:@selector(setBottomCornerRounded:)])
      [window setBottomCornerRounded:NO];

    // 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.
    NSSize minSize = [[self window] minSize];
    gfx::Rect windowRect = browser_->GetSavedWindowBounds();
    if (windowRect.width() < minSize.width)
    if (windowRect.height() < minSize.height)

    // 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_->type() & Browser::TYPE_POPUP &&
        windowRect.x() == 0 && windowRect.y() == 0) {
      gfx::Size size = windowRect.size();


    // Puts the incognito badge on the window frame, if necessary.
    [self installIncognitoBadge];

    // 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.
    tabStripController_.reset([[TabStripController alloc]
                                initWithView:[self tabStripView]
                                  switchView:[self tabContentArea]

    // Create the infobar container view, so we can pass it to the
    // ToolbarController.
        [[InfoBarContainerController alloc]
    [[[self window] contentView] addSubview:[infoBarContainerController_ view]];

    // 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.
    toolbarController_.reset([[ToolbarController alloc]
    [toolbarController_ setHasToolbar:[self hasToolbar]
                       hasLocationBar:[self hasLocationBar]];
    [[[self window] contentView] addSubview:[toolbarController_ view]];

    // Create a sub-controller for the bookmark bar.
        [[BookmarkBarController alloc]
               initialWidth:NSWidth([[[self window] contentView] frame])

    // Add bookmark bar to the view hierarchy, which also triggers the nib load.
    // The bookmark bar is defined (in the nib) to be bottom-aligned to its
    // parent view (among other things), so position and resize properties don't
    // need to be set.
    [[[self window] contentView] addSubview:[bookmarkBarController_ view]
                                 relativeTo:[toolbarController_ view]];
    [bookmarkBarController_ setBookmarkBarEnabled:[self supportsBookmarkBar]];

    // 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|.
    [self updateBookmarkBarVisibilityWithAnimation:NO];

    // Allow bar visibility to be changed.
    [self enableBarVisibilityUpdates];

    // Force a relayout of all the various bars.
    [self layoutSubviews];

    // Create the bridge for the status bubble.
    statusBubble_ = new StatusBubbleMac([self window], self);

    // Register for application hide/unhide notifications.
    [[NSNotificationCenter defaultCenter]
    [[NSNotificationCenter defaultCenter]

    // 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];

    // We are done initializing now.
    initializing_ = NO;
  return self;

- (void)dealloc {
  [downloadShelfController_ exiting];

  // Explicitly release |fullscreenController_| here, as it may call back to
  // this BWC in |-dealloc|.  We are required to call |-exitFullscreen| before
  // releasing the controller.
  [fullscreenController_ exitFullscreen];

  // Under certain testing configurations we may not actually own the browser.
  if (ownsBrowser_ == NO)

  [[NSNotificationCenter defaultCenter] removeObserver:self];

  [super dealloc];

- (BrowserWindow*)browserWindow {
  return windowShim_.get();

- (ToolbarController*)toolbarController {
  return toolbarController_.get();

- (TabStripController*)tabStripController {
  return tabStripController_.get();

- (StatusBubbleMac*)statusBubble {
  return statusBubble_;

- (LocationBar*)locationBarBridge {
  return [toolbarController_ locationBarBridge];

- (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)

// 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_->tabstrip_model()->HasNonPhantomTabs() ||
  [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)

- (void)attachConstrainedWindow:(ConstrainedWindowMac*)window {
  [tabStripController_ attachConstrainedWindow:window];

- (void)removeConstrainedWindow:(ConstrainedWindowMac*)window {
  [tabStripController_ removeConstrainedWindow:window];

- (void)updateDevToolsForContents:(TabContents*)contents {
  [tabStripController_ updateDevToolsForContents:contents];

// 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.
  base::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];

  if (browser_->tabstrip_model()->HasNonPhantomTabs()) {
    // 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];
    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 {
  [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 isFullscreen])
    [floatingBarBackingView_ setNeedsDisplay:YES];  // Okay even if nil.

- (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 isFullscreen])
    [floatingBarBackingView_ setNeedsDisplay:YES];  // Okay even if nil.

// 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 TabContents's RenderWidgetHostViewMac and tell it to activate.
  if (TabContents* contents = browser_->GetSelectedTabContents()) {
    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())

// 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]))

  // We need to deactivate the controls (in the "WebView"). To do this, get the
  // selected TabContents's RenderWidgetHostView and tell it to deactivate.
  if (TabContents* contents = browser_->GetSelectedTabContents()) {
    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())

// Called when we have been minimized.
- (void)windowDidMiniaturize:(NSNotification *)notification {
  // Let the selected RenderWidgetHostView know, so that it can tell plugins.
  if (TabContents* contents = browser_->GetSelectedTabContents()) {
    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())

// Called when we have been unminimized.
- (void)windowDidDeminiaturize:(NSNotification *)notification {
  // Let the selected RenderWidgetHostView know, so that it can tell plugins.
  if (TabContents* contents = browser_->GetSelectedTabContents()) {
    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())

// 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 (TabContents* contents = browser_->GetSelectedTabContents()) {
      if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())

// 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 (TabContents* contents = browser_->GetSelectedTabContents()) {
      if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())

// 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 * frame.size.width,
               std::min(kProportion * frame.size.height, frame.size.width));

  TabContents* contents = browser_->tabstrip_model()->GetSelectedTabContents();
  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->view()->preferred_width() + kScrollbarWidth);
    zoomedWidth = std::max(zoomedWidth,
                           std::min(intrinsicWidth, frame.size.width));

  // Never shrink from the current size on zoom (see above).
  NSRect currentFrame = [[self window] frame];
  zoomedWidth = std::max(zoomedWidth, currentFrame.size.width);

  // |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 > frame.origin.x + frame.size.width)
    frame.origin.x = frame.origin.x + frame.size.width - zoomedWidth;
    frame.origin.x = currentFrame.origin.x;

  // Set the width. Don't touch y or height.
  frame.size.width = zoomedWidth;

  return frame;

- (void)activate {
  [[self window] makeKeyAndOrderFront: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 = overlap.size.width * overlap.size.height;
    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 (newScrIntersectCurFr.size.width*newScrIntersectCurFr.size.height >=
      (curScrIntersectCurFr.size.width*curScrIntersectCurFr.size.height - 1.0))
    return YES;

  // If it wasn't reasonable, return NO.
  return NO;

// Adjusts the window height by the given amount.
- (void)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 isFullscreen] || deltaH == 0)

  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))

  // 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 &&
                   windowFrame.size.height == workarea.size.height);

  // 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)
  } else {
    isShrinkingFromZoomed_ = NO;

    // Don't bother with anything else.
    if (isZoomed)

  // 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(windowFrame.size.height, workarea.size.height);

    // 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* contentView = [window contentView];
  [contentView setAutoresizesSubviews:NO];
  [window setFrame:windowFrame display:NO];
  [contentView setAutoresizesSubviews: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 == [toolbarController_ view] ||
         view == [infoBarContainerController_ view] ||
         view == [downloadShelfController_ view] ||
         view == [bookmarkBarController_ view]);

  // 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 (frame.size.height == height)

  // 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:bookmarks::kHiddenState
  if ((shouldAdjustBookmarkHeight && view == [bookmarkBarController_ view]) ||
      view == [downloadShelfController_ view]) {
    [[self window] disableScreenUpdatesUntilFlush];
    CGFloat deltaH = height - frame.size.height;
    [self adjustWindowHeightBy:deltaH];

  frame.size.height = height;
  // TODO(rohitrao): Determine if calling setFrame: twice is bad.
  [view setFrame:frame];
  [self layoutSubviews];

- (void)setAnimationInProgress:(BOOL)inProgress {
  [[self tabContentArea] setFastResizeMode:inProgress];

// Update a toggle state for an NSMenuItem if modified.
// Take care to insure |item| looks like a NSMenuItem.
// Called by validateUserInterfaceItem:.
- (void)updateToggleStateWithTag:(NSInteger)tag forItem:(id)item {
  if (![item respondsToSelector:@selector(state)] ||
      ![item respondsToSelector:@selector(setState:)])

  // 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 = [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/views/toolbar_view.h.
  EncodingMenuController encoding_controller;
  if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) {
    Profile* profile = browser_->profile();
    TabContents* current_tab = browser_->GetSelectedTabContents();
    if (!current_tab) {
    const std::string encoding = current_tab->encoding();

    bool toggled = encoding_controller.IsItemChecked(profile, encoding, tag);
    NSInteger oldState = [item state];
    NSInteger newState = toggled ? NSOnState : NSOffState;
    if (oldState != newState)
      [item setState:newState];

- (BOOL)supportsFullscreen {
  // TODO(avi, thakis): GTMWindowSheetController has no api to move
  // tabsheets between windows. Until then, we have to prevent having to
  // move a tabsheet between windows, e.g. no fullscreen toggling
  NSArray* a = [[tabStripController_ sheetController] viewsWithAttachedSheets];
  return [a count] == 0;

// 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 (browser_->command_updater()->SupportsCommand(tag)) {
      // Generate return value (enabled state)
      enable = browser_->command_updater()->IsCommandEnabled(tag);
      switch (tag) {
        case IDC_CLOSE_TAB:
          // Disable "close tab" if we're not the key window or if there's only
          // one tab.
          enable &= [self numberOfTabs] > 1 && [[self window] isKeyWindow];
        case IDC_RESTORE_TAB:
          // We have to ask the Browser manually if we can restore. The
          // command updater doesn't know.
          enable &= browser_->CanRestoreTab();
        case IDC_FULLSCREEN: {
          enable &= [self supportsFullscreen];
          if ([static_cast<NSObject*>(item) isKindOfClass:[NSMenuItem class]]) {
            NSString* menuTitle = l10n_util::GetNSString(
                [self isFullscreen] ? IDS_EXIT_FULLSCREEN_MAC :
            [static_cast<NSMenuItem*>(item) setTitle:menuTitle];
        case IDC_SYNC_BOOKMARKS:
          enable &= ProfileSyncService::IsSyncEnabled();
          sync_ui_util::UpdateSyncItem(item, enable, browser_->profile());
          // 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 &= browser_->command_updater()->IsCommandEnabled(
                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 {
  // 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]]);
  NSInteger tag = [sender tag];
  switch (tag) {
    case IDC_RELOAD:
      if ([sender isKindOfClass:[NSButton class]]) {
        // We revert the bar when the reload button is pressed, but don't when
        // Command+r is pressed (Issue 15464). Unlike the event handler function
        // for Windows (ToolbarView::ButtonPressed()), this function handles
        // both reload button press event and Command+r press event. Thus the
        // 'isKindofClass' check is necessary.
        [targetController locationBarBridge]->Revert();
      NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
      if (modifierFlags & NSShiftKeyMask) {

// 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 {
  // 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]]);
  NSInteger tag = [sender tag];
  NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
  if (![[sender window] isMainWindow]) {
    // Remove the command key from the flags, it means "keep the window in
    // the background" in this case.
    modifierFlags &= ~NSCommandKeyMask;
          [NSApp currentEvent], modifierFlags));

// Called when another part of the internal codebase needs to execute a
// command.
- (void)executeCommand:(int)command {
  if (browser_->command_updater()->IsCommandEnabled(command))

// StatusBubble delegate method: tell the status bubble how far above the bottom
// of the window it should position itself.
- (CGFloat)verticalOffsetForStatusBubble {
  return verticalOffsetForStatusBubble_ +
         [[tabStripController_ activeTabContentsController] devToolsHeight];

- (GTMWindowSheetController*)sheetController {
  return [tabStripController_ sheetController];

- (void)updateToolbarWithContents:(TabContents*)tab
               shouldRestoreState:(BOOL)shouldRestore {
  [toolbarController_ updateToolbarWithContents:tab

- (void)setStarredState:(BOOL)isStarred {
  [toolbarController_ setStarredState:isStarred];

// 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 =
      [[self tabContentArea] frame].size.height - growBoxRect.size.height -
  return growBoxRect;

// Accept tabs from a BrowserWindowController with the same Profile.
- (BOOL)canReceiveFrom:(TabWindowController*)source {
  if (![source isKindOfClass:[BrowserWindowController class]]) {
    return NO;

  BrowserWindowController* realSource =
  if (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)moveTabView:(NSView*)view
     fromController:(TabWindowController*)dragController {
  if (dragController) {
    // Moving between windows. Figure out the TabContents to drop into our tab
    // model from the source window's model.
    BOOL isBrowser =
        [dragController isKindOfClass:[BrowserWindowController class]];
    if (!isBrowser) return;
    BrowserWindowController* dragBWC = (BrowserWindowController*)dragController;
    int index = [dragBWC->tabStripController_ modelIndexForTabView:view];
    TabContents* contents =
    // 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)

    // 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
    tabOrigin = [[view 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_->tabstrip_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 TabContents' 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_ dropTabContents:contents
  } else {
    // Moving within a window.
    int index = [tabStripController_ modelIndexForTabView:view];
    [tabStripController_ moveTabFromIndex:index];

  // Remove the placeholder since the drag is now complete.
  [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];

- (NSView*)selectedTabView {
  return [tabStripController_ selectedTabView];

- (void)setIsLoading:(BOOL)isLoading {
  [toolbarController_ setIsLoading:isLoading];

// Make the location bar the first responder, if possible.
- (void)focusLocationBar:(BOOL)selectAll {
  [toolbarController_ focusLocationBar:selectAll];

- (void)focusTabContents {
  [[self window] makeFirstResponder:[tabStripController_ selectedTabView]];

- (void)layoutTabs {
  [tabStripController_ layoutTabs];

- (TabWindowController*)detachTabToNewWindow:(TabView*)tabView {
  // Disable screen updates so that this appears as a single visual change.
  base::ScopedNSDisableScreenUpdates disabler;

  // Fetch the tab contents for the tab being dragged.
  int index = [tabStripController_ modelIndexForTabView:tabView];
  TabContents* contents = browser_->tabstrip_model()->GetTabContentsAt(index);

  // 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 = [tabView window];
  NSRect windowRect = [sourceWindow frame];
  NSScreen* screen = [sourceWindow screen];
  windowRect.origin.y =
      [screen frame].size.height - windowRect.size.height -
  gfx::Rect browserRect(windowRect.origin.x, windowRect.origin.y,
                        windowRect.size.width, windowRect.size.height);

  NSRect tabRect = [tabView frame];

  // Before detaching the tab, store the pinned state.
  bool isPinned = browser_->tabstrip_model()->IsTabPinned(index);

  // 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 TabContents' delegate, which gets hooked
  // up during creation of the new window.

  // Create the new window with a single tab in its model, the one being
  // dragged.
  DockInfo dockInfo;
  Browser* newBrowser =

  // Propagate the tab pinned state of the new tab (which is the only tab in
  // this new window).
  newBrowser->tabstrip_model()->SetTabPinned(0, isPinned);

  // Get the new controller by asking the new window for its delegate.
  BrowserWindowController* controller =
          [newBrowser->window()->GetNativeHandle() delegate]);
  DCHECK(controller && [controller isKindOfClass:[TabWindowController class]]);

  // Force the added tab to the right size (remove stretching.)
  tabRect.size.height = [TabStripController defaultTabHeight];

  // And make sure we use the correct frame in the new view.
  [[controller tabStripController] setFrameOfSelectedTab:tabRect];
  return controller;

- (void)insertPlaceholderForTab:(TabView*)tab
                      yStretchiness:(CGFloat)yStretchiness {
  [super insertPlaceholderForTab:tab frame:frame yStretchiness:yStretchiness];
  [tabStripController_ insertPlaceholderForTab:tab

- (void)removePlaceholder {
  [super removePlaceholder];
  [tabStripController_ insertPlaceholderForTab:nil

- (BOOL)tabDraggingAllowed {
  return [tabStripController_ tabDraggingAllowed];

- (BOOL)tabTearingAllowed {
  return ![self isFullscreen];

- (BOOL)windowMovementAllowed {
  return ![self isFullscreen];

- (BOOL)isTabFullyVisible:(TabView*)tab {
  return [tabStripController_ isTabFullyVisible:tab];

- (void)showNewTabButton:(BOOL)show {
  [tabStripController_ showNewTabButton:show];

- (BOOL)isBookmarkBarVisible {
  return [bookmarkBarController_ isVisible];

- (BOOL)isBookmarkBarAnimating {
  return [bookmarkBarController_ isAnimationRunning];

- (void)updateBookmarkBarVisibilityWithAnimation:(BOOL)animate {
      updateAndShowNormalBar:[self shouldShowBookmarkBar]
             showDetachedBar:[self shouldShowDetachedBookmarkBar]

- (BOOL)isDownloadShelfVisible {
  return downloadShelfController_ != nil &&
      [downloadShelfController_ isVisible];

- (DownloadShelfController*)downloadShelf {
  if (!downloadShelfController_.get()) {
    downloadShelfController_.reset([[DownloadShelfController alloc]
        initWithBrowser:browser_.get() resizeDelegate:self]);
    [[[self window] contentView] addSubview:[downloadShelfController_ view]];
    [downloadShelfController_ show:nil];
  return downloadShelfController_;

- (void)addFindBar:(FindBarCocoaController*)findBarCocoaController {
  // Shouldn't call addFindBar twice.

  // Create a controller for the findbar.
  findBarCocoaController_.reset([findBarCocoaController retain]);
  NSView *contentView = [[self window] contentView];
  [contentView addSubview:[findBarCocoaController_ view]
               relativeTo:[toolbarController_ view]];

  // Place the find bar immediately below the toolbar/attached bookmark bar. In
  // fullscreen mode, it hangs off the top of the screen when the bar is hidden.
  CGFloat maxY = [self placeBookmarkBarBelowInfoBar] ?
      NSMinY([[toolbarController_ view] frame]) :
      NSMinY([[bookmarkBarController_ view] frame]);
  CGFloat maxWidth = NSWidth([contentView frame]);
  [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:maxWidth];

- (NSWindow*)createFullscreenWindow {
  return [[[FullscreenWindow alloc] initForScreen:[[self window] screen]]

- (NSInteger)numberOfTabs {
  // count() includes pinned tabs (both live and phantom).
  return browser_->tabstrip_model()->count();

- (BOOL)hasLiveTabs {
  return browser_->tabstrip_model()->HasNonPhantomTabs();

- (NSString*)selectedTabTitle {
  TabContents* contents = browser_->tabstrip_model()->GetSelectedTabContents();
  return base::SysUTF16ToNSString(contents->GetTitle());

- (NSRect)regularWindowFrame {
  return [self isFullscreen] ? [savedRegularWindow_ frame] :
                               [[self window] frame];

// (Override of |TabWindowController| method.)
- (BOOL)hasTabStrip {
  return [self supportsWindowFeature:Browser::FEATURE_TABSTRIP];

- (void)selectTabWithContents:(TabContents*)newContents
                  userGesture:(bool)wasUserGesture {
  DCHECK(oldContents != newContents);

  // Update various elements that are interested in knowing the current
  // TabContents.

  // Update all the UI bits.

  // Update the bookmark bar.
  // TODO(viettrungluu): perhaps update to not terminate running animations (if
  // applicable)?
  [self updateBookmarkBarVisibilityWithAnimation:NO];

- (void)tabChangedWithContents:(TabContents*)contents
                    changeType:(TabStripModelObserver::TabChangeType)change {
  if (index == browser_->tabstrip_model()->selected_index()) {
    // 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)

    // 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)
      [self updateBookmarkBarVisibilityWithAnimation:NO];

- (void)userChangedTheme {
  // 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];

- (ThemeProvider*)themeProvider {
  return browser_->profile()->GetThemeProvider();

- (ThemedWindowStyle)themedWindowStyle {
  ThemedWindowStyle style = 0;
  if (browser_->profile()->IsOffTheRecord())
    style |= THEMED_INCOGNITO;

  Browser::Type type = browser_->type();
  if (type == Browser::TYPE_POPUP)
    style |= THEMED_POPUP;
  else if (type == Browser::TYPE_DEVTOOLS)
    style |= THEMED_DEVTOOLS;

  return style;

- (NSPoint)themePatternPhase {
  // Our patterns want to be drawn from the upper left hand corner of the view.
  // Cocoa wants to do it from the lower left of the window.
  // Rephase our pattern to fit this view. Some other views (Tabs, Toolbar etc.)
  // will phase their patterns relative to this so all the views look right.
  // To line up the background pattern with the pattern in the browser window
  // the background pattern for the tabs needs to be moved left by 5 pixels.
  const CGFloat kPatternHorizontalOffset = -5;
  NSView* tabStripView = [self tabStripView];
  NSRect tabStripViewWindowBounds = [tabStripView bounds];
  NSView* windowChromeView = [[[self window] contentView] superview];
  tabStripViewWindowBounds =
      [tabStripView convertRect:tabStripViewWindowBounds
  NSPoint phase = NSMakePoint(NSMinX(tabStripViewWindowBounds)
                                  + kPatternHorizontalOffset,
                                  + [TabStripController defaultTabHeight]);
  return phase;

- (NSPoint)pointForBubbleArrowTip {
  NSRect rect = [toolbarController_ starButtonInWindowCoordinates];
  // Determine the point of the arrow of the bubble window.
  NSPoint p = rect.origin;
  p.x += (NSWidth(rect) / 2.0) + 1.0;  // Star is not exactly in center.
  p.y += 4;
  return p;

// Show the bookmark bubble (e.g. user just clicked on the STAR).
- (void)showBookmarkBubbleForURL:(const GURL&)url
               alreadyBookmarked:(BOOL)alreadyMarked {
  if (!bookmarkBubbleController_) {
    BookmarkModel* model = browser_->profile()->GetBookmarkModel();
    const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url);
    bookmarkBubbleController_ =
        [[BookmarkBubbleController alloc] initWithParentWindow:[self window]
    [bookmarkBubbleController_ showWindow:self];
    NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
    [center addObserver:self
                 object:[bookmarkBubbleController_ window]];

// Nil out the weak bookmark bubble controller reference.
- (void)bubbleWindowWillClose:(NSNotification*)notification {
  DCHECK([notification object] == [bookmarkBubbleController_ window]);
  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  [center removeObserver:self
               object:[bookmarkBubbleController_ window]];
  bookmarkBubbleController_ = nil;

// Handle the editBookmarkNode: action sent from bookmark bubble controllers.
- (void)editBookmarkNode:(id)sender {
  BOOL responds = [sender respondsToSelector:@selector(node)];
  if (responds) {
    const BookmarkNode* node = [sender node];
    if (node) {
      // A BookmarkEditorController is a sheet that owns itself, and
      // deallocates itself when closed.
      [[[BookmarkEditorController alloc]
         initWithParentWindow:[self window]

// If the browser is in incognito mode, install the image view to decorate
// the window at the upper right. Use the same base y coordinate as the
// tab strip.
- (void)installIncognitoBadge {
  // Only install if this browser window is OTR and has a tab strip.
  if (!browser_->profile()->IsOffTheRecord() || ![self hasTabStrip])

  // Install the image into the badge view and size the view appropriately.
  // Hide it for now; positioning and showing will be done by the layout code.
  NSImage* image = nsimage_cache::ImageNamed(@"otr_icon.pdf");
  incognitoBadge_.reset([[IncognitoImageView alloc] init]);
  [incognitoBadge_ setImage:image];
  [incognitoBadge_ setFrameSize:[image size]];
  [incognitoBadge_ setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
  [incognitoBadge_ setHidden:YES];

  // Give it a shadow.
  scoped_nsobject<NSShadow> shadow([[NSShadow alloc] init]);
  [shadow.get() setShadowColor:[NSColor colorWithCalibratedWhite:0.0
  [shadow.get() setShadowOffset:NSMakeSize(0, -1)];
  [shadow setShadowBlurRadius:2.0];
  [incognitoBadge_ setShadow:shadow];

  // Install the view.
  [[[[self window] contentView] superview] addSubview:incognitoBadge_];

// Documented in 10.6+, but present starting in 10.5. Called when we get a
// three-finger swipe.
- (void)swipeWithEvent:(NSEvent*)event {
  // Map forwards and backwards to history; left is positive, right is negative.
  unsigned int command = 0;
  if ([event deltaX] > 0.5)
    command = IDC_BACK;
  else if ([event deltaX] < -0.5)
    command = IDC_FORWARD;
  else if ([event deltaY] > 0.5)
    ;  // TODO(pinkerton): figure out page-up, http://crbug.com/16305
  else if ([event 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 (browser_->command_updater()->IsCommandEnabled(command))

// Documented in 10.6+, but present starting in 10.5. Called repeatedly during
// a pinch gesture, with incremental change values.
- (void)magnifyWithEvent:(NSEvent*)event {
  // The deltaZ difference necessary to trigger a zoom action. Derived from
  // experimentation to find a value that feels reasonable.
  const float kZoomStepValue = 150;

  // Find the (absolute) thresholds on either side of the current zoom factor,
  // then convert those to actual numbers to trigger a zoom in or out.
  // This logic deliberately makes the range around the starting zoom value for
  // the gesture twice as large as the other ranges (i.e., the notches are at
  // ..., -3*step, -2*step, -step, step, 2*step, 3*step, ... but not at 0)
  // so that it's easier to get back to your starting point than it is to
  // overshoot.
  float nextStep = (abs(currentZoomStepDelta_) + 1) * kZoomStepValue;
  float backStep = abs(currentZoomStepDelta_) * kZoomStepValue;
  float zoomInThreshold = (currentZoomStepDelta_ >= 0) ? nextStep : -backStep;
  float zoomOutThreshold = (currentZoomStepDelta_ <= 0) ? -nextStep : backStep;

  unsigned int command = 0;
  totalMagnifyGestureAmount_ += [event deltaZ];
  if (totalMagnifyGestureAmount_ > zoomInThreshold) {
    command = IDC_ZOOM_PLUS;
  } else if (totalMagnifyGestureAmount_ < zoomOutThreshold) {
    command = IDC_ZOOM_MINUS;

  if (command && browser_->command_updater()->IsCommandEnabled(command)) {
    currentZoomStepDelta_ += (command == IDC_ZOOM_PLUS) ? 1 : -1;

// Documented in 10.6+, but present starting in 10.5. Called at the beginning
// of a gesture.
- (void)beginGestureWithEvent:(NSEvent*)event {
  totalMagnifyGestureAmount_ = 0;
  currentZoomStepDelta_ = 0;

// Delegate method called when window is resized.
- (void)windowDidResize:(NSNotification*)notification {
  // Resize (and possibly move) the status bubble. Note that we may get called
  // when the status bubble does not exist.
  if (statusBubble_) {

  // Let the selected RenderWidgetHostView know, so that it can tell plugins.
  if (TabContents* contents = browser_->GetSelectedTabContents()) {
    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())

// 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 unitestting possible.
- (void)openLearnMoreAboutCrashLink:(id)sender {
  if ([sender isKindOfClass:[SadTabController class]]) {
    SadTabController* sad_tab = static_cast<SadTabController*>(sender);
    TabContents* tab_contents = [sad_tab tabContents];
    if (tab_contents) {
      std::string linkUrl = l10n_util::GetStringUTF8(IDS_CRASH_REASON_URL);
      tab_contents->OpenURL(GURL(linkUrl), GURL(), CURRENT_TAB,

// 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 {
  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 (TabContents* contents = browser_->GetSelectedTabContents()) {
    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())

// 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
            toState:(bookmarks::VisualState)newState {
      setDividerOpacity:[bookmarkBarController_ toolbarDividerOpacity]];
  [self adjustToolbarAndBookmarkBarForCompression:
          [controller getDesiredToolbarHeightCompression]];

// (Needed for |BookmarkBarControllerDelegate| protocol.)
- (void)bookmarkBar:(BookmarkBarController*)controller
            toState:(bookmarks::VisualState)newState {
      setDividerOpacity:[bookmarkBarController_ toolbarDividerOpacity]];
  [self adjustToolbarAndBookmarkBarForCompression:
          [controller getDesiredToolbarHeightCompression]];

// (Private/TestingAPI)
- (void)resetWindowGrowthState {
  windowTopGrowth_ = 0;
  windowBottomGrowth_ = 0;
  isShrinkingFromZoomed_ = NO;

@end  // @implementation BrowserWindowController

@implementation BrowserWindowController(Fullscreen)

- (void)setFullscreen:(BOOL)fullscreen {
  // The logic in this function is a bit complicated and very carefully
  // arranged.  See the below comments for more details.

  if (fullscreen == [self isFullscreen])

  if (![self supportsFullscreen])

  // Fade to black.
  const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
  Boolean didFadeOut = NO;
  CGDisplayFadeReservationToken token;
  if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
      == kCGErrorSuccess) {
    didFadeOut = YES;
    CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
        kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);

  // Save the current first responder so we can restore after views are moved.
  NSWindow* window = [self window];
  scoped_nsobject<FocusTracker> focusTracker(
      [[FocusTracker alloc] initWithWindow:window]);
  BOOL showDropdown = [self floatingBarHasFocus];

  // While we move views (and focus) around, disable any bar visibility changes.
  [self disableBarVisibilityUpdates];

  // If we're entering fullscreen, create the fullscreen controller.  If we're
  // exiting fullscreen, kill the controller.
  if (fullscreen) {
    fullscreenController_.reset([[FullscreenController alloc]
  } else {
    [fullscreenController_ exitFullscreen];

  // Destroy the tab strip's sheet controller.  We will recreate it in the new
  // window when needed.
  [tabStripController_ destroySheetController];

  // Retain the tab strip view while we remove it from its superview.
  scoped_nsobject<NSView> tabStripView;
  if ([self hasTabStrip]) {
    tabStripView.reset([[self tabStripView] retain]);
    [tabStripView removeFromSuperview];

  // Ditto for the content view.
  scoped_nsobject<NSView> contentView([[window contentView] retain]);
  // Disable autoresizing of subviews while we move views around. This prevents
  // spurious renderer resizes.
  [contentView setAutoresizesSubviews:NO];
  [contentView removeFromSuperview];

  NSWindow* destWindow = nil;
  if (fullscreen) {
    savedRegularWindow_ = [window retain];
    destWindow = [self createFullscreenWindow];
  } else {
    destWindow = [savedRegularWindow_ autorelease];
    savedRegularWindow_ = nil;

  // Have to do this here, otherwise later calls can crash because the window
  // has no delegate.
  [window setDelegate:nil];
  [destWindow setDelegate:self];

  // With this call, valgrind complains that a "Conditional jump or move depends
  // on uninitialised value(s)".  The error happens in -[NSThemeFrame
  // drawOverlayRect:].  I'm pretty convinced this is an Apple bug, but there is
  // no visual impact.  I have been unable to tickle it away with other window
  // or view manipulation Cocoa calls.  Stack added to suppressions_mac.txt.
  [contentView setAutoresizesSubviews:YES];
  [destWindow setContentView:contentView];

  // Move the incognito badge if present.
  if (incognitoBadge_.get()) {
    [incognitoBadge_ removeFromSuperview];
    [incognitoBadge_ setHidden:YES];  // Will be shown in layout.
    [[[destWindow contentView] superview] addSubview:incognitoBadge_];

  // Add the tab strip after setting the content view and moving the incognito
  // badge (if any), so that the tab strip will be on top (in the z-order).
  if ([self hasTabStrip])
    [[[destWindow contentView] superview] addSubview:tabStripView];

  [window setWindowController:nil];
  [self setWindow:destWindow];
  [destWindow setWindowController:self];
  [self adjustUIForFullscreen:fullscreen];

  // When entering fullscreen mode, the controller forces a layout for us.  When
  // exiting, we need to call layoutSubviews manually.
  if (fullscreen) {
    [fullscreenController_ enterFullscreenForContentView:contentView
  } else {
    [self layoutSubviews];

  // Move the status bubble over, if we have one.
  if (statusBubble_)

  // The window needs to be onscreen before we can set its first responder.
  [destWindow makeKeyAndOrderFront:self];
  [focusTracker restoreFocusInWindow:destWindow];
  [window orderOut:self];

  // We're done moving focus, so re-enable bar visibility changes.
  [self enableBarVisibilityUpdates];

  // Fade back in.
  if (didFadeOut) {
    CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
        kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);

- (BOOL)isFullscreen {
  return fullscreenController_.get() && [fullscreenController_ isFullscreen];

- (void)resizeFullscreenWindow {
  DCHECK([self isFullscreen]);
  if (![self isFullscreen])

  NSWindow* window = [self window];
  [window setFrame:[[window screen] frame] display:YES];
  [self layoutSubviews];

- (CGFloat)floatingBarShownFraction {
  return floatingBarShownFraction_;

- (void)setFloatingBarShownFraction:(CGFloat)fraction {
  floatingBarShownFraction_ = fraction;
  [self layoutSubviews];

- (BOOL)isBarVisibilityLockedForOwner:(id)owner {
  return [barVisibilityLocks_ containsObject:owner];

- (void)lockBarVisibilityForOwner:(id)owner
                            delay:(BOOL)delay {
  if (![self isBarVisibilityLockedForOwner:owner]) {
    [barVisibilityLocks_ addObject:owner];

    // If enabled, show the overlay if necessary (and if in fullscreen mode).
    if (barVisibilityUpdatesEnabled_) {
      [fullscreenController_ ensureOverlayShownWithAnimation:animate

- (void)releaseBarVisibilityForOwner:(id)owner
                               delay:(BOOL)delay {
  if ([self isBarVisibilityLockedForOwner:owner]) {
    [barVisibilityLocks_ removeObject:owner];

    // If enabled, hide the overlay if necessary (and if in fullscreen mode).
    if (barVisibilityUpdatesEnabled_ &&
        ![barVisibilityLocks_ count]) {
      [fullscreenController_ ensureOverlayHiddenWithAnimation:animate

- (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(

- (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)isNormalWindow {
  return browser_->type() == Browser::TYPE_NORMAL;

@end  // @implementation BrowserWindowController(WindowType)

Generated by  Doxygen 1.6.0   Back to index