Switch global error menu icon to vectorized MD asset
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / browser_window_controller.mm
blobb2103789057b29653bce2c19ec402e8a00953ace
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 #import "chrome/browser/app_controller_mac.h"
19 #include "chrome/browser/bookmarks/bookmark_model_factory.h"
20 #include "chrome/browser/bookmarks/managed_bookmark_service_factory.h"
21 #include "chrome/browser/browser_process.h"
22 #include "chrome/browser/devtools/devtools_window.h"
23 #include "chrome/browser/extensions/extension_commands_global_registry.h"
24 #include "chrome/browser/fullscreen.h"
25 #include "chrome/browser/profiles/avatar_menu.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/profiles/profile_info_cache.h"
28 #include "chrome/browser/profiles/profile_manager.h"
29 #include "chrome/browser/profiles/profiles_state.h"
30 #include "chrome/browser/themes/theme_service.h"
31 #include "chrome/browser/themes/theme_service_factory.h"
32 #include "chrome/browser/translate/chrome_translate_client.h"
33 #include "chrome/browser/ui/bookmarks/bookmark_editor.h"
34 #include "chrome/browser/ui/bookmarks/bookmark_utils.h"
35 #include "chrome/browser/ui/browser.h"
36 #include "chrome/browser/ui/browser_command_controller.h"
37 #include "chrome/browser/ui/browser_commands.h"
38 #include "chrome/browser/ui/browser_dialogs.h"
39 #include "chrome/browser/ui/browser_instant_controller.h"
40 #include "chrome/browser/ui/browser_list.h"
41 #include "chrome/browser/ui/browser_window_state.h"
42 #import "chrome/browser/ui/cocoa/background_gradient_view.h"
43 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bar_controller.h"
44 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_bubble_observer_cocoa.h"
45 #import "chrome/browser/ui/cocoa/bookmarks/bookmark_editor_controller.h"
46 #import "chrome/browser/ui/cocoa/browser_window_cocoa.h"
47 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
48 #import "chrome/browser/ui/cocoa/browser_window_layout.h"
49 #import "chrome/browser/ui/cocoa/browser_window_utils.h"
50 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
51 #import "chrome/browser/ui/cocoa/download/download_shelf_controller.h"
52 #include "chrome/browser/ui/cocoa/extensions/extension_keybinding_registry_cocoa.h"
53 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
54 #import "chrome/browser/ui/cocoa/find_bar/find_bar_bridge.h"
55 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
56 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
57 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
58 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
59 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
60 #import "chrome/browser/ui/cocoa/location_bar/autocomplete_text_field_editor.h"
61 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
62 #import "chrome/browser/ui/cocoa/profiles/avatar_base_controller.h"
63 #import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h"
64 #import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h"
65 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
66 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
67 #import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h"
68 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
69 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
70 #import "chrome/browser/ui/cocoa/tabs/tab_view.h"
71 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
72 #import "chrome/browser/ui/cocoa/translate/translate_bubble_controller.h"
73 #include "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h"
74 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
75 #include "chrome/browser/ui/location_bar/location_bar.h"
76 #include "chrome/browser/ui/tabs/tab_strip_model.h"
77 #include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
78 #include "chrome/browser/ui/toolbar/encoding_menu_controller.h"
79 #include "chrome/browser/ui/translate/translate_bubble_model_impl.h"
80 #include "chrome/browser/ui/website_settings/permission_bubble_manager.h"
81 #include "chrome/browser/ui/window_sizer/window_sizer.h"
82 #include "chrome/common/chrome_switches.h"
83 #include "chrome/common/extensions/command.h"
84 #include "chrome/common/url_constants.h"
85 #include "chrome/grit/generated_resources.h"
86 #include "chrome/grit/locale_settings.h"
87 #include "components/bookmarks/browser/bookmark_model.h"
88 #include "components/bookmarks/managed/managed_bookmark_service.h"
89 #include "components/signin/core/common/profile_management_switches.h"
90 #include "components/translate/core/browser/translate_manager.h"
91 #include "components/translate/core/browser/translate_ui_delegate.h"
92 #include "components/web_modal/web_contents_modal_dialog_manager.h"
93 #include "content/public/browser/render_view_host.h"
94 #include "content/public/browser/render_widget_host_view.h"
95 #include "content/public/browser/web_contents.h"
96 #import "ui/base/cocoa/cocoa_base_utils.h"
97 #import "ui/base/cocoa/nsview_additions.h"
98 #include "ui/base/l10n/l10n_util.h"
99 #include "ui/base/l10n/l10n_util_mac.h"
100 #import "ui/gfx/mac/coordinate_conversion.h"
101 #include "ui/gfx/mac/scoped_cocoa_disable_screen_updates.h"
103 using bookmarks::BookmarkModel;
104 using bookmarks::BookmarkNode;
105 using l10n_util::GetStringUTF16;
106 using l10n_util::GetNSStringWithFixup;
107 using l10n_util::GetNSStringFWithFixup;
109 // ORGANIZATION: This is a big file. It is (in principle) organized as follows
110 // (in order):
111 // 1. Interfaces. Very short, one-time-use classes may include an implementation
112 //    immediately after their interface.
113 // 2. The general implementation section, ordered as follows:
114 //      i. Public methods and overrides.
115 //     ii. Overrides/implementations of undocumented methods.
116 //    iii. Delegate methods for various protocols, formal and informal, to which
117 //        |BrowserWindowController| conforms.
118 // 3. (temporary) Implementation sections for various categories.
120 // Private methods are defined and implemented separately in
121 // browser_window_controller_private.{h,mm}.
123 // Not all of the above guidelines are followed and more (re-)organization is
124 // needed. BUT PLEASE TRY TO KEEP THIS FILE ORGANIZED. I'd rather re-organize as
125 // little as possible, since doing so messes up the file's history.
127 // TODO(viettrungluu): [crbug.com/35543] on-going re-organization, splitting
128 // things into multiple files -- the plan is as follows:
129 // - in general, everything stays in browser_window_controller.h, but is split
130 //   off into categories (see below)
131 // - core stuff stays in browser_window_controller.mm
132 // - ... overrides also stay (without going into a category, in particular)
133 // - private stuff which everyone needs goes into
134 //   browser_window_controller_private.{h,mm}; if no one else needs them, they
135 //   can go in individual files (see below)
136 // - area/task-specific stuff go in browser_window_controller_<area>.mm
137 // - ... in categories called "(<Area>)" or "(<PrivateArea>)"
138 // Plan of action:
139 // - first re-organize into categories
140 // - then split into files
142 // Notes on self-inflicted (not user-inflicted) window resizing and moving:
144 // When the bookmark bar goes from hidden to shown (on a non-NTP) page, or when
145 // the download shelf goes from hidden to shown, we grow the window downwards in
146 // order to maintain a constant content area size. When either goes from shown
147 // to hidden, we consequently shrink the window from the bottom, also to keep
148 // the content area size constant. To keep things simple, if the window is not
149 // entirely on-screen, we don't grow/shrink the window.
151 // The complications come in when there isn't enough room (on screen) below the
152 // window to accomodate the growth. In this case, we grow the window first
153 // downwards, and then upwards. So, when it comes to shrinking, we do the
154 // opposite: shrink from the top by the amount by which we grew at the top, and
155 // then from the bottom -- unless the user moved/resized/zoomed the window, in
156 // which case we "reset state" and just shrink from the bottom.
158 // A further complication arises due to the way in which "zoom" ("maximize")
159 // works on Mac OS X. Basically, for our purposes, a window is "zoomed" whenever
160 // it occupies the full available vertical space. (Note that the green zoom
161 // button does not track zoom/unzoomed state per se, but basically relies on
162 // this heuristic.) We don't, in general, want to shrink the window if the
163 // window is zoomed (scenario: window is zoomed, download shelf opens -- which
164 // doesn't cause window growth, download shelf closes -- shouldn't cause the
165 // window to become unzoomed!). However, if we grew the window
166 // (upwards/downwards) to become zoomed in the first place, we *should* shrink
167 // the window by the amounts by which we grew (scenario: window occupies *most*
168 // of vertical space, download shelf opens causing growth so that window
169 // occupies all of vertical space -- i.e., window is effectively zoomed,
170 // download shelf closes -- should return the window to its previous state).
172 // A major complication is caused by the way grows/shrinks are handled and
173 // animated. Basically, the BWC doesn't see the global picture, but it sees
174 // grows and shrinks in small increments (as dictated by the animation). Thus
175 // window growth/shrinkage (at the top/bottom) have to be tracked incrementally.
176 // Allowing shrinking from the zoomed state also requires tracking: We check on
177 // any shrink whether we're both zoomed and have previously grown -- if so, we
178 // set a flag, and constrain any resize by the allowed amounts. On further
179 // shrinks, we check the flag (since the size/position of the window will no
180 // longer indicate that the window is shrinking from an apparent zoomed state)
181 // and if it's set we continue to constrain the resize.
183 using content::OpenURLParams;
184 using content::Referrer;
185 using content::RenderWidgetHostView;
186 using content::WebContents;
188 @interface NSWindow (NSPrivateApis)
189 // Note: These functions are private, use -[NSObject respondsToSelector:]
190 // before calling them.
192 - (void)setBottomCornerRounded:(BOOL)rounded;
194 - (NSRect)_growBoxRect;
196 @end
198 @implementation BrowserWindowController
200 + (BrowserWindowController*)browserWindowControllerForWindow:(NSWindow*)window {
201   while (window) {
202     id controller = [window windowController];
203     if ([controller isKindOfClass:[BrowserWindowController class]])
204       return (BrowserWindowController*)controller;
205     window = [window parentWindow];
206   }
207   return nil;
210 + (BrowserWindowController*)browserWindowControllerForView:(NSView*)view {
211   NSWindow* window = [view window];
212   return [BrowserWindowController browserWindowControllerForWindow:window];
215 // Load the browser window nib and do any Cocoa-specific initialization.
216 // Takes ownership of |browser|. Note that the nib also sets this controller
217 // up as the window's delegate.
218 - (id)initWithBrowser:(Browser*)browser {
219   return [self initWithBrowser:browser takeOwnership:YES];
222 // Private(TestingAPI) init routine with testing options.
223 - (id)initWithBrowser:(Browser*)browser takeOwnership:(BOOL)ownIt {
224   bool hasTabStrip = browser->SupportsWindowFeature(Browser::FEATURE_TABSTRIP);
225   if ((self = [super initTabWindowControllerWithTabStrip:hasTabStrip])) {
226     DCHECK(browser);
227     initializing_ = YES;
228     browser_.reset(browser);
229     ownsBrowser_ = ownIt;
230     NSWindow* window = [self window];
231     // Make the content view for the window have a layer. This will make all
232     // sub-views have layers. This is necessary to ensure correct layer
233     // ordering of all child views and their layers.
234     [[window contentView] setWantsLayer:YES];
235     windowShim_.reset(new BrowserWindowCocoa(browser, self));
237     // Set different minimum sizes on tabbed windows vs non-tabbed, e.g. popups.
238     // This has to happen before -enforceMinWindowSize: is called further down.
239     NSSize minSize = [self isTabbedWindow] ?
240       NSMakeSize(400, 272) : NSMakeSize(100, 122);
241     [[self window] setMinSize:minSize];
243     // Create the bar visibility lock set; 10 is arbitrary, but should hopefully
244     // be big enough to hold all locks that'll ever be needed.
245     barVisibilityLocks_.reset([[NSMutableSet setWithCapacity:10] retain]);
247     // Set the window to not have rounded corners, which prevents the resize
248     // control from being inset slightly and looking ugly. Only bother to do
249     // this on Snow Leopard; on Lion and later all windows have rounded bottom
250     // corners, and this won't work anyway.
251     if (base::mac::IsOSSnowLeopard() &&
252         [window respondsToSelector:@selector(setBottomCornerRounded:)])
253       [window setBottomCornerRounded:NO];
255     // Lion will attempt to automagically save and restore the UI. This
256     // functionality appears to be leaky (or at least interacts badly with our
257     // architecture) and thus BrowserWindowController never gets released. This
258     // prevents the browser from being able to quit <http://crbug.com/79113>.
259     if ([window respondsToSelector:@selector(setRestorable:)])
260       [window setRestorable:NO];
262     // Get the windows to swish in on Lion.
263     if ([window respondsToSelector:@selector(setAnimationBehavior:)])
264       [window setAnimationBehavior:NSWindowAnimationBehaviorDocumentWindow];
266     // Get the most appropriate size for the window, then enforce the
267     // minimum width and height. The window shim will handle flipping
268     // the coordinates for us so we can use it to save some code.
269     // Note that this may leave a significant portion of the window
270     // offscreen, but there will always be enough window onscreen to
271     // drag the whole window back into view.
272     ui::WindowShowState show_state = ui::SHOW_STATE_DEFAULT;
273     gfx::Rect desiredContentRect;
274     chrome::GetSavedWindowBoundsAndShowState(browser_.get(),
275                                              &desiredContentRect,
276                                              &show_state);
277     gfx::Rect windowRect = desiredContentRect;
278     windowRect = [self enforceMinWindowSize:windowRect];
280     // When we are given x/y coordinates of 0 on a created popup window, assume
281     // none were given by the window.open() command.
282     if (browser_->is_type_popup() &&
283         windowRect.x() == 0 && windowRect.y() == 0) {
284       gfx::Size size = windowRect.size();
285       windowRect.set_origin(
286           WindowSizer::GetDefaultPopupOrigin(size,
287                                              browser_->host_desktop_type()));
288     }
290     // Size and position the window.  Note that it is not yet onscreen.  Popup
291     // windows may get resized later on in this function, once the actual size
292     // of the toolbar/tabstrip is known.
293     windowShim_->SetBounds(windowRect);
295     // Puts the incognito badge on the window frame, if necessary.
296     [self installAvatar];
298     // Create a sub-controller for the docked devTools and add its view to the
299     // hierarchy.
300     devToolsController_.reset([[DevToolsController alloc] init]);
301     [[devToolsController_ view] setFrame:[[self tabContentArea] bounds]];
302     [[self tabContentArea] addSubview:[devToolsController_ view]];
304     // Create the overlayable contents controller.  This provides the switch
305     // view that TabStripController needs.
306     overlayableContentsController_.reset(
307         [[OverlayableContentsController alloc] init]);
308     [[overlayableContentsController_ view]
309         setFrame:[[devToolsController_ view] bounds]];
310     [[devToolsController_ view]
311         addSubview:[overlayableContentsController_ view]];
313     // Create a controller for the tab strip, giving it the model object for
314     // this window's Browser and the tab strip view. The controller will handle
315     // registering for the appropriate tab notifications from the back-end and
316     // managing the creation of new tabs.
317     [self createTabStripController];
319     // Create a controller for the toolbar, giving it the toolbar model object
320     // and the toolbar view from the nib. The controller will handle
321     // registering for the appropriate command state changes from the back-end.
322     // Adds the toolbar to the content area.
323     toolbarController_.reset([[ToolbarController alloc]
324         initWithCommands:browser->command_controller()->command_updater()
325                  profile:browser->profile()
326                  browser:browser]);
327     [toolbarController_ setHasToolbar:[self hasToolbar]
328                        hasLocationBar:[self hasLocationBar]];
330     // Create a sub-controller for the bookmark bar.
331     bookmarkBarController_.reset(
332         [[BookmarkBarController alloc]
333             initWithBrowser:browser_.get()
334                initialWidth:NSWidth([[[self window] contentView] frame])
335                    delegate:self
336              resizeDelegate:self]);
337     [bookmarkBarController_ setBookmarkBarEnabled:[self supportsBookmarkBar]];
339     // Create the infobar container view, so we can pass it to the
340     // ToolbarController.
341     infoBarContainerController_.reset(
342         [[InfoBarContainerController alloc] initWithResizeDelegate:self]);
343     [self updateInfoBarTipVisibility];
345     // We don't want to try and show the bar before it gets placed in its parent
346     // view, so this step shoudn't be inside the bookmark bar controller's
347     // |-awakeFromNib|.
348     windowShim_->BookmarkBarStateChanged(
349         BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
351     // Allow bar visibility to be changed.
352     [self enableBarVisibilityUpdates];
354     // Set the window to participate in Lion Fullscreen mode.  Setting this flag
355     // has no effect on Snow Leopard or earlier.  Panels can share a fullscreen
356     // space with a tabbed window, but they can not be primary fullscreen
357     // windows.
358     // This ensures the fullscreen button is appropriately positioned. It must
359     // be done before calling layoutSubviews because the new avatar button's
360     // position depends on the fullscreen button's position, as well as
361     // TabStripController's rightIndentForControls.
362     // The fullscreen button's position may depend on the old avatar button's
363     // width, but that does not require calling layoutSubviews first.
364     NSUInteger collectionBehavior = [window collectionBehavior];
365     collectionBehavior |=
366        browser_->type() == Browser::TYPE_TABBED ||
367            browser_->type() == Browser::TYPE_POPUP ?
368                NSWindowCollectionBehaviorFullScreenPrimary :
369                NSWindowCollectionBehaviorFullScreenAuxiliary;
370     [window setCollectionBehavior:collectionBehavior];
372     [self layoutSubviews];
374     // For a popup window, |desiredContentRect| contains the desired height of
375     // the content, not of the whole window.  Now that all the views are laid
376     // out, measure the current content area size and grow if needed.  The
377     // window has not been placed onscreen yet, so this extra resize will not
378     // cause visible jank.
379     if (browser_->is_type_popup()) {
380       CGFloat deltaH = desiredContentRect.height() -
381                        NSHeight([[self tabContentArea] frame]);
382       // Do not shrink the window, as that may break minimum size invariants.
383       if (deltaH > 0) {
384         // Convert from tabContentArea coordinates to window coordinates.
385         NSSize convertedSize =
386             [[self tabContentArea] convertSize:NSMakeSize(0, deltaH)
387                                         toView:nil];
388         NSRect frame = [[self window] frame];
389         frame.size.height += convertedSize.height;
390         frame.origin.y -= convertedSize.height;
391         [[self window] setFrame:frame display:NO];
392       }
393     }
395     // Create the bridge for the status bubble.
396     statusBubble_ = new StatusBubbleMac([self window], self);
398     // Register for application hide/unhide notifications.
399     [[NSNotificationCenter defaultCenter]
400          addObserver:self
401             selector:@selector(applicationDidHide:)
402                 name:NSApplicationDidHideNotification
403               object:nil];
404     [[NSNotificationCenter defaultCenter]
405          addObserver:self
406             selector:@selector(applicationDidUnhide:)
407                 name:NSApplicationDidUnhideNotification
408               object:nil];
410     // This must be done after the view is added to the window since it relies
411     // on the window bounds to determine whether to show buttons or not.
412     if ([self hasToolbar])  // Do not create the buttons in popups.
413       [toolbarController_ createBrowserActionButtons];
415     extension_keybinding_registry_.reset(
416         new ExtensionKeybindingRegistryCocoa(browser_->profile(),
417             [self window],
418             extensions::ExtensionKeybindingRegistry::ALL_EXTENSIONS,
419             windowShim_.get()));
421     // We are done initializing now.
422     initializing_ = NO;
423   }
424   return self;
427 - (void)dealloc {
428   browser_->tab_strip_model()->CloseAllTabs();
430   // Explicitly release |presentationModeController_| here, as it may call back
431   // to this BWC in |-dealloc|.  We are required to call |-exitPresentationMode|
432   // before releasing the controller.
433   [presentationModeController_ exitPresentationMode];
434   presentationModeController_.reset();
436   // Under certain testing configurations we may not actually own the browser.
437   if (ownsBrowser_ == NO)
438     ignore_result(browser_.release());
440   [[NSNotificationCenter defaultCenter] removeObserver:self];
442   // Inform reference counted objects that the Browser will be destroyed. This
443   // ensures they invalidate their weak Browser* to prevent use-after-free.
444   // These may outlive the Browser if they are retained by something else. For
445   // example, since 10.10, the Nib loader internally creates an NSDictionary
446   // that retains NSViewControllers and is autoreleased, so there is no way to
447   // guarantee that the [super dealloc] call below will also call dealloc on the
448   // controllers.
449   [toolbarController_ browserWillBeDestroyed];
450   [tabStripController_ browserWillBeDestroyed];
451   [findBarCocoaController_ browserWillBeDestroyed];
452   [downloadShelfController_ browserWillBeDestroyed];
453   [bookmarkBarController_ browserWillBeDestroyed];
454   [avatarButtonController_ browserWillBeDestroyed];
456   [super dealloc];
459 - (gfx::Rect)enforceMinWindowSize:(gfx::Rect)bounds {
460   gfx::Rect checkedBounds = bounds;
462   NSSize minSize = [[self window] minSize];
463   if (bounds.width() < minSize.width)
464       checkedBounds.set_width(minSize.width);
465   if (bounds.height() < minSize.height)
466       checkedBounds.set_height(minSize.height);
468   return checkedBounds;
471 - (BrowserWindow*)browserWindow {
472   return windowShim_.get();
475 - (ToolbarController*)toolbarController {
476   return toolbarController_.get();
479 - (TabStripController*)tabStripController {
480   return tabStripController_.get();
483 - (FindBarCocoaController*)findBarCocoaController {
484   return findBarCocoaController_.get();
487 - (InfoBarContainerController*)infoBarContainerController {
488   return infoBarContainerController_.get();
491 - (StatusBubbleMac*)statusBubble {
492   return statusBubble_;
495 - (LocationBarViewMac*)locationBarBridge {
496   return [toolbarController_ locationBarBridge];
499 - (NSView*)floatingBarBackingView {
500   return floatingBarBackingView_;
503 - (OverlayableContentsController*)overlayableContentsController {
504   return overlayableContentsController_;
507 - (Profile*)profile {
508   return browser_->profile();
511 - (AvatarBaseController*)avatarButtonController {
512   return avatarButtonController_.get();
515 - (void)destroyBrowser {
516   [NSApp removeWindowsItem:[self window]];
518   // We need the window to go away now.
519   // We can't actually use |-autorelease| here because there's an embedded
520   // run loop in the |-performClose:| which contains its own autorelease pool.
521   // Instead call it after a zero-length delay, which gets us back to the main
522   // event loop.
523   [self performSelector:@selector(autorelease)
524              withObject:nil
525              afterDelay:0];
528 // Called when the window meets the criteria to be closed (ie,
529 // |-windowShouldClose:| returns YES). We must be careful to preserve the
530 // semantics of BrowserWindow::Close() and not call the Browser's dtor directly
531 // from this method.
532 - (void)windowWillClose:(NSNotification*)notification {
533   DCHECK_EQ([notification object], [self window]);
534   DCHECK(browser_->tab_strip_model()->empty());
535   [savedRegularWindow_ close];
536   // We delete statusBubble here because we need to kill off the dependency
537   // that its window has on our window before our window goes away.
538   delete statusBubble_;
539   statusBubble_ = NULL;
540   // We can't actually use |-autorelease| here because there's an embedded
541   // run loop in the |-performClose:| which contains its own autorelease pool.
542   // Instead call it after a zero-length delay, which gets us back to the main
543   // event loop.
544   [self performSelector:@selector(autorelease)
545              withObject:nil
546              afterDelay:0];
549 - (void)updateDevToolsForContents:(WebContents*)contents {
550   BOOL layout_changed =
551       [devToolsController_ updateDevToolsForWebContents:contents
552                                             withProfile:browser_->profile()];
553   if (layout_changed && [findBarCocoaController_ isFindBarVisible])
554     [self layoutSubviews];
557 // Called when the user wants to close a window or from the shutdown process.
558 // The Browser object is in control of whether or not we're allowed to close. It
559 // may defer closing due to several states, such as onUnload handlers needing to
560 // be fired. If closing is deferred, the Browser will handle the processing
561 // required to get us to the closing state and (by watching for all the tabs
562 // going away) will again call to close the window when it's finally ready.
563 - (BOOL)windowShouldClose:(id)sender {
564   // Disable updates while closing all tabs to avoid flickering.
565   gfx::ScopedCocoaDisableScreenUpdates disabler;
566   // Give beforeunload handlers the chance to cancel the close before we hide
567   // the window below.
568   if (!browser_->ShouldCloseWindow())
569     return NO;
571   // saveWindowPositionIfNeeded: only works if we are the last active
572   // window, but orderOut: ends up activating another window, so we
573   // have to save the window position before we call orderOut:.
574   [self saveWindowPositionIfNeeded];
576   bool fast_tab_closing_enabled =
577       base::CommandLine::ForCurrentProcess()->HasSwitch(
578           switches::kEnableFastUnload);
580   if (!browser_->tab_strip_model()->empty()) {
581     // Tab strip isn't empty.  Hide the frame (so it appears to have closed
582     // immediately) and close all the tabs, allowing the renderers to shut
583     // down. When the tab strip is empty we'll be called back again.
584     [[self window] orderOut:self];
585     browser_->OnWindowClosing();
586     if (fast_tab_closing_enabled)
587       browser_->tab_strip_model()->CloseAllTabs();
588     return NO;
589   } else if (fast_tab_closing_enabled &&
590         !browser_->HasCompletedUnloadProcessing()) {
591     // The browser needs to finish running unload handlers.
592     // Hide the window (so it appears to have closed immediately), and
593     // the browser will call us back again when it is ready to close.
594     [[self window] orderOut:self];
595     return NO;
596   }
598   // the tab strip is empty, it's ok to close the window
599   return YES;
602 // Called right after our window became the main window.
603 - (void)windowDidBecomeMain:(NSNotification*)notification {
604   if (chrome::GetLastActiveBrowser() != browser_) {
605     BrowserList::SetLastActive(browser_.get());
606     [self saveWindowPositionIfNeeded];
607   }
609   NSView* rootView = [[[self window] contentView] superview];
610   [rootView cr_recursivelyInvokeBlock:^(id view) {
611       if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)])
612         [view windowDidChangeActive];
613   }];
615   extensions::ExtensionCommandsGlobalRegistry::Get(browser_->profile())
616       ->set_registry_for_active_window(extension_keybinding_registry_.get());
619 - (void)windowDidResignMain:(NSNotification*)notification {
620   NSView* rootView = [[[self window] contentView] superview];
621   [rootView cr_recursivelyInvokeBlock:^(id view) {
622       if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)])
623         [view windowDidChangeActive];
624   }];
626   extensions::ExtensionCommandsGlobalRegistry::Get(browser_->profile())
627       ->set_registry_for_active_window(nullptr);
630 // Called when we are activated (when we gain focus).
631 - (void)windowDidBecomeKey:(NSNotification*)notification {
632   // We need to activate the controls (in the "WebView"). To do this, get the
633   // selected WebContents's RenderWidgetHostView and tell it to activate.
634   if (WebContents* contents = [self webContents]) {
635     WebContents* devtools = DevToolsWindow::GetInTabWebContents(
636         contents, NULL);
637     if (devtools) {
638       RenderWidgetHostView* devtoolsView = devtools->GetRenderWidgetHostView();
639       if (devtoolsView && devtoolsView->HasFocus()) {
640         devtoolsView->SetActive(true);
641         return;
642       }
643     }
645     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
646       rwhv->SetActive(true);
647   }
650 // Called when we are deactivated (when we lose focus).
651 - (void)windowDidResignKey:(NSNotification*)notification {
652   // If our app is still active and we're still the key window, ignore this
653   // message, since it just means that a menu extra (on the "system status bar")
654   // was activated; we'll get another |-windowDidResignKey| if we ever really
655   // lose key window status.
656   if ([NSApp isActive] && ([NSApp keyWindow] == [self window]))
657     return;
659   // We need to deactivate the controls (in the "WebView"). To do this, get the
660   // selected WebContents's RenderWidgetHostView and tell it to deactivate.
661   if (WebContents* contents = [self webContents]) {
662     WebContents* devtools = DevToolsWindow::GetInTabWebContents(
663         contents, NULL);
664     if (devtools) {
665       RenderWidgetHostView* devtoolsView = devtools->GetRenderWidgetHostView();
666       if (devtoolsView && devtoolsView->HasFocus()) {
667         devtoolsView->SetActive(false);
668         return;
669       }
670     }
672     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
673       rwhv->SetActive(false);
674   }
677 // Called when we have been minimized.
678 - (void)windowDidMiniaturize:(NSNotification *)notification {
679   [self saveWindowPositionIfNeeded];
681   // Let the selected RenderWidgetHostView know, so that it can tell plugins.
682   if (WebContents* contents = [self webContents]) {
683     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
684       rwhv->SetWindowVisibility(false);
685   }
688 // Called when we have been unminimized.
689 - (void)windowDidDeminiaturize:(NSNotification *)notification {
690   // Let the selected RenderWidgetHostView know, so that it can tell plugins.
691   if (WebContents* contents = [self webContents]) {
692     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
693       rwhv->SetWindowVisibility(true);
694   }
697 // Called when the application has been hidden.
698 - (void)applicationDidHide:(NSNotification *)notification {
699   // Let the selected RenderWidgetHostView know, so that it can tell plugins
700   // (unless we are minimized, in which case nothing has really changed).
701   if (![[self window] isMiniaturized]) {
702     if (WebContents* contents = [self webContents]) {
703       if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
704         rwhv->SetWindowVisibility(false);
705     }
706   }
709 // Called when the application has been unhidden.
710 - (void)applicationDidUnhide:(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 = [self webContents]) {
715       if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
716         rwhv->SetWindowVisibility(true);
717     }
718   }
721 // Called when the user clicks the zoom button (or selects it from the Window
722 // menu) to determine the "standard size" of the window, based on the content
723 // and other factors. If the current size/location differs nontrivally from the
724 // standard size, Cocoa resizes the window to the standard size, and saves the
725 // current size as the "user size". If the current size/location is the same (up
726 // to a fudge factor) as the standard size, Cocoa resizes the window to the
727 // saved user size. (It is possible for the two to coincide.) In this way, the
728 // zoom button acts as a toggle. We determine the standard size based on the
729 // content, but enforce a minimum width (calculated using the dimensions of the
730 // screen) to ensure websites with small intrinsic width (such as google.com)
731 // don't end up with a wee window. Moreover, we always declare the standard
732 // width to be at least as big as the current width, i.e., we never want zooming
733 // to the standard width to shrink the window. This is consistent with other
734 // browsers' behaviour, and is desirable in multi-tab situations. Note, however,
735 // that the "toggle" behaviour means that the window can still be "unzoomed" to
736 // the user size.
737 // Note: this method is also called from -isZoomed. If the returned zoomed rect
738 // equals the current window's frame, -isZoomed returns YES.
739 - (NSRect)windowWillUseStandardFrame:(NSWindow*)window
740                         defaultFrame:(NSRect)frame {
741   // Forget that we grew the window up (if we in fact did).
742   [self resetWindowGrowthState];
744   // |frame| already fills the current screen. Never touch y and height since we
745   // always want to fill vertically.
747   // If the shift key is down, maximize. Hopefully this should make the
748   // "switchers" happy.
749   if ([[NSApp currentEvent] modifierFlags] & NSShiftKeyMask) {
750     return frame;
751   }
753   // To prevent strange results on portrait displays, the basic minimum zoomed
754   // width is the larger of: 60% of available width, 60% of available height
755   // (bounded by available width).
756   const CGFloat kProportion = 0.6;
757   CGFloat zoomedWidth =
758       std::max(kProportion * NSWidth(frame),
759                std::min(kProportion * NSHeight(frame), NSWidth(frame)));
761   WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
762   if (contents) {
763     // If the intrinsic width is bigger, then make it the zoomed width.
764     const int kScrollbarWidth = 16;  // TODO(viettrungluu): ugh.
765     CGFloat intrinsicWidth = static_cast<CGFloat>(
766         contents->GetPreferredSize().width() + kScrollbarWidth);
767     zoomedWidth = std::max(zoomedWidth,
768                            std::min(intrinsicWidth, NSWidth(frame)));
769   }
771   // Never shrink from the current size on zoom (see above).
772   NSRect currentFrame = [[self window] frame];
773   zoomedWidth = std::max(zoomedWidth, NSWidth(currentFrame));
775   // |frame| determines our maximum extents. We need to set the origin of the
776   // frame -- and only move it left if necessary.
777   if (currentFrame.origin.x + zoomedWidth > NSMaxX(frame))
778     frame.origin.x = NSMaxX(frame) - zoomedWidth;
779   else
780     frame.origin.x = currentFrame.origin.x;
782   // Set the width. Don't touch y or height.
783   frame.size.width = zoomedWidth;
785   return frame;
788 - (void)activate {
789   [BrowserWindowUtils activateWindowForController:self];
792 // Determine whether we should let a window zoom/unzoom to the given |newFrame|.
793 // We avoid letting unzoom move windows between screens, because it's really
794 // strange and unintuitive.
795 - (BOOL)windowShouldZoom:(NSWindow*)window toFrame:(NSRect)newFrame {
796   // Figure out which screen |newFrame| is on.
797   NSScreen* newScreen = nil;
798   CGFloat newScreenOverlapArea = 0.0;
799   for (NSScreen* screen in [NSScreen screens]) {
800     NSRect overlap = NSIntersectionRect(newFrame, [screen frame]);
801     CGFloat overlapArea = NSWidth(overlap) * NSHeight(overlap);
802     if (overlapArea > newScreenOverlapArea) {
803       newScreen = screen;
804       newScreenOverlapArea = overlapArea;
805     }
806   }
807   // If we're somehow not on any screen, allow the zoom.
808   if (!newScreen)
809     return YES;
811   // If the new screen is the current screen, we can return a definitive YES.
812   // Note: This check is not strictly necessary, but just short-circuits in the
813   // "no-brainer" case. To test the complicated logic below, comment this out!
814   NSScreen* curScreen = [window screen];
815   if (newScreen == curScreen)
816     return YES;
818   // Worry a little: What happens when a window is on two (or more) screens?
819   // E.g., what happens in a 50-50 scenario? Cocoa may reasonably elect to zoom
820   // to the other screen rather than staying on the officially current one. So
821   // we compare overlaps with the current window frame, and see if Cocoa's
822   // choice was reasonable (allowing a small rounding error). This should
823   // hopefully avoid us ever erroneously denying a zoom when a window is on
824   // multiple screens.
825   NSRect curFrame = [window frame];
826   NSRect newScrIntersectCurFr = NSIntersectionRect([newScreen frame], curFrame);
827   NSRect curScrIntersectCurFr = NSIntersectionRect([curScreen frame], curFrame);
828   if (NSWidth(newScrIntersectCurFr) * NSHeight(newScrIntersectCurFr) >=
829       (NSWidth(curScrIntersectCurFr) * NSHeight(curScrIntersectCurFr) - 1.0)) {
830     return YES;
831   }
833   // If it wasn't reasonable, return NO.
834   return NO;
837 // Adjusts the window height by the given amount.
838 - (BOOL)adjustWindowHeightBy:(CGFloat)deltaH {
839   // By not adjusting the window height when initializing, we can ensure that
840   // the window opens with the same size that was saved on close.
841   if (initializing_ || [self isInAnyFullscreenMode] || deltaH == 0)
842     return NO;
844   NSWindow* window = [self window];
845   NSRect windowFrame = [window frame];
846   NSRect workarea = [[window screen] visibleFrame];
848   // Prevent the window from growing smaller than its minimum height:
849   // http://crbug.com/230400 .
850   if (deltaH < 0) {
851     CGFloat minWindowHeight = [window minSize].height;
852     if (windowFrame.size.height + deltaH < minWindowHeight) {
853       // |deltaH| + |windowFrame.size.height| = |minWindowHeight|.
854       deltaH = minWindowHeight - windowFrame.size.height;
855     }
856     if (deltaH == 0) {
857       return NO;
858     }
859   }
861   // If the window is not already fully in the workarea, do not adjust its frame
862   // at all.
863   if (!NSContainsRect(workarea, windowFrame))
864     return NO;
866   // Record the position of the top/bottom of the window, so we can easily check
867   // whether we grew the window upwards/downwards.
868   CGFloat oldWindowMaxY = NSMaxY(windowFrame);
869   CGFloat oldWindowMinY = NSMinY(windowFrame);
871   // We are "zoomed" if we occupy the full vertical space.
872   bool isZoomed = (windowFrame.origin.y == workarea.origin.y &&
873                    NSHeight(windowFrame) == NSHeight(workarea));
875   // If we're shrinking the window....
876   if (deltaH < 0) {
877     bool didChange = false;
879     // Don't reset if not currently zoomed since shrinking can take several
880     // steps!
881     if (isZoomed)
882       isShrinkingFromZoomed_ = YES;
884     // If we previously grew at the top, shrink as much as allowed at the top
885     // first.
886     if (windowTopGrowth_ > 0) {
887       CGFloat shrinkAtTopBy = MIN(-deltaH, windowTopGrowth_);
888       windowFrame.size.height -= shrinkAtTopBy;  // Shrink the window.
889       deltaH += shrinkAtTopBy;            // Update the amount left to shrink.
890       windowTopGrowth_ -= shrinkAtTopBy;  // Update the growth state.
891       didChange = true;
892     }
894     // Similarly for the bottom (not an "else if" since we may have to
895     // simultaneously shrink at both the top and at the bottom). Note that
896     // |deltaH| may no longer be nonzero due to the above.
897     if (deltaH < 0 && windowBottomGrowth_ > 0) {
898       CGFloat shrinkAtBottomBy = MIN(-deltaH, windowBottomGrowth_);
899       windowFrame.origin.y += shrinkAtBottomBy;     // Move the window up.
900       windowFrame.size.height -= shrinkAtBottomBy;  // Shrink the window.
901       deltaH += shrinkAtBottomBy;               // Update the amount left....
902       windowBottomGrowth_ -= shrinkAtBottomBy;  // Update the growth state.
903       didChange = true;
904     }
906     // If we're shrinking from zoomed but we didn't change the top or bottom
907     // (since we've reached the limits imposed by |window...Growth_|), then stop
908     // here. Don't reset |isShrinkingFromZoomed_| since we might get called
909     // again for the same shrink.
910     if (isShrinkingFromZoomed_ && !didChange)
911       return NO;
912   } else {
913     isShrinkingFromZoomed_ = NO;
915     // Don't bother with anything else.
916     if (isZoomed)
917       return NO;
918   }
920   // Shrinking from zoomed is handled above (and is constrained by
921   // |window...Growth_|).
922   if (!isShrinkingFromZoomed_) {
923     // Resize the window down until it hits the bottom of the workarea, then if
924     // needed continue resizing upwards.  Do not resize the window to be taller
925     // than the current workarea.
926     // Resize the window as requested, keeping the top left corner fixed.
927     windowFrame.origin.y -= deltaH;
928     windowFrame.size.height += deltaH;
930     // If the bottom left corner is now outside the visible frame, move the
931     // window up to make it fit, but make sure not to move the top left corner
932     // out of the visible frame.
933     if (windowFrame.origin.y < workarea.origin.y) {
934       windowFrame.origin.y = workarea.origin.y;
935       windowFrame.size.height =
936           std::min(NSHeight(windowFrame), NSHeight(workarea));
937     }
939     // Record (if applicable) how much we grew the window in either direction.
940     // (N.B.: These only record growth, not shrinkage.)
941     if (NSMaxY(windowFrame) > oldWindowMaxY)
942       windowTopGrowth_ += NSMaxY(windowFrame) - oldWindowMaxY;
943     if (NSMinY(windowFrame) < oldWindowMinY)
944       windowBottomGrowth_ += oldWindowMinY - NSMinY(windowFrame);
945   }
947   // Disable subview resizing while resizing the window, or else we will get
948   // unwanted renderer resizes.  The calling code must call layoutSubviews to
949   // make things right again.
950   NSView* chromeContentView = [self chromeContentView];
951   BOOL autoresizesSubviews = [chromeContentView autoresizesSubviews];
952   [chromeContentView setAutoresizesSubviews:NO];
954   // On Yosemite the toolbar can flicker when hiding or showing the bookmarks
955   // bar. Here, |chromeContentView| is set to not autoresize its subviews during
956   // the window resize. Because |chromeContentView| is not flipped, if the
957   // window is getting shorter, the toolbar will move up within the window.
958   // Soon after, a call to layoutSubviews corrects its position. Passing NO to
959   // setFrame:display: should keep the toolbarView's intermediate position
960   // hidden, as should the prior call to disable screen updates. For some
961   // reason, neither prevents the toolbarView's intermediate position from
962   // becoming visible. Its subsequent appearance in its correct location causes
963   // the flicker. It may be that the Appkit assumes that updating the window
964   // immediately is not a big deal given that everything in it is layer-backed.
965   // Indeed, turning off layer backing for all ancestors of the toolbarView
966   // causes the flicker to go away.
967   //
968   // By shifting the toolbarView enough so that it's in its correct location
969   // immediately after the call to setFrame:display:, the toolbar will be in
970   // the right spot when the Appkit prematurely flushes the window contents to
971   // the screen. http://crbug.com/444080 .
972   if ([self hasToolbar]) {
973     NSView* toolbarView = [toolbarController_ view];
974     NSRect currentWindowFrame = [window frame];
975     NSRect toolbarViewFrame = [toolbarView frame];
976     toolbarViewFrame.origin.y += windowFrame.size.height -
977         currentWindowFrame.size.height;
978     [toolbarView setFrame:toolbarViewFrame];
979   }
981   [window setFrame:windowFrame display:NO];
982   [chromeContentView setAutoresizesSubviews:autoresizesSubviews];
983   return YES;
986 // Main method to resize browser window subviews.  This method should be called
987 // when resizing any child of the content view, rather than resizing the views
988 // directly.  If the view is already the correct height, does not force a
989 // relayout.
990 - (void)resizeView:(NSView*)view newHeight:(CGFloat)height {
991   // We should only ever be called for one of the following four views.
992   // |downloadShelfController_| may be nil. If we are asked to size the bookmark
993   // bar directly, its superview must be this controller's content view.
994   DCHECK(view);
995   DCHECK(view == [toolbarController_ view] ||
996          view == [infoBarContainerController_ view] ||
997          view == [downloadShelfController_ view] ||
998          view == [bookmarkBarController_ view]);
1000   // The infobar has insufficient information to determine its new height. It
1001   // knows the total height of all of the info bars (which is what it passes
1002   // into this method), but knows nothing about the maximum arrow height, which
1003   // is determined by this class.
1004   if (view == [infoBarContainerController_ view]) {
1005     base::scoped_nsobject<BrowserWindowLayout> layout(
1006         [[BrowserWindowLayout alloc] init]);
1007     [self updateLayoutParameters:layout];
1008     // Use the new height for the info bar.
1009     [layout setInfoBarHeight:height];
1011     chrome::LayoutOutput output = [layout computeLayout];
1013     height = NSHeight(output.infoBarFrame);
1014   }
1016   // Change the height of the view and call |-layoutSubViews|. We set the height
1017   // here without regard to where the view is on the screen or whether it needs
1018   // to "grow up" or "grow down."  The below call to |-layoutSubviews| will
1019   // position each view correctly.
1020   NSRect frame = [view frame];
1021   if (NSHeight(frame) == height)
1022     return;
1024   // Disable screen updates to prevent flickering.
1025   gfx::ScopedCocoaDisableScreenUpdates disabler;
1027   // Grow or shrink the window by the amount of the height change.  We adjust
1028   // the window height only in two cases:
1029   // 1) We are adjusting the height of the bookmark bar and it is currently
1030   // animating either open or closed.
1031   // 2) We are adjusting the height of the download shelf.
1032   //
1033   // We do not adjust the window height for bookmark bar changes on the NTP.
1034   BOOL shouldAdjustBookmarkHeight =
1035       [bookmarkBarController_ isAnimatingBetweenState:BookmarkBar::HIDDEN
1036                                              andState:BookmarkBar::SHOW];
1038   BOOL resizeRectDirty = NO;
1039   if ((shouldAdjustBookmarkHeight && view == [bookmarkBarController_ view]) ||
1040       view == [downloadShelfController_ view]) {
1041     CGFloat deltaH = height - NSHeight(frame);
1042     if ([self adjustWindowHeightBy:deltaH] &&
1043         view == [downloadShelfController_ view]) {
1044       // If the window height didn't change, the download shelf will change the
1045       // size of the contents. If the contents size doesn't change, send it
1046       // an explicit grow box invalidation (else, the resize message does that.)
1047       resizeRectDirty = YES;
1048     }
1049   }
1051   frame.size.height = height;
1052   // TODO(rohitrao): Determine if calling setFrame: twice is bad.
1053   [view setFrame:frame];
1054   [self layoutSubviews];
1056   if (resizeRectDirty) {
1057     // Send new resize rect to foreground tab.
1058     if (WebContents* contents = [self webContents]) {
1059       if (content::RenderViewHost* rvh = contents->GetRenderViewHost()) {
1060         rvh->ResizeRectChanged(windowShim_->GetRootWindowResizerRect());
1061       }
1062     }
1063   }
1066 // Update a toggle state for an NSMenuItem if modified.
1067 // Take care to ensure |item| looks like a NSMenuItem.
1068 // Called by validateUserInterfaceItem:.
1069 - (void)updateToggleStateWithTag:(NSInteger)tag forItem:(id)item {
1070   if (![item respondsToSelector:@selector(state)] ||
1071       ![item respondsToSelector:@selector(setState:)])
1072     return;
1074   // On Windows this logic happens in bookmark_bar_view.cc.  On the
1075   // Mac we're a lot more MVC happy so we've moved it into a
1076   // controller.  To be clear, this simply updates the menu item; it
1077   // does not display the bookmark bar itself.
1078   if (tag == IDC_SHOW_BOOKMARK_BAR) {
1079     bool toggled = windowShim_->IsBookmarkBarVisible();
1080     NSInteger oldState = [(NSMenuItem*)item state];
1081     NSInteger newState = toggled ? NSOnState : NSOffState;
1082     if (oldState != newState)
1083       [item setState:newState];
1084   }
1086   // Update the checked/Unchecked state of items in the encoding menu.
1087   // On Windows, this logic is part of |EncodingMenuModel| in
1088   // browser/ui/views/toolbar_view.h.
1089   EncodingMenuController encoding_controller;
1090   if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) {
1091     DCHECK(browser_.get());
1092     Profile* profile = browser_->profile();
1093     DCHECK(profile);
1094     WebContents* current_tab =
1095         browser_->tab_strip_model()->GetActiveWebContents();
1096     if (!current_tab)
1097       return;
1099     const std::string encoding = current_tab->GetEncoding();
1101     bool toggled = encoding_controller.IsItemChecked(profile, encoding, tag);
1102     NSInteger oldState = [(NSMenuItem*)item state];
1103     NSInteger newState = toggled ? NSOnState : NSOffState;
1104     if (oldState != newState)
1105       [item setState:newState];
1106   }
1109 // Called to validate menu and toolbar items when this window is key. All the
1110 // items we care about have been set with the |-commandDispatch:| or
1111 // |-commandDispatchUsingKeyModifiers:| actions and a target of FirstResponder
1112 // in IB. If it's not one of those, let it continue up the responder chain to be
1113 // handled elsewhere. We pull out the tag as the cross-platform constant to
1114 // differentiate and dispatch the various commands.
1115 // NOTE: we might have to handle state for app-wide menu items,
1116 // although we could cheat and directly ask the app controller if our
1117 // command_updater doesn't support the command. This may or may not be an issue,
1118 // too early to tell.
1119 - (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item {
1120   SEL action = [item action];
1121   BOOL enable = NO;
1122   if (action == @selector(commandDispatch:) ||
1123       action == @selector(commandDispatchUsingKeyModifiers:)) {
1124     NSInteger tag = [item tag];
1125     if (chrome::SupportsCommand(browser_.get(), tag)) {
1126       // Generate return value (enabled state)
1127       enable = chrome::IsCommandEnabled(browser_.get(), tag);
1128       switch (tag) {
1129         case IDC_CLOSE_TAB:
1130           // Disable "close tab" if the receiving window is not tabbed.
1131           // We simply check whether the item has a keyboard shortcut set here;
1132           // app_controller_mac.mm actually determines whether the item should
1133           // be enabled.
1134           if (NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item))
1135             enable &= !![[menuItem keyEquivalent] length];
1136           break;
1137         case IDC_FULLSCREEN: {
1138           if (NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item)) {
1139             if (chrome::mac::SupportsSystemFullscreen()) {
1140               [menuItem setTitle:[self titleForFullscreenMenuItem]];
1141             } else {
1142               [menuItem setHidden:YES];
1143             }
1144           }
1145           break;
1146         }
1147         case IDC_PRESENTATION_MODE: {
1148           if (NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item)) {
1149             [menuItem setTitle:[self titleForFullscreenMenuItem]];
1151             if (chrome::mac::SupportsSystemFullscreen())
1152               [menuItem setAlternate:YES];
1153           }
1154           break;
1155         }
1156         case IDC_SHOW_SIGNIN: {
1157           Profile* original_profile =
1158               browser_->profile()->GetOriginalProfile();
1159           [AppController updateSigninItem:item
1160                                shouldShow:enable
1161                            currentProfile:original_profile];
1162           break;
1163         }
1164         case IDC_BOOKMARK_PAGE: {
1165           // Extensions have the ability to hide the bookmark page menu item.
1166           // This only affects the bookmark page menu item under the main menu.
1167           // The bookmark page menu item under the wrench menu has its
1168           // visibility controlled by WrenchMenuModel.
1169           bool shouldHide =
1170               chrome::ShouldRemoveBookmarkThisPageUI(browser_->profile());
1171           NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item);
1172           [menuItem setHidden:shouldHide];
1173           break;
1174         }
1175         case IDC_BOOKMARK_ALL_TABS: {
1176           // Extensions have the ability to hide the bookmark all tabs menu
1177           // item.  This only affects the bookmark page menu item under the main
1178           // menu.  The bookmark page menu item under the wrench menu has its
1179           // visibility controlled by WrenchMenuModel.
1180           bool shouldHide =
1181               chrome::ShouldRemoveBookmarkOpenPagesUI(browser_->profile());
1182           NSMenuItem* menuItem = base::mac::ObjCCast<NSMenuItem>(item);
1183           [menuItem setHidden:shouldHide];
1184           break;
1185         }
1186         default:
1187           // Special handling for the contents of the Text Encoding submenu. On
1188           // Mac OS, instead of enabling/disabling the top-level menu item, we
1189           // enable/disable the submenu's contents (per Apple's HIG).
1190           EncodingMenuController encoding_controller;
1191           if (encoding_controller.DoesCommandBelongToEncodingMenu(tag)) {
1192             enable &= chrome::IsCommandEnabled(browser_.get(),
1193                                                IDC_ENCODING_MENU) ? YES : NO;
1194           }
1195       }
1197       // If the item is toggleable, find its toggle state and
1198       // try to update it.  This is a little awkward, but the alternative is
1199       // to check after a commandDispatch, which seems worse.
1200       [self updateToggleStateWithTag:tag forItem:item];
1201     }
1202   }
1203   return enable;
1206 // Called when the user picks a menu or toolbar item when this window is key.
1207 // Calls through to the browser object to execute the command. This assumes that
1208 // the command is supported and doesn't check, otherwise it would have been
1209 // disabled in the UI in validateUserInterfaceItem:.
1210 - (void)commandDispatch:(id)sender {
1211   DCHECK(sender);
1212   // Identify the actual BWC to which the command should be dispatched. It might
1213   // belong to a background window, yet this controller gets it because it is
1214   // the foreground window's controller and thus in the responder chain. Some
1215   // senders don't have this problem (for example, menus only operate on the
1216   // foreground window), so this is only an issue for senders that are part of
1217   // windows.
1218   BrowserWindowController* targetController = self;
1219   if ([sender respondsToSelector:@selector(window)])
1220     targetController = [[sender window] windowController];
1221   DCHECK([targetController isKindOfClass:[BrowserWindowController class]]);
1222   DCHECK(targetController->browser_.get());
1224   // When system fullscreen is available, it supercedes presentation mode.
1225   int tag = [sender tag];
1226   if (tag == IDC_PRESENTATION_MODE && chrome::mac::SupportsSystemFullscreen())
1227     tag = IDC_FULLSCREEN;
1229   chrome::ExecuteCommand(targetController->browser_.get(), tag);
1232 // Same as |-commandDispatch:|, but executes commands using a disposition
1233 // determined by the key flags. If the window is in the background and the
1234 // command key is down, ignore the command key, but process any other modifiers.
1235 - (void)commandDispatchUsingKeyModifiers:(id)sender {
1236   DCHECK(sender);
1238   if (![sender isEnabled]) {
1239     // This code is reachable e.g. if the user mashes the back button, queuing
1240     // up a bunch of events before the button's enabled state is updated:
1241     // http://crbug.com/63254
1242     return;
1243   }
1245   // See comment above for why we do this.
1246   BrowserWindowController* targetController = self;
1247   if ([sender respondsToSelector:@selector(window)])
1248     targetController = [[sender window] windowController];
1249   DCHECK([targetController isKindOfClass:[BrowserWindowController class]]);
1250   DCHECK(targetController->browser_.get());
1252   NSInteger command = [sender tag];
1253   NSUInteger modifierFlags = [[NSApp currentEvent] modifierFlags];
1254   if ((command == IDC_RELOAD) &&
1255       (modifierFlags & (NSShiftKeyMask | NSControlKeyMask))) {
1256     command = IDC_RELOAD_IGNORING_CACHE;
1257     // Mask off Shift and Control so they don't affect the disposition below.
1258     modifierFlags &= ~(NSShiftKeyMask | NSControlKeyMask);
1259   }
1260   if (![[sender window] isMainWindow]) {
1261     // Remove the command key from the flags, it means "keep the window in
1262     // the background" in this case.
1263     modifierFlags &= ~NSCommandKeyMask;
1264   }
1265   chrome::ExecuteCommandWithDisposition(
1266       targetController->browser_.get(), command,
1267       ui::WindowOpenDispositionFromNSEventWithFlags(
1268           [NSApp currentEvent], modifierFlags));
1271 - (BOOL)handledByExtensionCommand:(NSEvent*)event
1272     priority:(ui::AcceleratorManager::HandlerPriority)priority {
1273   return extension_keybinding_registry_->ProcessKeyEvent(
1274       content::NativeWebKeyboardEvent(event), priority);
1277 // StatusBubble delegate method: tell the status bubble the frame it should
1278 // position itself in.
1279 - (NSRect)statusBubbleBaseFrame {
1280   NSView* view = [overlayableContentsController_ view];
1281   return [view convertRect:[view bounds] toView:nil];
1284 - (void)updateToolbarWithContents:(WebContents*)tab {
1285   [toolbarController_ updateToolbarWithContents:tab];
1288 - (void)resetTabState:(WebContents*)tab {
1289   [toolbarController_ resetTabState:tab];
1292 - (void)setStarredState:(BOOL)isStarred {
1293   [toolbarController_ setStarredState:isStarred];
1296 - (void)setCurrentPageIsTranslated:(BOOL)on {
1297   [toolbarController_ setTranslateIconLit:on];
1300 - (void)onActiveTabChanged:(content::WebContents*)oldContents
1301                         to:(content::WebContents*)newContents {
1302   // No need to remove previous bubble. It will close itself.
1303   PermissionBubbleManager* manager(nullptr);
1304   if (oldContents) {
1305     manager = PermissionBubbleManager::FromWebContents(oldContents);
1306     if (manager)
1307       manager->HideBubble();
1308   }
1310   if (newContents) {
1311     manager = PermissionBubbleManager::FromWebContents(newContents);
1312     if (manager)
1313       manager->DisplayPendingRequests();
1314   }
1317 - (void)zoomChangedForActiveTab:(BOOL)canShowBubble {
1318   [toolbarController_ zoomChangedForActiveTab:canShowBubble];
1321 // Return the rect, in WebKit coordinates (flipped), of the window's grow box
1322 // in the coordinate system of the content area of the currently selected tab.
1323 // |windowGrowBox| needs to be in the window's coordinate system.
1324 - (NSRect)selectedTabGrowBoxRect {
1325   NSWindow* window = [self window];
1326   if (![window respondsToSelector:@selector(_growBoxRect)])
1327     return NSZeroRect;
1329   // Before we return a rect, we need to convert it from window coordinates
1330   // to tab content area coordinates and flip the coordinate system.
1331   NSRect growBoxRect =
1332       [[self tabContentArea] convertRect:[window _growBoxRect] fromView:nil];
1333   growBoxRect.origin.y =
1334       NSHeight([[self tabContentArea] frame]) - NSMaxY(growBoxRect);
1335   return growBoxRect;
1338 // Accept tabs from a BrowserWindowController with the same Profile.
1339 - (BOOL)canReceiveFrom:(TabWindowController*)source {
1340   BrowserWindowController* realSource =
1341       base::mac::ObjCCast<BrowserWindowController>(source);
1342   if (!realSource || browser_->profile() != realSource->browser_->profile()) {
1343     return NO;
1344   }
1346   // Can't drag a tab from a normal browser to a pop-up
1347   if (browser_->type() != realSource->browser_->type()) {
1348     return NO;
1349   }
1351   return YES;
1354 // Move a given tab view to the location of the current placeholder. If there is
1355 // no placeholder, it will go at the end. |controller| is the window controller
1356 // of a tab being dropped from a different window. It will be nil if the drag is
1357 // within the window, otherwise the tab is removed from that window before being
1358 // placed into this one. The implementation will call |-removePlaceholder| since
1359 // the drag is now complete.  This also calls |-layoutTabs| internally so
1360 // clients do not need to call it again.
1361 - (void)moveTabViews:(NSArray*)views
1362       fromController:(TabWindowController*)dragController {
1363   if (dragController) {
1364     // Moving between windows.
1365     NSView* activeTabView = [dragController activeTabView];
1366     BrowserWindowController* dragBWC =
1367         base::mac::ObjCCastStrict<BrowserWindowController>(dragController);
1369     // We will drop the tabs starting at indexOfPlaceholder, and increment from
1370     // there. We remove the placehoder before dropping the tabs, so that the
1371     // new tab animation's destination frame is correct.
1372     int tabIndex = [tabStripController_ indexOfPlaceholder];
1373     [self removePlaceholder];
1375     for (NSView* view in views) {
1376       // Figure out the WebContents to drop into our tab model from the source
1377       // window's model.
1378       int index = [dragBWC->tabStripController_ modelIndexForTabView:view];
1379       WebContents* contents =
1380           dragBWC->browser_->tab_strip_model()->GetWebContentsAt(index);
1381       // The tab contents may have gone away if given a window.close() while it
1382       // is being dragged. If so, bail, we've got nothing to drop.
1383       if (!contents)
1384         continue;
1386       // Convert |view|'s frame (which starts in the source tab strip's
1387       // coordinate system) to the coordinate system of the destination tab
1388       // strip. This needs to be done before being detached so the window
1389       // transforms can be performed.
1390       NSRect destinationFrame = [view frame];
1391       NSPoint tabOrigin = destinationFrame.origin;
1392       tabOrigin = [[dragController tabStripView] convertPoint:tabOrigin
1393                                                        toView:nil];
1394       tabOrigin = [[dragController window] convertBaseToScreen:tabOrigin];
1395       tabOrigin = [[self window] convertScreenToBase:tabOrigin];
1396       tabOrigin = [[self tabStripView] convertPoint:tabOrigin fromView:nil];
1397       destinationFrame.origin = tabOrigin;
1399       // Before the tab is detached from its originating tab strip, store the
1400       // pinned state so that it can be maintained between the windows.
1401       bool isPinned = dragBWC->browser_->tab_strip_model()->IsTabPinned(index);
1403       // Now that we have enough information about the tab, we can remove it
1404       // from the dragging window. We need to do this *before* we add it to the
1405       // new window as this will remove the WebContents' delegate.
1406       [dragController detachTabView:view];
1408       // Deposit it into our model at the appropriate location (it already knows
1409       // where it should go from tracking the drag). Doing this sets the tab's
1410       // delegate to be the Browser.
1411       [tabStripController_ dropWebContents:contents
1412                                    atIndex:tabIndex++
1413                                  withFrame:destinationFrame
1414                                asPinnedTab:isPinned
1415                                   activate:view == activeTabView];
1416     }
1417   } else {
1418     // Moving within a window.
1419     for (NSView* view in views) {
1420       int index = [tabStripController_ modelIndexForTabView:view];
1421       [tabStripController_ moveTabFromIndex:index];
1422     }
1423     [self removePlaceholder];
1424   }
1427 // Tells the tab strip to forget about this tab in preparation for it being
1428 // put into a different tab strip, such as during a drop on another window.
1429 - (void)detachTabView:(NSView*)view {
1430   int index = [tabStripController_ modelIndexForTabView:view];
1431   browser_->tab_strip_model()->DetachWebContentsAt(index);
1434 - (NSArray*)tabViews {
1435   return [tabStripController_ tabViews];
1438 - (NSView*)activeTabView {
1439   return [tabStripController_ activeTabView];
1442 - (void)setIsLoading:(BOOL)isLoading force:(BOOL)force {
1443   [toolbarController_ setIsLoading:isLoading force:force];
1446 // Make the location bar the first responder, if possible.
1447 - (void)focusLocationBar:(BOOL)selectAll {
1448   [toolbarController_ focusLocationBar:selectAll];
1451 - (void)focusTabContents {
1452   content::WebContents* const activeWebContents =
1453       browser_->tab_strip_model()->GetActiveWebContents();
1454   if (activeWebContents)
1455     activeWebContents->Focus();
1458 - (void)layoutTabs {
1459   [tabStripController_ layoutTabs];
1462 - (TabWindowController*)detachTabsToNewWindow:(NSArray*)tabViews
1463                                    draggedTab:(NSView*)draggedTab {
1464   DCHECK_GT([tabViews count], 0U);
1466   // Disable screen updates so that this appears as a single visual change.
1467   gfx::ScopedCocoaDisableScreenUpdates disabler;
1469   // Set the window size. Need to do this before we detach the tab so it's
1470   // still in the window. We have to flip the coordinates as that's what
1471   // is expected by the Browser code.
1472   NSWindow* sourceWindow = [draggedTab window];
1473   NSRect windowRect = [sourceWindow frame];
1474   NSScreen* screen = [sourceWindow screen];
1475   windowRect.origin.y = NSHeight([screen frame]) - NSMaxY(windowRect);
1476   gfx::Rect browserRect(windowRect.origin.x, windowRect.origin.y,
1477                         NSWidth(windowRect), NSHeight(windowRect));
1479   std::vector<TabStripModelDelegate::NewStripContents> contentses;
1480   TabStripModel* model = browser_->tab_strip_model();
1482   for (TabView* tabView in tabViews) {
1483     // Fetch the tab contents for the tab being dragged.
1484     int index = [tabStripController_ modelIndexForTabView:tabView];
1485     bool isPinned = model->IsTabPinned(index);
1486     bool isActive = (index == model->active_index());
1488     TabStripModelDelegate::NewStripContents item;
1489     item.web_contents = model->GetWebContentsAt(index);
1490     item.add_types =
1491         (isActive ? TabStripModel::ADD_ACTIVE : TabStripModel::ADD_NONE) |
1492         (isPinned ? TabStripModel::ADD_PINNED : TabStripModel::ADD_NONE);
1493     contentses.push_back(item);
1494   }
1496   for (TabView* tabView in tabViews) {
1497     int index = [tabStripController_ modelIndexForTabView:tabView];
1498     // Detach it from the source window, which just updates the model without
1499     // deleting the tab contents. This needs to come before creating the new
1500     // Browser because it clears the WebContents' delegate, which gets hooked
1501     // up during creation of the new window.
1502     model->DetachWebContentsAt(index);
1503   }
1505   // Create a new window with the dragged tabs in its model.
1506   Browser* newBrowser = browser_->tab_strip_model()->delegate()->
1507       CreateNewStripWithContents(contentses, browserRect, false);
1509   // Get the new controller by asking the new window for its delegate.
1510   BrowserWindowController* controller =
1511       reinterpret_cast<BrowserWindowController*>(
1512           [newBrowser->window()->GetNativeWindow() delegate]);
1513   DCHECK(controller && [controller isKindOfClass:[TabWindowController class]]);
1515   // And make sure we use the correct frame in the new view.
1516   TabStripController* tabStripController = [controller tabStripController];
1517   NSView* tabStrip = [self tabStripView];
1518   NSEnumerator* tabEnumerator = [tabViews objectEnumerator];
1519   for (NSView* newView in [tabStripController tabViews]) {
1520     NSView* oldView = [tabEnumerator nextObject];
1521     if (oldView) {
1522       // Pushes tabView's frame back inside the tabstrip.
1523       NSRect sourceTabRect = [oldView frame];
1524       NSSize tabOverflow =
1525           [self overflowFrom:[tabStrip convertRect:sourceTabRect toView:nil]
1526                           to:[tabStrip frame]];
1527       NSRect tabRect =
1528           NSOffsetRect(sourceTabRect, -tabOverflow.width, -tabOverflow.height);
1529       // Force the added tab to the right size (remove stretching.)
1530       tabRect.size.height = [TabStripController defaultTabHeight];
1532       [tabStripController setFrame:tabRect ofTabView:newView];
1533     }
1534   }
1536   return controller;
1539 - (void)insertPlaceholderForTab:(TabView*)tab
1540                           frame:(NSRect)frame {
1541   [super insertPlaceholderForTab:tab frame:frame];
1542   [tabStripController_ insertPlaceholderForTab:tab frame:frame];
1545 - (void)removePlaceholder {
1546   [super removePlaceholder];
1547   [tabStripController_ insertPlaceholderForTab:nil frame:NSZeroRect];
1550 - (BOOL)isDragSessionActive {
1551   // The tab can be dragged within the existing tab strip or detached
1552   // into its own window (then the overlay window will be present).
1553   return [[self tabStripController] isDragSessionActive] ||
1554          [self overlayWindow] != nil;
1557 - (BOOL)tabDraggingAllowed {
1558   return [tabStripController_ tabDraggingAllowed];
1561 - (BOOL)tabTearingAllowed {
1562   return ![self isInAnyFullscreenMode];
1565 - (BOOL)windowMovementAllowed {
1566   return ![self isInAnyFullscreenMode];
1569 - (BOOL)isTabFullyVisible:(TabView*)tab {
1570   return [tabStripController_ isTabFullyVisible:tab];
1573 - (void)showNewTabButton:(BOOL)show {
1574   [tabStripController_ showNewTabButton:show];
1577 - (BOOL)shouldShowAvatar {
1578   if (![self hasTabStrip])
1579     return NO;
1580   if (browser_->profile()->IsOffTheRecord())
1581     return YES;
1583   ProfileInfoCache& cache =
1584       g_browser_process->profile_manager()->GetProfileInfoCache();
1585   if (cache.GetIndexOfProfileWithPath(browser_->profile()->GetPath()) ==
1586       std::string::npos) {
1587     return NO;
1588   }
1590   return AvatarMenu::ShouldShowAvatarMenu();
1593 - (BOOL)shouldUseNewAvatarButton {
1594   return switches::IsNewAvatarMenu() &&
1595       profiles::IsRegularOrGuestSession(browser_.get());
1598 - (BOOL)isBookmarkBarVisible {
1599   return [bookmarkBarController_ isVisible];
1602 - (BOOL)isBookmarkBarAnimating {
1603   return [bookmarkBarController_ isAnimationRunning];
1606 - (BookmarkBarController*)bookmarkBarController {
1607   return bookmarkBarController_;
1610 - (DevToolsController*)devToolsController {
1611   return devToolsController_;
1614 - (BOOL)isDownloadShelfVisible {
1615   return downloadShelfController_ != nil &&
1616       [downloadShelfController_ isVisible];
1619 - (void)createAndAddDownloadShelf {
1620   if (!downloadShelfController_.get()) {
1621     downloadShelfController_.reset([[DownloadShelfController alloc]
1622         initWithBrowser:browser_.get() resizeDelegate:self]);
1623     [self.chromeContentView addSubview:[downloadShelfController_ view]];
1624   }
1627 - (DownloadShelfController*)downloadShelf {
1628   return downloadShelfController_;
1631 - (void)addFindBar:(FindBarCocoaController*)findBarCocoaController {
1632   // Shouldn't call addFindBar twice.
1633   DCHECK(!findBarCocoaController_.get());
1635   // Create a controller for the findbar.
1636   findBarCocoaController_.reset([findBarCocoaController retain]);
1637   [self layoutSubviews];
1638   [self updateSubviewZOrder];
1641 - (NSWindow*)createFullscreenWindow {
1642   return [[[FullscreenWindow alloc] initForScreen:[[self window] screen]]
1643            autorelease];
1646 - (NSInteger)numberOfTabs {
1647   // count() includes pinned tabs.
1648   return browser_->tab_strip_model()->count();
1651 - (BOOL)hasLiveTabs {
1652   return !browser_->tab_strip_model()->empty();
1655 - (NSString*)activeTabTitle {
1656   WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
1657   return base::SysUTF16ToNSString(contents->GetTitle());
1660 - (NSRect)regularWindowFrame {
1661   return [self isInAnyFullscreenMode] ? savedRegularWindowFrame_
1662                                       : [[self window] frame];
1665 // (Override of |TabWindowController| method.)
1666 - (BOOL)hasTabStrip {
1667   return [self supportsWindowFeature:Browser::FEATURE_TABSTRIP];
1670 - (BOOL)isTabDraggable:(NSView*)tabView {
1671   // TODO(avi, thakis): ConstrainedWindowSheetController has no api to move
1672   // tabsheets between windows. Until then, we have to prevent having to move a
1673   // tabsheet between windows, e.g. no tearing off of tabs.
1674   int index = [tabStripController_ modelIndexForTabView:tabView];
1675   WebContents* contents = browser_->tab_strip_model()->GetWebContentsAt(index);
1676   if (!contents)
1677     return NO;
1679   const web_modal::WebContentsModalDialogManager* manager =
1680       web_modal::WebContentsModalDialogManager::FromWebContents(contents);
1681   return !manager || !manager->IsDialogActive();
1684 // TabStripControllerDelegate protocol.
1685 - (void)onActivateTabWithContents:(WebContents*)contents {
1686   // Update various elements that are interested in knowing the current
1687   // WebContents.
1689   // Update all the UI bits.
1690   windowShim_->UpdateTitleBar();
1692   // Update the bookmark bar.
1693   // TODO(viettrungluu): perhaps update to not terminate running animations (if
1694   // applicable)?
1695   windowShim_->BookmarkBarStateChanged(
1696       BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
1698   [infoBarContainerController_ changeWebContents:contents];
1700   // Must do this after bookmark and infobar updates to avoid
1701   // unnecesary resize in contents.
1702   [devToolsController_ updateDevToolsForWebContents:contents
1703                                         withProfile:browser_->profile()];
1706 - (void)onTabChanged:(TabStripModelObserver::TabChangeType)change
1707         withContents:(WebContents*)contents {
1708   // Update titles if this is the currently selected tab and if it isn't just
1709   // the loading state which changed.
1710   if (change != TabStripModelObserver::LOADING_ONLY)
1711     windowShim_->UpdateTitleBar();
1713   // Update the bookmark bar if this is the currently selected tab and if it
1714   // isn't just the title which changed. This for transitions between the NTP
1715   // (showing its floating bookmark bar) and normal web pages (showing no
1716   // bookmark bar).
1717   // TODO(viettrungluu): perhaps update to not terminate running animations?
1718   if (change != TabStripModelObserver::TITLE_NOT_LOADING) {
1719     windowShim_->BookmarkBarStateChanged(
1720         BookmarkBar::DONT_ANIMATE_STATE_CHANGE);
1721   }
1724 - (void)onTabDetachedWithContents:(WebContents*)contents {
1725   [infoBarContainerController_ tabDetachedWithContents:contents];
1728 - (void)userChangedTheme {
1729   NSView* rootView = [[[self window] contentView] superview];
1730   [rootView cr_recursivelyInvokeBlock:^(id view) {
1731       if ([view conformsToProtocol:@protocol(ThemedWindowDrawing)])
1732         [view windowDidChangeTheme];
1734       // TODO(andresantoso): Remove this once all themed views respond to
1735       // windowDidChangeTheme above.
1736       [view setNeedsDisplay:YES];
1737   }];
1740 - (ui::ThemeProvider*)themeProvider {
1741   return ThemeServiceFactory::GetForProfile(browser_->profile());
1744 - (ThemedWindowStyle)themedWindowStyle {
1745   ThemedWindowStyle style = 0;
1746   if (browser_->profile()->IsOffTheRecord())
1747     style |= THEMED_INCOGNITO;
1749   if (browser_->is_devtools())
1750     style |= THEMED_DEVTOOLS;
1751   if (browser_->is_type_popup())
1752     style |= THEMED_POPUP;
1754   return style;
1757 - (NSPoint)themeImagePositionForAlignment:(ThemeImageAlignment)alignment {
1758   NSView* windowChromeView = [[[self window] contentView] superview];
1759   NSView* tabStripView = nil;
1760   if ([self hasTabStrip])
1761     tabStripView = [self tabStripView];
1762   return [BrowserWindowUtils themeImagePositionFor:windowChromeView
1763                                       withTabStrip:tabStripView
1764                                          alignment:alignment];
1767 - (NSPoint)bookmarkBubblePoint {
1768   return [toolbarController_ bookmarkBubblePoint];
1771 // Show the bookmark bubble (e.g. user just clicked on the STAR).
1772 - (void)showBookmarkBubbleForURL:(const GURL&)url
1773                alreadyBookmarked:(BOOL)alreadyMarked {
1774   if (bookmarkBubbleObserver_.get())
1775     return;
1777   bookmarkBubbleObserver_.reset(new BookmarkBubbleObserverCocoa(self));
1779   if (chrome::ToolkitViewsDialogsEnabled()) {
1780     chrome::ShowBookmarkBubbleViewsAtPoint(
1781         gfx::ScreenPointFromNSPoint(
1782             [[self window] convertBaseToScreen:[self bookmarkBubblePoint]]),
1783         [[self window] contentView], bookmarkBubbleObserver_.get(),
1784         browser_.get(), url, alreadyMarked);
1785   } else {
1786     BookmarkModel* model =
1787         BookmarkModelFactory::GetForProfile(browser_->profile());
1788     bookmarks::ManagedBookmarkService* managed =
1789         ManagedBookmarkServiceFactory::GetForProfile(browser_->profile());
1790     const BookmarkNode* node = model->GetMostRecentlyAddedUserNodeForURL(url);
1791     bookmarkBubbleController_ = [[BookmarkBubbleController alloc]
1792         initWithParentWindow:[self window]
1793               bubbleObserver:bookmarkBubbleObserver_.get()
1794                      managed:managed
1795                        model:model
1796                         node:node
1797            alreadyBookmarked:alreadyMarked];
1798     [bookmarkBubbleController_ showWindow:self];
1799   }
1800   DCHECK(bookmarkBubbleObserver_);
1803 - (void)bookmarkBubbleClosed {
1804   // Nil out the weak bookmark bubble controller reference.
1805   bookmarkBubbleController_ = nil;
1806   bookmarkBubbleObserver_.reset();
1809 // Handle the editBookmarkNode: action sent from bookmark bubble controllers.
1810 - (void)editBookmarkNode:(id)sender {
1811   BOOL responds = [sender respondsToSelector:@selector(node)];
1812   DCHECK(responds);
1813   if (responds) {
1814     const BookmarkNode* node = [sender node];
1815     if (node)
1816       BookmarkEditor::Show([self window], browser_->profile(),
1817           BookmarkEditor::EditDetails::EditNode(node),
1818           BookmarkEditor::SHOW_TREE);
1819   }
1822 - (void)showTranslateBubbleForWebContents:(content::WebContents*)contents
1823                                      step:(translate::TranslateStep)step
1824                                 errorType:(translate::TranslateErrors::Type)
1825                                 errorType {
1826   // TODO(hajimehoshi): The similar logic exists at TranslateBubbleView::
1827   // ShowBubble. This should be unified.
1828   if (translateBubbleController_) {
1829     // When the user reads the advanced setting panel, the bubble should not be
1830     // changed because they are focusing on the bubble.
1831     if (translateBubbleController_.webContents == contents &&
1832         translateBubbleController_.model->GetViewState() ==
1833         TranslateBubbleModel::VIEW_STATE_ADVANCED) {
1834       return;
1835     }
1836     if (step != translate::TRANSLATE_STEP_TRANSLATE_ERROR) {
1837       TranslateBubbleModel::ViewState viewState =
1838           TranslateBubbleModelImpl::TranslateStepToViewState(step);
1839       [translateBubbleController_ switchView:viewState];
1840     } else {
1841       [translateBubbleController_ switchToErrorView:errorType];
1842     }
1843     return;
1844   }
1846   std::string sourceLanguage;
1847   std::string targetLanguage;
1848   ChromeTranslateClient::GetTranslateLanguages(
1849       contents, &sourceLanguage, &targetLanguage);
1851   scoped_ptr<translate::TranslateUIDelegate> uiDelegate(
1852       new translate::TranslateUIDelegate(
1853           ChromeTranslateClient::GetManagerFromWebContents(contents)
1854               ->GetWeakPtr(),
1855           sourceLanguage,
1856           targetLanguage));
1857   scoped_ptr<TranslateBubbleModel> model(
1858       new TranslateBubbleModelImpl(step, uiDelegate.Pass()));
1859   translateBubbleController_ = [[TranslateBubbleController alloc]
1860                                  initWithParentWindow:self
1861                                                 model:model.Pass()
1862                                           webContents:contents];
1863   [translateBubbleController_ showWindow:nil];
1865   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1866   [center addObserver:self
1867              selector:@selector(translateBubbleWindowWillClose:)
1868                  name:NSWindowWillCloseNotification
1869                object:[translateBubbleController_ window]];
1872 - (void)dismissPermissionBubble {
1873   PermissionBubbleManager* manager = [self permissionBubbleManager];
1874   if (manager)
1875     manager->HideBubble();
1878 // Nil out the weak translate bubble controller reference.
1879 - (void)translateBubbleWindowWillClose:(NSNotification*)notification {
1880   DCHECK_EQ([notification object], [translateBubbleController_ window]);
1882   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
1883   [center removeObserver:self
1884                     name:NSWindowWillCloseNotification
1885                   object:[translateBubbleController_ window]];
1886   translateBubbleController_ = nil;
1889 // If the browser is in incognito mode or has multi-profiles, install the image
1890 // view to decorate the window at the upper right. Use the same base y
1891 // coordinate as the tab strip.
1892 - (void)installAvatar {
1893   // Install the image into the badge view. Hide it for now; positioning and
1894   // sizing will be done by the layout code. The AvatarIcon will choose which
1895   // image to display based on the browser. The AvatarButton will display
1896   // the browser profile's name unless the browser is incognito.
1897   NSView* view;
1898   if ([self shouldUseNewAvatarButton]) {
1899     avatarButtonController_.reset(
1900       [[AvatarButtonController alloc] initWithBrowser:browser_.get()]);
1901   } else {
1902     avatarButtonController_.reset(
1903       [[AvatarIconController alloc] initWithBrowser:browser_.get()]);
1904   }
1905   view = [avatarButtonController_ view];
1906   [view setAutoresizingMask:NSViewMinXMargin | NSViewMinYMargin];
1907   [view setHidden:![self shouldShowAvatar]];
1909   // Install the view.
1910   [[[self window] contentView] addSubview:view];
1913 // Called when we get a three-finger swipe.
1914 - (void)swipeWithEvent:(NSEvent*)event {
1915   CGFloat deltaX = [event deltaX];
1916   CGFloat deltaY = [event deltaY];
1918   // Map forwards and backwards to history; left is positive, right is negative.
1919   unsigned int command = 0;
1920   if (deltaX > 0.5) {
1921     command = IDC_BACK;
1922   } else if (deltaX < -0.5) {
1923     command = IDC_FORWARD;
1924   } else if (deltaY > 0.5) {
1925     // TODO(pinkerton): figure out page-up, http://crbug.com/16305
1926   } else if (deltaY < -0.5) {
1927     // TODO(pinkerton): figure out page-down, http://crbug.com/16305
1928   }
1930   // Ensure the command is valid first (ExecuteCommand() won't do that) and
1931   // then make it so.
1932   if (chrome::IsCommandEnabled(browser_.get(), command)) {
1933     chrome::ExecuteCommandWithDisposition(
1934         browser_.get(),
1935         command,
1936         ui::WindowOpenDispositionFromNSEvent(event));
1937   }
1940 // Delegate method called when window is resized.
1941 - (void)windowDidResize:(NSNotification*)notification {
1942   [self saveWindowPositionIfNeeded];
1944   // Resize (and possibly move) the status bubble. Note that we may get called
1945   // when the status bubble does not exist.
1946   if (statusBubble_) {
1947     statusBubble_->UpdateSizeAndPosition();
1948   }
1950   // Let the selected RenderWidgetHostView know, so that it can tell plugins.
1951   if (WebContents* contents = [self webContents]) {
1952     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
1953       rwhv->WindowFrameChanged();
1954   }
1956   // The FindBar needs to know its own position to properly detect overlaps
1957   // with find results. The position changes whenever the window is resized,
1958   // and |layoutSubviews| computes the FindBar's position.
1959   // TODO: calling |layoutSubviews| here is a waste, find a better way to
1960   // do this.
1961   if ([findBarCocoaController_ isFindBarVisible])
1962     [self layoutSubviews];
1965 // Handle the openLearnMoreAboutCrashLink: action from SadTabView when
1966 // "Learn more" link in "Aw snap" page (i.e. crash page or sad tab) is
1967 // clicked. Decoupling the action from its target makes unit testing possible.
1968 - (void)openLearnMoreAboutCrashLink:(id)sender {
1969   if (WebContents* contents = [self webContents]) {
1970     OpenURLParams params(GURL(chrome::kCrashReasonURL), Referrer(), CURRENT_TAB,
1971                          ui::PAGE_TRANSITION_LINK, false);
1972     contents->OpenURL(params);
1973   }
1976 // Delegate method called when window did move. (See below for why we don't use
1977 // |-windowWillMove:|, which is called less frequently than |-windowDidMove|
1978 // instead.)
1979 - (void)windowDidMove:(NSNotification*)notification {
1980   [self saveWindowPositionIfNeeded];
1982   NSWindow* window = [self window];
1983   NSRect windowFrame = [window frame];
1984   NSRect workarea = [[window screen] visibleFrame];
1986   // We reset the window growth state whenever the window is moved out of the
1987   // work area or away (up or down) from the bottom or top of the work area.
1988   // Unfortunately, Cocoa sends |-windowWillMove:| too frequently (including
1989   // when clicking on the title bar to activate), and of course
1990   // |-windowWillMove| is called too early for us to apply our heuristic. (The
1991   // heuristic we use for detecting window movement is that if |windowTopGrowth_
1992   // > 0|, then we should be at the bottom of the work area -- if we're not,
1993   // we've moved. Similarly for the other side.)
1994   if (!NSContainsRect(workarea, windowFrame) ||
1995       (windowTopGrowth_ > 0 && NSMinY(windowFrame) != NSMinY(workarea)) ||
1996       (windowBottomGrowth_ > 0 && NSMaxY(windowFrame) != NSMaxY(workarea)))
1997     [self resetWindowGrowthState];
1999   // Let the selected RenderWidgetHostView know, so that it can tell plugins.
2000   if (WebContents* contents = [self webContents]) {
2001     if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
2002       rwhv->WindowFrameChanged();
2003   }
2006 // Delegate method called when window will be resized; not called for
2007 // |-setFrame:display:|.
2008 - (NSSize)windowWillResize:(NSWindow*)sender toSize:(NSSize)frameSize {
2009   [self resetWindowGrowthState];
2010   return frameSize;
2013 // Delegate method: see |NSWindowDelegate| protocol.
2014 - (id)windowWillReturnFieldEditor:(NSWindow*)sender toObject:(id)obj {
2015   // Ask the toolbar controller if it wants to return a custom field editor
2016   // for the specific object.
2017   return [toolbarController_ customFieldEditorForObject:obj];
2020 // (Needed for |BookmarkBarControllerDelegate| protocol.)
2021 - (void)bookmarkBar:(BookmarkBarController*)controller
2022  didChangeFromState:(BookmarkBar::State)oldState
2023             toState:(BookmarkBar::State)newState {
2024   [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
2025   [self adjustToolbarAndBookmarkBarForCompression:
2026           [controller getDesiredToolbarHeightCompression]];
2029 // (Needed for |BookmarkBarControllerDelegate| protocol.)
2030 - (void)bookmarkBar:(BookmarkBarController*)controller
2031 willAnimateFromState:(BookmarkBar::State)oldState
2032             toState:(BookmarkBar::State)newState {
2033   [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
2034   [self adjustToolbarAndBookmarkBarForCompression:
2035           [controller getDesiredToolbarHeightCompression]];
2038 // (Private/TestingAPI)
2039 - (void)resetWindowGrowthState {
2040   windowTopGrowth_ = 0;
2041   windowBottomGrowth_ = 0;
2042   isShrinkingFromZoomed_ = NO;
2045 - (NSSize)overflowFrom:(NSRect)source
2046                     to:(NSRect)target {
2047   // If |source|'s boundary is outside of |target|'s, set its distance
2048   // to |x|.  Note that |source| can overflow to both side, but we
2049   // have nothing to do for such case.
2050   CGFloat x = 0;
2051   if (NSMaxX(target) < NSMaxX(source)) // |source| overflows to right
2052     x = NSMaxX(source) - NSMaxX(target);
2053   else if (NSMinX(source) < NSMinX(target)) // |source| overflows to left
2054     x = NSMinX(source) - NSMinX(target);
2056   // Same as |x| above.
2057   CGFloat y = 0;
2058   if (NSMaxY(target) < NSMaxY(source))
2059     y = NSMaxY(source) - NSMaxY(target);
2060   else if (NSMinY(source) < NSMinY(target))
2061     y = NSMinY(source) - NSMinY(target);
2063   return NSMakeSize(x, y);
2066 // (Private/TestingAPI)
2067 - (ExclusiveAccessBubbleWindowController*)
2068         exclusiveAccessBubbleWindowController {
2069   return exclusiveAccessBubbleWindowController_.get();
2072 - (NSRect)omniboxPopupAnchorRect {
2073   // Start with toolbar rect.
2074   NSView* toolbarView = [toolbarController_ view];
2075   NSRect anchorRect = [toolbarView frame];
2077   // Adjust to account for height and possible bookmark bar. Compress by 1
2078   // to account for the separator.
2079   anchorRect.origin.y =
2080       NSMaxY(anchorRect) - [toolbarController_ desiredHeightForCompression:1];
2082   // Shift to window base coordinates.
2083   return [[toolbarView superview] convertRect:anchorRect toView:nil];
2086 - (void)sheetDidEnd:(NSWindow*)sheet
2087          returnCode:(NSInteger)code
2088             context:(void*)context {
2089   [sheet orderOut:self];
2092 - (void)executeExtensionCommand:(const std::string&)extension_id
2093                         command:(const extensions::Command&)command {
2094   // Global commands are handled by the ExtensionCommandsGlobalRegistry
2095   // instance.
2096   DCHECK(!command.global());
2097   extension_keybinding_registry_->ExecuteCommand(extension_id,
2098                                                  command.accelerator());
2101 @end  // @implementation BrowserWindowController
2103 @implementation BrowserWindowController(Fullscreen)
2105 - (void)handleLionToggleFullscreen {
2106   DCHECK(base::mac::IsOSLionOrLater());
2107   chrome::ExecuteCommand(browser_.get(), IDC_FULLSCREEN);
2110 - (NSString*)titleForFullscreenMenuItem {
2111   if (!chrome::mac::SupportsSystemFullscreen()) {
2112     return l10n_util::GetNSString([self inPresentationMode]
2113                                       ? IDS_EXIT_PRESENTATION_MAC
2114                                       : IDS_ENTER_PRESENTATION_MAC);
2115   }
2117   return l10n_util::GetNSString([self isInAppKitFullscreen]
2118                                     ? IDS_EXIT_FULLSCREEN_MAC
2119                                     : IDS_ENTER_FULLSCREEN_MAC);
2122 - (void)enterBrowserFullscreenWithToolbar:(BOOL)withToolbar {
2123   if (!chrome::mac::SupportsSystemFullscreen()) {
2124     if (![self isInImmersiveFullscreen])
2125       [self enterImmersiveFullscreen];
2126     return;
2127   }
2129   if ([self isInAppKitFullscreen]) {
2130     [self updateFullscreenWithToolbar:withToolbar];
2131   } else {
2132     // Need to invoke AppKit Fullscreen API. Presentation mode (if set) will
2133     // automatically be enabled in |-windowWillEnterFullScreen:|.
2134     enteringPresentationMode_ = !withToolbar;
2135     [self enterAppKitFullscreen];
2136   }
2139 - (void)updateFullscreenWithToolbar:(BOOL)withToolbar {
2140   [self adjustUIForSlidingFullscreenStyle:
2141             withToolbar ? fullscreen_mac::OMNIBOX_TABS_PRESENT
2142                         : fullscreen_mac::OMNIBOX_TABS_HIDDEN];
2145 - (void)updateFullscreenExitBubbleURL:(const GURL&)url
2146                            bubbleType:(ExclusiveAccessBubbleType)bubbleType {
2147   fullscreenUrl_ = url;
2148   exclusiveAccessBubbleType_ = bubbleType;
2149   [self layoutSubviews];
2150   [self showFullscreenExitBubbleIfNecessary];
2153 - (BOOL)isInAnyFullscreenMode {
2154   return [self isInImmersiveFullscreen] || [self isInAppKitFullscreen];
2157 - (BOOL)isInImmersiveFullscreen {
2158   return fullscreenWindow_.get() != nil || enteringImmersiveFullscreen_;
2161 - (BOOL)isInAppKitFullscreen {
2162   return !exitingAppKitFullscreen_ &&
2163          (([[self window] styleMask] & NSFullScreenWindowMask) ==
2164               NSFullScreenWindowMask ||
2165           enteringAppKitFullscreen_);
2168 - (void)enterExtensionFullscreenForURL:(const GURL&)url
2169                             bubbleType:(ExclusiveAccessBubbleType)bubbleType {
2170   if (chrome::mac::SupportsSystemFullscreen()) {
2171     fullscreenUrl_ = url;
2172     exclusiveAccessBubbleType_ = bubbleType;
2173     [self enterBrowserFullscreenWithToolbar:NO];
2174   } else {
2175     [self enterImmersiveFullscreen];
2176     DCHECK(!url.is_empty());
2177     [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType];
2178   }
2181 - (void)enterWebContentFullscreenForURL:(const GURL&)url
2182                              bubbleType:(ExclusiveAccessBubbleType)bubbleType {
2183   [self enterImmersiveFullscreen];
2184   if (!url.is_empty())
2185     [self updateFullscreenExitBubbleURL:url bubbleType:bubbleType];
2188 - (void)exitAnyFullscreen {
2189   // TODO(erikchen): Fullscreen modes should stack. Should be able to exit
2190   // Immersive Fullscreen and still be in AppKit Fullscreen.
2191   if ([self isInAppKitFullscreen])
2192     [self exitAppKitFullscreen];
2193   if ([self isInImmersiveFullscreen])
2194     [self exitImmersiveFullscreen];
2197 - (BOOL)inPresentationMode {
2198   return presentationModeController_.get() &&
2199          [presentationModeController_ inPresentationMode] &&
2200          presentationModeController_.get().slidingStyle ==
2201              fullscreen_mac::OMNIBOX_TABS_HIDDEN;
2204 - (void)resizeFullscreenWindow {
2205   DCHECK([self isInAnyFullscreenMode]);
2206   if (![self isInAnyFullscreenMode])
2207     return;
2209   NSWindow* window = [self window];
2210   [window setFrame:[[window screen] frame] display:YES];
2211   [self layoutSubviews];
2214 - (BOOL)isBarVisibilityLockedForOwner:(id)owner {
2215   DCHECK(owner);
2216   DCHECK(barVisibilityLocks_);
2217   return [barVisibilityLocks_ containsObject:owner];
2220 - (void)lockBarVisibilityForOwner:(id)owner
2221                     withAnimation:(BOOL)animate
2222                             delay:(BOOL)delay {
2223   if (![self isBarVisibilityLockedForOwner:owner]) {
2224     [barVisibilityLocks_ addObject:owner];
2226     // If enabled, show the overlay if necessary (and if in presentation mode).
2227     if (barVisibilityUpdatesEnabled_) {
2228       [presentationModeController_ ensureOverlayShownWithAnimation:animate
2229                                                              delay:delay];
2230     }
2231   }
2234 - (void)releaseBarVisibilityForOwner:(id)owner
2235                        withAnimation:(BOOL)animate
2236                                delay:(BOOL)delay {
2237   if ([self isBarVisibilityLockedForOwner:owner]) {
2238     [barVisibilityLocks_ removeObject:owner];
2240     // If enabled, hide the overlay if necessary (and if in presentation mode).
2241     if (barVisibilityUpdatesEnabled_ &&
2242         ![barVisibilityLocks_ count]) {
2243       [presentationModeController_ ensureOverlayHiddenWithAnimation:animate
2244                                                               delay:delay];
2245     }
2246   }
2249 - (BOOL)floatingBarHasFocus {
2250   NSResponder* focused = [[self window] firstResponder];
2251   return [focused isKindOfClass:[AutocompleteTextFieldEditor class]];
2254 @end  // @implementation BrowserWindowController(Fullscreen)
2257 @implementation BrowserWindowController(WindowType)
2259 - (BOOL)supportsWindowFeature:(int)feature {
2260   return browser_->SupportsWindowFeature(
2261       static_cast<Browser::WindowFeature>(feature));
2264 - (BOOL)hasTitleBar {
2265   return [self supportsWindowFeature:Browser::FEATURE_TITLEBAR];
2268 - (BOOL)hasToolbar {
2269   return [self supportsWindowFeature:Browser::FEATURE_TOOLBAR];
2272 - (BOOL)hasLocationBar {
2273   return [self supportsWindowFeature:Browser::FEATURE_LOCATIONBAR];
2276 - (BOOL)supportsBookmarkBar {
2277   return [self supportsWindowFeature:Browser::FEATURE_BOOKMARKBAR];
2280 - (BOOL)isTabbedWindow {
2281   return browser_->is_type_tabbed();
2284 @end  // @implementation BrowserWindowController(WindowType)