• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#import "chrome/browser/ui/cocoa/browser_window_controller.h"
6
7#include <cmath>
8#include <numeric>
9
10#include "base/command_line.h"
11#include "base/mac/bundle_locations.h"
12#include "base/mac/mac_util.h"
13#import "base/mac/sdk_forward_declarations.h"
14#include "base/strings/sys_string_conversions.h"
15#include "base/strings/utf_string_conversions.h"
16#include "chrome/app/chrome_command_ids.h"  // IDC_*
17#include "chrome/browser/bookmarks/bookmark_model_factory.h"
18#include "chrome/browser/browser_process.h"
19#include "chrome/browser/devtools/devtools_window.h"
20#include "chrome/browser/fullscreen.h"
21#include "chrome/browser/profiles/avatar_menu.h"
22#include "chrome/browser/profiles/profile.h"
23#include "chrome/browser/profiles/profile_info_cache.h"
24#include "chrome/browser/profiles/profile_manager.h"
25#include "chrome/browser/signin/signin_ui_util.h"
26#include "chrome/browser/themes/theme_service.h"
27#include "chrome/browser/themes/theme_service_factory.h"
28#include "chrome/browser/ui/bookmarks/bookmark_editor.h"
29#include "chrome/browser/ui/browser.h"
30#include "chrome/browser/ui/browser_command_controller.h"
31#include "chrome/browser/ui/browser_commands.h"
32#include "chrome/browser/ui/browser_instant_controller.h"
33#include "chrome/browser/ui/browser_list.h"
34#include "chrome/browser/ui/browser_window_state.h"
35#import "chrome/browser/ui/cocoa/background_gradient_view.h"
36#import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
37#import "chrome/browser/ui/cocoa/bookmarks/bookmark_editor_controller.h"
38#import "chrome/browser/ui/cocoa/browser/avatar_button_controller.h"
39#import "chrome/browser/ui/cocoa/browser_window_cocoa.h"
40#import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
41#import "chrome/browser/ui/cocoa/browser_window_utils.h"
42#import "chrome/browser/ui/cocoa/dev_tools_controller.h"
43#import "chrome/browser/ui/cocoa/download/download_shelf_controller.h"
44#include "chrome/browser/ui/cocoa/extensions/extension_keybinding_registry_cocoa.h"
45#import "chrome/browser/ui/cocoa/fast_resize_view.h"
46#import "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h"
47#import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
48#import "chrome/browser/ui/cocoa/framed_browser_window.h"
49#import "chrome/browser/ui/cocoa/fullscreen_window.h"
50#import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
51#import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h"
52#import "chrome/browser/ui/cocoa/nsview_additions.h"
53#import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
54#import "chrome/browser/ui/cocoa/status_bubble_mac.h"
55#import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
56#import "chrome/browser/ui/cocoa/tab_contents/sad_tab_controller.h"
57#import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h"
58#import "chrome/browser/ui/cocoa/tabpose_window.h"
59#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
60#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
61#import "chrome/browser/ui/cocoa/tabs/tab_view.h"
62#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
63#include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
64#include "chrome/browser/ui/omnibox/location_bar.h"
65#include "chrome/browser/ui/tabs/dock_info.h"
66#include "chrome/browser/ui/tabs/tab_strip_model.h"
67#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
68#include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
69#include "chrome/browser/ui/window_sizer/window_sizer.h"
70#include "chrome/common/chrome_switches.h"
71#include "chrome/common/url_constants.h"
72#include "components/web_modal/web_contents_modal_dialog_manager.h"
73#include "content/public/browser/render_view_host.h"
74#include "content/public/browser/render_widget_host_view.h"
75#include "content/public/browser/web_contents.h"
76#include "content/public/browser/web_contents_view.h"
77#include "content/public/common/content_switches.h"
78#include "grit/chromium_strings.h"
79#include "grit/generated_resources.h"
80#include "grit/locale_settings.h"
81#import "ui/base/cocoa/cocoa_event_utils.h"
82#include "ui/base/l10n/l10n_util.h"
83#include "ui/base/l10n/l10n_util_mac.h"
84#include "ui/gfx/mac/scoped_ns_disable_screen_updates.h"
85
86using l10n_util::GetStringUTF16;
87using l10n_util::GetNSStringWithFixup;
88using l10n_util::GetNSStringFWithFixup;
89
90// ORGANIZATION: This is a big file. It is (in principle) organized as follows
91// (in order):
92// 1. Interfaces. Very short, one-time-use classes may include an implementation
93//    immediately after their interface.
94// 2. The general implementation section, ordered as follows:
95//      i. Public methods and overrides.
96//     ii. Overrides/implementations of undocumented methods.
97//    iii. Delegate methods for various protocols, formal and informal, to which
98//        |BrowserWindowController| conforms.
99// 3. (temporary) Implementation sections for various categories.
100//
101// Private methods are defined and implemented separately in
102// browser_window_controller_private.{h,mm}.
103//
104// Not all of the above guidelines are followed and more (re-)organization is
105// needed. BUT PLEASE TRY TO KEEP THIS FILE ORGANIZED. I'd rather re-organize as
106// little as possible, since doing so messes up the file's history.
107//
108// TODO(viettrungluu): [crbug.com/35543] on-going re-organization, splitting
109// things into multiple files -- the plan is as follows:
110// - in general, everything stays in browser_window_controller.h, but is split
111//   off into categories (see below)
112// - core stuff stays in browser_window_controller.mm
113// - ... overrides also stay (without going into a category, in particular)
114// - private stuff which everyone needs goes into
115//   browser_window_controller_private.{h,mm}; if no one else needs them, they
116//   can go in individual files (see below)
117// - area/task-specific stuff go in browser_window_controller_<area>.mm
118// - ... in categories called "(<Area>)" or "(<PrivateArea>)"
119// Plan of action:
120// - first re-organize into categories
121// - then split into files
122
123// Notes on self-inflicted (not user-inflicted) window resizing and moving:
124//
125// When the bookmark bar goes from hidden to shown (on a non-NTP) page, or when
126// the download shelf goes from hidden to shown, we grow the window downwards in
127// order to maintain a constant content area size. When either goes from shown
128// to hidden, we consequently shrink the window from the bottom, also to keep
129// the content area size constant. To keep things simple, if the window is not
130// entirely on-screen, we don't grow/shrink the window.
131//
132// The complications come in when there isn't enough room (on screen) below the
133// window to accomodate the growth. In this case, we grow the window first
134// downwards, and then upwards. So, when it comes to shrinking, we do the
135// opposite: shrink from the top by the amount by which we grew at the top, and
136// then from the bottom -- unless the user moved/resized/zoomed the window, in
137// which case we "reset state" and just shrink from the bottom.
138//
139// A further complication arises due to the way in which "zoom" ("maximize")
140// works on Mac OS X. Basically, for our purposes, a window is "zoomed" whenever
141// it occupies the full available vertical space. (Note that the green zoom
142// button does not track zoom/unzoomed state per se, but basically relies on
143// this heuristic.) We don't, in general, want to shrink the window if the
144// window is zoomed (scenario: window is zoomed, download shelf opens -- which
145// doesn't cause window growth, download shelf closes -- shouldn't cause the
146// window to become unzoomed!). However, if we grew the window
147// (upwards/downwards) to become zoomed in the first place, we *should* shrink
148// the window by the amounts by which we grew (scenario: window occupies *most*
149// of vertical space, download shelf opens causing growth so that window
150// occupies all of vertical space -- i.e., window is effectively zoomed,
151// download shelf closes -- should return the window to its previous state).
152//
153// A major complication is caused by the way grows/shrinks are handled and
154// animated. Basically, the BWC doesn't see the global picture, but it sees
155// grows and shrinks in small increments (as dictated by the animation). Thus
156// window growth/shrinkage (at the top/bottom) have to be tracked incrementally.
157// Allowing shrinking from the zoomed state also requires tracking: We check on
158// any shrink whether we're both zoomed and have previously grown -- if so, we
159// set a flag, and constrain any resize by the allowed amounts. On further
160// shrinks, we check the flag (since the size/position of the window will no
161// longer indicate that the window is shrinking from an apparent zoomed state)
162// and if it's set we continue to constrain the resize.
163
164using content::OpenURLParams;
165using content::Referrer;
166using content::RenderWidgetHostView;
167using content::WebContents;
168using web_modal::WebContentsModalDialogManager;
169
170@interface NSWindow (NSPrivateApis)
171// Note: These functions are private, use -[NSObject respondsToSelector:]
172// before calling them.
173
174- (void)setBottomCornerRounded:(BOOL)rounded;
175
176- (NSRect)_growBoxRect;
177
178@end
179
180// Replicate specific 10.7 SDK declarations for building with prior SDKs.
181#if !defined(MAC_OS_X_VERSION_10_7) || \
182    MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_7
183
184enum {
185  NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
186  NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8
187};
188
189enum {
190  NSFullScreenWindowMask = 1 << 14
191};
192
193@interface NSWindow (LionSDKDeclarations)
194- (void)setRestorable:(BOOL)flag;
195@end
196
197#endif  // MAC_OS_X_VERSION_10_7
198
199@implementation BrowserWindowController
200
201+ (BrowserWindowController*)browserWindowControllerForWindow:(NSWindow*)window {
202  while (window) {
203    id controller = [window windowController];
204    if ([controller isKindOfClass:[BrowserWindowController class]])
205      return (BrowserWindowController*)controller;
206    window = [window parentWindow];
207  }
208  return nil;
209}
210
211+ (BrowserWindowController*)browserWindowControllerForView:(NSView*)view {
212  NSWindow* window = [view window];
213  return [BrowserWindowController browserWindowControllerForWindow:window];
214}
215
216+ (void)updateSigninItem:(id)signinItem
217              shouldShow:(BOOL)showSigninMenuItem
218          currentProfile:(Profile*)profile {
219  DCHECK([signinItem isKindOfClass:[NSMenuItem class]]);
220  NSMenuItem* signinMenuItem = static_cast<NSMenuItem*>(signinItem);
221
222  // Look for a separator immediately after the menu item so it can be hidden
223  // or shown appropriately along with the signin menu item.
224  NSMenuItem* followingSeparator = nil;
225  NSMenu* menu = [signinItem menu];
226  if (menu) {
227    NSInteger signinItemIndex = [menu indexOfItem:signinMenuItem];
228    DCHECK_NE(signinItemIndex, -1);
229    if ((signinItemIndex + 1) < [menu numberOfItems]) {
230      NSMenuItem* menuItem = [menu itemAtIndex:(signinItemIndex + 1)];
231      if ([menuItem isSeparatorItem]) {
232        followingSeparator = menuItem;
233      }
234    }
235  }
236
237  base::string16 label = signin_ui_util::GetSigninMenuLabel(profile);
238  [signinMenuItem setTitle:l10n_util::FixUpWindowsStyleLabel(label)];
239  [signinMenuItem setHidden:!showSigninMenuItem];
240  [followingSeparator setHidden:!showSigninMenuItem];
241}
242
243// Load the browser window nib and do any Cocoa-specific initialization.
244// Takes ownership of |browser|. Note that the nib also sets this controller
245// up as the window's delegate.
246- (id)initWithBrowser:(Browser*)browser {
247  return [self initWithBrowser:browser takeOwnership:YES];
248}
249
250// Private(TestingAPI) init routine with testing options.
251- (id)initWithBrowser:(Browser*)browser takeOwnership:(BOOL)ownIt {
252  bool hasTabStrip = browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP);
253  if ((self = [super initTabWindowControllerWithTabStrip:hasTabStrip])) {
254    DCHECK(browser);
255    initializing_ = YES;
256    browser_.reset(browser);
257    ownsBrowser_ = ownIt;
258    NSWindow* window = [self window];
259    windowShim_.reset(new BrowserWindowCocoa(browser, self));
260
261    // Eagerly enable core animation if requested.
262    if ([self coreAnimationStatus] ==
263            browser_window_controller::kCoreAnimationEnabledAlways) {
264      [[[self window] contentView] setWantsLayer:YES];
265      [[self tabStripView] setWantsLayer:YES];
266    }
267
268    // Set different minimum sizes on tabbed windows vs non-tabbed, e.g. popups.
269    // This has to happen before -enforceMinWindowSize: is called further down.
270    NSSize minSize = [self isTabbedWindow] ?
271      NSMakeSize(400, 272) : NSMakeSize(100, 122);
272    [[self window] setMinSize:minSize];
273
274    // Create the bar visibility lock set; 10 is arbitrary, but should hopefully
275    // be big enough to hold all locks that'll ever be needed.
276    barVisibilityLocks_.reset([[NSMutableSet setWithCapacity:10] retain]);
277
278    // Set the window to not have rounded corners, which prevents the resize
279    // control from being inset slightly and looking ugly. Only bother to do
280    // this on Snow Leopard; on Lion and later all windows have rounded bottom
281    // corners, and this won't work anyway.
282    if (base::mac::IsOSSnowLeopard() &&
283        [window respondsToSelector:@selector(setBottomCornerRounded:)])
284      [window setBottomCornerRounded:NO];
285
286    // Lion will attempt to automagically save and restore the UI. This
287    // functionality appears to be leaky (or at least interacts badly with our
288    // architecture) and thus BrowserWindowController never gets released. This
289    // prevents the browser from being able to quit <http://crbug.com/79113>.
290    if ([window respondsToSelector:@selector(setRestorable:)])
291      [window setRestorable:NO];
292
293    // Get the windows to swish in on Lion.
294    if ([window respondsToSelector:@selector(setAnimationBehavior:)])
295      [window setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow];
296
297    // Get the most appropriate size for the window, then enforce the
298    // minimum width and height. The window shim will handle flipping
299    // the coordinates for us so we can use it to save some code.
300    // Note that this may leave a significant portion of the window
301    // offscreen, but there will always be enough window onscreen to
302    // drag the whole window back into view.
303    ui::WindowShowState show_state = ui::SHOW_STATE_DEFAULT;
304    gfx::Rect desiredContentRect;
305    chrome::GetSavedWindowBoundsAndShowState(browser_.get(),
306                                             &desiredContentRect,
307                                             &show_state);
308    gfx::Rect windowRect = desiredContentRect;
309    windowRect = [self enforceMinWindowSize:windowRect];
310
311    // When we are given x/y coordinates of 0 on a created popup window, assume
312    // none were given by the window.open() command.
313    if (browser_->is_type_popup() &&
314        windowRect.x() == 0 && windowRect.y() == 0) {
315      gfx::Size size = windowRect.size();
316      windowRect.set_origin(
317          WindowSizer::GetDefaultPopupOrigin(size,
318                                             browser_->host_desktop_type()));
319    }
320
321    // Size and position the window.  Note that it is not yet onscreen.  Popup
322    // windows may get resized later on in this function, once the actual size
323    // of the toolbar/tabstrip is known.
324    windowShim_->SetBounds(windowRect);
325
326    // Puts the incognito badge on the window frame, if necessary.
327    [self installAvatar];
328
329    // Create a sub-controller for the docked devTools and add its view to the
330    // hierarchy.
331    devToolsController_.reset([[DevToolsController alloc] init]);
332    [[devToolsController_ view] setFrame:[[self tabContentArea] bounds]];
333    [[self tabContentArea] addSubview:[devToolsController_ view]];
334
335    // Create the overlayable contents controller.  This provides the switch
336    // view that TabStripController needs.
337    overlayableContentsController_.reset(
338        [[OverlayableContentsController alloc] initWithBrowser:browser]);
339    [[overlayableContentsController_ view]
340        setFrame:[[devToolsController_ view] bounds]];
341    [[devToolsController_ view]
342        addSubview:[overlayableContentsController_ view]];
343
344    // Create a controller for the tab strip, giving it the model object for
345    // this window's Browser and the tab strip view. The controller will handle
346    // registering for the appropriate tab notifications from the back-end and
347    // managing the creation of new tabs.
348    [self createTabStripController];
349
350    // Create a controller for the toolbar, giving it the toolbar model object
351    // and the toolbar view from the nib. The controller will handle
352    // registering for the appropriate command state changes from the back-end.
353    // Adds the toolbar to the content area.
354    toolbarController_.reset([[ToolbarController alloc]
355              initWithCommands:browser->command_controller()->command_updater()
356                       profile:browser->profile()
357                       browser:browser
358                resizeDelegate:self]);
359    [toolbarController_ setHasToolbar:[self hasToolbar]
360                       hasLocationBar:[self hasLocationBar]];
361
362    // Create a sub-controller for the bookmark bar.
363    bookmarkBarController_.reset(
364        [[BookmarkBarController alloc]
365            initWithBrowser:browser_.get()
366               initialWidth:NSWidth([[[self window] contentView] frame])
367                   delegate:self
368             resizeDelegate:self]);
369    [bookmarkBarController_ setBookmarkBarEnabled:[self supportsBookmarkBar]];
370
371    // Create the infobar container view, so we can pass it to the
372    // ToolbarController.
373    infoBarContainerController_.reset(
374        [[InfoBarContainerController alloc] initWithResizeDelegate:self]);
375    [self updateInfoBarTipVisibility];
376
377    // We don't want to try and show the bar before it gets placed in its parent
378    // view, so this step shoudn't be inside the bookmark bar controller's
379    // |-awakeFromNib|.
380    windowShim_->BookmarkBarStateChanged(
381        BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
382
383    // Allow bar visibility to be changed.
384    [self enableBarVisibilityUpdates];
385
386    // Force a relayout of all the various bars.
387    [self layoutSubviews];
388
389    // Set the window to participate in Lion Fullscreen mode.  Setting this flag
390    // has no effect on Snow Leopard or earlier.  Panels can share a fullscreen
391    // space with a tabbed window, but they can not be primary fullscreen
392    // windows.  Do this after |-layoutSubviews| so that the fullscreen button
393    // can be adjusted in FramedBrowserWindow.
394    NSUInteger collectionBehavior = [window collectionBehavior];
395    collectionBehavior |=
396       browser_->type() == Browser::TYPE_TABBED ||
397           browser_->type() == Browser::TYPE_POPUP ?
398               NSWindowCollectionBehaviorFullScreenPrimary :
399               NSWindowCollectionBehaviorFullScreenAuxiliary;
400    [window setCollectionBehavior:collectionBehavior];
401
402    // For a popup window, |desiredContentRect| contains the desired height of
403    // the content, not of the whole window.  Now that all the views are laid
404    // out, measure the current content area size and grow if needed.  The
405    // window has not been placed onscreen yet, so this extra resize will not
406    // cause visible jank.
407    if (browser_->is_type_popup()) {
408      CGFloat deltaH = desiredContentRect.height() -
409                       NSHeight([[self tabContentArea] frame]);
410      // Do not shrink the window, as that may break minimum size invariants.
411      if (deltaH > 0) {
412        // Convert from tabContentArea coordinates to window coordinates.
413        NSSize convertedSize =
414            [[self tabContentArea] convertSize:NSMakeSize(0, deltaH)
415                                        toView:nil];
416        NSRect frame = [[self window] frame];
417        frame.size.height += convertedSize.height;
418        frame.origin.y -= convertedSize.height;
419        [[self window] setFrame:frame display:NO];
420      }
421    }
422
423    // Create the bridge for the status bubble.
424    statusBubble_ = new StatusBubbleMac([self window], self);
425
426    // Register for application hide/unhide notifications.
427    [[NSNotificationCenter defaultCenter]
428         addObserver:self
429            selector:@selector(applicationDidHide:)
430                name:NSApplicationDidHideNotification
431              object:nil];
432    [[NSNotificationCenter defaultCenter]
433         addObserver:self
434            selector:@selector(applicationDidUnhide:)
435                name:NSApplicationDidUnhideNotification
436              object:nil];
437
438    // This must be done after the view is added to the window since it relies
439    // on the window bounds to determine whether to show buttons or not.
440    if ([self hasToolbar])  // Do not create the buttons in popups.
441      [toolbarController_ createBrowserActionButtons];
442
443    extension_keybinding_registry_.reset(
444        new ExtensionKeybindingRegistryCocoa(browser_->profile(),
445            [self window],
446            extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
447            windowShim_.get()));
448
449    // We are done initializing now.
450    initializing_ = NO;
451  }
452  return self;
453}
454
455- (void)dealloc {
456  browser_->tab_strip_model()->CloseAllTabs();
457  [downloadShelfController_ exiting];
458
459  // Explicitly release |presentationModeController_| here, as it may call back
460  // to this BWC in |-dealloc|.  We are required to call |-exitPresentationMode|
461  // before releasing the controller.
462  [presentationModeController_ exitPresentationMode];
463  presentationModeController_.reset();
464
465  // Under certain testing configurations we may not actually own the browser.
466  if (ownsBrowser_ == NO)
467    ignore_result(browser_.release());
468
469  [[NSNotificationCenter defaultCenter] removeObserver:self];
470
471  [super dealloc];
472}
473
474- (gfx::Rect)enforceMinWindowSize:(gfx::Rect)bounds {
475  gfx::Rect checkedBounds = bounds;
476
477  NSSize minSize = [[self window] minSize];
478  if (bounds.width() < minSize.width)
479      checkedBounds.set_width(minSize.width);
480  if (bounds.height() < minSize.height)
481      checkedBounds.set_height(minSize.height);
482
483  return checkedBounds;
484}
485
486- (BrowserWindow*)browserWindow {
487  return windowShim_.get();
488}
489
490- (ToolbarController*)toolbarController {
491  return toolbarController_.get();
492}
493
494- (TabStripController*)tabStripController {
495  return tabStripController_.get();
496}
497
498- (FindBarCocoaController*)findBarCocoaController {
499  return findBarCocoaController_.get();
500}
501
502- (InfoBarContainerController*)infoBarContainerController {
503  return infoBarContainerController_.get();
504}
505
506- (StatusBubbleMac*)statusBubble {
507  return statusBubble_;
508}
509
510- (LocationBarViewMac*)locationBarBridge {
511  return [toolbarController_ locationBarBridge];
512}
513
514- (NSView*)floatingBarBackingView {
515  return floatingBarBackingView_;
516}
517
518- (OverlayableContentsController*)overlayableContentsController {
519  return overlayableContentsController_;
520}
521
522- (Profile*)profile {
523  return browser_->profile();
524}
525
526- (AvatarButtonController*)avatarButtonController {
527  return avatarButtonController_.get();
528}
529
530- (void)destroyBrowser {
531  [NSApp removeWindowsItem:[self window]];
532
533  // We need the window to go away now.
534  // We can't actually use |-autorelease| here because there's an embedded
535  // run loop in the |-performClose:| which contains its own autorelease pool.
536  // Instead call it after a zero-length delay, which gets us back to the main
537  // event loop.
538  [self performSelector:@selector(autorelease)
539             withObject:nil
540             afterDelay:0];
541}
542
543// Called when the window meets the criteria to be closed (ie,
544// |-windowShouldClose:| returns YES). We must be careful to preserve the
545// semantics of BrowserWindow::Close() and not call the Browser's dtor directly
546// from this method.
547- (void)windowWillClose:(NSNotification*)notification {
548  DCHECK_EQ([notification object], [self window]);
549  DCHECK(browser_->tab_strip_model()->empty());
550  [savedRegularWindow_ close];
551  // We delete statusBubble here because we need to kill off the dependency
552  // that its window has on our window before our window goes away.
553  delete statusBubble_;
554  statusBubble_ = NULL;
555  // We can't actually use |-autorelease| here because there's an embedded
556  // run loop in the |-performClose:| which contains its own autorelease pool.
557  // Instead call it after a zero-length delay, which gets us back to the main
558  // event loop.
559  [self performSelector:@selector(autorelease)
560             withObject:nil
561             afterDelay:0];
562}
563
564- (void)updateDevToolsForContents:(WebContents*)contents {
565  [devToolsController_ updateDevToolsForWebContents:contents
566                                        withProfile:browser_->profile()];
567  [self updateAllowOverlappingViews:[self inPresentationMode]];
568}
569
570// Called when the user wants to close a window or from the shutdown process.
571// The Browser object is in control of whether or not we're allowed to close. It
572// may defer closing due to several states, such as onUnload handlers needing to
573// be fired. If closing is deferred, the Browser will handle the processing
574// required to get us to the closing state and (by watching for all the tabs
575// going away) will again call to close the window when it's finally ready.
576- (BOOL)windowShouldClose:(id)sender {
577  // Disable updates while closing all tabs to avoid flickering.
578  gfx::ScopedNSDisableScreenUpdates disabler;
579  // Give beforeunload handlers the chance to cancel the close before we hide
580  // the window below.
581  if (!browser_->ShouldCloseWindow())
582    return NO;
583
584  // saveWindowPositionIfNeeded: only works if we are the last active
585  // window, but orderOut: ends up activating another window, so we
586  // have to save the window position before we call orderOut:.
587  [self saveWindowPositionIfNeeded];
588
589  bool fast_tab_closing_enabled =
590      CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableFastUnload);
591
592  if (!browser_->tab_strip_model()->empty()) {
593    // Tab strip isn't empty.  Hide the frame (so it appears to have closed
594    // immediately) and close all the tabs, allowing the renderers to shut
595    // down. When the tab strip is empty we'll be called back again.
596    [[self window] orderOut:self];
597    browser_->OnWindowClosing();
598    if (fast_tab_closing_enabled)
599      browser_->tab_strip_model()->CloseAllTabs();
600    return NO;
601  } else if (fast_tab_closing_enabled &&
602        !browser_->HasCompletedUnloadProcessing()) {
603    // The browser needs to finish running unload handlers.
604    // Hide the window (so it appears to have closed immediately), and
605    // the browser will call us back again when it is ready to close.
606    [[self window] orderOut:self];
607    return NO;
608  }
609
610  // the tab strip is empty, it's ok to close the window
611  return YES;
612}
613
614// Called right after our window became the main window.
615- (void)windowDidBecomeMain:(NSNotification*)notification {
616  BrowserList::SetLastActive(browser_.get());
617  [self saveWindowPositionIfNeeded];
618
619  // TODO(dmaclach): Instead of redrawing the whole window, views that care
620  // about the active window state should be registering for notifications.
621  [[self window] setViewsNeedDisplay:YES];
622
623  // TODO(viettrungluu): For some reason, the above doesn't suffice.
624  if ([self isFullscreen])
625    [floatingBarBackingView_ setNeedsDisplay:YES];  // Okay even if nil.
626}
627
628- (void)windowDidResignMain:(NSNotification*)notification {
629  // TODO(dmaclach): Instead of redrawing the whole window, views that care
630  // about the active window state should be registering for notifications.
631  [[self window] setViewsNeedDisplay:YES];
632
633  // TODO(viettrungluu): For some reason, the above doesn't suffice.
634  if ([self isFullscreen])
635    [floatingBarBackingView_ setNeedsDisplay:YES];  // Okay even if nil.
636}
637
638// Called when we are activated (when we gain focus).
639- (void)windowDidBecomeKey:(NSNotification*)notification {
640  // We need to activate the controls (in the "WebView"). To do this, get the
641  // selected WebContents's RenderWidgetHostView and tell it to activate.
642  if (WebContents* contents =
643          browser_->tab_strip_model()->GetActiveWebContents()) {
644
645    DevToolsWindow* devtoolsWindow =
646        DevToolsWindow::GetDockedInstanceForInspectedTab(contents);
647    if (devtoolsWindow) {
648      RenderWidgetHostView* devtoolsView =
649          devtoolsWindow->web_contents()->GetRenderWidgetHostView();
650      if (devtoolsView && devtoolsView->HasFocus()) {
651        devtoolsView->SetActive(true);
652        return;
653      }
654    }
655
656    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
657      rwhv->SetActive(true);
658  }
659}
660
661// Called when we are deactivated (when we lose focus).
662- (void)windowDidResignKey:(NSNotification*)notification {
663  // If our app is still active and we're still the key window, ignore this
664  // message, since it just means that a menu extra (on the "system status bar")
665  // was activated; we'll get another |-windowDidResignKey| if we ever really
666  // lose key window status.
667  if ([NSApp isActive] && ([NSApp keyWindow] == [self window]))
668    return;
669
670  // We need to deactivate the controls (in the "WebView"). To do this, get the
671  // selected WebContents's RenderWidgetHostView and tell it to deactivate.
672  if (WebContents* contents =
673          browser_->tab_strip_model()->GetActiveWebContents()) {
674
675    DevToolsWindow* devtoolsWindow =
676        DevToolsWindow::GetDockedInstanceForInspectedTab(contents);
677    if (devtoolsWindow) {
678      RenderWidgetHostView* devtoolsView =
679          devtoolsWindow->web_contents()->GetRenderWidgetHostView();
680      if (devtoolsView && devtoolsView->HasFocus()) {
681        devtoolsView->SetActive(false);
682        return;
683      }
684    }
685
686    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
687      rwhv->SetActive(false);
688  }
689}
690
691// Called when we have been minimized.
692- (void)windowDidMiniaturize:(NSNotification *)notification {
693  [self saveWindowPositionIfNeeded];
694
695  // Let the selected RenderWidgetHostView know, so that it can tell plugins.
696  if (WebContents* contents =
697          browser_->tab_strip_model()->GetActiveWebContents()) {
698    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
699      rwhv->SetWindowVisibility(false);
700  }
701}
702
703// Called when we have been unminimized.
704- (void)windowDidDeminiaturize:(NSNotification *)notification {
705  // Let the selected RenderWidgetHostView know, so that it can tell plugins.
706  if (WebContents* contents =
707          browser_->tab_strip_model()->GetActiveWebContents()) {
708    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
709      rwhv->SetWindowVisibility(true);
710  }
711}
712
713// Called when the application has been hidden.
714- (void)applicationDidHide:(NSNotification *)notification {
715  // Let the selected RenderWidgetHostView know, so that it can tell plugins
716  // (unless we are minimized, in which case nothing has really changed).
717  if (![[self window] isMiniaturized]) {
718  if (WebContents* contents =
719          browser_->tab_strip_model()->GetActiveWebContents()) {
720      if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
721        rwhv->SetWindowVisibility(false);
722    }
723  }
724}
725
726// Called when the application has been unhidden.
727- (void)applicationDidUnhide:(NSNotification *)notification {
728  // Let the selected RenderWidgetHostView know, so that it can tell plugins
729  // (unless we are minimized, in which case nothing has really changed).
730  if (![[self window] isMiniaturized]) {
731  if (WebContents* contents =
732          browser_->tab_strip_model()->GetActiveWebContents()) {
733      if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
734        rwhv->SetWindowVisibility(true);
735    }
736  }
737}
738
739// Called when the user clicks the zoom button (or selects it from the Window
740// menu) to determine the "standard size" of the window, based on the content
741// and other factors. If the current size/location differs nontrivally from the
742// standard size, Cocoa resizes the window to the standard size, and saves the
743// current size as the "user size". If the current size/location is the same (up
744// to a fudge factor) as the standard size, Cocoa resizes the window to the
745// saved user size. (It is possible for the two to coincide.) In this way, the
746// zoom button acts as a toggle. We determine the standard size based on the
747// content, but enforce a minimum width (calculated using the dimensions of the
748// screen) to ensure websites with small intrinsic width (such as google.com)
749// don't end up with a wee window. Moreover, we always declare the standard
750// width to be at least as big as the current width, i.e., we never want zooming
751// to the standard width to shrink the window. This is consistent with other
752// browsers' behaviour, and is desirable in multi-tab situations. Note, however,
753// that the "toggle" behaviour means that the window can still be "unzoomed" to
754// the user size.
755- (NSRect)windowWillUseStandardFrame:(NSWindow*)window
756                        defaultFrame:(NSRect)frame {
757  // Forget that we grew the window up (if we in fact did).
758  [self resetWindowGrowthState];
759
760  // |frame| already fills the current screen. Never touch y and height since we
761  // always want to fill vertically.
762
763  // If the shift key is down, maximize. Hopefully this should make the
764  // "switchers" happy.
765  if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
766    return frame;
767  }
768
769  // To prevent strange results on portrait displays, the basic minimum zoomed
770  // width is the larger of: 60% of available width, 60% of available height
771  // (bounded by available width).
772  const CGFloat kProportion = 0.6;
773  CGFloat zoomedWidth =
774      std::max(kProportion * NSWidth(frame),
775               std::min(kProportion * NSHeight(frame), NSWidth(frame)));
776
777  WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
778  if (contents) {
779    // If the intrinsic width is bigger, then make it the zoomed width.
780    const int kScrollbarWidth = 16;  // TODO(viettrungluu): ugh.
781    CGFloat intrinsicWidth = static_cast<CGFloat>(
782        contents->GetPreferredSize().width() + kScrollbarWidth);
783    zoomedWidth = std::max(zoomedWidth,
784                           std::min(intrinsicWidth, NSWidth(frame)));
785  }
786
787  // Never shrink from the current size on zoom (see above).
788  NSRect currentFrame = [[self window] frame];
789  zoomedWidth = std::max(zoomedWidth, NSWidth(currentFrame));
790
791  // |frame| determines our maximum extents. We need to set the origin of the
792  // frame -- and only move it left if necessary.
793  if (currentFrame.origin.x + zoomedWidth > NSMaxX(frame))
794    frame.origin.x = NSMaxX(frame) - zoomedWidth;
795  else
796    frame.origin.x = currentFrame.origin.x;
797
798  // Set the width. Don't touch y or height.
799  frame.size.width = zoomedWidth;
800
801  return frame;
802}
803
804- (void)activate {
805  [BrowserWindowUtils activateWindowForController:self];
806}
807
808// Determine whether we should let a window zoom/unzoom to the given |newFrame|.
809// We avoid letting unzoom move windows between screens, because it's really
810// strange and unintuitive.
811- (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame {
812  // Figure out which screen |newFrame| is on.
813  NSScreen* newScreen = nil;
814  CGFloat newScreenOverlapArea = 0.0;
815  for (NSScreen* screen in [NSScreen screens]) {
816    NSRect overlap = NSIntersectionRect(newFrame, [screen frame]);
817    CGFloat overlapArea = NSWidth(overlap) * NSHeight(overlap);
818    if (overlapArea > newScreenOverlapArea) {
819      newScreen = screen;
820      newScreenOverlapArea = overlapArea;
821    }
822  }
823  // If we're somehow not on any screen, allow the zoom.
824  if (!newScreen)
825    return YES;
826
827  // If the new screen is the current screen, we can return a definitive YES.
828  // Note: This check is not strictly necessary, but just short-circuits in the
829  // "no-brainer" case. To test the complicated logic below, comment this out!
830  NSScreen* curScreen = [window screen];
831  if (newScreen == curScreen)
832    return YES;
833
834  // Worry a little: What happens when a window is on two (or more) screens?
835  // E.g., what happens in a 50-50 scenario? Cocoa may reasonably elect to zoom
836  // to the other screen rather than staying on the officially current one. So
837  // we compare overlaps with the current window frame, and see if Cocoa's
838  // choice was reasonable (allowing a small rounding error). This should
839  // hopefully avoid us ever erroneously denying a zoom when a window is on
840  // multiple screens.
841  NSRect curFrame = [window frame];
842  NSRect newScrIntersectCurFr = NSIntersectionRect([newScreen frame], curFrame);
843  NSRect curScrIntersectCurFr = NSIntersectionRect([curScreen frame], curFrame);
844  if (NSWidth(newScrIntersectCurFr) * NSHeight(newScrIntersectCurFr) >=
845      (NSWidth(curScrIntersectCurFr) * NSHeight(curScrIntersectCurFr) - 1.0)) {
846    return YES;
847  }
848
849  // If it wasn't reasonable, return NO.
850  return NO;
851}
852
853// Adjusts the window height by the given amount.
854- (BOOL)adjustWindowHeightBy:(CGFloat)deltaH {
855  // By not adjusting the window height when initializing, we can ensure that
856  // the window opens with the same size that was saved on close.
857  if (initializing_ || [self isFullscreen] || deltaH == 0)
858    return NO;
859
860  NSWindow* window = [self window];
861  NSRect windowFrame = [window frame];
862  NSRect workarea = [[window screen] visibleFrame];
863
864  // If the window is not already fully in the workarea, do not adjust its frame
865  // at all.
866  if (!NSContainsRect(workarea, windowFrame))
867    return NO;
868
869  // Record the position of the top/bottom of the window, so we can easily check
870  // whether we grew the window upwards/downwards.
871  CGFloat oldWindowMaxY = NSMaxY(windowFrame);
872  CGFloat oldWindowMinY = NSMinY(windowFrame);
873
874  // We are "zoomed" if we occupy the full vertical space.
875  bool isZoomed = (windowFrame.origin.y == workarea.origin.y &&
876                   NSHeight(windowFrame) == NSHeight(workarea));
877
878  // If we're shrinking the window....
879  if (deltaH < 0) {
880    bool didChange = false;
881
882    // Don't reset if not currently zoomed since shrinking can take several
883    // steps!
884    if (isZoomed)
885      isShrinkingFromZoomed_ = YES;
886
887    // If we previously grew at the top, shrink as much as allowed at the top
888    // first.
889    if (windowTopGrowth_ > 0) {
890      CGFloat shrinkAtTopBy = MIN(-deltaH, windowTopGrowth_);
891      windowFrame.size.height -= shrinkAtTopBy;  // Shrink the window.
892      deltaH += shrinkAtTopBy;            // Update the amount left to shrink.
893      windowTopGrowth_ -= shrinkAtTopBy;  // Update the growth state.
894      didChange = true;
895    }
896
897    // Similarly for the bottom (not an "else if" since we may have to
898    // simultaneously shrink at both the top and at the bottom). Note that
899    // |deltaH| may no longer be nonzero due to the above.
900    if (deltaH < 0 && windowBottomGrowth_ > 0) {
901      CGFloat shrinkAtBottomBy = MIN(-deltaH, windowBottomGrowth_);
902      windowFrame.origin.y += shrinkAtBottomBy;     // Move the window up.
903      windowFrame.size.height -= shrinkAtBottomBy;  // Shrink the window.
904      deltaH += shrinkAtBottomBy;               // Update the amount left....
905      windowBottomGrowth_ -= shrinkAtBottomBy;  // Update the growth state.
906      didChange = true;
907    }
908
909    // If we're shrinking from zoomed but we didn't change the top or bottom
910    // (since we've reached the limits imposed by |window...Growth_|), then stop
911    // here. Don't reset |isShrinkingFromZoomed_| since we might get called
912    // again for the same shrink.
913    if (isShrinkingFromZoomed_ && !didChange)
914      return NO;
915  } else {
916    isShrinkingFromZoomed_ = NO;
917
918    // Don't bother with anything else.
919    if (isZoomed)
920      return NO;
921  }
922
923  // Shrinking from zoomed is handled above (and is constrained by
924  // |window...Growth_|).
925  if (!isShrinkingFromZoomed_) {
926    // Resize the window down until it hits the bottom of the workarea, then if
927    // needed continue resizing upwards.  Do not resize the window to be taller
928    // than the current workarea.
929    // Resize the window as requested, keeping the top left corner fixed.
930    windowFrame.origin.y -= deltaH;
931    windowFrame.size.height += deltaH;
932
933    // If the bottom left corner is now outside the visible frame, move the
934    // window up to make it fit, but make sure not to move the top left corner
935    // out of the visible frame.
936    if (windowFrame.origin.y < workarea.origin.y) {
937      windowFrame.origin.y = workarea.origin.y;
938      windowFrame.size.height =
939          std::min(NSHeight(windowFrame), NSHeight(workarea));
940    }
941
942    // Record (if applicable) how much we grew the window in either direction.
943    // (N.B.: These only record growth, not shrinkage.)
944    if (NSMaxY(windowFrame) > oldWindowMaxY)
945      windowTopGrowth_ += NSMaxY(windowFrame) - oldWindowMaxY;
946    if (NSMinY(windowFrame) < oldWindowMinY)
947      windowBottomGrowth_ += oldWindowMinY - NSMinY(windowFrame);
948  }
949
950  // Disable subview resizing while resizing the window, or else we will get
951  // unwanted renderer resizes.  The calling code must call layoutSubviews to
952  // make things right again.
953  NSView* contentView = [window contentView];
954  [contentView setAutoresizesSubviews:NO];
955  [window setFrame:windowFrame display:NO];
956  [contentView setAutoresizesSubviews:YES];
957  return YES;
958}
959
960// Main method to resize browser window subviews.  This method should be called
961// when resizing any child of the content view, rather than resizing the views
962// directly.  If the view is already the correct height, does not force a
963// relayout.
964- (void)resizeView:(NSView*)view newHeight:(CGFloat)height {
965  // We should only ever be called for one of the following four views.
966  // |downloadShelfController_| may be nil. If we are asked to size the bookmark
967  // bar directly, its superview must be this controller's content view.
968  DCHECK(view);
969  DCHECK(view == [toolbarController_ view] ||
970         view == [infoBarContainerController_ view] ||
971         view == [downloadShelfController_ view] ||
972         view == [bookmarkBarController_ view]);
973
974  // Change the height of the view and call |-layoutSubViews|. We set the height
975  // here without regard to where the view is on the screen or whether it needs
976  // to "grow up" or "grow down."  The below call to |-layoutSubviews| will
977  // position each view correctly.
978  NSRect frame = [view frame];
979  if (NSHeight(frame) == height)
980    return;
981
982  // Disable screen updates to prevent flickering.
983  if (view == [bookmarkBarController_ view] ||
984      view == [downloadShelfController_ view]) {
985    [[self window] disableScreenUpdatesUntilFlush];
986  }
987
988  // Grow or shrink the window by the amount of the height change.  We adjust
989  // the window height only in two cases:
990  // 1) We are adjusting the height of the bookmark bar and it is currently
991  // animating either open or closed.
992  // 2) We are adjusting the height of the download shelf.
993  //
994  // We do not adjust the window height for bookmark bar changes on the NTP.
995  BOOL shouldAdjustBookmarkHeight =
996      [bookmarkBarController_ isAnimatingBetweenState:BookmarkBar::HIDDEN
997                                             andState:BookmarkBar::SHOW];
998
999  BOOL resizeRectDirty = NO;
1000  if ((shouldAdjustBookmarkHeight && view == [bookmarkBarController_ view]) ||
1001      view == [downloadShelfController_ view]) {
1002    CGFloat deltaH = height - NSHeight(frame);
1003    if ([self adjustWindowHeightBy:deltaH] &&
1004        view == [downloadShelfController_ view]) {
1005      // If the window height didn't change, the download shelf will change the
1006      // size of the contents. If the contents size doesn't change, send it
1007      // an explicit grow box invalidation (else, the resize message does that.)
1008      resizeRectDirty = YES;
1009    }
1010  }
1011
1012  frame.size.height = height;
1013  // TODO(rohitrao): Determine if calling setFrame: twice is bad.
1014  [view setFrame:frame];
1015  [self layoutSubviews];
1016
1017  if (resizeRectDirty) {
1018    // Send new resize rect to foreground tab.
1019    if (content::WebContents* contents =
1020            browser_->tab_strip_model()->GetActiveWebContents()) {
1021      if (content::RenderViewHost* rvh = contents->GetRenderViewHost()) {
1022        rvh->ResizeRectChanged(windowShim_->GetRootWindowResizerRect());
1023      }
1024    }
1025  }
1026}
1027
1028- (void)setAnimationInProgress:(BOOL)inProgress {
1029  [[self tabContentArea] setFastResizeMode:inProgress];
1030}
1031
1032// Update a toggle state for an NSMenuItem if modified.
1033// Take care to ensure |item| looks like a NSMenuItem.
1034// Called by validateUserInterfaceItem:.
1035- (void)updateToggleStateWithTag:(NSInteger)tag forItem:(id)item {
1036  if (![item respondsToSelector:@selector(state)] ||
1037      ![item respondsToSelector:@selector(setState:)])
1038    return;
1039
1040  // On Windows this logic happens in bookmark_bar_view.cc.  On the
1041  // Mac we're a lot more MVC happy so we've moved it into a
1042  // controller.  To be clear, this simply updates the menu item; it
1043  // does not display the bookmark bar itself.
1044  if (tag == IDC_SHOW_BOOKMARK_BAR) {
1045    bool toggled = windowShim_->IsBookmarkBarVisible();
1046    NSInteger oldState = [item state];
1047    NSInteger newState = toggled ? NSOnState : NSOffState;
1048    if (oldState != newState)
1049      [item setState:newState];
1050  }
1051
1052  // Update the checked/Unchecked state of items in the encoding menu.
1053  // On Windows, this logic is part of |EncodingMenuModel| in
1054  // browser/ui/views/toolbar_view.h.
1055  EncodingMenuController encoding_controller;
1056  if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) {
1057    DCHECK(browser_.get());
1058    Profile* profile = browser_->profile();
1059    DCHECK(profile);
1060    WebContents* current_tab =
1061        browser_->tab_strip_model()->GetActiveWebContents();
1062    if (!current_tab)
1063      return;
1064
1065    const std::string encoding = current_tab->GetEncoding();
1066
1067    bool toggled = encoding_controller.IsItemChecked(profile, encoding, tag);
1068    NSInteger oldState = [item state];
1069    NSInteger newState = toggled ? NSOnState : NSOffState;
1070    if (oldState != newState)
1071      [item setState:newState];
1072  }
1073}
1074
1075// Called to validate menu and toolbar items when this window is key. All the
1076// items we care about have been set with the |-commandDispatch:| or
1077// |-commandDispatchUsingKeyModifiers:| actions and a target of FirstResponder
1078// in IB. If it's not one of those, let it continue up the responder chain to be
1079// handled elsewhere. We pull out the tag as the cross-platform constant to
1080// differentiate and dispatch the various commands.
1081// NOTE: we might have to handle state for app-wide menu items,
1082// although we could cheat and directly ask the app controller if our
1083// command_updater doesn't support the command. This may or may not be an issue,
1084// too early to tell.
1085- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
1086  SEL action = [item action];
1087  BOOL enable = NO;
1088  if (action == @selector(commandDispatch:) ||
1089      action == @selector(commandDispatchUsingKeyModifiers:)) {
1090    NSInteger tag = [item tag];
1091    if (chrome::SupportsCommand(browser_.get(), tag)) {
1092      // Generate return value (enabled state)
1093      enable = chrome::IsCommandEnabled(browser_.get(), tag);
1094      switch (tag) {
1095        case IDC_CLOSE_TAB:
1096          // Disable "close tab" if the receiving window is not tabbed.
1097          // We simply check whether the item has a keyboard shortcut set here;
1098          // app_controller_mac.mm actually determines whether the item should
1099          // be enabled.
1100          if ([static_cast<NSObject*>(item) isKindOfClass:[NSMenuItem class]])
1101            enable &= !![[static_cast<NSMenuItem*>(item) keyEquivalent] length];
1102          break;
1103        case IDC_FULLSCREEN: {
1104          if ([static_cast<NSObject*>(item) isKindOfClass:[NSMenuItem class]]) {
1105            NSString* menuTitle = l10n_util::GetNSString(
1106                [self isFullscreen] && ![self inPresentationMode] ?
1107                    IDS_EXIT_FULLSCREEN_MAC :
1108                    IDS_ENTER_FULLSCREEN_MAC);
1109            [static_cast<NSMenuItem*>(item) setTitle:menuTitle];
1110
1111            if (!chrome::mac::SupportsSystemFullscreen())
1112              [static_cast<NSMenuItem*>(item) setHidden:YES];
1113          }
1114          break;
1115        }
1116        case IDC_PRESENTATION_MODE: {
1117          if ([static_cast<NSObject*>(item) isKindOfClass:[NSMenuItem class]]) {
1118            NSString* menuTitle = l10n_util::GetNSString(
1119                [self inPresentationMode] ? IDS_EXIT_PRESENTATION_MAC :
1120                                            IDS_ENTER_PRESENTATION_MAC);
1121            [static_cast<NSMenuItem*>(item) setTitle:menuTitle];
1122          }
1123          break;
1124        }
1125        case IDC_SHOW_SIGNIN: {
1126          Profile* original_profile =
1127              browser_->profile()->GetOriginalProfile();
1128          [BrowserWindowController updateSigninItem:item
1129                                         shouldShow:enable
1130                                     currentProfile:original_profile];
1131          break;
1132        }
1133        default:
1134          // Special handling for the contents of the Text Encoding submenu. On
1135          // Mac OS, instead of enabling/disabling the top-level menu item, we
1136          // enable/disable the submenu's contents (per Apple's HIG).
1137          EncodingMenuController encoding_controller;
1138          if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) {
1139            enable &= chrome::IsCommandEnabled(browser_.get(),
1140                                               IDC_ENCODING_MENU) ? YES : NO;
1141          }
1142      }
1143
1144      // If the item is toggleable, find its toggle state and
1145      // try to update it.  This is a little awkward, but the alternative is
1146      // to check after a commandDispatch, which seems worse.
1147      [self updateToggleStateWithTag:tag forItem:item];
1148    }
1149  }
1150  return enable;
1151}
1152
1153// Called when the user picks a menu or toolbar item when this window is key.
1154// Calls through to the browser object to execute the command. This assumes that
1155// the command is supported and doesn't check, otherwise it would have been
1156// disabled in the UI in validateUserInterfaceItem:.
1157- (void)commandDispatch:(id)sender {
1158  DCHECK(sender);
1159  // Identify the actual BWC to which the command should be dispatched. It might
1160  // belong to a background window, yet this controller gets it because it is
1161  // the foreground window's controller and thus in the responder chain. Some
1162  // senders don't have this problem (for example, menus only operate on the
1163  // foreground window), so this is only an issue for senders that are part of
1164  // windows.
1165  BrowserWindowController* targetController = self;
1166  if ([sender respondsToSelector:@selector(window)])
1167    targetController = [[sender window] windowController];
1168  DCHECK([targetController isKindOfClass:[BrowserWindowController class]]);
1169  DCHECK(targetController->browser_.get());
1170  chrome::ExecuteCommand(targetController->browser_.get(), [sender tag]);
1171}
1172
1173// Same as |-commandDispatch:|, but executes commands using a disposition
1174// determined by the key flags. If the window is in the background and the
1175// command key is down, ignore the command key, but process any other modifiers.
1176- (void)commandDispatchUsingKeyModifiers:(id)sender {
1177  DCHECK(sender);
1178
1179  if (![sender isEnabled]) {
1180    // This code is reachable e.g. if the user mashes the back button, queuing
1181    // up a bunch of events before the button's enabled state is updated:
1182    // http://crbug.com/63254
1183    return;
1184  }
1185
1186  // See comment above for why we do this.
1187  BrowserWindowController* targetController = self;
1188  if ([sender respondsToSelector:@selector(window)])
1189    targetController = [[sender window] windowController];
1190  DCHECK([targetController isKindOfClass:[BrowserWindowController class]]);
1191  NSInteger command = [sender tag];
1192  NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
1193  if ((command == IDC_RELOAD) &&
1194      (modifierFlags & (NSShiftKeyMask | NSControlKeyMask))) {
1195    command = IDC_RELOAD_IGNORING_CACHE;
1196    // Mask off Shift and Control so they don't affect the disposition below.
1197    modifierFlags &= ~(NSShiftKeyMask | NSControlKeyMask);
1198  }
1199  if (![[sender window] isMainWindow]) {
1200    // Remove the command key from the flags, it means "keep the window in
1201    // the background" in this case.
1202    modifierFlags &= ~NSCommandKeyMask;
1203  }
1204  WindowOpenDisposition disposition =
1205      ui::WindowOpenDispositionFromNSEventWithFlags(
1206          [NSApp currentEvent], modifierFlags);
1207  switch (command) {
1208    case IDC_BACK:
1209    case IDC_FORWARD:
1210    case IDC_RELOAD:
1211    case IDC_RELOAD_IGNORING_CACHE:
1212      if (disposition == CURRENT_TAB) {
1213        // Forcibly reset the location bar, since otherwise it won't discard any
1214        // ongoing user edits, since it doesn't realize this is a user-initiated
1215        // action.
1216        [targetController locationBarBridge]->Revert();
1217      }
1218  }
1219  DCHECK(targetController->browser_.get());
1220  chrome::ExecuteCommandWithDisposition(targetController->browser_.get(),
1221                                        command, disposition);
1222}
1223
1224// Called when another part of the internal codebase needs to execute a
1225// command.
1226- (void)executeCommand:(int)command {
1227  chrome::ExecuteCommand(browser_.get(), command);
1228}
1229
1230- (BOOL)handledByExtensionCommand:(NSEvent*)event {
1231  return extension_keybinding_registry_->ProcessKeyEvent(
1232      content::NativeWebKeyboardEvent(event));
1233}
1234
1235// StatusBubble delegate method: tell the status bubble the frame it should
1236// position itself in.
1237- (NSRect)statusBubbleBaseFrame {
1238  NSView* view = [overlayableContentsController_ view];
1239  return [view convertRect:[view bounds] toView:nil];
1240}
1241
1242- (void)updateToolbarWithContents:(WebContents*)tab {
1243  [toolbarController_ updateToolbarWithContents:tab];
1244}
1245
1246- (void)setStarredState:(BOOL)isStarred {
1247  [toolbarController_ setStarredState:isStarred];
1248}
1249
1250- (void)zoomChangedForActiveTab:(BOOL)canShowBubble {
1251  [toolbarController_ zoomChangedForActiveTab:canShowBubble];
1252}
1253
1254// Return the rect, in WebKit coordinates (flipped), of the window's grow box
1255// in the coordinate system of the content area of the currently selected tab.
1256// |windowGrowBox| needs to be in the window's coordinate system.
1257- (NSRect)selectedTabGrowBoxRect {
1258  NSWindow* window = [self window];
1259  if (![window respondsToSelector:@selector(_growBoxRect)])
1260    return NSZeroRect;
1261
1262  // Before we return a rect, we need to convert it from window coordinates
1263  // to tab content area coordinates and flip the coordinate system.
1264  NSRect growBoxRect =
1265      [[self tabContentArea] convertRect:[window _growBoxRect] fromView:nil];
1266  growBoxRect.origin.y =
1267      NSHeight([[self tabContentArea] frame]) - NSMaxY(growBoxRect);
1268  return growBoxRect;
1269}
1270
1271// Accept tabs from a BrowserWindowController with the same Profile.
1272- (BOOL)canReceiveFrom:(TabWindowController*)source {
1273  if (![source isKindOfClass:[BrowserWindowController class]]) {
1274    return NO;
1275  }
1276
1277  BrowserWindowController* realSource =
1278      static_cast<BrowserWindowController*>(source);
1279  if (browser_->profile() != realSource->browser_->profile()) {
1280    return NO;
1281  }
1282
1283  // Can't drag a tab from a normal browser to a pop-up
1284  if (browser_->type() != realSource->browser_->type()) {
1285    return NO;
1286  }
1287
1288  return YES;
1289}
1290
1291// Move a given tab view to the location of the current placeholder. If there is
1292// no placeholder, it will go at the end. |controller| is the window controller
1293// of a tab being dropped from a different window. It will be nil if the drag is
1294// within the window, otherwise the tab is removed from that window before being
1295// placed into this one. The implementation will call |-removePlaceholder| since
1296// the drag is now complete.  This also calls |-layoutTabs| internally so
1297// clients do not need to call it again.
1298- (void)moveTabView:(NSView*)view
1299     fromController:(TabWindowController*)dragController {
1300  if (dragController) {
1301    // Moving between windows. Figure out the WebContents to drop into our tab
1302    // model from the source window's model.
1303    BOOL isBrowser =
1304        [dragController isKindOfClass:[BrowserWindowController class]];
1305    DCHECK(isBrowser);
1306    if (!isBrowser) return;
1307    BrowserWindowController* dragBWC = (BrowserWindowController*)dragController;
1308    int index = [dragBWC->tabStripController_ modelIndexForTabView:view];
1309    WebContents* contents =
1310        dragBWC->browser_->tab_strip_model()->GetWebContentsAt(index);
1311    // The tab contents may have gone away if given a window.close() while it
1312    // is being dragged. If so, bail, we've got nothing to drop.
1313    if (!contents)
1314      return;
1315
1316    // Convert |view|'s frame (which starts in the source tab strip's coordinate
1317    // system) to the coordinate system of the destination tab strip. This needs
1318    // to be done before being detached so the window transforms can be
1319    // performed.
1320    NSRect destinationFrame = [view frame];
1321    NSPoint tabOrigin = destinationFrame.origin;
1322    tabOrigin = [[dragController tabStripView] convertPoint:tabOrigin
1323                                                     toView:nil];
1324    tabOrigin = [[view window] convertBaseToScreen:tabOrigin];
1325    tabOrigin = [[self window] convertScreenToBase:tabOrigin];
1326    tabOrigin = [[self tabStripView] convertPoint:tabOrigin fromView:nil];
1327    destinationFrame.origin = tabOrigin;
1328
1329    // Before the tab is detached from its originating tab strip, store the
1330    // pinned state so that it can be maintained between the windows.
1331    bool isPinned = dragBWC->browser_->tab_strip_model()->IsTabPinned(index);
1332
1333    // Now that we have enough information about the tab, we can remove it from
1334    // the dragging window. We need to do this *before* we add it to the new
1335    // window as this will remove the WebContents' delegate.
1336    [dragController detachTabView:view];
1337
1338    // Deposit it into our model at the appropriate location (it already knows
1339    // where it should go from tracking the drag). Doing this sets the tab's
1340    // delegate to be the Browser.
1341    [tabStripController_ dropWebContents:contents
1342                               withFrame:destinationFrame
1343                             asPinnedTab:isPinned];
1344  } else {
1345    // Moving within a window.
1346    int index = [tabStripController_ modelIndexForTabView:view];
1347    [tabStripController_ moveTabFromIndex:index];
1348  }
1349
1350  // Remove the placeholder since the drag is now complete.
1351  [self removePlaceholder];
1352}
1353
1354// Tells the tab strip to forget about this tab in preparation for it being
1355// put into a different tab strip, such as during a drop on another window.
1356- (void)detachTabView:(NSView*)view {
1357  int index = [tabStripController_ modelIndexForTabView:view];
1358  browser_->tab_strip_model()->DetachWebContentsAt(index);
1359}
1360
1361- (NSView*)activeTabView {
1362  return [tabStripController_ activeTabView];
1363}
1364
1365- (void)setIsLoading:(BOOL)isLoading force:(BOOL)force {
1366  [toolbarController_ setIsLoading:isLoading force:force];
1367}
1368
1369// Make the location bar the first responder, if possible.
1370- (void)focusLocationBar:(BOOL)selectAll {
1371  [toolbarController_ focusLocationBar:selectAll];
1372}
1373
1374- (void)focusTabContents {
1375  [[self window] makeFirstResponder:[tabStripController_ activeTabView]];
1376}
1377
1378- (void)layoutTabs {
1379  [tabStripController_ layoutTabs];
1380}
1381
1382- (TabWindowController*)detachTabToNewWindow:(TabView*)tabView {
1383  // Disable screen updates so that this appears as a single visual change.
1384  gfx::ScopedNSDisableScreenUpdates disabler;
1385
1386  // Fetch the tab contents for the tab being dragged.
1387  int index = [tabStripController_ modelIndexForTabView:tabView];
1388  WebContents* contents = browser_->tab_strip_model()->GetWebContentsAt(index);
1389
1390  // Set the window size. Need to do this before we detach the tab so it's
1391  // still in the window. We have to flip the coordinates as that's what
1392  // is expected by the Browser code.
1393  NSWindow* sourceWindow = [tabView window];
1394  NSRect windowRect = [sourceWindow frame];
1395  NSScreen* screen = [sourceWindow screen];
1396  windowRect.origin.y = NSHeight([screen frame]) - NSMaxY(windowRect);
1397  gfx::Rect browserRect(windowRect.origin.x, windowRect.origin.y,
1398                        NSWidth(windowRect), NSHeight(windowRect));
1399
1400  NSRect sourceTabRect = [tabView frame];
1401  NSView* tabStrip = [self tabStripView];
1402
1403  // Pushes tabView's frame back inside the tabstrip.
1404  NSSize tabOverflow =
1405      [self overflowFrom:[tabStrip convertRect:sourceTabRect toView:nil]
1406                      to:[tabStrip frame]];
1407  NSRect tabRect = NSOffsetRect(sourceTabRect,
1408                                -tabOverflow.width, -tabOverflow.height);
1409
1410  // Before detaching the tab, store the pinned state.
1411  bool isPinned = browser_->tab_strip_model()->IsTabPinned(index);
1412
1413  // Detach it from the source window, which just updates the model without
1414  // deleting the tab contents. This needs to come before creating the new
1415  // Browser because it clears the WebContents' delegate, which gets hooked
1416  // up during creation of the new window.
1417  browser_->tab_strip_model()->DetachWebContentsAt(index);
1418
1419  // Create the new window with a single tab in its model, the one being
1420  // dragged.
1421  DockInfo dockInfo;
1422  TabStripModelDelegate::NewStripContents item;
1423  item.web_contents = contents;
1424  item.add_types = TabStripModel::ADD_ACTIVE |
1425                   (isPinned ? TabStripModel::ADD_PINNED
1426                             : TabStripModel::ADD_NONE);
1427  std::vector<TabStripModelDelegate::NewStripContents> contentses;
1428  contentses.push_back(item);
1429  Browser* newBrowser = browser_->tab_strip_model()->delegate()->
1430      CreateNewStripWithContents(contentses, browserRect, dockInfo, false);
1431
1432  // Get the new controller by asking the new window for its delegate.
1433  BrowserWindowController* controller =
1434      reinterpret_cast<BrowserWindowController*>(
1435          [newBrowser->window()->GetNativeWindow() delegate]);
1436  DCHECK(controller && [controller isKindOfClass:[TabWindowController class]]);
1437
1438  // Force the added tab to the right size (remove stretching.)
1439  tabRect.size.height = [TabStripController defaultTabHeight];
1440
1441  // And make sure we use the correct frame in the new view.
1442  [[controller tabStripController] setFrameOfActiveTab:tabRect];
1443  return controller;
1444}
1445
1446- (void)insertPlaceholderForTab:(TabView*)tab
1447                          frame:(NSRect)frame {
1448  [super insertPlaceholderForTab:tab frame:frame];
1449  [tabStripController_ insertPlaceholderForTab:tab frame:frame];
1450}
1451
1452- (void)removePlaceholder {
1453  [super removePlaceholder];
1454  [tabStripController_ insertPlaceholderForTab:nil frame:NSZeroRect];
1455}
1456
1457- (BOOL)isDragSessionActive {
1458  // The tab can be dragged within the existing tab strip or detached
1459  // into its own window (then the overlay window will be present).
1460  return [[self tabStripController] isDragSessionActive] ||
1461         [self overlayWindow] != nil;
1462}
1463
1464- (BOOL)tabDraggingAllowed {
1465  return [tabStripController_ tabDraggingAllowed];
1466}
1467
1468- (BOOL)tabTearingAllowed {
1469  return ![self isFullscreen];
1470}
1471
1472- (BOOL)windowMovementAllowed {
1473  return ![self isFullscreen];
1474}
1475
1476- (BOOL)isTabFullyVisible:(TabView*)tab {
1477  return [tabStripController_ isTabFullyVisible:tab];
1478}
1479
1480- (void)showNewTabButton:(BOOL)show {
1481  [tabStripController_ showNewTabButton:show];
1482}
1483
1484- (BOOL)shouldShowAvatar {
1485  if (![self hasTabStrip])
1486    return NO;
1487  if (browser_->profile()->IsOffTheRecord())
1488    return YES;
1489
1490  ProfileInfoCache& cache =
1491      g_browser_process->profile_manager()->GetProfileInfoCache();
1492  if (cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath()) ==
1493      std::string::npos) {
1494    return NO;
1495  }
1496
1497  return AvatarMenu::ShouldShowAvatarMenu();
1498}
1499
1500- (BOOL)isBookmarkBarVisible {
1501  return [bookmarkBarController_ isVisible];
1502}
1503
1504- (BOOL)isBookmarkBarAnimating {
1505  return [bookmarkBarController_ isAnimationRunning];
1506}
1507
1508- (BookmarkBarController*)bookmarkBarController {
1509  return bookmarkBarController_;
1510}
1511
1512- (DevToolsController*)devToolsController {
1513  return devToolsController_;
1514}
1515
1516- (BOOL)isDownloadShelfVisible {
1517  return downloadShelfController_ != nil &&
1518      [downloadShelfController_ isVisible];
1519}
1520
1521- (DownloadShelfController*)downloadShelf {
1522  if (!downloadShelfController_.get()) {
1523    downloadShelfController_.reset([[DownloadShelfController alloc]
1524        initWithBrowser:browser_.get() resizeDelegate:self]);
1525    [[[self window] contentView] addSubview:[downloadShelfController_ view]];
1526  }
1527  return downloadShelfController_;
1528}
1529
1530- (void)addFindBar:(FindBarCocoaController*)findBarCocoaController {
1531  // Shouldn't call addFindBar twice.
1532  DCHECK(!findBarCocoaController_.get());
1533
1534  // Create a controller for the findbar.
1535  findBarCocoaController_.reset([findBarCocoaController retain]);
1536  [self layoutSubviews];
1537  [self updateSubviewZOrder:[self inPresentationMode]];
1538}
1539
1540- (NSWindow*)createFullscreenWindow {
1541  return [[[FullscreenWindow alloc] initForScreen:[[self window] screen]]
1542           autorelease];
1543}
1544
1545- (NSInteger)numberOfTabs {
1546  // count() includes pinned tabs.
1547  return browser_->tab_strip_model()->count();
1548}
1549
1550- (BOOL)hasLiveTabs {
1551  return !browser_->tab_strip_model()->empty();
1552}
1553
1554- (NSString*)activeTabTitle {
1555  WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
1556  return base::SysUTF16ToNSString(contents->GetTitle());
1557}
1558
1559- (NSRect)regularWindowFrame {
1560  return [self isFullscreen] ? savedRegularWindowFrame_ :
1561                               [[self window] frame];
1562}
1563
1564// (Override of |TabWindowController| method.)
1565- (BOOL)hasTabStrip {
1566  return [self supportsWindowFeature:Browser::FEATURE_TABSTRIP];
1567}
1568
1569- (BOOL)isTabDraggable:(NSView*)tabView {
1570  // TODO(avi, thakis): ConstrainedWindowSheetController has no api to move
1571  // tabsheets between windows. Until then, we have to prevent having to move a
1572  // tabsheet between windows, e.g. no tearing off of tabs.
1573  int index = [tabStripController_ modelIndexForTabView:tabView];
1574  WebContents* contents = browser_->tab_strip_model()->GetWebContentsAt(index);
1575  if (!contents)
1576    return NO;
1577  return !WebContentsModalDialogManager::FromWebContents(contents)->
1578      IsDialogActive();
1579}
1580
1581// TabStripControllerDelegate protocol.
1582- (void)onActivateTabWithContents:(WebContents*)contents {
1583  // Update various elements that are interested in knowing the current
1584  // WebContents.
1585
1586  // Update all the UI bits.
1587  windowShim_->UpdateTitleBar();
1588
1589  [devToolsController_ updateDevToolsForWebContents:contents
1590                                        withProfile:browser_->profile()];
1591
1592  // Update the bookmark bar.
1593  // Must do it after devtools updates, otherwise bookmark bar might
1594  // call resizeView -> layoutSubviews and cause unnecessary relayout.
1595  // TODO(viettrungluu): perhaps update to not terminate running animations (if
1596  // applicable)?
1597  windowShim_->BookmarkBarStateChanged(
1598      BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
1599
1600  [infoBarContainerController_ changeWebContents:contents];
1601
1602  [self updateAllowOverlappingViews:[self inPresentationMode]];
1603}
1604
1605- (void)onTabChanged:(TabStripModelObserver::TabChangeType)change
1606        withContents:(WebContents*)contents {
1607  // Update titles if this is the currently selected tab and if it isn't just
1608  // the loading state which changed.
1609  if (change != TabStripModelObserver::LOADING_ONLY)
1610    windowShim_->UpdateTitleBar();
1611
1612  // Update the bookmark bar if this is the currently selected tab and if it
1613  // isn't just the title which changed. This for transitions between the NTP
1614  // (showing its floating bookmark bar) and normal web pages (showing no
1615  // bookmark bar).
1616  // TODO(viettrungluu): perhaps update to not terminate running animations?
1617  if (change != TabStripModelObserver::TITLE_NOT_LOADING) {
1618    windowShim_->BookmarkBarStateChanged(
1619        BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
1620  }
1621}
1622
1623- (void)onTabDetachedWithContents:(WebContents*)contents {
1624  [infoBarContainerController_ tabDetachedWithContents:contents];
1625}
1626
1627- (void)userChangedTheme {
1628  NSView* contentView = [[self window] contentView];
1629  [[contentView superview] cr_recursivelySetNeedsDisplay:YES];
1630}
1631
1632- (ui::ThemeProvider*)themeProvider {
1633  return ThemeServiceFactory::GetForProfile(browser_->profile());
1634}
1635
1636- (ThemedWindowStyle)themedWindowStyle {
1637  ThemedWindowStyle style = 0;
1638  if (browser_->profile()->IsOffTheRecord())
1639    style |= THEMED_INCOGNITO;
1640
1641  if (browser_->is_devtools())
1642    style |= THEMED_DEVTOOLS;
1643  if (browser_->is_type_popup())
1644    style |= THEMED_POPUP;
1645
1646  return style;
1647}
1648
1649- (NSPoint)themeImagePositionForAlignment:(ThemeImageAlignment)alignment {
1650  NSView* windowChromeView = [[[self window] contentView] superview];
1651  NSView* tabStripView = nil;
1652  if ([self hasTabStrip])
1653    tabStripView = [self tabStripView];
1654  return [BrowserWindowUtils themeImagePositionFor:windowChromeView
1655                                      withTabStrip:tabStripView
1656                                         alignment:alignment];
1657}
1658
1659- (NSPoint)bookmarkBubblePoint {
1660  return [toolbarController_ bookmarkBubblePoint];
1661}
1662
1663// Show the bookmark bubble (e.g. user just clicked on the STAR).
1664- (void)showBookmarkBubbleForURL:(const GURL&)url
1665               alreadyBookmarked:(BOOL)alreadyMarked {
1666  if (!bookmarkBubbleController_) {
1667    BookmarkModel* model =
1668        BookmarkModelFactory::GetForProfile(browser_->profile());
1669    const BookmarkNode* node = model->GetMostRecentlyAddedNodeForURL(url);
1670    bookmarkBubbleController_ =
1671        [[BookmarkBubbleController alloc] initWithParentWindow:[self window]
1672                                                         model:model
1673                                                          node:node
1674                                             alreadyBookmarked:alreadyMarked];
1675    [bookmarkBubbleController_ showWindow:self];
1676    NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1677    [center addObserver:self
1678               selector:@selector(bookmarkBubbleWindowWillClose:)
1679                   name:NSWindowWillCloseNotification
1680                 object:[bookmarkBubbleController_ window]];
1681  }
1682}
1683
1684// Nil out the weak bookmark bubble controller reference.
1685- (void)bookmarkBubbleWindowWillClose:(NSNotification*)notification {
1686  DCHECK_EQ([notification object], [bookmarkBubbleController_ window]);
1687
1688  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1689  [center removeObserver:self
1690                    name:NSWindowWillCloseNotification
1691                  object:[bookmarkBubbleController_ window]];
1692  bookmarkBubbleController_ = nil;
1693}
1694
1695// Handle the editBookmarkNode: action sent from bookmark bubble controllers.
1696- (void)editBookmarkNode:(id)sender {
1697  BOOL responds = [sender respondsToSelector:@selector(node)];
1698  DCHECK(responds);
1699  if (responds) {
1700    const BookmarkNode* node = [sender node];
1701    if (node)
1702      BookmarkEditor::Show([self window], browser_->profile(),
1703          BookmarkEditor::EditDetails::EditNode(node),
1704          BookmarkEditor::SHOW_TREE);
1705  }
1706}
1707
1708// If the browser is in incognito mode or has multi-profiles, install the image
1709// view to decorate the window at the upper right. Use the same base y
1710// coordinate as the tab strip.
1711- (void)installAvatar {
1712  // Install the image into the badge view. Hide it for now; positioning and
1713  // sizing will be done by the layout code. The AvatarButton will choose which
1714  // image to display based on the browser.
1715  avatarButtonController_.reset(
1716      [[AvatarButtonController alloc] initWithBrowser:browser_.get()]);
1717
1718  NSView* view = [avatarButtonController_ view];
1719  [view setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
1720  [view setHidden:![self shouldShowAvatar]];
1721
1722  // Install the view.
1723  [[[[self window] contentView] superview] addSubview:view];
1724}
1725
1726// Called when we get a three-finger swipe.
1727- (void)swipeWithEvent:(NSEvent*)event {
1728  CGFloat deltaX = [event deltaX];
1729  CGFloat deltaY = [event deltaY];
1730
1731  // Map forwards and backwards to history; left is positive, right is negative.
1732  unsigned int command = 0;
1733  if (deltaX > 0.5) {
1734    command = IDC_BACK;
1735  } else if (deltaX < -0.5) {
1736    command = IDC_FORWARD;
1737  } else if (deltaY > 0.5) {
1738    // TODO(pinkerton): figure out page-up, http://crbug.com/16305
1739  } else if (deltaY < -0.5) {
1740    // TODO(pinkerton): figure out page-down, http://crbug.com/16305
1741    chrome::ExecuteCommand(browser_.get(), IDC_TABPOSE);
1742  }
1743
1744  // Ensure the command is valid first (ExecuteCommand() won't do that) and
1745  // then make it so.
1746  if (chrome::IsCommandEnabled(browser_.get(), command)) {
1747    chrome::ExecuteCommandWithDisposition(
1748        browser_.get(),
1749        command,
1750        ui::WindowOpenDispositionFromNSEvent(event));
1751  }
1752}
1753
1754// Called repeatedly during a pinch gesture, with incremental change values.
1755- (void)magnifyWithEvent:(NSEvent*)event {
1756  // The deltaZ difference necessary to trigger a zoom action. Derived from
1757  // experimentation to find a value that feels reasonable.
1758  const float kZoomStepValue = 0.6;
1759
1760  // Find the (absolute) thresholds on either side of the current zoom factor,
1761  // then convert those to actual numbers to trigger a zoom in or out.
1762  // This logic deliberately makes the range around the starting zoom value for
1763  // the gesture twice as large as the other ranges (i.e., the notches are at
1764  // ..., -3*step, -2*step, -step, step, 2*step, 3*step, ... but not at 0)
1765  // so that it's easier to get back to your starting point than it is to
1766  // overshoot.
1767  float nextStep = (abs(currentZoomStepDelta_) + 1) * kZoomStepValue;
1768  float backStep = abs(currentZoomStepDelta_) * kZoomStepValue;
1769  float zoomInThreshold = (currentZoomStepDelta_ >= 0) ? nextStep : -backStep;
1770  float zoomOutThreshold = (currentZoomStepDelta_ <= 0) ? -nextStep : backStep;
1771
1772  unsigned int command = 0;
1773  totalMagnifyGestureAmount_ += [event magnification];
1774  if (totalMagnifyGestureAmount_ > zoomInThreshold) {
1775    command = IDC_ZOOM_PLUS;
1776  } else if (totalMagnifyGestureAmount_ < zoomOutThreshold) {
1777    command = IDC_ZOOM_MINUS;
1778  }
1779
1780  if (command && chrome::IsCommandEnabled(browser_.get(), command)) {
1781    currentZoomStepDelta_ += (command == IDC_ZOOM_PLUS) ? 1 : -1;
1782    chrome::ExecuteCommandWithDisposition(
1783        browser_.get(),
1784        command,
1785        ui::WindowOpenDispositionFromNSEvent(event));
1786  }
1787}
1788
1789// Delegate method called when window is resized.
1790- (void)windowDidResize:(NSNotification*)notification {
1791  [self saveWindowPositionIfNeeded];
1792
1793  // Resize (and possibly move) the status bubble. Note that we may get called
1794  // when the status bubble does not exist.
1795  if (statusBubble_) {
1796    statusBubble_->UpdateSizeAndPosition();
1797  }
1798
1799  // Let the selected RenderWidgetHostView know, so that it can tell plugins.
1800  if (WebContents* contents =
1801          browser_->tab_strip_model()->GetActiveWebContents()) {
1802    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
1803      rwhv->WindowFrameChanged();
1804  }
1805
1806  // The FindBar needs to know its own position to properly detect overlaps
1807  // with find results. The position changes whenever the window is resized,
1808  // and |layoutSubviews| computes the FindBar's position.
1809  // TODO: calling |layoutSubviews| here is a waste, find a better way to
1810  // do this.
1811  if ([findBarCocoaController_ isFindBarVisible])
1812    [self layoutSubviews];
1813}
1814
1815// Handle the openLearnMoreAboutCrashLink: action from SadTabController when
1816// "Learn more" link in "Aw snap" page (i.e. crash page or sad tab) is
1817// clicked. Decoupling the action from its target makes unit testing possible.
1818- (void)openLearnMoreAboutCrashLink:(id)sender {
1819  if ([sender isKindOfClass:[SadTabController class]]) {
1820    SadTabController* sad_tab = static_cast<SadTabController*>(sender);
1821    WebContents* web_contents = [sad_tab webContents];
1822    if (web_contents) {
1823      OpenURLParams params(
1824          GURL(chrome::kCrashReasonURL), Referrer(), CURRENT_TAB,
1825          content::PAGE_TRANSITION_LINK, false);
1826      web_contents->OpenURL(params);
1827    }
1828  }
1829}
1830
1831// Delegate method called when window did move. (See below for why we don't use
1832// |-windowWillMove:|, which is called less frequently than |-windowDidMove|
1833// instead.)
1834- (void)windowDidMove:(NSNotification*)notification {
1835  [self saveWindowPositionIfNeeded];
1836
1837  NSWindow* window = [self window];
1838  NSRect windowFrame = [window frame];
1839  NSRect workarea = [[window screen] visibleFrame];
1840
1841  // We reset the window growth state whenever the window is moved out of the
1842  // work area or away (up or down) from the bottom or top of the work area.
1843  // Unfortunately, Cocoa sends |-windowWillMove:| too frequently (including
1844  // when clicking on the title bar to activate), and of course
1845  // |-windowWillMove| is called too early for us to apply our heuristic. (The
1846  // heuristic we use for detecting window movement is that if |windowTopGrowth_
1847  // > 0|, then we should be at the bottom of the work area -- if we're not,
1848  // we've moved. Similarly for the other side.)
1849  if (!NSContainsRect(workarea, windowFrame) ||
1850      (windowTopGrowth_ > 0 && NSMinY(windowFrame) != NSMinY(workarea)) ||
1851      (windowBottomGrowth_ > 0 && NSMaxY(windowFrame) != NSMaxY(workarea)))
1852    [self resetWindowGrowthState];
1853
1854  // Let the selected RenderWidgetHostView know, so that it can tell plugins.
1855  if (WebContents* contents =
1856          browser_->tab_strip_model()->GetActiveWebContents()) {
1857    if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
1858      rwhv->WindowFrameChanged();
1859  }
1860}
1861
1862// Delegate method called when window will be resized; not called for
1863// |-setFrame:display:|.
1864- (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize {
1865  [self resetWindowGrowthState];
1866  return frameSize;
1867}
1868
1869// Delegate method: see |NSWindowDelegate| protocol.
1870- (id)windowWillReturnFieldEditor:(NSWindow*)sender toObject:(id)obj {
1871  // Ask the toolbar controller if it wants to return a custom field editor
1872  // for the specific object.
1873  return [toolbarController_ customFieldEditorForObject:obj];
1874}
1875
1876// (Needed for |BookmarkBarControllerDelegate| protocol.)
1877- (void)bookmarkBar:(BookmarkBarController*)controller
1878 didChangeFromState:(BookmarkBar::State)oldState
1879            toState:(BookmarkBar::State)newState {
1880  [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
1881  [self adjustToolbarAndBookmarkBarForCompression:
1882          [controller getDesiredToolbarHeightCompression]];
1883}
1884
1885// (Needed for |BookmarkBarControllerDelegate| protocol.)
1886- (void)bookmarkBar:(BookmarkBarController*)controller
1887willAnimateFromState:(BookmarkBar::State)oldState
1888            toState:(BookmarkBar::State)newState {
1889  [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
1890  [self adjustToolbarAndBookmarkBarForCompression:
1891          [controller getDesiredToolbarHeightCompression]];
1892}
1893
1894// (Private/TestingAPI)
1895- (void)resetWindowGrowthState {
1896  windowTopGrowth_ = 0;
1897  windowBottomGrowth_ = 0;
1898  isShrinkingFromZoomed_ = NO;
1899}
1900
1901- (NSSize)overflowFrom:(NSRect)source
1902                    to:(NSRect)target {
1903  // If |source|'s boundary is outside of |target|'s, set its distance
1904  // to |x|.  Note that |source| can overflow to both side, but we
1905  // have nothing to do for such case.
1906  CGFloat x = 0;
1907  if (NSMaxX(target) < NSMaxX(source)) // |source| overflows to right
1908    x = NSMaxX(source) - NSMaxX(target);
1909  else if (NSMinX(source) < NSMinX(target)) // |source| overflows to left
1910    x = NSMinX(source) - NSMinX(target);
1911
1912  // Same as |x| above.
1913  CGFloat y = 0;
1914  if (NSMaxY(target) < NSMaxY(source))
1915    y = NSMaxY(source) - NSMaxY(target);
1916  else if (NSMinY(source) < NSMinY(target))
1917    y = NSMinY(source) - NSMinY(target);
1918
1919  return NSMakeSize(x, y);
1920}
1921
1922// (Private/TestingAPI)
1923- (FullscreenExitBubbleController*)fullscreenExitBubbleController {
1924  return fullscreenExitBubbleController_.get();
1925}
1926
1927- (NSRect)omniboxPopupAnchorRect {
1928  // Start with toolbar rect.
1929  NSView* toolbarView = [toolbarController_ view];
1930  NSRect anchorRect = [toolbarView frame];
1931
1932  // Adjust to account for height and possible bookmark bar. Compress by 1
1933  // to account for the separator.
1934  anchorRect.origin.y =
1935      NSMaxY(anchorRect) - [toolbarController_ desiredHeightForCompression:1];
1936
1937  // Shift to window base coordinates.
1938  return [[toolbarView superview] convertRect:anchorRect toView:nil];
1939}
1940
1941- (void)layoutInfoBars {
1942  [self layoutSubviews];
1943}
1944
1945- (void)sheetDidEnd:(NSWindow*)sheet
1946         returnCode:(NSInteger)code
1947            context:(void*)context {
1948  [sheet orderOut:self];
1949}
1950
1951- (void)onFindBarVisibilityChanged {
1952  [self updateAllowOverlappingViews:[self inPresentationMode]];
1953}
1954
1955- (void)onOverlappedViewShown {
1956  ++overlappedViewCount_;
1957  [self updateAllowOverlappingViews:[self inPresentationMode]];
1958}
1959
1960- (void)onOverlappedViewHidden {
1961  --overlappedViewCount_;
1962  [self updateAllowOverlappingViews:[self inPresentationMode]];
1963}
1964
1965@end  // @implementation BrowserWindowController
1966
1967
1968@implementation BrowserWindowController(Fullscreen)
1969
1970- (void)handleLionToggleFullscreen {
1971  DCHECK(base::mac::IsOSLionOrLater());
1972  chrome::ExecuteCommand(browser_.get(), IDC_FULLSCREEN);
1973}
1974
1975// On Lion, this method is called by either the Lion fullscreen button or the
1976// "Enter Full Screen" menu item.  On Snow Leopard, this function is never
1977// called by the UI directly, but it provides the implementation for
1978// |-setPresentationMode:|.
1979- (void)setFullscreen:(BOOL)fullscreen {
1980  if (fullscreen == [self isFullscreen])
1981    return;
1982
1983  if (!chrome::IsCommandEnabled(browser_.get(), IDC_FULLSCREEN))
1984    return;
1985
1986  if (chrome::mac::SupportsSystemFullscreen() && !fullscreenWindow_) {
1987    enteredPresentationModeFromFullscreen_ = YES;
1988    if ([[self window] isKindOfClass:[FramedBrowserWindow class]])
1989      [static_cast<FramedBrowserWindow*>([self window]) toggleSystemFullScreen];
1990  } else {
1991    if (fullscreen)
1992      [self enterFullscreenForSnowLeopard];
1993    else
1994      [self exitFullscreenForSnowLeopard];
1995  }
1996}
1997
1998- (void)enterFullscreen {
1999  [self setFullscreen:YES];
2000}
2001
2002- (void)exitFullscreen {
2003  [self setFullscreen:NO];
2004}
2005
2006- (void)updateFullscreenExitBubbleURL:(const GURL&)url
2007                           bubbleType:(FullscreenExitBubbleType)bubbleType {
2008  fullscreenUrl_ = url;
2009  fullscreenBubbleType_ = bubbleType;
2010  [self layoutSubviews];
2011  [self showFullscreenExitBubbleIfNecessary];
2012}
2013
2014- (BOOL)isFullscreen {
2015  return (fullscreenWindow_.get() != nil) ||
2016         ([[self window] styleMask] & NSFullScreenWindowMask) ||
2017         enteringFullscreen_;
2018}
2019
2020// On Lion, this function is called by either the presentation mode toggle
2021// button or the "Enter Presentation Mode" menu item.  In the latter case, this
2022// function also triggers the Lion machinery to enter fullscreen mode as well as
2023// set presentation mode.  On Snow Leopard, this function is called by the
2024// "Enter Presentation Mode" menu item, and triggering presentation mode always
2025// moves the user into fullscreen mode.
2026- (void)setPresentationMode:(BOOL)presentationMode
2027                        url:(const GURL&)url
2028                 bubbleType:(FullscreenExitBubbleType)bubbleType {
2029  fullscreenUrl_ = url;
2030  fullscreenBubbleType_ = bubbleType;
2031
2032  // Presentation mode on systems without fullscreen support maps directly to
2033  // fullscreen mode.
2034  if (!chrome::mac::SupportsSystemFullscreen()) {
2035    [self setFullscreen:presentationMode];
2036    return;
2037  }
2038
2039  if (presentationMode) {
2040    BOOL fullscreen = [self isFullscreen];
2041    enteredPresentationModeFromFullscreen_ = fullscreen;
2042    enteringPresentationMode_ = YES;
2043
2044    if (fullscreen) {
2045      // If already in fullscreen mode, just toggle the presentation mode
2046      // setting.  Go through an elaborate dance to force the overlay to show,
2047      // then animate out once the mouse moves away.  This helps draw attention
2048      // to the fact that the UI is in an overlay.  Focus the tab contents
2049      // because the omnibox is the most likely source of bar visibility locks,
2050      // and taking focus away from the omnibox releases its lock.
2051      [self lockBarVisibilityForOwner:self withAnimation:NO delay:NO];
2052      [self focusTabContents];
2053      [self setPresentationModeInternal:YES forceDropdown:YES];
2054      [self releaseBarVisibilityForOwner:self withAnimation:YES delay:YES];
2055      // Since -windowDidEnterFullScreen: won't be called in the
2056      // fullscreen --> presentation mode case, manually show the exit bubble
2057      // and notify the change happened with WindowFullscreenStateChanged().
2058      [self showFullscreenExitBubbleIfNecessary];
2059      browser_->WindowFullscreenStateChanged();
2060    } else {
2061      // If not in fullscreen mode, trigger the Lion fullscreen mode machinery.
2062      // Presentation mode will automatically be enabled in
2063      // |-windowWillEnterFullScreen:|.
2064      NSWindow* window = [self window];
2065      if ([window isKindOfClass:[FramedBrowserWindow class]])
2066        [static_cast<FramedBrowserWindow*>(window) toggleSystemFullScreen];
2067    }
2068  } else {
2069    // Exiting presentation mode does not exit system fullscreen; it merely
2070    // switches from presentation mode to normal fullscreen.
2071    [self setPresentationModeInternal:NO forceDropdown:NO];
2072
2073    // Since -windowDidExitFullScreen: won't be called in the
2074    // presentation mode --> normal fullscreen case, manually show the exit
2075    // bubble and notify the change happened with
2076    // WindowFullscreenStateChanged().
2077    [self showFullscreenExitBubbleIfNecessary];
2078    browser_->WindowFullscreenStateChanged();
2079  }
2080}
2081
2082- (void)enterPresentationModeForURL:(const GURL&)url
2083                         bubbleType:(FullscreenExitBubbleType)bubbleType {
2084  [self setPresentationMode:YES url:url bubbleType:bubbleType];
2085}
2086
2087- (void)exitPresentationMode {
2088  // url: and bubbleType: are ignored when leaving presentation mode.
2089  [self setPresentationMode:NO url:GURL() bubbleType:FEB_TYPE_NONE];
2090}
2091
2092- (void)enterFullscreenForURL:(const GURL&)url
2093                   bubbleType:(FullscreenExitBubbleType)bubbleType {
2094  // This method may only be called in simplified fullscreen mode.
2095  const CommandLine* command_line = CommandLine::ForCurrentProcess();
2096  DCHECK(command_line->HasSwitch(switches::kEnableSimplifiedFullscreen));
2097
2098  [self enterFullscreenForSnowLeopard];
2099  [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType];
2100}
2101
2102- (BOOL)inPresentationMode {
2103  return presentationModeController_.get() &&
2104      [presentationModeController_ inPresentationMode];
2105}
2106
2107- (void)resizeFullscreenWindow {
2108  DCHECK([self isFullscreen]);
2109  if (![self isFullscreen])
2110    return;
2111
2112  NSWindow* window = [self window];
2113  [window setFrame:[[window screen] frame] display:YES];
2114  [self layoutSubviews];
2115}
2116
2117- (CGFloat)floatingBarShownFraction {
2118  return floatingBarShownFraction_;
2119}
2120
2121- (void)setFloatingBarShownFraction:(CGFloat)fraction {
2122  floatingBarShownFraction_ = fraction;
2123  [self layoutSubviews];
2124}
2125
2126- (BOOL)isBarVisibilityLockedForOwner:(id)owner {
2127  DCHECK(owner);
2128  DCHECK(barVisibilityLocks_);
2129  return [barVisibilityLocks_ containsObject:owner];
2130}
2131
2132- (void)lockBarVisibilityForOwner:(id)owner
2133                    withAnimation:(BOOL)animate
2134                            delay:(BOOL)delay {
2135  if (![self isBarVisibilityLockedForOwner:owner]) {
2136    [barVisibilityLocks_ addObject:owner];
2137
2138    // If enabled, show the overlay if necessary (and if in presentation mode).
2139    if (barVisibilityUpdatesEnabled_) {
2140      [presentationModeController_ ensureOverlayShownWithAnimation:animate
2141                                                             delay:delay];
2142    }
2143  }
2144}
2145
2146- (void)releaseBarVisibilityForOwner:(id)owner
2147                       withAnimation:(BOOL)animate
2148                               delay:(BOOL)delay {
2149  if ([self isBarVisibilityLockedForOwner:owner]) {
2150    [barVisibilityLocks_ removeObject:owner];
2151
2152    // If enabled, hide the overlay if necessary (and if in presentation mode).
2153    if (barVisibilityUpdatesEnabled_ &&
2154        ![barVisibilityLocks_ count]) {
2155      [presentationModeController_ ensureOverlayHiddenWithAnimation:animate
2156                                                              delay:delay];
2157    }
2158  }
2159}
2160
2161- (BOOL)floatingBarHasFocus {
2162  NSResponder* focused = [[self window] firstResponder];
2163  return [focused isKindOfClass:[AutocompleteTextFieldEditor class]];
2164}
2165
2166- (void)tabposeWillClose:(NSNotification*)notif {
2167  // Re-show the container after Tabpose closes.
2168  [[infoBarContainerController_ view] setHidden:NO];
2169}
2170
2171- (void)openTabpose {
2172  NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
2173  BOOL slomo = (modifierFlags & NSShiftKeyMask) != 0;
2174
2175  // Cover info bars, inspector window, and detached bookmark bar on NTP.
2176  // Do not cover download shelf.
2177  NSRect activeArea = [[self tabContentArea] frame];
2178  // Take out the anti-spoof height so that Tabpose doesn't draw on top of the
2179  // browser chrome.
2180  activeArea.size.height +=
2181      NSHeight([[infoBarContainerController_ view] frame]) -
2182          [infoBarContainerController_ overlappingTipHeight];
2183  if ([self isBookmarkBarVisible] && [self placeBookmarkBarBelowInfoBar]) {
2184    NSView* bookmarkBarView = [bookmarkBarController_ view];
2185    activeArea.size.height += NSHeight([bookmarkBarView frame]);
2186  }
2187
2188  // Hide the infobar container so that the anti-spoof bulge doesn't show when
2189  // Tabpose is open.
2190  [[infoBarContainerController_ view] setHidden:YES];
2191
2192  TabposeWindow* window =
2193      [TabposeWindow openTabposeFor:[self window]
2194                               rect:activeArea
2195                              slomo:slomo
2196                      tabStripModel:browser_->tab_strip_model()];
2197
2198  // When the Tabpose window closes, the infobar container needs to be made
2199  // visible again.
2200  NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
2201  [center addObserver:self
2202             selector:@selector(tabposeWillClose:)
2203                 name:NSWindowWillCloseNotification
2204               object:window];
2205}
2206
2207@end  // @implementation BrowserWindowController(Fullscreen)
2208
2209
2210@implementation BrowserWindowController(WindowType)
2211
2212- (BOOL)supportsWindowFeature:(int)feature {
2213  return browser_->SupportsWindowFeature(
2214      static_cast<Browser::WindowFeature>(feature));
2215}
2216
2217- (BOOL)hasTitleBar {
2218  return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR];
2219}
2220
2221- (BOOL)hasToolbar {
2222  return [self supportsWindowFeature:Browser::FEATURE_TOOLBAR];
2223}
2224
2225- (BOOL)hasLocationBar {
2226  return [self supportsWindowFeature:Browser::FEATURE_LOCATIONBAR];
2227}
2228
2229- (BOOL)supportsBookmarkBar {
2230  return [self supportsWindowFeature:Browser::FEATURE_BOOKMARKBAR];
2231}
2232
2233- (BOOL)isTabbedWindow {
2234  return browser_->is_type_tabbed();
2235}
2236
2237@end  // @implementation BrowserWindowController(WindowType)
2238