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