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