Add new certificateProvider extension API.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / browser_window_controller.mm
blobc4205301f799e87d52ba2ceacf4e8451191d9afc
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/browser_window_controller.h"
7 #include <cmath>
8 #include <numeric>
10 #include "base/command_line.h"
11 #include "base/mac/bundle_locations.h"
12 #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/bookmarks/managed_bookmark_service_factory.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/devtools/devtools_window.h"
22 #include "chrome/browser/extensions/extension_commands_global_registry.h"
23 #include "chrome/browser/fullscreen.h"
24 #include "chrome/browser/profiles/avatar_menu.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/profiles/profile_info_cache.h"
27 #include "chrome/browser/profiles/profile_manager.h"
28 #include "chrome/browser/profiles/profiles_state.h"
29 #include "chrome/browser/themes/theme_service.h"
30 #include "chrome/browser/themes/theme_service_factory.h"
31 #include "chrome/browser/translate/chrome_translate_client.h"
32 #include "chrome/browser/ui/bookmarks/bookmark_editor.h"
33 #include "chrome/browser/ui/browser.h"
34 #include "chrome/browser/ui/browser_command_controller.h"
35 #include "chrome/browser/ui/browser_commands.h"
36 #include "chrome/browser/ui/browser_dialogs.h"
37 #include "chrome/browser/ui/browser_instant_controller.h"
38 #include "chrome/browser/ui/browser_list.h"
39 #include "chrome/browser/ui/browser_window_state.h"
40 #import "chrome/browser/ui/cocoa/background_gradient_view.h"
41 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
42 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bubble_observer_cocoa.h"
43 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_editor_controller.h"
44 #import "chrome/browser/ui/cocoa/browser_window_cocoa.h"
45 #import "chrome/browser/ui/cocoa/browser_window_command_handler.h"
46 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
47 #import "chrome/browser/ui/cocoa/browser_window_layout.h"
48 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
49 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
50 #import "chrome/browser/ui/cocoa/download/download_shelf_controller.h"
51 #include "chrome/browser/ui/cocoa/extensions/extension_keybinding_registry_cocoa.h"
52 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
53 #import "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h"
54 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
55 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
56 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
57 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
58 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
59 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h"
60 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
61 #import "chrome/browser/ui/cocoa/profiles/avatar_base_controller.h"
62 #import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h"
63 #import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h"
64 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
65 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
66 #import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h"
67 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
68 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
69 #import "chrome/browser/ui/cocoa/tabs/tab_view.h"
70 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
71 #import "chrome/browser/ui/cocoa/translate/translate_bubble_controller.h"
72 #include "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h"
73 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
74 #include "chrome/browser/ui/location_bar/location_bar.h"
75 #include "chrome/browser/ui/tabs/tab_strip_model.h"
76 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
77 #include "chrome/browser/ui/translate/translate_bubble_model_impl.h"
78 #include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
79 #include "chrome/browser/ui/window_sizer/window_sizer.h"
80 #include "chrome/common/chrome_switches.h"
81 #include "chrome/common/extensions/command.h"
82 #include "chrome/common/url_constants.h"
83 #include "chrome/grit/generated_resources.h"
84 #include "chrome/grit/locale_settings.h"
85 #include "components/bookmarks/browser/bookmark_model.h"
86 #include "components/bookmarks/managed/managed_bookmark_service.h"
87 #include "components/signin/core/common/profile_management_switches.h"
88 #include "components/translate/core/browser/translate_manager.h"
89 #include "components/translate/core/browser/translate_ui_delegate.h"
90 #include "components/web_modal/web_contents_modal_dialog_manager.h"
91 #include "content/public/browser/render_view_host.h"
92 #include "content/public/browser/render_widget_host_view.h"
93 #include "content/public/browser/web_contents.h"
94 #import "ui/base/cocoa/cocoa_base_utils.h"
95 #import "ui/base/cocoa/nsview_additions.h"
96 #include "ui/base/l10n/l10n_util.h"
97 #include "ui/base/l10n/l10n_util_mac.h"
98 #import "ui/gfx/mac/coordinate_conversion.h"
99 #include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h"
101 using bookmarks::BookmarkModel;
102 using bookmarks::BookmarkNode;
103 using l10n_util::GetStringUTF16;
104 using l10n_util::GetNSStringWithFixup;
105 using l10n_util::GetNSStringFWithFixup;
107 // ORGANIZATION: This is a big file. It is (in principle) organized as follows
108 // (in order):
109 // 1. Interfaces. Very short, one-time-use classes may include an implementation
110 //    immediately after their interface.
111 // 2. The general implementation section, ordered as follows:
112 //      i. Public methods and overrides.
113 //     ii. Overrides/implementations of undocumented methods.
114 //    iii. Delegate methods for various protocols, formal and informal, to which
115 //        |BrowserWindowController| conforms.
116 // 3. (temporary) Implementation sections for various categories.
118 // Private methods are defined and implemented separately in
119 // browser_window_controller_private.{h,mm}.
121 // Not all of the above guidelines are followed and more (re-)organization is
122 // needed. BUT PLEASE TRY TO KEEP THIS FILE ORGANIZED. I'd rather re-organize as
123 // little as possible, since doing so messes up the file's history.
125 // TODO(viettrungluu): [crbug.com/35543] on-going re-organization, splitting
126 // things into multiple files -- the plan is as follows:
127 // - in general, everything stays in browser_window_controller.h, but is split
128 //   off into categories (see below)
129 // - core stuff stays in browser_window_controller.mm
130 // - ... overrides also stay (without going into a category, in particular)
131 // - private stuff which everyone needs goes into
132 //   browser_window_controller_private.{h,mm}; if no one else needs them, they
133 //   can go in individual files (see below)
134 // - area/task-specific stuff go in browser_window_controller_<area>.mm
135 // - ... in categories called "(<Area>)" or "(<PrivateArea>)"
136 // Plan of action:
137 // - first re-organize into categories
138 // - then split into files
140 // Notes on self-inflicted (not user-inflicted) window resizing and moving:
142 // When the bookmark bar goes from hidden to shown (on a non-NTP) page, or when
143 // the download shelf goes from hidden to shown, we grow the window downwards in
144 // order to maintain a constant content area size. When either goes from shown
145 // to hidden, we consequently shrink the window from the bottom, also to keep
146 // the content area size constant. To keep things simple, if the window is not
147 // entirely on-screen, we don't grow/shrink the window.
149 // The complications come in when there isn't enough room (on screen) below the
150 // window to accomodate the growth. In this case, we grow the window first
151 // downwards, and then upwards. So, when it comes to shrinking, we do the
152 // opposite: shrink from the top by the amount by which we grew at the top, and
153 // then from the bottom -- unless the user moved/resized/zoomed the window, in
154 // which case we "reset state" and just shrink from the bottom.
156 // A further complication arises due to the way in which "zoom" ("maximize")
157 // works on Mac OS X. Basically, for our purposes, a window is "zoomed" whenever
158 // it occupies the full available vertical space. (Note that the green zoom
159 // button does not track zoom/unzoomed state per se, but basically relies on
160 // this heuristic.) We don't, in general, want to shrink the window if the
161 // window is zoomed (scenario: window is zoomed, download shelf opens -- which
162 // doesn't cause window growth, download shelf closes -- shouldn't cause the
163 // window to become unzoomed!). However, if we grew the window
164 // (upwards/downwards) to become zoomed in the first place, we *should* shrink
165 // the window by the amounts by which we grew (scenario: window occupies *most*
166 // of vertical space, download shelf opens causing growth so that window
167 // occupies all of vertical space -- i.e., window is effectively zoomed,
168 // download shelf closes -- should return the window to its previous state).
170 // A major complication is caused by the way grows/shrinks are handled and
171 // animated. Basically, the BWC doesn't see the global picture, but it sees
172 // grows and shrinks in small increments (as dictated by the animation). Thus
173 // window growth/shrinkage (at the top/bottom) have to be tracked incrementally.
174 // Allowing shrinking from the zoomed state also requires tracking: We check on
175 // any shrink whether we're both zoomed and have previously grown -- if so, we
176 // set a flag, and constrain any resize by the allowed amounts. On further
177 // shrinks, we check the flag (since the size/position of the window will no
178 // longer indicate that the window is shrinking from an apparent zoomed state)
179 // and if it's set we continue to constrain the resize.
181 using content::OpenURLParams;
182 using content::Referrer;
183 using content::RenderWidgetHostView;
184 using content::WebContents;
186 @interface NSWindow (NSPrivateApis)
187 // Note: These functions are private, use -[NSObject respondsToSelector:]
188 // before calling them.
190 - (void)setBottomCornerRounded:(BOOL)rounded;
192 - (NSRect)_growBoxRect;
194 @end
196 @implementation BrowserWindowController
198 + (BrowserWindowController*)browserWindowControllerForWindow:(NSWindow*)window {
199   while (window) {
200     id controller = [window windowController];
201     if ([controller isKindOfClass:[BrowserWindowController class]])
202       return (BrowserWindowController*)controller;
203     window = [window parentWindow];
204   }
205   return nil;
208 + (BrowserWindowController*)browserWindowControllerForView:(NSView*)view {
209   NSWindow* window = [view window];
210   return [BrowserWindowController browserWindowControllerForWindow:window];
213 // Load the browser window nib and do any Cocoa-specific initialization.
214 // Takes ownership of |browser|. Note that the nib also sets this controller
215 // up as the window's delegate.
216 - (id)initWithBrowser:(Browser*)browser {
217   return [self initWithBrowser:browser takeOwnership:YES];
220 // Private(TestingAPI) init routine with testing options.
221 - (id)initWithBrowser:(Browser*)browser takeOwnership:(BOOL)ownIt {
222   bool hasTabStrip = browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP);
223   if ((self = [super initTabWindowControllerWithTabStrip:hasTabStrip])) {
224     DCHECK(browser);
225     initializing_ = YES;
226     browser_.reset(browser);
227     ownsBrowser_ = ownIt;
228     NSWindow* window = [self window];
229     // Make the window handle browser window commands.
230     [base::mac::ObjCCastStrict<ChromeEventProcessingWindow>(window)
231         setCommandHandler:[[[BrowserWindowCommandHandler alloc] init]
232                               autorelease]];
234     // Make the content view for the window have a layer. This will make all
235     // sub-views have layers. This is necessary to ensure correct layer
236     // ordering of all child views and their layers.
237     [[window contentView] setWantsLayer:YES];
238     windowShim_.reset(new BrowserWindowCocoa(browser, self));
240     // Set different minimum sizes on tabbed windows vs non-tabbed, e.g. popups.
241     // This has to happen before -enforceMinWindowSize: is called further down.
242     NSSize minSize = [self isTabbedWindow] ?
243       NSMakeSize(400, 272) : NSMakeSize(100, 122);
244     [[self window] setMinSize:minSize];
246     // Create the bar visibility lock set; 10 is arbitrary, but should hopefully
247     // be big enough to hold all locks that'll ever be needed.
248     barVisibilityLocks_.reset([[NSMutableSet setWithCapacity:10] retain]);
250     // Set the window to not have rounded corners, which prevents the resize
251     // control from being inset slightly and looking ugly. Only bother to do
252     // this on Snow Leopard; on Lion and later all windows have rounded bottom
253     // corners, and this won't work anyway.
254     if (base::mac::IsOSSnowLeopard() &&
255         [window respondsToSelector:@selector(setBottomCornerRounded:)])
256       [window setBottomCornerRounded:NO];
258     // Lion will attempt to automagically save and restore the UI. This
259     // functionality appears to be leaky (or at least interacts badly with our
260     // architecture) and thus BrowserWindowController never gets released. This
261     // prevents the browser from being able to quit <http://crbug.com/79113>.
262     if ([window respondsToSelector:@selector(setRestorable:)])
263       [window setRestorable:NO];
265     // Get the windows to swish in on Lion.
266     if ([window respondsToSelector:@selector(setAnimationBehavior:)])
267       [window setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow];
269     // Get the most appropriate size for the window, then enforce the
270     // minimum width and height. The window shim will handle flipping
271     // the coordinates for us so we can use it to save some code.
272     // Note that this may leave a significant portion of the window
273     // offscreen, but there will always be enough window onscreen to
274     // drag the whole window back into view.
275     ui::WindowShowState show_state = ui::SHOW_STATE_DEFAULT;
276     gfx::Rect desiredContentRect;
277     chrome::GetSavedWindowBoundsAndShowState(browser_.get(),
278                                              &desiredContentRect,
279                                              &show_state);
280     gfx::Rect windowRect = desiredContentRect;
281     windowRect = [self enforceMinWindowSize:windowRect];
283     // When we are given x/y coordinates of 0 on a created popup window, assume
284     // none were given by the window.open() command.
285     if (browser_->is_type_popup() &&
286         windowRect.x() == 0 && windowRect.y() == 0) {
287       gfx::Size size = windowRect.size();
288       windowRect.set_origin(
289           WindowSizer::GetDefaultPopupOrigin(size,
290                                              browser_->host_desktop_type()));
291     }
293     // Size and position the window.  Note that it is not yet onscreen.  Popup
294     // windows may get resized later on in this function, once the actual size
295     // of the toolbar/tabstrip is known.
296     windowShim_->SetBounds(windowRect);
298     // Puts the incognito badge on the window frame, if necessary.
299     [self installAvatar];
301     // Create a sub-controller for the docked devTools and add its view to the
302     // hierarchy.
303     devToolsController_.reset([[DevToolsController alloc] init]);
304     [[devToolsController_ view] setFrame:[[self tabContentArea] bounds]];
305     [[self tabContentArea] addSubview:[devToolsController_ view]];
307     // Create the overlayable contents controller.  This provides the switch
308     // view that TabStripController needs.
309     overlayableContentsController_.reset(
310         [[OverlayableContentsController alloc] init]);
311     [[overlayableContentsController_ view]
312         setFrame:[[devToolsController_ view] bounds]];
313     [[devToolsController_ view]
314         addSubview:[overlayableContentsController_ view]];
316     // Create a controller for the tab strip, giving it the model object for
317     // this window's Browser and the tab strip view. The controller will handle
318     // registering for the appropriate tab notifications from the back-end and
319     // managing the creation of new tabs.
320     [self createTabStripController];
322     // Create a controller for the toolbar, giving it the toolbar model object
323     // and the toolbar view from the nib. The controller will handle
324     // registering for the appropriate command state changes from the back-end.
325     // Adds the toolbar to the content area.
326     toolbarController_.reset([[ToolbarController alloc]
327         initWithCommands:browser->command_controller()->command_updater()
328                  profile:browser->profile()
329                  browser:browser]);
330     [toolbarController_ setHasToolbar:[self hasToolbar]
331                        hasLocationBar:[self hasLocationBar]];
333     // Create a sub-controller for the bookmark bar.
334     bookmarkBarController_.reset(
335         [[BookmarkBarController alloc]
336             initWithBrowser:browser_.get()
337                initialWidth:NSWidth([[[self window] contentView] frame])
338                    delegate:self
339              resizeDelegate:self]);
340     [bookmarkBarController_ setBookmarkBarEnabled:[self supportsBookmarkBar]];
342     // Create the infobar container view, so we can pass it to the
343     // ToolbarController.
344     infoBarContainerController_.reset(
345         [[InfoBarContainerController alloc] initWithResizeDelegate:self]);
346     [self updateInfoBarTipVisibility];
348     // We don't want to try and show the bar before it gets placed in its parent
349     // view, so this step shoudn't be inside the bookmark bar controller's
350     // |-awakeFromNib|.
351     windowShim_->BookmarkBarStateChanged(
352         BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
354     // Allow bar visibility to be changed.
355     [self enableBarVisibilityUpdates];
357     // Set the window to participate in Lion Fullscreen mode.  Setting this flag
358     // has no effect on Snow Leopard or earlier.  Panels can share a fullscreen
359     // space with a tabbed window, but they can not be primary fullscreen
360     // windows.
361     // This ensures the fullscreen button is appropriately positioned. It must
362     // be done before calling layoutSubviews because the new avatar button's
363     // position depends on the fullscreen button's position, as well as
364     // TabStripController's rightIndentForControls.
365     // The fullscreen button's position may depend on the old avatar button's
366     // width, but that does not require calling layoutSubviews first.
367     NSUInteger collectionBehavior = [window collectionBehavior];
368     collectionBehavior |=
369        browser_->type() == Browser::TYPE_TABBED ||
370            browser_->type() == Browser::TYPE_POPUP ?
371                NSWindowCollectionBehaviorFullScreenPrimary :
372                NSWindowCollectionBehaviorFullScreenAuxiliary;
373     [window setCollectionBehavior:collectionBehavior];
375     [self layoutSubviews];
377     // For a popup window, |desiredContentRect| contains the desired height of
378     // the content, not of the whole window.  Now that all the views are laid
379     // out, measure the current content area size and grow if needed.  The
380     // window has not been placed onscreen yet, so this extra resize will not
381     // cause visible jank.
382     if (browser_->is_type_popup()) {
383       CGFloat deltaH = desiredContentRect.height() -
384                        NSHeight([[self tabContentArea] frame]);
385       // Do not shrink the window, as that may break minimum size invariants.
386       if (deltaH > 0) {
387         // Convert from tabContentArea coordinates to window coordinates.
388         NSSize convertedSize =
389             [[self tabContentArea] convertSize:NSMakeSize(0, deltaH)
390                                         toView:nil];
391         NSRect frame = [[self window] frame];
392         frame.size.height += convertedSize.height;
393         frame.origin.y -= convertedSize.height;
394         [[self window] setFrame:frame display:NO];
395       }
396     }
398     // Create the bridge for the status bubble.
399     statusBubble_ = new StatusBubbleMac([self window], self);
401     // Register for application hide/unhide notifications.
402     [[NSNotificationCenter defaultCenter]
403          addObserver:self
404             selector:@selector(applicationDidHide:)
405                 name:NSApplicationDidHideNotification
406               object:nil];
407     [[NSNotificationCenter defaultCenter]
408          addObserver:self
409             selector:@selector(applicationDidUnhide:)
410                 name:NSApplicationDidUnhideNotification
411               object:nil];
413     // This must be done after the view is added to the window since it relies
414     // on the window bounds to determine whether to show buttons or not.
415     if ([self hasToolbar])  // Do not create the buttons in popups.
416       [toolbarController_ createBrowserActionButtons];
418     extension_keybinding_registry_.reset(
419         new ExtensionKeybindingRegistryCocoa(browser_->profile(),
420             [self window],
421             extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
422             windowShim_.get()));
424     // We are done initializing now.
425     initializing_ = NO;
426   }
427   return self;
430 - (void)dealloc {
431   browser_->tab_strip_model()->CloseAllTabs();
433   // Explicitly release |presentationModeController_| here, as it may call back
434   // to this BWC in |-dealloc|.  We are required to call |-exitPresentationMode|
435   // before releasing the controller.
436   [presentationModeController_ exitPresentationMode];
437   presentationModeController_.reset();
439   // Under certain testing configurations we may not actually own the browser.
440   if (ownsBrowser_ == NO)
441     ignore_result(browser_.release());
443   [[NSNotificationCenter defaultCenter] removeObserver:self];
445   // Inform reference counted objects that the Browser will be destroyed. This
446   // ensures they invalidate their weak Browser* to prevent use-after-free.
447   // These may outlive the Browser if they are retained by something else. For
448   // example, since 10.10, the Nib loader internally creates an NSDictionary
449   // that retains NSViewControllers and is autoreleased, so there is no way to
450   // guarantee that the [super dealloc] call below will also call dealloc on the
451   // controllers.
452   [toolbarController_ browserWillBeDestroyed];
453   [tabStripController_ browserWillBeDestroyed];
454   [findBarCocoaController_ browserWillBeDestroyed];
455   [downloadShelfController_ browserWillBeDestroyed];
456   [bookmarkBarController_ browserWillBeDestroyed];
457   [avatarButtonController_ browserWillBeDestroyed];
459   [super dealloc];
462 - (gfx::Rect)enforceMinWindowSize:(gfx::Rect)bounds {
463   gfx::Rect checkedBounds = bounds;
465   NSSize minSize = [[self window] minSize];
466   if (bounds.width() < minSize.width)
467       checkedBounds.set_width(minSize.width);
468   if (bounds.height() < minSize.height)
469       checkedBounds.set_height(minSize.height);
471   return checkedBounds;
474 - (BrowserWindow*)browserWindow {
475   return windowShim_.get();
478 - (ToolbarController*)toolbarController {
479   return toolbarController_.get();
482 - (TabStripController*)tabStripController {
483   return tabStripController_.get();
486 - (FindBarCocoaController*)findBarCocoaController {
487   return findBarCocoaController_.get();
490 - (InfoBarContainerController*)infoBarContainerController {
491   return infoBarContainerController_.get();
494 - (StatusBubbleMac*)statusBubble {
495   return statusBubble_;
498 - (LocationBarViewMac*)locationBarBridge {
499   return [toolbarController_ locationBarBridge];
502 - (NSView*)floatingBarBackingView {
503   return floatingBarBackingView_;
506 - (OverlayableContentsController*)overlayableContentsController {
507   return overlayableContentsController_;
510 - (Profile*)profile {
511   return browser_->profile();
514 - (AvatarBaseController*)avatarButtonController {
515   return avatarButtonController_.get();
518 - (void)destroyBrowser {
519   [NSApp removeWindowsItem:[self window]];
521   // We need the window to go away now.
522   // We can't actually use |-autorelease| here because there's an embedded
523   // run loop in the |-performClose:| which contains its own autorelease pool.
524   // Instead call it after a zero-length delay, which gets us back to the main
525   // event loop.
526   [self performSelector:@selector(autorelease)
527              withObject:nil
528              afterDelay:0];
531 // Called when the window meets the criteria to be closed (ie,
532 // |-windowShouldClose:| returns YES). We must be careful to preserve the
533 // semantics of BrowserWindow::Close() and not call the Browser's dtor directly
534 // from this method.
535 - (void)windowWillClose:(NSNotification*)notification {
536   DCHECK_EQ([notification object], [self window]);
537   DCHECK(browser_->tab_strip_model()->empty());
538   [savedRegularWindow_ close];
539   // We delete statusBubble here because we need to kill off the dependency
540   // that its window has on our window before our window goes away.
541   delete statusBubble_;
542   statusBubble_ = NULL;
543   // We can't actually use |-autorelease| here because there's an embedded
544   // run loop in the |-performClose:| which contains its own autorelease pool.
545   // Instead call it after a zero-length delay, which gets us back to the main
546   // event loop.
547   [self performSelector:@selector(autorelease)
548              withObject:nil
549              afterDelay:0];
552 - (void)updateDevToolsForContents:(WebContents*)contents {
553   BOOL layout_changed =
554       [devToolsController_ updateDevToolsForWebContents:contents
555                                             withProfile:browser_->profile()];
556   if (layout_changed && [findBarCocoaController_ isFindBarVisible])
557     [self layoutSubviews];
560 // Called when the user wants to close a window or from the shutdown process.
561 // The Browser object is in control of whether or not we're allowed to close. It
562 // may defer closing due to several states, such as onUnload handlers needing to
563 // be fired. If closing is deferred, the Browser will handle the processing
564 // required to get us to the closing state and (by watching for all the tabs
565 // going away) will again call to close the window when it's finally ready.
566 - (BOOL)windowShouldClose:(id)sender {
567   // Disable updates while closing all tabs to avoid flickering.
568   gfx::ScopedCocoaDisableScreenUpdates disabler;
569   // Give beforeunload handlers the chance to cancel the close before we hide
570   // the window below.
571   if (!browser_->ShouldCloseWindow())
572     return NO;
574   // saveWindowPositionIfNeeded: only works if we are the last active
575   // window, but orderOut: ends up activating another window, so we
576   // have to save the window position before we call orderOut:.
577   [self saveWindowPositionIfNeeded];
579   bool fast_tab_closing_enabled =
580       base::CommandLine::ForCurrentProcess()->HasSwitch(
581           switches::kEnableFastUnload);
583   if (!browser_->tab_strip_model()->empty()) {
584     // Tab strip isn't empty.  Hide the frame (so it appears to have closed
585     // immediately) and close all the tabs, allowing the renderers to shut
586     // down. When the tab strip is empty we'll be called back again.
587     [[self window] orderOut:self];
588     browser_->OnWindowClosing();
589     if (fast_tab_closing_enabled)
590       browser_->tab_strip_model()->CloseAllTabs();
591     return NO;
592   } else if (fast_tab_closing_enabled &&
593         !browser_->HasCompletedUnloadProcessing()) {
594     // The browser needs to finish running unload handlers.
595     // Hide the window (so it appears to have closed immediately), and
596     // the browser will call us back again when it is ready to close.
597     [[self window] orderOut:self];
598     return NO;
599   }
601   // the tab strip is empty, it's ok to close the window
602   return YES;
605 // Called right after our window became the main window.
606 - (void)windowDidBecomeMain:(NSNotification*)notification {
607   if (chrome::GetLastActiveBrowser() != browser_) {
608     BrowserList::SetLastActive(browser_.get());
609     [self saveWindowPositionIfNeeded];
610   }
612   NSView* rootView = [[[self window] contentView] superview];
613   [rootView cr_recursivelyInvokeBlock:^(id view) {
614       if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)])
615         [view windowDidChangeActive];
616   }];
618   extensions::ExtensionCommandsGlobalRegistry::Get(browser_->profile())
619       ->set_registry_for_active_window(extension_keybinding_registry_.get());
622 - (void)windowDidResignMain:(NSNotification*)notification {
623   NSView* rootView = [[[self window] contentView] superview];
624   [rootView cr_recursivelyInvokeBlock:^(id view) {
625       if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)])
626         [view windowDidChangeActive];
627   }];
629   extensions::ExtensionCommandsGlobalRegistry::Get(browser_->profile())
630       ->set_registry_for_active_window(nullptr);
633 // Called when we are activated (when we gain focus).
634 - (void)windowDidBecomeKey:(NSNotification*)notification {
635   // We need to activate the controls (in the "WebView"). To do this, get the
636   // selected WebContents's RenderWidgetHostView and tell it to activate.
637   if (WebContents* contents = [self webContents]) {
638     WebContents* devtools = DevToolsWindow::GetInTabWebContents(
639         contents, NULL);
640     if (devtools) {
641       RenderWidgetHostView* devtoolsView = devtools->GetRenderWidgetHostView();
642       if (devtoolsView && devtoolsView->HasFocus()) {
643         devtoolsView->SetActive(true);
644         return;
645       }
646     }
648     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
649       rwhv->SetActive(true);
650   }
653 // Called when we are deactivated (when we lose focus).
654 - (void)windowDidResignKey:(NSNotification*)notification {
655   // If our app is still active and we're still the key window, ignore this
656   // message, since it just means that a menu extra (on the "system status bar")
657   // was activated; we'll get another |-windowDidResignKey| if we ever really
658   // lose key window status.
659   if ([NSApp isActive] && ([NSApp keyWindow] == [self window]))
660     return;
662   // We need to deactivate the controls (in the "WebView"). To do this, get the
663   // selected WebContents's RenderWidgetHostView and tell it to deactivate.
664   if (WebContents* contents = [self webContents]) {
665     WebContents* devtools = DevToolsWindow::GetInTabWebContents(
666         contents, NULL);
667     if (devtools) {
668       RenderWidgetHostView* devtoolsView = devtools->GetRenderWidgetHostView();
669       if (devtoolsView && devtoolsView->HasFocus()) {
670         devtoolsView->SetActive(false);
671         return;
672       }
673     }
675     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
676       rwhv->SetActive(false);
677   }
680 // Called when we have been minimized.
681 - (void)windowDidMiniaturize:(NSNotification *)notification {
682   [self saveWindowPositionIfNeeded];
684   // Let the selected RenderWidgetHostView know, so that it can tell plugins.
685   if (WebContents* contents = [self webContents]) {
686     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
687       rwhv->SetWindowVisibility(false);
688   }
691 // Called when we have been unminimized.
692 - (void)windowDidDeminiaturize:(NSNotification *)notification {
693   // Let the selected RenderWidgetHostView know, so that it can tell plugins.
694   if (WebContents* contents = [self webContents]) {
695     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
696       rwhv->SetWindowVisibility(true);
697   }
700 // Called when the application has been hidden.
701 - (void)applicationDidHide:(NSNotification *)notification {
702   // Let the selected RenderWidgetHostView know, so that it can tell plugins
703   // (unless we are minimized, in which case nothing has really changed).
704   if (![[self window] isMiniaturized]) {
705     if (WebContents* contents = [self webContents]) {
706       if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
707         rwhv->SetWindowVisibility(false);
708     }
709   }
712 // Called when the application has been unhidden.
713 - (void)applicationDidUnhide:(NSNotification *)notification {
714   // Let the selected RenderWidgetHostView know, so that it can tell plugins
715   // (unless we are minimized, in which case nothing has really changed).
716   if (![[self window] isMiniaturized]) {
717     if (WebContents* contents = [self webContents]) {
718       if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
719         rwhv->SetWindowVisibility(true);
720     }
721   }
724 // Called when the user clicks the zoom button (or selects it from the Window
725 // menu) to determine the "standard size" of the window, based on the content
726 // and other factors. If the current size/location differs nontrivally from the
727 // standard size, Cocoa resizes the window to the standard size, and saves the
728 // current size as the "user size". If the current size/location is the same (up
729 // to a fudge factor) as the standard size, Cocoa resizes the window to the
730 // saved user size. (It is possible for the two to coincide.) In this way, the
731 // zoom button acts as a toggle. We determine the standard size based on the
732 // content, but enforce a minimum width (calculated using the dimensions of the
733 // screen) to ensure websites with small intrinsic width (such as google.com)
734 // don't end up with a wee window. Moreover, we always declare the standard
735 // width to be at least as big as the current width, i.e., we never want zooming
736 // to the standard width to shrink the window. This is consistent with other
737 // browsers' behaviour, and is desirable in multi-tab situations. Note, however,
738 // that the "toggle" behaviour means that the window can still be "unzoomed" to
739 // the user size.
740 // Note: this method is also called from -isZoomed. If the returned zoomed rect
741 // equals the current window's frame, -isZoomed returns YES.
742 - (NSRect)windowWillUseStandardFrame:(NSWindow*)window
743                         defaultFrame:(NSRect)frame {
744   // Forget that we grew the window up (if we in fact did).
745   [self resetWindowGrowthState];
747   // |frame| already fills the current screen. Never touch y and height since we
748   // always want to fill vertically.
750   // If the shift key is down, maximize. Hopefully this should make the
751   // "switchers" happy.
752   if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
753     return frame;
754   }
756   // To prevent strange results on portrait displays, the basic minimum zoomed
757   // width is the larger of: 60% of available width, 60% of available height
758   // (bounded by available width).
759   const CGFloat kProportion = 0.6;
760   CGFloat zoomedWidth =
761       std::max(kProportion * NSWidth(frame),
762                std::min(kProportion * NSHeight(frame), NSWidth(frame)));
764   WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
765   if (contents) {
766     // If the intrinsic width is bigger, then make it the zoomed width.
767     const int kScrollbarWidth = 16;  // TODO(viettrungluu): ugh.
768     CGFloat intrinsicWidth = static_cast<CGFloat>(
769         contents->GetPreferredSize().width() + kScrollbarWidth);
770     zoomedWidth = std::max(zoomedWidth,
771                            std::min(intrinsicWidth, NSWidth(frame)));
772   }
774   // Never shrink from the current size on zoom (see above).
775   NSRect currentFrame = [[self window] frame];
776   zoomedWidth = std::max(zoomedWidth, NSWidth(currentFrame));
778   // |frame| determines our maximum extents. We need to set the origin of the
779   // frame -- and only move it left if necessary.
780   if (currentFrame.origin.x + zoomedWidth > NSMaxX(frame))
781     frame.origin.x = NSMaxX(frame) - zoomedWidth;
782   else
783     frame.origin.x = currentFrame.origin.x;
785   // Set the width. Don't touch y or height.
786   frame.size.width = zoomedWidth;
788   return frame;
791 - (void)activate {
792   [BrowserWindowUtils activateWindowForController:self];
795 // Determine whether we should let a window zoom/unzoom to the given |newFrame|.
796 // We avoid letting unzoom move windows between screens, because it's really
797 // strange and unintuitive.
798 - (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame {
799   // Figure out which screen |newFrame| is on.
800   NSScreen* newScreen = nil;
801   CGFloat newScreenOverlapArea = 0.0;
802   for (NSScreen* screen in [NSScreen screens]) {
803     NSRect overlap = NSIntersectionRect(newFrame, [screen frame]);
804     CGFloat overlapArea = NSWidth(overlap) * NSHeight(overlap);
805     if (overlapArea > newScreenOverlapArea) {
806       newScreen = screen;
807       newScreenOverlapArea = overlapArea;
808     }
809   }
810   // If we're somehow not on any screen, allow the zoom.
811   if (!newScreen)
812     return YES;
814   // If the new screen is the current screen, we can return a definitive YES.
815   // Note: This check is not strictly necessary, but just short-circuits in the
816   // "no-brainer" case. To test the complicated logic below, comment this out!
817   NSScreen* curScreen = [window screen];
818   if (newScreen == curScreen)
819     return YES;
821   // Worry a little: What happens when a window is on two (or more) screens?
822   // E.g., what happens in a 50-50 scenario? Cocoa may reasonably elect to zoom
823   // to the other screen rather than staying on the officially current one. So
824   // we compare overlaps with the current window frame, and see if Cocoa's
825   // choice was reasonable (allowing a small rounding error). This should
826   // hopefully avoid us ever erroneously denying a zoom when a window is on
827   // multiple screens.
828   NSRect curFrame = [window frame];
829   NSRect newScrIntersectCurFr = NSIntersectionRect([newScreen frame], curFrame);
830   NSRect curScrIntersectCurFr = NSIntersectionRect([curScreen frame], curFrame);
831   if (NSWidth(newScrIntersectCurFr) * NSHeight(newScrIntersectCurFr) >=
832       (NSWidth(curScrIntersectCurFr) * NSHeight(curScrIntersectCurFr) - 1.0)) {
833     return YES;
834   }
836   // If it wasn't reasonable, return NO.
837   return NO;
840 // Adjusts the window height by the given amount.
841 - (BOOL)adjustWindowHeightBy:(CGFloat)deltaH {
842   // By not adjusting the window height when initializing, we can ensure that
843   // the window opens with the same size that was saved on close.
844   if (initializing_ || [self isInAnyFullscreenMode] || deltaH == 0)
845     return NO;
847   NSWindow* window = [self window];
848   NSRect windowFrame = [window frame];
849   NSRect workarea = [[window screen] visibleFrame];
851   // Prevent the window from growing smaller than its minimum height:
852   // http://crbug.com/230400 .
853   if (deltaH < 0) {
854     CGFloat minWindowHeight = [window minSize].height;
855     if (windowFrame.size.height + deltaH < minWindowHeight) {
856       // |deltaH| + |windowFrame.size.height| = |minWindowHeight|.
857       deltaH = minWindowHeight - windowFrame.size.height;
858     }
859     if (deltaH == 0) {
860       return NO;
861     }
862   }
864   // If the window is not already fully in the workarea, do not adjust its frame
865   // at all.
866   if (!NSContainsRect(workarea, windowFrame))
867     return NO;
869   // Record the position of the top/bottom of the window, so we can easily check
870   // whether we grew the window upwards/downwards.
871   CGFloat oldWindowMaxY = NSMaxY(windowFrame);
872   CGFloat oldWindowMinY = NSMinY(windowFrame);
874   // We are "zoomed" if we occupy the full vertical space.
875   bool isZoomed = (windowFrame.origin.y == workarea.origin.y &&
876                    NSHeight(windowFrame) == NSHeight(workarea));
878   // If we're shrinking the window....
879   if (deltaH < 0) {
880     bool didChange = false;
882     // Don't reset if not currently zoomed since shrinking can take several
883     // steps!
884     if (isZoomed)
885       isShrinkingFromZoomed_ = YES;
887     // If we previously grew at the top, shrink as much as allowed at the top
888     // first.
889     if (windowTopGrowth_ > 0) {
890       CGFloat shrinkAtTopBy = MIN(-deltaH, windowTopGrowth_);
891       windowFrame.size.height -= shrinkAtTopBy;  // Shrink the window.
892       deltaH += shrinkAtTopBy;            // Update the amount left to shrink.
893       windowTopGrowth_ -= shrinkAtTopBy;  // Update the growth state.
894       didChange = true;
895     }
897     // Similarly for the bottom (not an "else if" since we may have to
898     // simultaneously shrink at both the top and at the bottom). Note that
899     // |deltaH| may no longer be nonzero due to the above.
900     if (deltaH < 0 && windowBottomGrowth_ > 0) {
901       CGFloat shrinkAtBottomBy = MIN(-deltaH, windowBottomGrowth_);
902       windowFrame.origin.y += shrinkAtBottomBy;     // Move the window up.
903       windowFrame.size.height -= shrinkAtBottomBy;  // Shrink the window.
904       deltaH += shrinkAtBottomBy;               // Update the amount left....
905       windowBottomGrowth_ -= shrinkAtBottomBy;  // Update the growth state.
906       didChange = true;
907     }
909     // If we're shrinking from zoomed but we didn't change the top or bottom
910     // (since we've reached the limits imposed by |window...Growth_|), then stop
911     // here. Don't reset |isShrinkingFromZoomed_| since we might get called
912     // again for the same shrink.
913     if (isShrinkingFromZoomed_ && !didChange)
914       return NO;
915   } else {
916     isShrinkingFromZoomed_ = NO;
918     // Don't bother with anything else.
919     if (isZoomed)
920       return NO;
921   }
923   // Shrinking from zoomed is handled above (and is constrained by
924   // |window...Growth_|).
925   if (!isShrinkingFromZoomed_) {
926     // Resize the window down until it hits the bottom of the workarea, then if
927     // needed continue resizing upwards.  Do not resize the window to be taller
928     // than the current workarea.
929     // Resize the window as requested, keeping the top left corner fixed.
930     windowFrame.origin.y -= deltaH;
931     windowFrame.size.height += deltaH;
933     // If the bottom left corner is now outside the visible frame, move the
934     // window up to make it fit, but make sure not to move the top left corner
935     // out of the visible frame.
936     if (windowFrame.origin.y < workarea.origin.y) {
937       windowFrame.origin.y = workarea.origin.y;
938       windowFrame.size.height =
939           std::min(NSHeight(windowFrame), NSHeight(workarea));
940     }
942     // Record (if applicable) how much we grew the window in either direction.
943     // (N.B.: These only record growth, not shrinkage.)
944     if (NSMaxY(windowFrame) > oldWindowMaxY)
945       windowTopGrowth_ += NSMaxY(windowFrame) - oldWindowMaxY;
946     if (NSMinY(windowFrame) < oldWindowMinY)
947       windowBottomGrowth_ += oldWindowMinY - NSMinY(windowFrame);
948   }
950   // Disable subview resizing while resizing the window, or else we will get
951   // unwanted renderer resizes.  The calling code must call layoutSubviews to
952   // make things right again.
953   NSView* chromeContentView = [self chromeContentView];
954   BOOL autoresizesSubviews = [chromeContentView autoresizesSubviews];
955   [chromeContentView setAutoresizesSubviews:NO];
957   // On Yosemite the toolbar can flicker when hiding or showing the bookmarks
958   // bar. Here, |chromeContentView| is set to not autoresize its subviews during
959   // the window resize. Because |chromeContentView| is not flipped, if the
960   // window is getting shorter, the toolbar will move up within the window.
961   // Soon after, a call to layoutSubviews corrects its position. Passing NO to
962   // setFrame:display: should keep the toolbarView's intermediate position
963   // hidden, as should the prior call to disable screen updates. For some
964   // reason, neither prevents the toolbarView's intermediate position from
965   // becoming visible. Its subsequent appearance in its correct location causes
966   // the flicker. It may be that the Appkit assumes that updating the window
967   // immediately is not a big deal given that everything in it is layer-backed.
968   // Indeed, turning off layer backing for all ancestors of the toolbarView
969   // causes the flicker to go away.
970   //
971   // By shifting the toolbarView enough so that it's in its correct location
972   // immediately after the call to setFrame:display:, the toolbar will be in
973   // the right spot when the Appkit prematurely flushes the window contents to
974   // the screen. http://crbug.com/444080 .
975   if ([self hasToolbar]) {
976     NSView* toolbarView = [toolbarController_ view];
977     NSRect currentWindowFrame = [window frame];
978     NSRect toolbarViewFrame = [toolbarView frame];
979     toolbarViewFrame.origin.y += windowFrame.size.height -
980         currentWindowFrame.size.height;
981     [toolbarView setFrame:toolbarViewFrame];
982   }
984   [window setFrame:windowFrame display:NO];
985   [chromeContentView setAutoresizesSubviews:autoresizesSubviews];
986   return YES;
989 // Main method to resize browser window subviews.  This method should be called
990 // when resizing any child of the content view, rather than resizing the views
991 // directly.  If the view is already the correct height, does not force a
992 // relayout.
993 - (void)resizeView:(NSView*)view newHeight:(CGFloat)height {
994   // We should only ever be called for one of the following four views.
995   // |downloadShelfController_| may be nil. If we are asked to size the bookmark
996   // bar directly, its superview must be this controller's content view.
997   DCHECK(view);
998   DCHECK(view == [toolbarController_ view] ||
999          view == [infoBarContainerController_ view] ||
1000          view == [downloadShelfController_ view] ||
1001          view == [bookmarkBarController_ view]);
1003   // The infobar has insufficient information to determine its new height. It
1004   // knows the total height of all of the info bars (which is what it passes
1005   // into this method), but knows nothing about the maximum arrow height, which
1006   // is determined by this class.
1007   if (view == [infoBarContainerController_ view]) {
1008     base::scoped_nsobject<BrowserWindowLayout> layout(
1009         [[BrowserWindowLayout alloc] init]);
1010     [self updateLayoutParameters:layout];
1011     // Use the new height for the info bar.
1012     [layout setInfoBarHeight:height];
1014     chrome::LayoutOutput output = [layout computeLayout];
1016     height = NSHeight(output.infoBarFrame);
1017   }
1019   // Change the height of the view and call |-layoutSubViews|. We set the height
1020   // here without regard to where the view is on the screen or whether it needs
1021   // to "grow up" or "grow down."  The below call to |-layoutSubviews| will
1022   // position each view correctly.
1023   NSRect frame = [view frame];
1024   if (NSHeight(frame) == height)
1025     return;
1027   // Disable screen updates to prevent flickering.
1028   gfx::ScopedCocoaDisableScreenUpdates disabler;
1030   // Grow or shrink the window by the amount of the height change.  We adjust
1031   // the window height only in two cases:
1032   // 1) We are adjusting the height of the bookmark bar and it is currently
1033   // animating either open or closed.
1034   // 2) We are adjusting the height of the download shelf.
1035   //
1036   // We do not adjust the window height for bookmark bar changes on the NTP.
1037   BOOL shouldAdjustBookmarkHeight =
1038       [bookmarkBarController_ isAnimatingBetweenState:BookmarkBar::HIDDEN
1039                                              andState:BookmarkBar::SHOW];
1041   BOOL resizeRectDirty = NO;
1042   if ((shouldAdjustBookmarkHeight && view == [bookmarkBarController_ view]) ||
1043       view == [downloadShelfController_ view]) {
1044     CGFloat deltaH = height - NSHeight(frame);
1045     if ([self adjustWindowHeightBy:deltaH] &&
1046         view == [downloadShelfController_ view]) {
1047       // If the window height didn't change, the download shelf will change the
1048       // size of the contents. If the contents size doesn't change, send it
1049       // an explicit grow box invalidation (else, the resize message does that.)
1050       resizeRectDirty = YES;
1051     }
1052   }
1054   frame.size.height = height;
1055   // TODO(rohitrao): Determine if calling setFrame: twice is bad.
1056   [view setFrame:frame];
1057   [self layoutSubviews];
1059   if (resizeRectDirty) {
1060     // Send new resize rect to foreground tab.
1061     if (WebContents* contents = [self webContents]) {
1062       if (content::RenderViewHost* rvh = contents->GetRenderViewHost()) {
1063         rvh->ResizeRectChanged(windowShim_->GetRootWindowResizerRect());
1064       }
1065     }
1066   }
1069 - (BOOL)handledByExtensionCommand:(NSEvent*)event
1070     priority:(ui::AcceleratorManager::HandlerPriority)priority {
1071   return extension_keybinding_registry_->ProcessKeyEvent(
1072       content::NativeWebKeyboardEvent(event), priority);
1075 // StatusBubble delegate method: tell the status bubble the frame it should
1076 // position itself in.
1077 - (NSRect)statusBubbleBaseFrame {
1078   NSView* view = [overlayableContentsController_ view];
1079   return [view convertRect:[view bounds] toView:nil];
1082 - (void)updateToolbarWithContents:(WebContents*)tab {
1083   [toolbarController_ updateToolbarWithContents:tab];
1086 - (void)resetTabState:(WebContents*)tab {
1087   [toolbarController_ resetTabState:tab];
1090 - (void)setStarredState:(BOOL)isStarred {
1091   [toolbarController_ setStarredState:isStarred];
1094 - (void)setCurrentPageIsTranslated:(BOOL)on {
1095   [toolbarController_ setTranslateIconLit:on];
1098 - (void)onActiveTabChanged:(content::WebContents*)oldContents
1099                         to:(content::WebContents*)newContents {
1100   // No need to remove previous bubble. It will close itself.
1101   PermissionBubbleManager* manager(nullptr);
1102   if (oldContents) {
1103     manager = PermissionBubbleManager::FromWebContents(oldContents);
1104     if (manager)
1105       manager->HideBubble();
1106   }
1108   if (newContents) {
1109     manager = PermissionBubbleManager::FromWebContents(newContents);
1110     if (manager)
1111       manager->DisplayPendingRequests();
1112   }
1115 - (void)zoomChangedForActiveTab:(BOOL)canShowBubble {
1116   [toolbarController_ zoomChangedForActiveTab:canShowBubble];
1119 // Return the rect, in WebKit coordinates (flipped), of the window's grow box
1120 // in the coordinate system of the content area of the currently selected tab.
1121 // |windowGrowBox| needs to be in the window's coordinate system.
1122 - (NSRect)selectedTabGrowBoxRect {
1123   NSWindow* window = [self window];
1124   if (![window respondsToSelector:@selector(_growBoxRect)])
1125     return NSZeroRect;
1127   // Before we return a rect, we need to convert it from window coordinates
1128   // to tab content area coordinates and flip the coordinate system.
1129   NSRect growBoxRect =
1130       [[self tabContentArea] convertRect:[window _growBoxRect] fromView:nil];
1131   growBoxRect.origin.y =
1132       NSHeight([[self tabContentArea] frame]) - NSMaxY(growBoxRect);
1133   return growBoxRect;
1136 // Accept tabs from a BrowserWindowController with the same Profile.
1137 - (BOOL)canReceiveFrom:(TabWindowController*)source {
1138   BrowserWindowController* realSource =
1139       base::mac::ObjCCast<BrowserWindowController>(source);
1140   if (!realSource || browser_->profile() != realSource->browser_->profile()) {
1141     return NO;
1142   }
1144   // Can't drag a tab from a normal browser to a pop-up
1145   if (browser_->type() != realSource->browser_->type()) {
1146     return NO;
1147   }
1149   return YES;
1152 // Move a given tab view to the location of the current placeholder. If there is
1153 // no placeholder, it will go at the end. |controller| is the window controller
1154 // of a tab being dropped from a different window. It will be nil if the drag is
1155 // within the window, otherwise the tab is removed from that window before being
1156 // placed into this one. The implementation will call |-removePlaceholder| since
1157 // the drag is now complete.  This also calls |-layoutTabs| internally so
1158 // clients do not need to call it again.
1159 - (void)moveTabViews:(NSArray*)views
1160       fromController:(TabWindowController*)dragController {
1161   if (dragController) {
1162     // Moving between windows.
1163     NSView* activeTabView = [dragController activeTabView];
1164     BrowserWindowController* dragBWC =
1165         base::mac::ObjCCastStrict<BrowserWindowController>(dragController);
1167     // We will drop the tabs starting at indexOfPlaceholder, and increment from
1168     // there. We remove the placehoder before dropping the tabs, so that the
1169     // new tab animation's destination frame is correct.
1170     int tabIndex = [tabStripController_ indexOfPlaceholder];
1171     [self removePlaceholder];
1173     for (NSView* view in views) {
1174       // Figure out the WebContents to drop into our tab model from the source
1175       // window's model.
1176       int index = [dragBWC->tabStripController_ modelIndexForTabView:view];
1177       WebContents* contents =
1178           dragBWC->browser_->tab_strip_model()->GetWebContentsAt(index);
1179       // The tab contents may have gone away if given a window.close() while it
1180       // is being dragged. If so, bail, we've got nothing to drop.
1181       if (!contents)
1182         continue;
1184       // Convert |view|'s frame (which starts in the source tab strip's
1185       // coordinate system) to the coordinate system of the destination tab
1186       // strip. This needs to be done before being detached so the window
1187       // transforms can be performed.
1188       NSRect destinationFrame = [view frame];
1189       NSPoint tabOrigin = destinationFrame.origin;
1190       tabOrigin = [[dragController tabStripView] convertPoint:tabOrigin
1191                                                        toView:nil];
1192       tabOrigin = [[dragController window] convertBaseToScreen:tabOrigin];
1193       tabOrigin = [[self window] convertScreenToBase:tabOrigin];
1194       tabOrigin = [[self tabStripView] convertPoint:tabOrigin fromView:nil];
1195       destinationFrame.origin = tabOrigin;
1197       // Before the tab is detached from its originating tab strip, store the
1198       // pinned state so that it can be maintained between the windows.
1199       bool isPinned = dragBWC->browser_->tab_strip_model()->IsTabPinned(index);
1201       // Now that we have enough information about the tab, we can remove it
1202       // from the dragging window. We need to do this *before* we add it to the
1203       // new window as this will remove the WebContents' delegate.
1204       [dragController detachTabView:view];
1206       // Deposit it into our model at the appropriate location (it already knows
1207       // where it should go from tracking the drag). Doing this sets the tab's
1208       // delegate to be the Browser.
1209       [tabStripController_ dropWebContents:contents
1210                                    atIndex:tabIndex++
1211                                  withFrame:destinationFrame
1212                                asPinnedTab:isPinned
1213                                   activate:view == activeTabView];
1214     }
1215   } else {
1216     // Moving within a window.
1217     for (NSView* view in views) {
1218       int index = [tabStripController_ modelIndexForTabView:view];
1219       [tabStripController_ moveTabFromIndex:index];
1220     }
1221     [self removePlaceholder];
1222   }
1225 // Tells the tab strip to forget about this tab in preparation for it being
1226 // put into a different tab strip, such as during a drop on another window.
1227 - (void)detachTabView:(NSView*)view {
1228   int index = [tabStripController_ modelIndexForTabView:view];
1229   browser_->tab_strip_model()->DetachWebContentsAt(index);
1232 - (NSArray*)tabViews {
1233   return [tabStripController_ tabViews];
1236 - (NSView*)activeTabView {
1237   return [tabStripController_ activeTabView];
1240 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force {
1241   [toolbarController_ setIsLoading:isLoading force:force];
1244 // Make the location bar the first responder, if possible.
1245 - (void)focusLocationBar:(BOOL)selectAll {
1246   [toolbarController_ focusLocationBar:selectAll];
1249 - (void)focusTabContents {
1250   content::WebContents* const activeWebContents =
1251       browser_->tab_strip_model()->GetActiveWebContents();
1252   if (activeWebContents)
1253     activeWebContents->Focus();
1256 - (void)layoutTabs {
1257   [tabStripController_ layoutTabs];
1260 - (TabWindowController*)detachTabsToNewWindow:(NSArray*)tabViews
1261                                    draggedTab:(NSView*)draggedTab {
1262   DCHECK_GT([tabViews count], 0U);
1264   // Disable screen updates so that this appears as a single visual change.
1265   gfx::ScopedCocoaDisableScreenUpdates disabler;
1267   // Set the window size. Need to do this before we detach the tab so it's
1268   // still in the window. We have to flip the coordinates as that's what
1269   // is expected by the Browser code.
1270   NSWindow* sourceWindow = [draggedTab window];
1271   NSRect windowRect = [sourceWindow frame];
1272   NSScreen* screen = [sourceWindow screen];
1273   windowRect.origin.y = NSHeight([screen frame]) - NSMaxY(windowRect);
1274   gfx::Rect browserRect(windowRect.origin.x, windowRect.origin.y,
1275                         NSWidth(windowRect), NSHeight(windowRect));
1277   std::vector<TabStripModelDelegate::NewStripContents> contentses;
1278   TabStripModel* model = browser_->tab_strip_model();
1280   for (TabView* tabView in tabViews) {
1281     // Fetch the tab contents for the tab being dragged.
1282     int index = [tabStripController_ modelIndexForTabView:tabView];
1283     bool isPinned = model->IsTabPinned(index);
1284     bool isActive = (index == model->active_index());
1286     TabStripModelDelegate::NewStripContents item;
1287     item.web_contents = model->GetWebContentsAt(index);
1288     item.add_types =
1289         (isActive ? TabStripModel::ADD_ACTIVE : TabStripModel::ADD_NONE) |
1290         (isPinned ? TabStripModel::ADD_PINNED : TabStripModel::ADD_NONE);
1291     contentses.push_back(item);
1292   }
1294   for (TabView* tabView in tabViews) {
1295     int index = [tabStripController_ modelIndexForTabView:tabView];
1296     // Detach it from the source window, which just updates the model without
1297     // deleting the tab contents. This needs to come before creating the new
1298     // Browser because it clears the WebContents' delegate, which gets hooked
1299     // up during creation of the new window.
1300     model->DetachWebContentsAt(index);
1301   }
1303   // Create a new window with the dragged tabs in its model.
1304   Browser* newBrowser = browser_->tab_strip_model()->delegate()->
1305       CreateNewStripWithContents(contentses, browserRect, false);
1307   // Get the new controller by asking the new window for its delegate.
1308   BrowserWindowController* controller =
1309       reinterpret_cast<BrowserWindowController*>(
1310           [newBrowser->window()->GetNativeWindow() delegate]);
1311   DCHECK(controller && [controller isKindOfClass:[TabWindowController class]]);
1313   // And make sure we use the correct frame in the new view.
1314   TabStripController* tabStripController = [controller tabStripController];
1315   NSView* tabStrip = [self tabStripView];
1316   NSEnumerator* tabEnumerator = [tabViews objectEnumerator];
1317   for (NSView* newView in [tabStripController tabViews]) {
1318     NSView* oldView = [tabEnumerator nextObject];
1319     if (oldView) {
1320       // Pushes tabView's frame back inside the tabstrip.
1321       NSRect sourceTabRect = [oldView frame];
1322       NSSize tabOverflow =
1323           [self overflowFrom:[tabStrip convertRect:sourceTabRect toView:nil]
1324                           to:[tabStrip frame]];
1325       NSRect tabRect =
1326           NSOffsetRect(sourceTabRect, -tabOverflow.width, -tabOverflow.height);
1327       // Force the added tab to the right size (remove stretching.)
1328       tabRect.size.height = [TabStripController defaultTabHeight];
1330       [tabStripController setFrame:tabRect ofTabView:newView];
1331     }
1332   }
1334   return controller;
1337 - (void)insertPlaceholderForTab:(TabView*)tab
1338                           frame:(NSRect)frame {
1339   [super insertPlaceholderForTab:tab frame:frame];
1340   [tabStripController_ insertPlaceholderForTab:tab frame:frame];
1343 - (void)removePlaceholder {
1344   [super removePlaceholder];
1345   [tabStripController_ insertPlaceholderForTab:nil frame:NSZeroRect];
1348 - (BOOL)isDragSessionActive {
1349   // The tab can be dragged within the existing tab strip or detached
1350   // into its own window (then the overlay window will be present).
1351   return [[self tabStripController] isDragSessionActive] ||
1352          [self overlayWindow] != nil;
1355 - (BOOL)tabDraggingAllowed {
1356   return [tabStripController_ tabDraggingAllowed];
1359 - (BOOL)tabTearingAllowed {
1360   return ![self isInAnyFullscreenMode];
1363 - (BOOL)windowMovementAllowed {
1364   return ![self isInAnyFullscreenMode];
1367 - (BOOL)isTabFullyVisible:(TabView*)tab {
1368   return [tabStripController_ isTabFullyVisible:tab];
1371 - (void)showNewTabButton:(BOOL)show {
1372   [tabStripController_ showNewTabButton:show];
1375 - (BOOL)shouldShowAvatar {
1376   if (![self hasTabStrip])
1377     return NO;
1378   if (browser_->profile()->IsOffTheRecord())
1379     return YES;
1381   ProfileInfoCache& cache =
1382       g_browser_process->profile_manager()->GetProfileInfoCache();
1383   if (cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath()) ==
1384       std::string::npos) {
1385     return NO;
1386   }
1388   return AvatarMenu::ShouldShowAvatarMenu();
1391 - (BOOL)shouldUseNewAvatarButton {
1392   return switches::IsNewAvatarMenu() &&
1393       profiles::IsRegularOrGuestSession(browser_.get());
1396 - (BOOL)isBookmarkBarVisible {
1397   return [bookmarkBarController_ isVisible];
1400 - (BOOL)isBookmarkBarAnimating {
1401   return [bookmarkBarController_ isAnimationRunning];
1404 - (BookmarkBarController*)bookmarkBarController {
1405   return bookmarkBarController_;
1408 - (DevToolsController*)devToolsController {
1409   return devToolsController_;
1412 - (BOOL)isDownloadShelfVisible {
1413   return downloadShelfController_ != nil &&
1414       [downloadShelfController_ isVisible];
1417 - (void)createAndAddDownloadShelf {
1418   if (!downloadShelfController_.get()) {
1419     downloadShelfController_.reset([[DownloadShelfController alloc]
1420         initWithBrowser:browser_.get() resizeDelegate:self]);
1421     [self.chromeContentView addSubview:[downloadShelfController_ view]];
1422   }
1425 - (DownloadShelfController*)downloadShelf {
1426   return downloadShelfController_;
1429 - (void)addFindBar:(FindBarCocoaController*)findBarCocoaController {
1430   // Shouldn't call addFindBar twice.
1431   DCHECK(!findBarCocoaController_.get());
1433   // Create a controller for the findbar.
1434   findBarCocoaController_.reset([findBarCocoaController retain]);
1435   [self layoutSubviews];
1436   [self updateSubviewZOrder];
1439 - (NSWindow*)createFullscreenWindow {
1440   return [[[FullscreenWindow alloc] initForScreen:[[self window] screen]]
1441            autorelease];
1444 - (NSInteger)numberOfTabs {
1445   // count() includes pinned tabs.
1446   return browser_->tab_strip_model()->count();
1449 - (BOOL)hasLiveTabs {
1450   return !browser_->tab_strip_model()->empty();
1453 - (NSString*)activeTabTitle {
1454   WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
1455   return base::SysUTF16ToNSString(contents->GetTitle());
1458 - (NSRect)regularWindowFrame {
1459   return [self isInAnyFullscreenMode] ? savedRegularWindowFrame_
1460                                       : [[self window] frame];
1463 // (Override of |TabWindowController| method.)
1464 - (BOOL)hasTabStrip {
1465   return [self supportsWindowFeature:Browser::FEATURE_TABSTRIP];
1468 - (BOOL)isTabDraggable:(NSView*)tabView {
1469   // TODO(avi, thakis): ConstrainedWindowSheetController has no api to move
1470   // tabsheets between windows. Until then, we have to prevent having to move a
1471   // tabsheet between windows, e.g. no tearing off of tabs.
1472   int index = [tabStripController_ modelIndexForTabView:tabView];
1473   WebContents* contents = browser_->tab_strip_model()->GetWebContentsAt(index);
1474   if (!contents)
1475     return NO;
1477   const web_modal::WebContentsModalDialogManager* manager =
1478       web_modal::WebContentsModalDialogManager::FromWebContents(contents);
1479   return !manager || !manager->IsDialogActive();
1482 // TabStripControllerDelegate protocol.
1483 - (void)onActivateTabWithContents:(WebContents*)contents {
1484   // Update various elements that are interested in knowing the current
1485   // WebContents.
1487   // Update all the UI bits.
1488   windowShim_->UpdateTitleBar();
1490   // Update the bookmark bar.
1491   // TODO(viettrungluu): perhaps update to not terminate running animations (if
1492   // applicable)?
1493   windowShim_->BookmarkBarStateChanged(
1494       BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
1496   [infoBarContainerController_ changeWebContents:contents];
1498   // Must do this after bookmark and infobar updates to avoid
1499   // unnecesary resize in contents.
1500   [devToolsController_ updateDevToolsForWebContents:contents
1501                                         withProfile:browser_->profile()];
1504 - (void)onTabChanged:(TabStripModelObserver::TabChangeType)change
1505         withContents:(WebContents*)contents {
1506   // Update titles if this is the currently selected tab and if it isn't just
1507   // the loading state which changed.
1508   if (change != TabStripModelObserver::LOADING_ONLY)
1509     windowShim_->UpdateTitleBar();
1511   // Update the bookmark bar if this is the currently selected tab and if it
1512   // isn't just the title which changed. This for transitions between the NTP
1513   // (showing its floating bookmark bar) and normal web pages (showing no
1514   // bookmark bar).
1515   // TODO(viettrungluu): perhaps update to not terminate running animations?
1516   if (change != TabStripModelObserver::TITLE_NOT_LOADING) {
1517     windowShim_->BookmarkBarStateChanged(
1518         BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
1519   }
1522 - (void)onTabDetachedWithContents:(WebContents*)contents {
1523   [infoBarContainerController_ tabDetachedWithContents:contents];
1526 - (void)userChangedTheme {
1527   NSView* rootView = [[[self window] contentView] superview];
1528   [rootView cr_recursivelyInvokeBlock:^(id view) {
1529       if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)])
1530         [view windowDidChangeTheme];
1532       // TODO(andresantoso): Remove this once all themed views respond to
1533       // windowDidChangeTheme above.
1534       [view setNeedsDisplay:YES];
1535   }];
1538 - (ui::ThemeProvider*)themeProvider {
1539   return ThemeServiceFactory::GetForProfile(browser_->profile());
1542 - (ThemedWindowStyle)themedWindowStyle {
1543   ThemedWindowStyle style = 0;
1544   if (browser_->profile()->IsOffTheRecord())
1545     style |= THEMED_INCOGNITO;
1547   if (browser_->is_devtools())
1548     style |= THEMED_DEVTOOLS;
1549   if (browser_->is_type_popup())
1550     style |= THEMED_POPUP;
1552   return style;
1555 - (NSPoint)themeImagePositionForAlignment:(ThemeImageAlignment)alignment {
1556   NSView* windowChromeView = [[[self window] contentView] superview];
1557   NSView* tabStripView = nil;
1558   if ([self hasTabStrip])
1559     tabStripView = [self tabStripView];
1560   return [BrowserWindowUtils themeImagePositionFor:windowChromeView
1561                                       withTabStrip:tabStripView
1562                                          alignment:alignment];
1565 - (NSPoint)bookmarkBubblePoint {
1566   return [toolbarController_ bookmarkBubblePoint];
1569 // Show the bookmark bubble (e.g. user just clicked on the STAR).
1570 - (void)showBookmarkBubbleForURL:(const GURL&)url
1571                alreadyBookmarked:(BOOL)alreadyMarked {
1572   if (bookmarkBubbleObserver_.get())
1573     return;
1575   bookmarkBubbleObserver_.reset(new BookmarkBubbleObserverCocoa(self));
1577   if (chrome::ToolkitViewsDialogsEnabled()) {
1578     chrome::ShowBookmarkBubbleViewsAtPoint(
1579         gfx::ScreenPointFromNSPoint(
1580             [[self window] convertBaseToScreen:[self bookmarkBubblePoint]]),
1581         [[self window] contentView], bookmarkBubbleObserver_.get(),
1582         browser_.get(), url, alreadyMarked);
1583   } else {
1584     BookmarkModel* model =
1585         BookmarkModelFactory::GetForProfile(browser_->profile());
1586     bookmarks::ManagedBookmarkService* managed =
1587         ManagedBookmarkServiceFactory::GetForProfile(browser_->profile());
1588     const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url);
1589     bookmarkBubbleController_ = [[BookmarkBubbleController alloc]
1590         initWithParentWindow:[self window]
1591               bubbleObserver:bookmarkBubbleObserver_.get()
1592                      managed:managed
1593                        model:model
1594                         node:node
1595            alreadyBookmarked:alreadyMarked];
1596     [bookmarkBubbleController_ showWindow:self];
1597   }
1598   DCHECK(bookmarkBubbleObserver_);
1601 - (void)bookmarkBubbleClosed {
1602   // Nil out the weak bookmark bubble controller reference.
1603   bookmarkBubbleController_ = nil;
1604   bookmarkBubbleObserver_.reset();
1607 // Handle the editBookmarkNode: action sent from bookmark bubble controllers.
1608 - (void)editBookmarkNode:(id)sender {
1609   BOOL responds = [sender respondsToSelector:@selector(node)];
1610   DCHECK(responds);
1611   if (responds) {
1612     const BookmarkNode* node = [sender node];
1613     if (node)
1614       BookmarkEditor::Show([self window], browser_->profile(),
1615           BookmarkEditor::EditDetails::EditNode(node),
1616           BookmarkEditor::SHOW_TREE);
1617   }
1620 - (void)showTranslateBubbleForWebContents:(content::WebContents*)contents
1621                                      step:(translate::TranslateStep)step
1622                                 errorType:(translate::TranslateErrors::Type)
1623                                 errorType {
1624   // TODO(hajimehoshi): The similar logic exists at TranslateBubbleView::
1625   // ShowBubble. This should be unified.
1626   if (translateBubbleController_) {
1627     // When the user reads the advanced setting panel, the bubble should not be
1628     // changed because they are focusing on the bubble.
1629     if (translateBubbleController_.webContents == contents &&
1630         translateBubbleController_.model->GetViewState() ==
1631         TranslateBubbleModel::VIEW_STATE_ADVANCED) {
1632       return;
1633     }
1634     if (step != translate::TRANSLATE_STEP_TRANSLATE_ERROR) {
1635       TranslateBubbleModel::ViewState viewState =
1636           TranslateBubbleModelImpl::TranslateStepToViewState(step);
1637       [translateBubbleController_ switchView:viewState];
1638     } else {
1639       [translateBubbleController_ switchToErrorView:errorType];
1640     }
1641     return;
1642   }
1644   std::string sourceLanguage;
1645   std::string targetLanguage;
1646   ChromeTranslateClient::GetTranslateLanguages(
1647       contents, &sourceLanguage, &targetLanguage);
1649   scoped_ptr<translate::TranslateUIDelegate> uiDelegate(
1650       new translate::TranslateUIDelegate(
1651           ChromeTranslateClient::GetManagerFromWebContents(contents)
1652               ->GetWeakPtr(),
1653           sourceLanguage,
1654           targetLanguage));
1655   scoped_ptr<TranslateBubbleModel> model(
1656       new TranslateBubbleModelImpl(step, uiDelegate.Pass()));
1657   translateBubbleController_ = [[TranslateBubbleController alloc]
1658                                  initWithParentWindow:self
1659                                                 model:model.Pass()
1660                                           webContents:contents];
1661   [translateBubbleController_ showWindow:nil];
1663   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1664   [center addObserver:self
1665              selector:@selector(translateBubbleWindowWillClose:)
1666                  name:NSWindowWillCloseNotification
1667                object:[translateBubbleController_ window]];
1670 - (void)dismissPermissionBubble {
1671   PermissionBubbleManager* manager = [self permissionBubbleManager];
1672   if (manager)
1673     manager->HideBubble();
1676 // Nil out the weak translate bubble controller reference.
1677 - (void)translateBubbleWindowWillClose:(NSNotification*)notification {
1678   DCHECK_EQ([notification object], [translateBubbleController_ window]);
1680   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1681   [center removeObserver:self
1682                     name:NSWindowWillCloseNotification
1683                   object:[translateBubbleController_ window]];
1684   translateBubbleController_ = nil;
1687 // If the browser is in incognito mode or has multi-profiles, install the image
1688 // view to decorate the window at the upper right. Use the same base y
1689 // coordinate as the tab strip.
1690 - (void)installAvatar {
1691   // Install the image into the badge view. Hide it for now; positioning and
1692   // sizing will be done by the layout code. The AvatarIcon will choose which
1693   // image to display based on the browser. The AvatarButton will display
1694   // the browser profile's name unless the browser is incognito.
1695   NSView* view;
1696   if ([self shouldUseNewAvatarButton]) {
1697     avatarButtonController_.reset(
1698       [[AvatarButtonController alloc] initWithBrowser:browser_.get()]);
1699   } else {
1700     avatarButtonController_.reset(
1701       [[AvatarIconController alloc] initWithBrowser:browser_.get()]);
1702   }
1703   view = [avatarButtonController_ view];
1704   [view setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
1705   [view setHidden:![self shouldShowAvatar]];
1707   // Install the view.
1708   [[[self window] contentView] addSubview:view];
1711 // Called when we get a three-finger swipe.
1712 - (void)swipeWithEvent:(NSEvent*)event {
1713   CGFloat deltaX = [event deltaX];
1714   CGFloat deltaY = [event deltaY];
1716   // Map forwards and backwards to history; left is positive, right is negative.
1717   unsigned int command = 0;
1718   if (deltaX > 0.5) {
1719     command = IDC_BACK;
1720   } else if (deltaX < -0.5) {
1721     command = IDC_FORWARD;
1722   } else if (deltaY > 0.5) {
1723     // TODO(pinkerton): figure out page-up, http://crbug.com/16305
1724   } else if (deltaY < -0.5) {
1725     // TODO(pinkerton): figure out page-down, http://crbug.com/16305
1726   }
1728   // Ensure the command is valid first (ExecuteCommand() won't do that) and
1729   // then make it so.
1730   if (chrome::IsCommandEnabled(browser_.get(), command)) {
1731     chrome::ExecuteCommandWithDisposition(
1732         browser_.get(),
1733         command,
1734         ui::WindowOpenDispositionFromNSEvent(event));
1735   }
1738 // Delegate method called when window is resized.
1739 - (void)windowDidResize:(NSNotification*)notification {
1740   [self saveWindowPositionIfNeeded];
1742   // Resize (and possibly move) the status bubble. Note that we may get called
1743   // when the status bubble does not exist.
1744   if (statusBubble_) {
1745     statusBubble_->UpdateSizeAndPosition();
1746   }
1748   // Let the selected RenderWidgetHostView know, so that it can tell plugins.
1749   if (WebContents* contents = [self webContents]) {
1750     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
1751       rwhv->WindowFrameChanged();
1752   }
1754   // The FindBar needs to know its own position to properly detect overlaps
1755   // with find results. The position changes whenever the window is resized,
1756   // and |layoutSubviews| computes the FindBar's position.
1757   // TODO: calling |layoutSubviews| here is a waste, find a better way to
1758   // do this.
1759   if ([findBarCocoaController_ isFindBarVisible])
1760     [self layoutSubviews];
1763 // Handle the openLearnMoreAboutCrashLink: action from SadTabView when
1764 // "Learn more" link in "Aw snap" page (i.e. crash page or sad tab) is
1765 // clicked. Decoupling the action from its target makes unit testing possible.
1766 - (void)openLearnMoreAboutCrashLink:(id)sender {
1767   if (WebContents* contents = [self webContents]) {
1768     OpenURLParams params(GURL(chrome::kCrashReasonURL), Referrer(), CURRENT_TAB,
1769                          ui::PAGE_TRANSITION_LINK, false);
1770     contents->OpenURL(params);
1771   }
1774 // Delegate method called when window did move. (See below for why we don't use
1775 // |-windowWillMove:|, which is called less frequently than |-windowDidMove|
1776 // instead.)
1777 - (void)windowDidMove:(NSNotification*)notification {
1778   [self saveWindowPositionIfNeeded];
1780   NSWindow* window = [self window];
1781   NSRect windowFrame = [window frame];
1782   NSRect workarea = [[window screen] visibleFrame];
1784   // We reset the window growth state whenever the window is moved out of the
1785   // work area or away (up or down) from the bottom or top of the work area.
1786   // Unfortunately, Cocoa sends |-windowWillMove:| too frequently (including
1787   // when clicking on the title bar to activate), and of course
1788   // |-windowWillMove| is called too early for us to apply our heuristic. (The
1789   // heuristic we use for detecting window movement is that if |windowTopGrowth_
1790   // > 0|, then we should be at the bottom of the work area -- if we're not,
1791   // we've moved. Similarly for the other side.)
1792   if (!NSContainsRect(workarea, windowFrame) ||
1793       (windowTopGrowth_ > 0 && NSMinY(windowFrame) != NSMinY(workarea)) ||
1794       (windowBottomGrowth_ > 0 && NSMaxY(windowFrame) != NSMaxY(workarea)))
1795     [self resetWindowGrowthState];
1797   // Let the selected RenderWidgetHostView know, so that it can tell plugins.
1798   if (WebContents* contents = [self webContents]) {
1799     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
1800       rwhv->WindowFrameChanged();
1801   }
1804 // Delegate method called when window will be resized; not called for
1805 // |-setFrame:display:|.
1806 - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize {
1807   [self resetWindowGrowthState];
1808   return frameSize;
1811 // Delegate method: see |NSWindowDelegate| protocol.
1812 - (id)windowWillReturnFieldEditor:(NSWindow*)sender toObject:(id)obj {
1813   // Ask the toolbar controller if it wants to return a custom field editor
1814   // for the specific object.
1815   return [toolbarController_ customFieldEditorForObject:obj];
1818 // (Needed for |BookmarkBarControllerDelegate| protocol.)
1819 - (void)bookmarkBar:(BookmarkBarController*)controller
1820  didChangeFromState:(BookmarkBar::State)oldState
1821             toState:(BookmarkBar::State)newState {
1822   [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
1823   [self adjustToolbarAndBookmarkBarForCompression:
1824           [controller getDesiredToolbarHeightCompression]];
1827 // (Needed for |BookmarkBarControllerDelegate| protocol.)
1828 - (void)bookmarkBar:(BookmarkBarController*)controller
1829 willAnimateFromState:(BookmarkBar::State)oldState
1830             toState:(BookmarkBar::State)newState {
1831   [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
1832   [self adjustToolbarAndBookmarkBarForCompression:
1833           [controller getDesiredToolbarHeightCompression]];
1836 // (Private/TestingAPI)
1837 - (void)resetWindowGrowthState {
1838   windowTopGrowth_ = 0;
1839   windowBottomGrowth_ = 0;
1840   isShrinkingFromZoomed_ = NO;
1843 - (NSSize)overflowFrom:(NSRect)source
1844                     to:(NSRect)target {
1845   // If |source|'s boundary is outside of |target|'s, set its distance
1846   // to |x|.  Note that |source| can overflow to both side, but we
1847   // have nothing to do for such case.
1848   CGFloat x = 0;
1849   if (NSMaxX(target) < NSMaxX(source)) // |source| overflows to right
1850     x = NSMaxX(source) - NSMaxX(target);
1851   else if (NSMinX(source) < NSMinX(target)) // |source| overflows to left
1852     x = NSMinX(source) - NSMinX(target);
1854   // Same as |x| above.
1855   CGFloat y = 0;
1856   if (NSMaxY(target) < NSMaxY(source))
1857     y = NSMaxY(source) - NSMaxY(target);
1858   else if (NSMinY(source) < NSMinY(target))
1859     y = NSMinY(source) - NSMinY(target);
1861   return NSMakeSize(x, y);
1864 // (Private/TestingAPI)
1865 - (ExclusiveAccessBubbleWindowController*)
1866         exclusiveAccessBubbleWindowController {
1867   return exclusiveAccessBubbleWindowController_.get();
1870 - (NSRect)omniboxPopupAnchorRect {
1871   // Start with toolbar rect.
1872   NSView* toolbarView = [toolbarController_ view];
1873   NSRect anchorRect = [toolbarView frame];
1875   // Adjust to account for height and possible bookmark bar. Compress by 1
1876   // to account for the separator.
1877   anchorRect.origin.y =
1878       NSMaxY(anchorRect) - [toolbarController_ desiredHeightForCompression:1];
1880   // Shift to window base coordinates.
1881   return [[toolbarView superview] convertRect:anchorRect toView:nil];
1884 - (void)sheetDidEnd:(NSWindow*)sheet
1885          returnCode:(NSInteger)code
1886             context:(void*)context {
1887   [sheet orderOut:self];
1890 - (void)executeExtensionCommand:(const std::string&)extension_id
1891                         command:(const extensions::Command&)command {
1892   // Global commands are handled by the ExtensionCommandsGlobalRegistry
1893   // instance.
1894   DCHECK(!command.global());
1895   extension_keybinding_registry_->ExecuteCommand(extension_id,
1896                                                  command.accelerator());
1899 @end  // @implementation BrowserWindowController
1901 @implementation BrowserWindowController(Fullscreen)
1903 - (void)handleLionToggleFullscreen {
1904   DCHECK(base::mac::IsOSLionOrLater());
1905   chrome::ExecuteCommand(browser_.get(), IDC_FULLSCREEN);
1908 - (void)enterBrowserFullscreenWithToolbar:(BOOL)withToolbar {
1909   if (!chrome::mac::SupportsSystemFullscreen()) {
1910     if (![self isInImmersiveFullscreen])
1911       [self enterImmersiveFullscreen];
1912     return;
1913   }
1915   if ([self isInAppKitFullscreen]) {
1916     [self updateFullscreenWithToolbar:withToolbar];
1917   } else {
1918     // Need to invoke AppKit Fullscreen API. Presentation mode (if set) will
1919     // automatically be enabled in |-windowWillEnterFullScreen:|.
1920     enteringPresentationMode_ = !withToolbar;
1921     [self enterAppKitFullscreen];
1922   }
1925 - (void)updateFullscreenWithToolbar:(BOOL)withToolbar {
1926   [self adjustUIForSlidingFullscreenStyle:
1927             withToolbar ? fullscreen_mac::OMNIBOX_TABS_PRESENT
1928                         : fullscreen_mac::OMNIBOX_TABS_HIDDEN];
1931 - (void)updateFullscreenExitBubbleURL:(const GURL&)url
1932                            bubbleType:(ExclusiveAccessBubbleType)bubbleType {
1933   fullscreenUrl_ = url;
1934   exclusiveAccessBubbleType_ = bubbleType;
1935   [self layoutSubviews];
1936   [self showFullscreenExitBubbleIfNecessary];
1939 - (BOOL)isInAnyFullscreenMode {
1940   return [self isInImmersiveFullscreen] || [self isInAppKitFullscreen];
1943 - (BOOL)isInImmersiveFullscreen {
1944   return fullscreenWindow_.get() != nil || enteringImmersiveFullscreen_;
1947 - (BOOL)isInAppKitFullscreen {
1948   return !exitingAppKitFullscreen_ &&
1949          (([[self window] styleMask] & NSFullScreenWindowMask) ==
1950               NSFullScreenWindowMask ||
1951           enteringAppKitFullscreen_);
1954 - (void)enterExtensionFullscreenForURL:(const GURL&)url
1955                             bubbleType:(ExclusiveAccessBubbleType)bubbleType {
1956   if (chrome::mac::SupportsSystemFullscreen()) {
1957     fullscreenUrl_ = url;
1958     exclusiveAccessBubbleType_ = bubbleType;
1959     [self enterBrowserFullscreenWithToolbar:NO];
1960   } else {
1961     [self enterImmersiveFullscreen];
1962     DCHECK(!url.is_empty());
1963     [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType];
1964   }
1967 - (void)enterWebContentFullscreenForURL:(const GURL&)url
1968                              bubbleType:(ExclusiveAccessBubbleType)bubbleType {
1969   [self enterImmersiveFullscreen];
1970   if (!url.is_empty())
1971     [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType];
1974 - (void)exitAnyFullscreen {
1975   // TODO(erikchen): Fullscreen modes should stack. Should be able to exit
1976   // Immersive Fullscreen and still be in AppKit Fullscreen.
1977   if ([self isInAppKitFullscreen])
1978     [self exitAppKitFullscreen];
1979   if ([self isInImmersiveFullscreen])
1980     [self exitImmersiveFullscreen];
1983 - (BOOL)inPresentationMode {
1984   return presentationModeController_.get() &&
1985          [presentationModeController_ inPresentationMode] &&
1986          presentationModeController_.get().slidingStyle ==
1987              fullscreen_mac::OMNIBOX_TABS_HIDDEN;
1990 - (void)resizeFullscreenWindow {
1991   DCHECK([self isInAnyFullscreenMode]);
1992   if (![self isInAnyFullscreenMode])
1993     return;
1995   NSWindow* window = [self window];
1996   [window setFrame:[[window screen] frame] display:YES];
1997   [self layoutSubviews];
2000 - (BOOL)isBarVisibilityLockedForOwner:(id)owner {
2001   DCHECK(owner);
2002   DCHECK(barVisibilityLocks_);
2003   return [barVisibilityLocks_ containsObject:owner];
2006 - (void)lockBarVisibilityForOwner:(id)owner
2007                     withAnimation:(BOOL)animate
2008                             delay:(BOOL)delay {
2009   if (![self isBarVisibilityLockedForOwner:owner]) {
2010     [barVisibilityLocks_ addObject:owner];
2012     // If enabled, show the overlay if necessary (and if in presentation mode).
2013     if (barVisibilityUpdatesEnabled_) {
2014       [presentationModeController_ ensureOverlayShownWithAnimation:animate
2015                                                              delay:delay];
2016     }
2017   }
2020 - (void)releaseBarVisibilityForOwner:(id)owner
2021                        withAnimation:(BOOL)animate
2022                                delay:(BOOL)delay {
2023   if ([self isBarVisibilityLockedForOwner:owner]) {
2024     [barVisibilityLocks_ removeObject:owner];
2026     // If enabled, hide the overlay if necessary (and if in presentation mode).
2027     if (barVisibilityUpdatesEnabled_ &&
2028         ![barVisibilityLocks_ count]) {
2029       [presentationModeController_ ensureOverlayHiddenWithAnimation:animate
2030                                                               delay:delay];
2031     }
2032   }
2035 - (BOOL)floatingBarHasFocus {
2036   NSResponder* focused = [[self window] firstResponder];
2037   return [focused isKindOfClass:[AutocompleteTextFieldEditor class]];
2040 @end  // @implementation BrowserWindowController(Fullscreen)
2043 @implementation BrowserWindowController(WindowType)
2045 - (BOOL)supportsWindowFeature:(int)feature {
2046   return browser_->SupportsWindowFeature(
2047       static_cast<Browser::WindowFeature>(feature));
2050 - (BOOL)hasTitleBar {
2051   return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR];
2054 - (BOOL)hasToolbar {
2055   return [self supportsWindowFeature:Browser::FEATURE_TOOLBAR];
2058 - (BOOL)hasLocationBar {
2059   return [self supportsWindowFeature:Browser::FEATURE_LOCATIONBAR];
2062 - (BOOL)supportsBookmarkBar {
2063   return [self supportsWindowFeature:Browser::FEATURE_BOOKMARKBAR];
2066 - (BOOL)isTabbedWindow {
2067   return browser_->is_type_tabbed();
2070 @end  // @implementation BrowserWindowController(WindowType)