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