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