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