Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / browser_window_controller.mm
blob6533f6ecf9c9cf755185480be48707e9092aa31e
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.
5 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
7 #include <cmath>
8 #include <numeric>
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"
86 using l10n_util::GetStringUTF16;
87 using l10n_util::GetNSStringWithFixup;
88 using l10n_util::GetNSStringFWithFixup;
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.
101 // Private methods are defined and implemented separately in
102 // browser_window_controller_private.{h,mm}.
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.
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
123 // Notes on self-inflicted (not user-inflicted) window resizing and moving:
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.
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.
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).
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.
164 using content::OpenURLParams;
165 using content::Referrer;
166 using content::RenderWidgetHostView;
167 using content::WebContents;
168 using web_modal::WebContentsModalDialogManager;
170 @interface NSWindow (NSPrivateApis)
171 // Note: These functions are private, use -[NSObject respondsToSelector:]
172 // before calling them.
174 - (void)setBottomCornerRounded:(BOOL)rounded;
176 - (NSRect)_growBoxRect;
178 @end
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
184 enum {
185   NSWindowCollectionBehaviorFullScreenPrimary = 1 << 7,
186   NSWindowCollectionBehaviorFullScreenAuxiliary = 1 << 8
189 enum {
190   NSFullScreenWindowMask = 1 << 14
193 @interface NSWindow (LionSDKDeclarations)
194 - (void)setRestorable:(BOOL)flag;
195 @end
197 #endif  // MAC_OS_X_VERSION_10_7
199 @implementation BrowserWindowController
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;
211 + (BrowserWindowController*)browserWindowControllerForView:(NSView*)view {
212   NSWindow* window = [view window];
213   return [BrowserWindowController browserWindowControllerForWindow:window];
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);
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   }
237   base::string16 label = signin_ui_util::GetSigninMenuLabel(profile);
238   [signinMenuItem setTitle:l10n_util::FixUpWindowsStyleLabel(label)];
239   [signinMenuItem setHidden:!showSigninMenuItem];
240   [followingSeparator setHidden:!showSigninMenuItem];
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];
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));
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     }
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];
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]);
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];
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];
293     // Get the windows to swish in on Lion.
294     if ([window respondsToSelector:@selector(setAnimationBehavior:)])
295       [window setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow];
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];
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     }
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);
326     // Puts the incognito badge on the window frame, if necessary.
327     [self installAvatar];
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]];
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]];
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];
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]];
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]];
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];
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);
383     // Allow bar visibility to be changed.
384     [self enableBarVisibilityUpdates];
386     // Force a relayout of all the various bars.
387     [self layoutSubviews];
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];
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     }
423     // Create the bridge for the status bubble.
424     statusBubble_ = new StatusBubbleMac([self window], self);
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];
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];
443     extension_keybinding_registry_.reset(
444         new ExtensionKeybindingRegistryCocoa(browser_->profile(),
445             [self window],
446             extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
447             windowShim_.get()));
449     // We are done initializing now.
450     initializing_ = NO;
451   }
452   return self;
455 - (void)dealloc {
456   browser_->tab_strip_model()->CloseAllTabs();
457   [downloadShelfController_ exiting];
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();
465   // Under certain testing configurations we may not actually own the browser.
466   if (ownsBrowser_ == NO)
467     ignore_result(browser_.release());
469   [[NSNotificationCenter defaultCenter] removeObserver:self];
471   [super dealloc];
474 - (gfx::Rect)enforceMinWindowSize:(gfx::Rect)bounds {
475   gfx::Rect checkedBounds = bounds;
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);
483   return checkedBounds;
486 - (BrowserWindow*)browserWindow {
487   return windowShim_.get();
490 - (ToolbarController*)toolbarController {
491   return toolbarController_.get();
494 - (TabStripController*)tabStripController {
495   return tabStripController_.get();
498 - (FindBarCocoaController*)findBarCocoaController {
499   return findBarCocoaController_.get();
502 - (InfoBarContainerController*)infoBarContainerController {
503   return infoBarContainerController_.get();
506 - (StatusBubbleMac*)statusBubble {
507   return statusBubble_;
510 - (LocationBarViewMac*)locationBarBridge {
511   return [toolbarController_ locationBarBridge];
514 - (NSView*)floatingBarBackingView {
515   return floatingBarBackingView_;
518 - (OverlayableContentsController*)overlayableContentsController {
519   return overlayableContentsController_;
522 - (Profile*)profile {
523   return browser_->profile();
526 - (AvatarButtonController*)avatarButtonController {
527   return avatarButtonController_.get();
530 - (void)destroyBrowser {
531   [NSApp removeWindowsItem:[self window]];
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];
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];
564 - (void)updateDevToolsForContents:(WebContents*)contents {
565   [devToolsController_ updateDevToolsForWebContents:contents
566                                         withProfile:browser_->profile()];
567   [self updateAllowOverlappingViews:[self inPresentationMode]];
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;
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];
589   bool fast_tab_closing_enabled =
590       CommandLine::ForCurrentProcess()->HasSwitch(switches::kEnableFastUnload);
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   }
610   // the tab strip is empty, it's ok to close the window
611   return YES;
614 // Called right after our window became the main window.
615 - (void)windowDidBecomeMain:(NSNotification*)notification {
616   BrowserList::SetLastActive(browser_.get());
617   [self saveWindowPositionIfNeeded];
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];
623   // TODO(viettrungluu): For some reason, the above doesn't suffice.
624   if ([self isFullscreen])
625     [floatingBarBackingView_ setNeedsDisplay:YES];  // Okay even if nil.
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];
633   // TODO(viettrungluu): For some reason, the above doesn't suffice.
634   if ([self isFullscreen])
635     [floatingBarBackingView_ setNeedsDisplay:YES];  // Okay even if nil.
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()) {
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     }
656     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
657       rwhv->SetActive(true);
658   }
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;
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()) {
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     }
686     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
687       rwhv->SetActive(false);
688   }
691 // Called when we have been minimized.
692 - (void)windowDidMiniaturize:(NSNotification *)notification {
693   [self saveWindowPositionIfNeeded];
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   }
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   }
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   }
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   }
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];
760   // |frame| already fills the current screen. Never touch y and height since we
761   // always want to fill vertically.
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   }
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)));
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   }
787   // Never shrink from the current size on zoom (see above).
788   NSRect currentFrame = [[self window] frame];
789   zoomedWidth = std::max(zoomedWidth, NSWidth(currentFrame));
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;
798   // Set the width. Don't touch y or height.
799   frame.size.width = zoomedWidth;
801   return frame;
804 - (void)activate {
805   [BrowserWindowUtils activateWindowForController:self];
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;
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;
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   }
849   // If it wasn't reasonable, return NO.
850   return NO;
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;
860   NSWindow* window = [self window];
861   NSRect windowFrame = [window frame];
862   NSRect workarea = [[window screen] visibleFrame];
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;
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);
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));
878   // If we're shrinking the window....
879   if (deltaH < 0) {
880     bool didChange = false;
882     // Don't reset if not currently zoomed since shrinking can take several
883     // steps!
884     if (isZoomed)
885       isShrinkingFromZoomed_ = YES;
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     }
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     }
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;
918     // Don't bother with anything else.
919     if (isZoomed)
920       return NO;
921   }
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;
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     }
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   }
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;
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]);
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;
982   // Disable screen updates to prevent flickering.
983   if (view == [bookmarkBarController_ view] ||
984       view == [downloadShelfController_ view]) {
985     [[self window] disableScreenUpdatesUntilFlush];
986   }
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];
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   }
1012   frame.size.height = height;
1013   // TODO(rohitrao): Determine if calling setFrame: twice is bad.
1014   [view setFrame:frame];
1015   [self layoutSubviews];
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   }
1028 - (void)setAnimationInProgress:(BOOL)inProgress {
1029   [[self tabContentArea] setFastResizeMode:inProgress];
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;
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   }
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;
1065     const std::string encoding = current_tab->GetEncoding();
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   }
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];
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       }
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;
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]);
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);
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   }
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);
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);
1230 - (BOOL)handledByExtensionCommand:(NSEvent*)event {
1231   return extension_keybinding_registry_->ProcessKeyEvent(
1232       content::NativeWebKeyboardEvent(event));
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];
1242 - (void)updateToolbarWithContents:(WebContents*)tab {
1243   [toolbarController_ updateToolbarWithContents:tab];
1246 - (void)setStarredState:(BOOL)isStarred {
1247   [toolbarController_ setStarredState:isStarred];
1250 - (void)zoomChangedForActiveTab:(BOOL)canShowBubble {
1251   [toolbarController_ zoomChangedForActiveTab:canShowBubble];
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;
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;
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   }
1277   BrowserWindowController* realSource =
1278       static_cast<BrowserWindowController*>(source);
1279   if (browser_->profile() != realSource->browser_->profile()) {
1280     return NO;
1281   }
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   }
1288   return YES;
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;
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;
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);
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];
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   }
1350   // Remove the placeholder since the drag is now complete.
1351   [self removePlaceholder];
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);
1361 - (NSView*)activeTabView {
1362   return [tabStripController_ activeTabView];
1365 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force {
1366   [toolbarController_ setIsLoading:isLoading force:force];
1369 // Make the location bar the first responder, if possible.
1370 - (void)focusLocationBar:(BOOL)selectAll {
1371   [toolbarController_ focusLocationBar:selectAll];
1374 - (void)focusTabContents {
1375   [[self window] makeFirstResponder:[tabStripController_ activeTabView]];
1378 - (void)layoutTabs {
1379   [tabStripController_ layoutTabs];
1382 - (TabWindowController*)detachTabToNewWindow:(TabView*)tabView {
1383   // Disable screen updates so that this appears as a single visual change.
1384   gfx::ScopedNSDisableScreenUpdates disabler;
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);
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));
1400   NSRect sourceTabRect = [tabView frame];
1401   NSView* tabStrip = [self tabStripView];
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);
1410   // Before detaching the tab, store the pinned state.
1411   bool isPinned = browser_->tab_strip_model()->IsTabPinned(index);
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);
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);
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]]);
1438   // Force the added tab to the right size (remove stretching.)
1439   tabRect.size.height = [TabStripController defaultTabHeight];
1441   // And make sure we use the correct frame in the new view.
1442   [[controller tabStripController] setFrameOfActiveTab:tabRect];
1443   return controller;
1446 - (void)insertPlaceholderForTab:(TabView*)tab
1447                           frame:(NSRect)frame {
1448   [super insertPlaceholderForTab:tab frame:frame];
1449   [tabStripController_ insertPlaceholderForTab:tab frame:frame];
1452 - (void)removePlaceholder {
1453   [super removePlaceholder];
1454   [tabStripController_ insertPlaceholderForTab:nil frame:NSZeroRect];
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;
1464 - (BOOL)tabDraggingAllowed {
1465   return [tabStripController_ tabDraggingAllowed];
1468 - (BOOL)tabTearingAllowed {
1469   return ![self isFullscreen];
1472 - (BOOL)windowMovementAllowed {
1473   return ![self isFullscreen];
1476 - (BOOL)isTabFullyVisible:(TabView*)tab {
1477   return [tabStripController_ isTabFullyVisible:tab];
1480 - (void)showNewTabButton:(BOOL)show {
1481   [tabStripController_ showNewTabButton:show];
1484 - (BOOL)shouldShowAvatar {
1485   if (![self hasTabStrip])
1486     return NO;
1487   if (browser_->profile()->IsOffTheRecord())
1488     return YES;
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   }
1497   return AvatarMenu::ShouldShowAvatarMenu();
1500 - (BOOL)isBookmarkBarVisible {
1501   return [bookmarkBarController_ isVisible];
1504 - (BOOL)isBookmarkBarAnimating {
1505   return [bookmarkBarController_ isAnimationRunning];
1508 - (BookmarkBarController*)bookmarkBarController {
1509   return bookmarkBarController_;
1512 - (DevToolsController*)devToolsController {
1513   return devToolsController_;
1516 - (BOOL)isDownloadShelfVisible {
1517   return downloadShelfController_ != nil &&
1518       [downloadShelfController_ isVisible];
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_;
1530 - (void)addFindBar:(FindBarCocoaController*)findBarCocoaController {
1531   // Shouldn't call addFindBar twice.
1532   DCHECK(!findBarCocoaController_.get());
1534   // Create a controller for the findbar.
1535   findBarCocoaController_.reset([findBarCocoaController retain]);
1536   [self layoutSubviews];
1537   [self updateSubviewZOrder:[self inPresentationMode]];
1540 - (NSWindow*)createFullscreenWindow {
1541   return [[[FullscreenWindow alloc] initForScreen:[[self window] screen]]
1542            autorelease];
1545 - (NSInteger)numberOfTabs {
1546   // count() includes pinned tabs.
1547   return browser_->tab_strip_model()->count();
1550 - (BOOL)hasLiveTabs {
1551   return !browser_->tab_strip_model()->empty();
1554 - (NSString*)activeTabTitle {
1555   WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
1556   return base::SysUTF16ToNSString(contents->GetTitle());
1559 - (NSRect)regularWindowFrame {
1560   return [self isFullscreen] ? savedRegularWindowFrame_ :
1561                                [[self window] frame];
1564 // (Override of |TabWindowController| method.)
1565 - (BOOL)hasTabStrip {
1566   return [self supportsWindowFeature:Browser::FEATURE_TABSTRIP];
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();
1581 // TabStripControllerDelegate protocol.
1582 - (void)onActivateTabWithContents:(WebContents*)contents {
1583   // Update various elements that are interested in knowing the current
1584   // WebContents.
1586   // Update all the UI bits.
1587   windowShim_->UpdateTitleBar();
1589   // Update the bookmark bar.
1590   // TODO(viettrungluu): perhaps update to not terminate running animations (if
1591   // applicable)?
1592   windowShim_->BookmarkBarStateChanged(
1593       BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
1595   [infoBarContainerController_ changeWebContents:contents];
1597   // Must do this after bookmark and infobar updates to avoid
1598   // unnecesary resize in contents.
1599   [devToolsController_ updateDevToolsForWebContents:contents
1600                                         withProfile:browser_->profile()];
1602   [self updateAllowOverlappingViews:[self inPresentationMode]];
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();
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   }
1623 - (void)onTabDetachedWithContents:(WebContents*)contents {
1624   [infoBarContainerController_ tabDetachedWithContents:contents];
1627 - (void)userChangedTheme {
1628   NSView* contentView = [[self window] contentView];
1629   [[contentView superview] cr_recursivelySetNeedsDisplay:YES];
1632 - (ui::ThemeProvider*)themeProvider {
1633   return ThemeServiceFactory::GetForProfile(browser_->profile());
1636 - (ThemedWindowStyle)themedWindowStyle {
1637   ThemedWindowStyle style = 0;
1638   if (browser_->profile()->IsOffTheRecord())
1639     style |= THEMED_INCOGNITO;
1641   if (browser_->is_devtools())
1642     style |= THEMED_DEVTOOLS;
1643   if (browser_->is_type_popup())
1644     style |= THEMED_POPUP;
1646   return style;
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];
1659 - (NSPoint)bookmarkBubblePoint {
1660   return [toolbarController_ bookmarkBubblePoint];
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   }
1684 // Nil out the weak bookmark bubble controller reference.
1685 - (void)bookmarkBubbleWindowWillClose:(NSNotification*)notification {
1686   DCHECK_EQ([notification object], [bookmarkBubbleController_ window]);
1688   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1689   [center removeObserver:self
1690                     name:NSWindowWillCloseNotification
1691                   object:[bookmarkBubbleController_ window]];
1692   bookmarkBubbleController_ = nil;
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   }
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()]);
1718   NSView* view = [avatarButtonController_ view];
1719   [view setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
1720   [view setHidden:![self shouldShowAvatar]];
1722   // Install the view.
1723   [[[[self window] contentView] superview] addSubview:view];
1726 // Called when we get a three-finger swipe.
1727 - (void)swipeWithEvent:(NSEvent*)event {
1728   CGFloat deltaX = [event deltaX];
1729   CGFloat deltaY = [event deltaY];
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   }
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   }
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;
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;
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   }
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   }
1789 // Delegate method called when window is resized.
1790 - (void)windowDidResize:(NSNotification*)notification {
1791   [self saveWindowPositionIfNeeded];
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   }
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   }
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];
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   }
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];
1837   NSWindow* window = [self window];
1838   NSRect windowFrame = [window frame];
1839   NSRect workarea = [[window screen] visibleFrame];
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];
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   }
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;
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];
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]];
1885 // (Needed for |BookmarkBarControllerDelegate| protocol.)
1886 - (void)bookmarkBar:(BookmarkBarController*)controller
1887 willAnimateFromState:(BookmarkBar::State)oldState
1888             toState:(BookmarkBar::State)newState {
1889   [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
1890   [self adjustToolbarAndBookmarkBarForCompression:
1891           [controller getDesiredToolbarHeightCompression]];
1894 // (Private/TestingAPI)
1895 - (void)resetWindowGrowthState {
1896   windowTopGrowth_ = 0;
1897   windowBottomGrowth_ = 0;
1898   isShrinkingFromZoomed_ = NO;
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);
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);
1919   return NSMakeSize(x, y);
1922 // (Private/TestingAPI)
1923 - (FullscreenExitBubbleController*)fullscreenExitBubbleController {
1924   return fullscreenExitBubbleController_.get();
1927 - (NSRect)omniboxPopupAnchorRect {
1928   // Start with toolbar rect.
1929   NSView* toolbarView = [toolbarController_ view];
1930   NSRect anchorRect = [toolbarView frame];
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];
1937   // Shift to window base coordinates.
1938   return [[toolbarView superview] convertRect:anchorRect toView:nil];
1941 - (void)layoutInfoBars {
1942   [self layoutSubviews];
1945 - (void)sheetDidEnd:(NSWindow*)sheet
1946          returnCode:(NSInteger)code
1947             context:(void*)context {
1948   [sheet orderOut:self];
1951 - (void)onFindBarVisibilityChanged {
1952   [self updateAllowOverlappingViews:[self inPresentationMode]];
1955 - (void)onOverlappedViewShown {
1956   ++overlappedViewCount_;
1957   [self updateAllowOverlappingViews:[self inPresentationMode]];
1960 - (void)onOverlappedViewHidden {
1961   --overlappedViewCount_;
1962   [self updateAllowOverlappingViews:[self inPresentationMode]];
1965 @end  // @implementation BrowserWindowController
1968 @implementation BrowserWindowController(Fullscreen)
1970 - (void)handleLionToggleFullscreen {
1971   DCHECK(base::mac::IsOSLionOrLater());
1972   chrome::ExecuteCommand(browser_.get(), IDC_FULLSCREEN);
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;
1983   if (!chrome::IsCommandEnabled(browser_.get(), IDC_FULLSCREEN))
1984     return;
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   }
1998 - (void)enterFullscreen {
1999   [self setFullscreen:YES];
2002 - (void)exitFullscreen {
2003   [self setFullscreen:NO];
2006 - (void)updateFullscreenExitBubbleURL:(const GURL&)url
2007                            bubbleType:(FullscreenExitBubbleType)bubbleType {
2008   fullscreenUrl_ = url;
2009   fullscreenBubbleType_ = bubbleType;
2010   [self layoutSubviews];
2011   [self showFullscreenExitBubbleIfNecessary];
2014 - (BOOL)isFullscreen {
2015   return (fullscreenWindow_.get() != nil) ||
2016          ([[self window] styleMask] & NSFullScreenWindowMask) ||
2017          enteringFullscreen_;
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;
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   }
2039   if (presentationMode) {
2040     BOOL fullscreen = [self isFullscreen];
2041     enteredPresentationModeFromFullscreen_ = fullscreen;
2042     enteringPresentationMode_ = YES;
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];
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   }
2082 - (void)enterPresentationModeForURL:(const GURL&)url
2083                          bubbleType:(FullscreenExitBubbleType)bubbleType {
2084   [self setPresentationMode:YES url:url bubbleType:bubbleType];
2087 - (void)exitPresentationMode {
2088   // url: and bubbleType: are ignored when leaving presentation mode.
2089   [self setPresentationMode:NO url:GURL() bubbleType:FEB_TYPE_NONE];
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));
2098   [self enterFullscreenForSnowLeopard];
2099   [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType];
2102 - (BOOL)inPresentationMode {
2103   return presentationModeController_.get() &&
2104       [presentationModeController_ inPresentationMode];
2107 - (void)resizeFullscreenWindow {
2108   DCHECK([self isFullscreen]);
2109   if (![self isFullscreen])
2110     return;
2112   NSWindow* window = [self window];
2113   [window setFrame:[[window screen] frame] display:YES];
2114   [self layoutSubviews];
2117 - (CGFloat)floatingBarShownFraction {
2118   return floatingBarShownFraction_;
2121 - (void)setFloatingBarShownFraction:(CGFloat)fraction {
2122   floatingBarShownFraction_ = fraction;
2123   [self layoutSubviews];
2126 - (BOOL)isBarVisibilityLockedForOwner:(id)owner {
2127   DCHECK(owner);
2128   DCHECK(barVisibilityLocks_);
2129   return [barVisibilityLocks_ containsObject:owner];
2132 - (void)lockBarVisibilityForOwner:(id)owner
2133                     withAnimation:(BOOL)animate
2134                             delay:(BOOL)delay {
2135   if (![self isBarVisibilityLockedForOwner:owner]) {
2136     [barVisibilityLocks_ addObject:owner];
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   }
2146 - (void)releaseBarVisibilityForOwner:(id)owner
2147                        withAnimation:(BOOL)animate
2148                                delay:(BOOL)delay {
2149   if ([self isBarVisibilityLockedForOwner:owner]) {
2150     [barVisibilityLocks_ removeObject:owner];
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   }
2161 - (BOOL)floatingBarHasFocus {
2162   NSResponder* focused = [[self window] firstResponder];
2163   return [focused isKindOfClass:[AutocompleteTextFieldEditor class]];
2166 - (void)tabposeWillClose:(NSNotification*)notif {
2167   // Re-show the container after Tabpose closes.
2168   [[infoBarContainerController_ view] setHidden:NO];
2171 - (void)openTabpose {
2172   NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
2173   BOOL slomo = (modifierFlags & NSShiftKeyMask) != 0;
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   }
2188   // Hide the infobar container so that the anti-spoof bulge doesn't show when
2189   // Tabpose is open.
2190   [[infoBarContainerController_ view] setHidden:YES];
2192   TabposeWindow* window =
2193       [TabposeWindow openTabposeFor:[self window]
2194                                rect:activeArea
2195                               slomo:slomo
2196                       tabStripModel:browser_->tab_strip_model()];
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];
2207 @end  // @implementation BrowserWindowController(Fullscreen)
2210 @implementation BrowserWindowController(WindowType)
2212 - (BOOL)supportsWindowFeature:(int)feature {
2213   return browser_->SupportsWindowFeature(
2214       static_cast<Browser::WindowFeature>(feature));
2217 - (BOOL)hasTitleBar {
2218   return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR];
2221 - (BOOL)hasToolbar {
2222   return [self supportsWindowFeature:Browser::FEATURE_TOOLBAR];
2225 - (BOOL)hasLocationBar {
2226   return [self supportsWindowFeature:Browser::FEATURE_LOCATIONBAR];
2229 - (BOOL)supportsBookmarkBar {
2230   return [self supportsWindowFeature:Browser::FEATURE_BOOKMARKBAR];
2233 - (BOOL)isTabbedWindow {
2234   return browser_->is_type_tabbed();
2237 @end  // @implementation BrowserWindowController(WindowType)