- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / browser_window_controller_private.mm
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
6
7 #include <cmath>
8
9 #include "base/command_line.h"
10 #import "base/mac/scoped_nsobject.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/prefs/scoped_user_pref_update.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/fullscreen.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/profiles/profile_info_util.h"
17 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_window_state.h"
20 #import "chrome/browser/ui/cocoa/browser/avatar_button_controller.h"
21 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
22 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
23 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
24 #import "chrome/browser/ui/cocoa/floating_bar_backing_view.h"
25 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
26 #import "chrome/browser/ui/cocoa/fullscreen_mode_controller.h"
27 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
28 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
29 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
30 #import "chrome/browser/ui/cocoa/nsview_additions.h"
31 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
32 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
33 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
34 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
35 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
36 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
37 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
38 #include "chrome/browser/ui/tabs/tab_strip_model.h"
39 #include "chrome/common/chrome_switches.h"
40 #include "chrome/common/pref_names.h"
41 #include "content/public/browser/render_widget_host_view.h"
42 #include "content/public/browser/web_contents.h"
43 #include "content/public/browser/web_contents_view.h"
44 #import "ui/base/cocoa/focus_tracker.h"
45 #include "ui/base/ui_base_types.h"
46
47 using content::RenderWidgetHostView;
48 using content::WebContents;
49
50 namespace {
51
52 // Space between the incognito badge and the right edge of the window.
53 const CGFloat kAvatarRightOffset = 4;
54
55 // The amount by which to shrink the tab strip (on the right) when the
56 // incognito badge is present.
57 const CGFloat kAvatarTabStripShrink = 18;
58
59 // Insets for the location bar, used when the full toolbar is hidden.
60 // TODO(viettrungluu): We can argue about the "correct" insetting; I like the
61 // following best, though arguably 0 inset is better/more correct.
62 const CGFloat kLocBarLeftRightInset = 1;
63 const CGFloat kLocBarTopInset = 0;
64 const CGFloat kLocBarBottomInset = 1;
65
66 }  // namespace
67
68 @implementation BrowserWindowController(Private)
69
70 // Create the tab strip controller.
71 - (void)createTabStripController {
72   DCHECK([overlayableContentsController_ activeContainer]);
73   DCHECK([[overlayableContentsController_ activeContainer] window]);
74   tabStripController_.reset([[TabStripController alloc]
75       initWithView:[self tabStripView]
76         switchView:[overlayableContentsController_ activeContainer]
77            browser:browser_.get()
78           delegate:self]);
79 }
80
81 - (void)saveWindowPositionIfNeeded {
82   if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
83     return;
84
85   // If we're in fullscreen mode, save the position of the regular window
86   // instead.
87   NSWindow* window = [self isFullscreen] ? savedRegularWindow_ : [self window];
88
89   // Window positions are stored relative to the origin of the primary monitor.
90   NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
91   NSScreen* windowScreen = [window screen];
92
93   // Start with the window's frame, which is in virtual coordinates.
94   // Do some y twiddling to flip the coordinate system.
95   gfx::Rect bounds(NSRectToCGRect([window frame]));
96   bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
97
98   // Browser::SaveWindowPlacement saves information for session restore.
99   ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
100   if ([window isMiniaturized])
101     show_state = ui::SHOW_STATE_MINIMIZED;
102   else if ([self isFullscreen])
103     show_state = ui::SHOW_STATE_FULLSCREEN;
104   chrome::SaveWindowPlacement(browser_.get(), bounds, show_state);
105
106   // |windowScreen| can be nil (for example, if the monitor arrangement was
107   // changed while in fullscreen mode).  If we see a nil screen, return without
108   // saving.
109   // TODO(rohitrao): We should just not save anything for fullscreen windows.
110   // http://crbug.com/36479.
111   if (!windowScreen)
112     return;
113
114   // Only save main window information to preferences.
115   PrefService* prefs = browser_->profile()->GetPrefs();
116   if (!prefs || browser_ != chrome::GetLastActiveBrowser())
117     return;
118
119   // Save the current work area, in flipped coordinates.
120   gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
121   workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
122
123   DictionaryPrefUpdate update(
124       prefs,
125       chrome::GetWindowPlacementKey(browser_.get()).c_str());
126   DictionaryValue* windowPreferences = update.Get();
127   windowPreferences->SetInteger("left", bounds.x());
128   windowPreferences->SetInteger("top", bounds.y());
129   windowPreferences->SetInteger("right", bounds.right());
130   windowPreferences->SetInteger("bottom", bounds.bottom());
131   windowPreferences->SetBoolean("maximized", false);
132   windowPreferences->SetBoolean("always_on_top", false);
133   windowPreferences->SetInteger("work_area_left", workArea.x());
134   windowPreferences->SetInteger("work_area_top", workArea.y());
135   windowPreferences->SetInteger("work_area_right", workArea.right());
136   windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
137 }
138
139 - (NSRect)window:(NSWindow*)window
140 willPositionSheet:(NSWindow*)sheet
141        usingRect:(NSRect)defaultSheetRect {
142   // Position the sheet as follows:
143   //  - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
144   //    bookmark bar is disabled), position the sheet immediately below the
145   //    normal toolbar.
146   //  - If the bookmark bar is shown (attached to the normal toolbar), position
147   //    the sheet below the bookmark bar.
148   //  - If the bookmark bar is currently animating, position the sheet according
149   //    to where the bar will be when the animation ends.
150   switch ([bookmarkBarController_ currentState]) {
151     case BookmarkBar::SHOW: {
152       NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame];
153       defaultSheetRect.origin.y = bookmarkBarFrame.origin.y;
154       break;
155     }
156     case BookmarkBar::HIDDEN:
157     case BookmarkBar::DETACHED: {
158       if ([self hasToolbar]) {
159         NSRect toolbarFrame = [[toolbarController_ view] frame];
160         defaultSheetRect.origin.y = toolbarFrame.origin.y;
161       } else {
162         // The toolbar is not shown in application mode. The sheet should be
163         // located at the top of the window, under the title of the window.
164         defaultSheetRect.origin.y = NSHeight([[window contentView] frame]) -
165                                     defaultSheetRect.size.height;
166       }
167       break;
168     }
169   }
170   return defaultSheetRect;
171 }
172
173 - (void)layoutSubviews {
174   // With the exception of the top tab strip, the subviews which we lay out are
175   // subviews of the content view, so we mainly work in the content view's
176   // coordinate system. Note, however, that the content view's coordinate system
177   // and the window's base coordinate system should coincide.
178   NSWindow* window = [self window];
179   NSView* contentView = [window contentView];
180   NSRect contentBounds = [contentView bounds];
181   CGFloat minX = NSMinX(contentBounds);
182   CGFloat minY = NSMinY(contentBounds);
183   CGFloat width = NSWidth(contentBounds);
184
185   BOOL useSimplifiedFullscreen = CommandLine::ForCurrentProcess()->HasSwitch(
186       switches::kEnableSimplifiedFullscreen);
187
188   // Suppress title drawing if necessary.
189   if ([window respondsToSelector:@selector(setShouldHideTitle:)])
190     [(id)window setShouldHideTitle:![self hasTitleBar]];
191
192   // Update z-order. The code below depends on this.
193   [self updateSubviewZOrder:[self inPresentationMode]];
194
195   BOOL inPresentationMode = [self inPresentationMode];
196   CGFloat floatingBarHeight = [self floatingBarHeight];
197   // In presentation mode, |yOffset| accounts for the sliding position of the
198   // floating bar and the extra offset needed to dodge the menu bar.
199   CGFloat yOffset = inPresentationMode && !useSimplifiedFullscreen ?
200       (std::floor((1 - floatingBarShownFraction_) * floatingBarHeight) -
201           [presentationModeController_ floatingBarVerticalOffset]) : 0;
202   CGFloat maxY = NSMaxY(contentBounds) + yOffset;
203
204   if ([self hasTabStrip]) {
205     // If we need to lay out the top tab strip, replace |maxY| with a higher
206     // value, and then lay out the tab strip.
207     NSRect windowFrame = [contentView convertRect:[window frame] fromView:nil];
208     maxY = NSHeight(windowFrame) + yOffset;
209     if (useSimplifiedFullscreen && [self isFullscreen]) {
210       CGFloat tabStripHeight = NSHeight([[self tabStripView] frame]);
211       CGFloat revealAmount = (1 - floatingBarShownFraction_) * tabStripHeight;
212       // In simplified fullscreen, only the toolbar is visible by default, and
213       // the tabstrip and menu bar come down (each separately) when the user
214       // mouses near the top of the window. Push the maxY of the toolbar up by
215       // the amount of the tabstrip that is revealed, while removing the amount
216       // of space needed by the menu bar.
217       maxY += std::floor(
218           revealAmount - [fullscreenModeController_ menuBarHeight]);
219     }
220     maxY = [self layoutTabStripAtMaxY:maxY
221                                 width:width
222                            fullscreen:[self isFullscreen]];
223   }
224
225   // Sanity-check |maxY|.
226   DCHECK_GE(maxY, minY);
227   DCHECK_LE(maxY, NSMaxY(contentBounds) + yOffset);
228
229   // Place the toolbar at the top of the reserved area.
230   maxY = [self layoutToolbarAtMinX:minX maxY:maxY width:width];
231
232   // If we're not displaying the bookmark bar below the info bar, then it goes
233   // immediately below the toolbar.
234   BOOL placeBookmarkBarBelowInfoBar = [self placeBookmarkBarBelowInfoBar];
235   if (!placeBookmarkBarBelowInfoBar)
236     maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width];
237
238   // The floating bar backing view doesn't actually add any height.
239   NSRect floatingBarBackingRect =
240       NSMakeRect(minX, maxY, width, floatingBarHeight);
241   [self layoutFloatingBarBackingView:floatingBarBackingRect
242                     presentationMode:inPresentationMode];
243
244   // Place the find bar immediately below the toolbar/attached bookmark bar. In
245   // presentation mode, it hangs off the top of the screen when the bar is
246   // hidden.
247   [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:width];
248   [fullscreenExitBubbleController_ positionInWindowAtTop:maxY width:width];
249
250   // If in presentation mode, reset |maxY| to top of screen, so that the
251   // floating bar slides over the things which appear to be in the content area.
252   if (inPresentationMode ||
253       (useSimplifiedFullscreen && !fullscreenUrl_.is_empty())) {
254     maxY = NSMaxY(contentBounds);
255   }
256
257   // Also place the info bar container immediate below the toolbar, except in
258   // presentation mode in which case it's at the top of the visual content area.
259   maxY = [self layoutInfoBarAtMinX:minX maxY:maxY width:width];
260
261   // If the bookmark bar is detached, place it next in the visual content area.
262   if (placeBookmarkBarBelowInfoBar)
263     maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width];
264
265   // Place the download shelf, if any, at the bottom of the view.
266   minY = [self layoutDownloadShelfAtMinX:minX minY:minY width:width];
267
268   // Finally, the content area takes up all of the remaining space.
269   NSRect contentAreaRect = NSMakeRect(minX, minY, width, maxY - minY);
270   [self layoutTabContentArea:contentAreaRect];
271
272   // Normally, we don't need to tell the toolbar whether or not to show the
273   // divider, but things break down during animation.
274   [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
275 }
276
277 - (CGFloat)floatingBarHeight {
278   if (![self inPresentationMode])
279     return 0;
280
281   CGFloat totalHeight = [presentationModeController_ floatingBarVerticalOffset];
282
283   if ([self hasTabStrip])
284     totalHeight += NSHeight([[self tabStripView] frame]);
285
286   if ([self hasToolbar]) {
287     totalHeight += NSHeight([[toolbarController_ view] frame]);
288   } else if ([self hasLocationBar]) {
289     totalHeight += NSHeight([[toolbarController_ view] frame]) +
290                    kLocBarTopInset + kLocBarBottomInset;
291   }
292
293   if (![self placeBookmarkBarBelowInfoBar])
294     totalHeight += NSHeight([[bookmarkBarController_ view] frame]);
295
296   return totalHeight;
297 }
298
299 - (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
300                           width:(CGFloat)width
301                      fullscreen:(BOOL)fullscreen {
302   // Nothing to do if no tab strip.
303   if (![self hasTabStrip])
304     return maxY;
305
306   NSView* tabStripView = [self tabStripView];
307   CGFloat tabStripHeight = NSHeight([tabStripView frame]);
308   maxY -= tabStripHeight;
309   [tabStripView setFrame:NSMakeRect(0, maxY, width, tabStripHeight)];
310
311   // Set left indentation based on fullscreen mode status.
312   [tabStripController_ setLeftIndentForControls:(fullscreen ? 0 :
313       [[tabStripController_ class] defaultLeftIndentForControls])];
314
315   // Lay out the icognito/avatar badge because calculating the indentation on
316   // the right depends on it.
317   if ([self shouldShowAvatar]) {
318     NSView* avatarButton = [avatarButtonController_ view];
319     CGFloat buttonHeight = std::min(
320         static_cast<CGFloat>(profiles::kAvatarIconHeight), tabStripHeight);
321     [avatarButton setFrameSize:NSMakeSize(NSWidth([avatarButton frame]),
322                                           buttonHeight)];
323
324     // Actually place the badge *above* |maxY|, by +2 to miss the divider.
325     CGFloat badgeXOffset = -kAvatarRightOffset;
326     CGFloat badgeYOffset = 2 * [[avatarButton superview] cr_lineWidth];
327     NSPoint origin =
328         NSMakePoint(width - NSWidth([avatarButton frame]) + badgeXOffset,
329                     maxY + badgeYOffset);
330     [avatarButton setFrameOrigin:origin];
331     [avatarButton setHidden:NO];  // Make sure it's shown.
332   }
333
334   // Calculate the right indentation.  The default indentation built into the
335   // tabstrip leaves enough room for the fullscreen button or presentation mode
336   // toggle button on Lion.  On non-Lion systems, the right indent needs to be
337   // adjusted to make room for the new tab button when an avatar is present.
338   CGFloat rightIndent = 0;
339   if (base::mac::IsOSLionOrLater() &&
340       [[self window] isKindOfClass:[FramedBrowserWindow class]]) {
341     FramedBrowserWindow* window =
342         static_cast<FramedBrowserWindow*>([self window]);
343     rightIndent += -[window fullScreenButtonOriginAdjustment].x;
344   } else if ([self shouldShowAvatar]) {
345     rightIndent += kAvatarTabStripShrink;
346     NSButton* labelButton = [avatarButtonController_ labelButtonView];
347     if (labelButton)
348       rightIndent += NSWidth([labelButton frame]) + kAvatarRightOffset;
349   }
350   [tabStripController_ setRightIndentForControls:rightIndent];
351
352   // Go ahead and layout the tabs.
353   [tabStripController_ layoutTabsWithoutAnimation];
354
355   return maxY;
356 }
357
358 - (CGFloat)layoutToolbarAtMinX:(CGFloat)minX
359                           maxY:(CGFloat)maxY
360                          width:(CGFloat)width {
361   NSView* toolbarView = [toolbarController_ view];
362   NSRect toolbarFrame = [toolbarView frame];
363   if ([self hasToolbar]) {
364     // The toolbar is present in the window, so we make room for it.
365     DCHECK(![toolbarView isHidden]);
366     toolbarFrame.origin.x = minX;
367     toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame);
368     toolbarFrame.size.width = width;
369     maxY -= NSHeight(toolbarFrame);
370   } else {
371     if ([self hasLocationBar]) {
372       // Location bar is present with no toolbar. Put a border of
373       // |kLocBar...Inset| pixels around the location bar.
374       // TODO(viettrungluu): This is moderately ridiculous. The toolbar should
375       // really be aware of what its height should be (the way the toolbar
376       // compression stuff is currently set up messes things up).
377       DCHECK(![toolbarView isHidden]);
378       toolbarFrame.origin.x = kLocBarLeftRightInset;
379       toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame) - kLocBarTopInset;
380       toolbarFrame.size.width = width - 2 * kLocBarLeftRightInset;
381       maxY -= kLocBarTopInset + NSHeight(toolbarFrame) + kLocBarBottomInset;
382     } else {
383       DCHECK([toolbarView isHidden]);
384     }
385   }
386   [toolbarView setFrame:toolbarFrame];
387   return maxY;
388 }
389
390 - (BOOL)placeBookmarkBarBelowInfoBar {
391   // If we are currently displaying the NTP detached bookmark bar or animating
392   // to/from it (from/to anything else), we display the bookmark bar below the
393   // info bar.
394   return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
395          [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
396          [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
397 }
398
399 - (CGFloat)layoutBookmarkBarAtMinX:(CGFloat)minX
400                               maxY:(CGFloat)maxY
401                              width:(CGFloat)width {
402   [bookmarkBarController_ updateHiddenState];
403
404   NSView* bookmarkBarView = [bookmarkBarController_ view];
405   NSRect frame = [bookmarkBarView frame];
406   frame.origin.x = minX;
407   frame.origin.y = maxY - NSHeight(frame);
408   frame.size.width = width;
409   [bookmarkBarView setFrame:frame];
410   maxY -= NSHeight(frame);
411
412   // Pin the bookmark bar to the top of the window and make the width flexible.
413   [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
414
415   // TODO(viettrungluu): Does this really belong here? Calling it shouldn't be
416   // necessary in the non-NTP case.
417   [bookmarkBarController_ layoutSubviews];
418
419   return maxY;
420 }
421
422 - (void)layoutFloatingBarBackingView:(NSRect)frame
423                     presentationMode:(BOOL)presentationMode {
424   // Only display when in presentation mode.
425   if (presentationMode) {
426     // For certain window types such as app windows (e.g., the dev tools
427     // window), there's no actual overlay. (Displaying one would result in an
428     // overly sliding in only under the menu, which gives an ugly effect.)
429     if (floatingBarBackingView_.get()) {
430       // Set its frame.
431       [floatingBarBackingView_ setFrame:frame];
432     }
433
434     // But we want the logic to work as usual (for show/hide/etc. purposes).
435     [presentationModeController_ overlayFrameChanged:frame];
436   } else {
437     // Okay to call even if |floatingBarBackingView_| is nil.
438     if ([floatingBarBackingView_ superview])
439       [floatingBarBackingView_ removeFromSuperview];
440   }
441 }
442
443 - (CGFloat)layoutInfoBarAtMinX:(CGFloat)minX
444                           maxY:(CGFloat)maxY
445                          width:(CGFloat)width {
446   NSView* containerView = [infoBarContainerController_ view];
447   NSRect containerFrame = [containerView frame];
448   maxY -= NSHeight(containerFrame);
449   maxY += [infoBarContainerController_ overlappingTipHeight];
450   containerFrame.origin.x = minX;
451   containerFrame.origin.y = maxY;
452   containerFrame.size.width = width;
453   [containerView setFrame:containerFrame];
454   return maxY;
455 }
456
457 - (CGFloat)layoutDownloadShelfAtMinX:(CGFloat)minX
458                                 minY:(CGFloat)minY
459                                width:(CGFloat)width {
460   if (downloadShelfController_.get()) {
461     NSView* downloadView = [downloadShelfController_ view];
462     NSRect downloadFrame = [downloadView frame];
463     downloadFrame.origin.x = minX;
464     downloadFrame.origin.y = minY;
465     downloadFrame.size.width = width;
466     [downloadView setFrame:downloadFrame];
467     minY += NSHeight(downloadFrame);
468   }
469   return minY;
470 }
471
472 - (void)layoutTabContentArea:(NSRect)newFrame {
473   NSView* tabContentView = [self tabContentArea];
474   NSRect tabContentFrame = [tabContentView frame];
475
476   bool contentShifted =
477       NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
478       NSMinX(tabContentFrame) != NSMinX(newFrame);
479
480   tabContentFrame = newFrame;
481   [tabContentView setFrame:tabContentFrame];
482
483   // If the relayout shifts the content area up or down, let the renderer know.
484   if (contentShifted) {
485     if (WebContents* contents =
486             browser_->tab_strip_model()->GetActiveWebContents()) {
487       if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
488         rwhv->WindowFrameChanged();
489     }
490   }
491 }
492
493 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
494   CGFloat newHeight =
495       [toolbarController_ desiredHeightForCompression:compression];
496   NSRect toolbarFrame = [[toolbarController_ view] frame];
497   CGFloat deltaH = newHeight - toolbarFrame.size.height;
498
499   if (deltaH == 0)
500     return;
501
502   toolbarFrame.size.height = newHeight;
503   NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
504   bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
505   [[toolbarController_ view] setFrame:toolbarFrame];
506   [[bookmarkBarController_ view] setFrame:bookmarkFrame];
507   [self layoutSubviews];
508 }
509
510 // Fullscreen and presentation mode methods
511
512 - (BOOL)shouldShowPresentationModeToggle {
513   return chrome::mac::SupportsSystemFullscreen() && [self isFullscreen];
514 }
515
516 - (void)moveViewsForFullscreenForSnowLeopard:(BOOL)fullscreen
517     regularWindow:(NSWindow*)regularWindow
518     fullscreenWindow:(NSWindow*)fullscreenWindow {
519   // This method is only for systems without fullscreen support.
520   DCHECK(!chrome::mac::SupportsSystemFullscreen());
521
522   NSWindow* sourceWindow = fullscreen ? regularWindow : fullscreenWindow;
523   NSWindow* destWindow = fullscreen ? fullscreenWindow : regularWindow;
524
525   // Close the bookmark bubble, if it's open.  Use |-ok:| instead of |-cancel:|
526   // or |-close| because that matches the behavior when the bubble loses key
527   // status.
528   [bookmarkBubbleController_ ok:self];
529
530   // Save the current first responder so we can restore after views are moved.
531   base::scoped_nsobject<FocusTracker> focusTracker(
532       [[FocusTracker alloc] initWithWindow:sourceWindow]);
533
534   // While we move views (and focus) around, disable any bar visibility changes.
535   [self disableBarVisibilityUpdates];
536
537   // Retain the tab strip view while we remove it from its superview.
538   base::scoped_nsobject<NSView> tabStripView;
539   if ([self hasTabStrip]) {
540     tabStripView.reset([[self tabStripView] retain]);
541     [tabStripView removeFromSuperview];
542   }
543
544   // Ditto for the content view.
545   base::scoped_nsobject<NSView> contentView(
546       [[sourceWindow contentView] retain]);
547   // Disable autoresizing of subviews while we move views around. This prevents
548   // spurious renderer resizes.
549   [contentView setAutoresizesSubviews:NO];
550   [contentView removeFromSuperview];
551
552   // Have to do this here, otherwise later calls can crash because the window
553   // has no delegate.
554   [sourceWindow setDelegate:nil];
555   [destWindow setDelegate:self];
556
557   // With this call, valgrind complains that a "Conditional jump or move depends
558   // on uninitialised value(s)".  The error happens in -[NSThemeFrame
559   // drawOverlayRect:].  I'm pretty convinced this is an Apple bug, but there is
560   // no visual impact.  I have been unable to tickle it away with other window
561   // or view manipulation Cocoa calls.  Stack added to suppressions_mac.txt.
562   [contentView setAutoresizesSubviews:YES];
563   [destWindow setContentView:contentView];
564
565   // Move the incognito badge if present.
566   if ([self shouldShowAvatar]) {
567     [[avatarButtonController_ view] removeFromSuperview];
568     [[avatarButtonController_ view] setHidden:YES];  // Will be shown in layout.
569     [[[destWindow contentView] superview] addSubview:
570         [avatarButtonController_ view]];
571   }
572
573   // Add the tab strip after setting the content view and moving the incognito
574   // badge (if any), so that the tab strip will be on top (in the z-order).
575   if ([self hasTabStrip])
576     [[[destWindow contentView] superview] addSubview:tabStripView];
577
578   [sourceWindow setWindowController:nil];
579   [self setWindow:destWindow];
580   [destWindow setWindowController:self];
581
582   // Move the status bubble over, if we have one.
583   if (statusBubble_)
584     statusBubble_->SwitchParentWindow(destWindow);
585
586   // Move the title over.
587   [destWindow setTitle:[sourceWindow title]];
588
589   // The window needs to be onscreen before we can set its first responder.
590   // Ordering the window to the front can change the active Space (either to
591   // the window's old Space or to the application's assigned Space). To prevent
592   // this by temporarily change the collectionBehavior.
593   NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior];
594   [destWindow setCollectionBehavior:
595       NSWindowCollectionBehaviorMoveToActiveSpace];
596   [destWindow makeKeyAndOrderFront:self];
597   [destWindow setCollectionBehavior:behavior];
598
599   [focusTracker restoreFocusInWindow:destWindow];
600   [sourceWindow orderOut:self];
601
602   // We're done moving focus, so re-enable bar visibility changes.
603   [self enableBarVisibilityUpdates];
604 }
605
606 - (void)setPresentationModeInternal:(BOOL)presentationMode
607                       forceDropdown:(BOOL)forceDropdown {
608   if (presentationMode == [self inPresentationMode])
609     return;
610
611   if (presentationMode) {
612     BOOL fullscreen_for_tab =
613         browser_->fullscreen_controller()->IsFullscreenForTabOrPending();
614     BOOL kiosk_mode =
615         CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
616     BOOL showDropdown = !fullscreen_for_tab &&
617         !kiosk_mode &&
618         (forceDropdown || [self floatingBarHasFocus]);
619     NSView* contentView = [[self window] contentView];
620     presentationModeController_.reset(
621         [[PresentationModeController alloc] initWithBrowserController:self]);
622     [presentationModeController_ enterPresentationModeForContentView:contentView
623                                  showDropdown:showDropdown];
624   } else {
625     [presentationModeController_ exitPresentationMode];
626     presentationModeController_.reset();
627   }
628
629   [self adjustUIForPresentationMode:presentationMode];
630   [self layoutSubviews];
631 }
632
633 // TODO(rohitrao): This method is misnamed now, since there is a flag that
634 // enables 10.6-style fullscreen on newer OSes.
635 - (void)enterFullscreenForSnowLeopard {
636   // Fade to black.
637   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
638   Boolean didFadeOut = NO;
639   CGDisplayFadeReservationToken token;
640   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
641       == kCGErrorSuccess) {
642     didFadeOut = YES;
643     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
644         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
645   }
646
647   // Create the fullscreen window.  After this line, isFullscreen will return
648   // YES.
649   fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
650   savedRegularWindow_ = [[self window] retain];
651   savedRegularWindowFrame_ = [savedRegularWindow_ frame];
652
653   [self moveViewsForFullscreenForSnowLeopard:YES
654                                regularWindow:[self window]
655                             fullscreenWindow:fullscreenWindow_.get()];
656
657   // When simplified fullscreen is enabled, do not enter presentation mode.
658   const CommandLine* command_line = CommandLine::ForCurrentProcess();
659   if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)) {
660     // TODO(rohitrao): Add code to manage the menubar here.
661   } else {
662     [self adjustUIForPresentationMode:YES];
663     [self setPresentationModeInternal:YES forceDropdown:NO];
664   }
665
666   [self layoutSubviews];
667
668   [self windowDidEnterFullScreen:nil];
669
670   // Fade back in.
671   if (didFadeOut) {
672     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
673         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
674     CGReleaseDisplayFadeReservation(token);
675   }
676 }
677
678 - (void)exitFullscreenForSnowLeopard {
679   // TODO(rohitrao): This method is misnamed now, since there is a flag that
680   // enables 10.6-style fullscreen on newer OSes.
681   DCHECK(!chrome::mac::SupportsSystemFullscreen());
682
683   // Fade to black.
684   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
685   Boolean didFadeOut = NO;
686   CGDisplayFadeReservationToken token;
687   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
688       == kCGErrorSuccess) {
689     didFadeOut = YES;
690     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
691         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
692   }
693
694   // When simplified fullscreen is enabled, the menubar status is managed
695   // directly by BWC.
696   const CommandLine* command_line = CommandLine::ForCurrentProcess();
697   if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)) {
698     // TODO(rohitrao): Add code to manage the menubar here.
699   }
700
701   [self windowWillExitFullScreen:nil];
702
703   [self moveViewsForFullscreenForSnowLeopard:NO
704                                         regularWindow:savedRegularWindow_
705                                      fullscreenWindow:fullscreenWindow_.get()];
706
707   // When exiting fullscreen mode, we need to call layoutSubviews manually.
708   [savedRegularWindow_ autorelease];
709   savedRegularWindow_ = nil;
710   fullscreenWindow_.reset();
711   [self layoutSubviews];
712
713   [self windowDidExitFullScreen:nil];
714
715   // Fade back in.
716   if (didFadeOut) {
717     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
718         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
719     CGReleaseDisplayFadeReservation(token);
720   }
721 }
722
723 // TODO(rohitrao): This function has shrunk into uselessness, and
724 // |-setFullscreen:| has grown rather large.  Find a good way to break up
725 // |-setFullscreen:| into smaller pieces.  http://crbug.com/36449
726 - (void)adjustUIForPresentationMode:(BOOL)fullscreen {
727   // Create the floating bar backing view if necessary.
728   if (fullscreen && !floatingBarBackingView_.get() &&
729       ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
730     floatingBarBackingView_.reset(
731         [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
732     [floatingBarBackingView_ setAutoresizingMask:(NSViewWidthSizable |
733                                                   NSViewMinYMargin)];
734   }
735
736   // Force the bookmark bar z-order to update.
737   [[bookmarkBarController_ view] removeFromSuperview];
738   [self updateSubviewZOrder:fullscreen];
739   [self updateAllowOverlappingViews:fullscreen];
740 }
741
742 - (void)showFullscreenExitBubbleIfNecessary {
743   // This method is called in response to
744   // |-updateFullscreenExitBubbleURL:bubbleType:|. If on Lion the system is
745   // transitioning, do not show the bubble because it will cause visual jank
746   // <http://crbug.com/130649>. This will be called again as part of
747   // |-windowDidEnterFullScreen:|, so arrange to do that work then instead.
748   if (enteringFullscreen_)
749     return;
750
751   [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
752
753   if (fullscreenBubbleType_ == FEB_TYPE_NONE ||
754       fullscreenBubbleType_ == FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) {
755     // Show no exit instruction bubble on Mac when in Browser Fullscreen.
756     [self destroyFullscreenExitBubbleIfNecessary];
757   } else {
758     [fullscreenExitBubbleController_ closeImmediately];
759     fullscreenExitBubbleController_.reset(
760         [[FullscreenExitBubbleController alloc]
761             initWithOwner:self
762                   browser:browser_.get()
763                       url:fullscreenUrl_
764                bubbleType:fullscreenBubbleType_]);
765     [fullscreenExitBubbleController_ showWindow];
766   }
767 }
768
769 - (void)destroyFullscreenExitBubbleIfNecessary {
770   [fullscreenExitBubbleController_ closeImmediately];
771   fullscreenExitBubbleController_.reset();
772 }
773
774 - (void)contentViewDidResize:(NSNotification*)notification {
775   [self layoutSubviews];
776 }
777
778 - (void)registerForContentViewResizeNotifications {
779   [[NSNotificationCenter defaultCenter]
780       addObserver:self
781          selector:@selector(contentViewDidResize:)
782              name:NSViewFrameDidChangeNotification
783            object:[[self window] contentView]];
784 }
785
786 - (void)deregisterForContentViewResizeNotifications {
787   [[NSNotificationCenter defaultCenter]
788       removeObserver:self
789                 name:NSViewFrameDidChangeNotification
790               object:[[self window] contentView]];
791 }
792
793 - (NSSize)window:(NSWindow*)window
794     willUseFullScreenContentSize:(NSSize)proposedSize {
795   return proposedSize;
796 }
797
798 - (NSApplicationPresentationOptions)window:(NSWindow*)window
799     willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
800   return (opt |
801           NSApplicationPresentationAutoHideDock |
802           NSApplicationPresentationAutoHideMenuBar);
803 }
804
805 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
806   [self registerForContentViewResizeNotifications];
807
808   NSWindow* window = [self window];
809   savedRegularWindowFrame_ = [window frame];
810   BOOL mode = enteringPresentationMode_ ||
811        browser_->fullscreen_controller()->IsFullscreenForTabOrPending();
812   enteringFullscreen_ = YES;
813   [self setPresentationModeInternal:mode forceDropdown:NO];
814 }
815
816 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
817   if (chrome::mac::SupportsSystemFullscreen())
818     [self deregisterForContentViewResizeNotifications];
819   enteringFullscreen_ = NO;
820   enteringPresentationMode_ = NO;
821
822   const CommandLine* command_line = CommandLine::ForCurrentProcess();
823   if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen) &&
824       fullscreenUrl_.is_empty()) {
825     fullscreenModeController_.reset([[FullscreenModeController alloc]
826         initWithBrowserWindowController:self]);
827   }
828
829   [self showFullscreenExitBubbleIfNecessary];
830   browser_->WindowFullscreenStateChanged();
831 }
832
833 - (void)windowWillExitFullScreen:(NSNotification*)notification {
834   if (chrome::mac::SupportsSystemFullscreen())
835     [self registerForContentViewResizeNotifications];
836   fullscreenModeController_.reset();
837   [self destroyFullscreenExitBubbleIfNecessary];
838   [self setPresentationModeInternal:NO forceDropdown:NO];
839 }
840
841 - (void)windowDidExitFullScreen:(NSNotification*)notification {
842   if (chrome::mac::SupportsSystemFullscreen())
843     [self deregisterForContentViewResizeNotifications];
844   browser_->WindowFullscreenStateChanged();
845 }
846
847 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
848   [self deregisterForContentViewResizeNotifications];
849   enteringFullscreen_ = NO;
850   [self setPresentationModeInternal:NO forceDropdown:NO];
851
852   // Force a relayout to try and get the window back into a reasonable state.
853   [self layoutSubviews];
854 }
855
856 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
857   [self deregisterForContentViewResizeNotifications];
858
859   // Force a relayout to try and get the window back into a reasonable state.
860   [self layoutSubviews];
861 }
862
863 - (void)enableBarVisibilityUpdates {
864   // Early escape if there's nothing to do.
865   if (barVisibilityUpdatesEnabled_)
866     return;
867
868   barVisibilityUpdatesEnabled_ = YES;
869
870   if ([barVisibilityLocks_ count])
871     [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
872   else
873     [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
874 }
875
876 - (void)disableBarVisibilityUpdates {
877   // Early escape if there's nothing to do.
878   if (!barVisibilityUpdatesEnabled_)
879     return;
880
881   barVisibilityUpdatesEnabled_ = NO;
882   [presentationModeController_ cancelAnimationAndTimers];
883 }
884
885 - (CGFloat)toolbarDividerOpacity {
886   return [bookmarkBarController_ toolbarDividerOpacity];
887 }
888
889 - (void)updateSubviewZOrder:(BOOL)inPresentationMode {
890   NSView* contentView = [[self window] contentView];
891   NSView* toolbarView = [toolbarController_ view];
892
893   if (inPresentationMode) {
894     // Toolbar is above tab contents so that it can slide down from top of
895     // screen.
896     [contentView cr_ensureSubview:toolbarView
897                      isPositioned:NSWindowAbove
898                        relativeTo:[self tabContentArea]];
899   } else {
900     // Toolbar is below tab contents so that the info bar arrow can appear above
901     // it.
902     [contentView cr_ensureSubview:toolbarView
903                      isPositioned:NSWindowBelow
904                        relativeTo:[self tabContentArea]];
905   }
906
907   // The bookmark bar is always below the toolbar.
908   [contentView cr_ensureSubview:[bookmarkBarController_ view]
909                    isPositioned:NSWindowBelow
910                      relativeTo:toolbarView];
911
912   if (inPresentationMode) {
913     // In presentation mode the info bar is below all other views.
914     [contentView cr_ensureSubview:[infoBarContainerController_ view]
915                      isPositioned:NSWindowBelow
916                        relativeTo:[self tabContentArea]];
917   } else {
918     // Above the toolbar but still below tab contents. Similar to the bookmark
919     // bar, this allows Instant results to be above the info bar.
920     [contentView cr_ensureSubview:[infoBarContainerController_ view]
921                      isPositioned:NSWindowAbove
922                        relativeTo:toolbarView];
923   }
924
925   // The find bar is above everything.
926   if (findBarCocoaController_) {
927     NSView* relativeView = nil;
928     if (inPresentationMode)
929       relativeView = toolbarView;
930     else
931       relativeView = [self tabContentArea];
932     [contentView cr_ensureSubview:[findBarCocoaController_ view]
933                      isPositioned:NSWindowAbove
934                        relativeTo:relativeView];
935   }
936
937   if (floatingBarBackingView_) {
938     if ([floatingBarBackingView_ cr_isBelowView:[self tabContentArea]])
939       [floatingBarBackingView_ removeFromSuperview];
940     if ([self placeBookmarkBarBelowInfoBar]) {
941       [contentView cr_ensureSubview:floatingBarBackingView_
942                        isPositioned:NSWindowAbove
943                          relativeTo:[bookmarkBarController_ view]];
944     } else {
945       [contentView cr_ensureSubview:floatingBarBackingView_
946                        isPositioned:NSWindowBelow
947                          relativeTo:[bookmarkBarController_ view]];
948     }
949   }
950 }
951
952 - (BOOL)shouldAllowOverlappingViews:(BOOL)inPresentationMode {
953   if (inPresentationMode)
954     return YES;
955
956   if (findBarCocoaController_ &&
957       ![[findBarCocoaController_ findBarView] isHidden]) {
958     return YES;
959   }
960
961   if (overlappedViewCount_)
962     return YES;
963
964   return NO;
965 }
966
967 - (void)updateAllowOverlappingViews:(BOOL)inPresentationMode {
968   WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
969   if (!contents)
970     return;
971
972   BOOL allowOverlappingViews =
973       [self shouldAllowOverlappingViews:inPresentationMode];
974
975   // The rendering path with overlapping views disabled causes bugs when
976   // transitioning between composited and non-composited mode.
977   // http://crbug.com/279472
978   allowOverlappingViews = YES;
979
980   if (allowOverlappingViews &&
981       [self coreAnimationStatus] ==
982           browser_window_controller::kCoreAnimationEnabledLazy) {
983     [[[self window] contentView] setWantsLayer:YES];
984     [[self tabStripView] setWantsLayer:YES];
985   }
986
987   contents->GetView()->SetAllowOverlappingViews(allowOverlappingViews);
988
989   DevToolsWindow* devToolsWindow =
990       DevToolsWindow::GetDockedInstanceForInspectedTab(contents);
991   if (devToolsWindow) {
992     devToolsWindow->web_contents()->GetView()->
993         SetAllowOverlappingViews(allowOverlappingViews);
994   }
995 }
996
997 - (void)updateInfoBarTipVisibility {
998   // If there's no toolbar then hide the infobar tip.
999   [infoBarContainerController_
1000       setShouldSuppressTopInfoBarTip:![self hasToolbar]];
1001 }
1002
1003 - (browser_window_controller::CoreAnimationStatus)coreAnimationStatus {
1004   // TODO(sail) Remove this.
1005   if (!CommandLine::ForCurrentProcess()->HasSwitch(
1006           switches::kUseCoreAnimation)) {
1007     return browser_window_controller::kCoreAnimationDisabled;
1008   }
1009   if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
1010           switches::kUseCoreAnimation) == "lazy") {
1011     return browser_window_controller::kCoreAnimationEnabledLazy;
1012   }
1013   if (CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
1014           switches::kUseCoreAnimation) == "disabled") {
1015     return browser_window_controller::kCoreAnimationDisabled;
1016   }
1017   return browser_window_controller::kCoreAnimationEnabledAlways;
1018 }
1019
1020 @end  // @implementation BrowserWindowController(Private)