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