mac: Fix a bug where a fullscreened window on Mavericks is too short.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / browser_window_controller_private.mm
blob0396108693c7032360f5e2b965316780aa80be26
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.
5 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
7 #include <cmath>
9 #include "base/command_line.h"
10 #include "base/mac/bind_objc_block.h"
11 #include "base/mac/mac_util.h"
12 #import "base/mac/scoped_nsobject.h"
13 #import "base/mac/sdk_forward_declarations.h"
14 #include "base/metrics/histogram.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/prefs/scoped_user_pref_update.h"
17 #include "chrome/browser/browser_process.h"
18 #include "chrome/browser/fullscreen.h"
19 #include "chrome/browser/profiles/profile.h"
20 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
21 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/browser/ui/browser_window_state.h"
24 #import "chrome/browser/ui/cocoa/browser_window_layout.h"
25 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
26 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
27 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
28 #import "chrome/browser/ui/cocoa/floating_bar_backing_view.h"
29 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
30 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
31 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
32 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
33 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
34 #import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h"
35 #import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h"
36 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
37 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
38 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
39 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
40 #import "chrome/browser/ui/cocoa/version_independent_window.h"
41 #import "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h"
42 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
43 #include "chrome/browser/ui/tabs/tab_strip_model.h"
44 #include "chrome/common/chrome_switches.h"
45 #include "chrome/common/pref_names.h"
46 #include "content/public/browser/render_widget_host_view.h"
47 #include "content/public/browser/web_contents.h"
48 #import "ui/base/cocoa/focus_tracker.h"
49 #import "ui/base/cocoa/nsview_additions.h"
50 #include "ui/base/ui_base_types.h"
52 using content::RenderWidgetHostView;
53 using content::WebContents;
55 namespace {
57 // Each time the user enters fullscreen, a single histogram enumeration is
58 // recorded. There are several relevant parameters, whose values are mapped
59 // directly into individual bits of the enumeration.
61 // + Fullscreen Mechanism: The mechanism by which the window's size is changed
62 // to encompass the entire screen. Bit 0.
63 //   - AppKit (value of bit: 1)
64 //   - Immersive (value of bit: 0)
66 // + Primary Screen: Whether the window is located on the screen at index 0.
67 // Depending on OSX version, this has different implications for menu bar
68 // visibility. Bit 1.
69 //   - Primary (value of bit: 1)
70 //   - Secondary (value of bit: 0)
72 // + Displays have separate spaces: An option available in Mission Control in
73 // OSX 10.9+. Bit 2.
74 //   - On (value of bit: 1)
75 //   - Off (value of bit: 0)
77 // + Multiple screens: Whether the user has multiple screens. If the window is
78 // located on a secondary screen, then there must be multiple screens. Bit 3.
79 //   - Yes (value of bit: 1)
80 //   - No (value of bit: 0)
82 enum FullscreenMechanism {
83   IMMERSIVE_FULLSCREEN_MECHANISM,
84   APPKIT_FULLSCREEN_MECHANISM,
87 enum {
88   FULLSCREEN_MECHANISM_BIT = 0,
89   PRIMARY_SCREEN_BIT = 1,
90   DISPLAYS_SEPARATE_SPACES_BIT = 2,
91   MULTIPLE_SCREENS_BIT = 3,
92   BIT_COUNT
95 // Emits a histogram entry indicating that |window| is being made fullscreen.
96 void RecordFullscreenHistogram(FullscreenMechanism mechanism,
97                                NSWindow* window) {
98   NSArray* screens = [NSScreen screens];
99   bool primary_screen = ([[window screen] isEqual:[screens objectAtIndex:0]]);
100   bool displays_have_separate_spaces =
101       [NSScreen respondsToSelector:@selector(screensHaveSeparateSpaces)] &&
102       [NSScreen screensHaveSeparateSpaces];
103   bool multiple_screens = [screens count] > 1;
105   int output = 0;
106   if (mechanism == APPKIT_FULLSCREEN_MECHANISM)
107     output += 1 << FULLSCREEN_MECHANISM_BIT;
109   if (primary_screen)
110     output += 1 << PRIMARY_SCREEN_BIT;
112   if (displays_have_separate_spaces)
113     output += 1 << DISPLAYS_SEPARATE_SPACES_BIT;
115   if (multiple_screens)
116     output += 1 << MULTIPLE_SCREENS_BIT;
118   int max_output = 1 << BIT_COUNT;
119   UMA_HISTOGRAM_ENUMERATION("OSX.Fullscreen.Enter", output, max_output);
122 }  // namespace
124 @implementation BrowserWindowController(Private)
126 // Create the tab strip controller.
127 - (void)createTabStripController {
128   DCHECK([overlayableContentsController_ activeContainer]);
129   DCHECK([[overlayableContentsController_ activeContainer] window]);
130   tabStripController_.reset([[TabStripController alloc]
131       initWithView:[self tabStripView]
132         switchView:[overlayableContentsController_ activeContainer]
133            browser:browser_.get()
134           delegate:self]);
137 - (void)saveWindowPositionIfNeeded {
138   if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
139     return;
141   // If we're in fullscreen mode, save the position of the regular window
142   // instead.
143   NSWindow* window =
144       [self isInAnyFullscreenMode] ? savedRegularWindow_ : [self window];
146   // Window positions are stored relative to the origin of the primary monitor.
147   NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
148   NSScreen* windowScreen = [window screen];
150   // Start with the window's frame, which is in virtual coordinates.
151   // Do some y twiddling to flip the coordinate system.
152   gfx::Rect bounds(NSRectToCGRect([window frame]));
153   bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
155   // Browser::SaveWindowPlacement saves information for session restore.
156   ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
157   if ([window isMiniaturized])
158     show_state = ui::SHOW_STATE_MINIMIZED;
159   else if ([self isInAnyFullscreenMode])
160     show_state = ui::SHOW_STATE_FULLSCREEN;
161   chrome::SaveWindowPlacement(browser_.get(), bounds, show_state);
163   // |windowScreen| can be nil (for example, if the monitor arrangement was
164   // changed while in fullscreen mode).  If we see a nil screen, return without
165   // saving.
166   // TODO(rohitrao): We should just not save anything for fullscreen windows.
167   // http://crbug.com/36479.
168   if (!windowScreen)
169     return;
171   // Only save main window information to preferences.
172   PrefService* prefs = browser_->profile()->GetPrefs();
173   if (!prefs || browser_ != chrome::GetLastActiveBrowser())
174     return;
176   // Save the current work area, in flipped coordinates.
177   gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
178   workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
180   scoped_ptr<DictionaryPrefUpdate> update =
181       chrome::GetWindowPlacementDictionaryReadWrite(
182           chrome::GetWindowName(browser_.get()),
183           browser_->profile()->GetPrefs());
184   base::DictionaryValue* windowPreferences = update->Get();
185   windowPreferences->SetInteger("left", bounds.x());
186   windowPreferences->SetInteger("top", bounds.y());
187   windowPreferences->SetInteger("right", bounds.right());
188   windowPreferences->SetInteger("bottom", bounds.bottom());
189   windowPreferences->SetBoolean("maximized", false);
190   windowPreferences->SetBoolean("always_on_top", false);
191   windowPreferences->SetInteger("work_area_left", workArea.x());
192   windowPreferences->SetInteger("work_area_top", workArea.y());
193   windowPreferences->SetInteger("work_area_right", workArea.right());
194   windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
197 - (NSRect)window:(NSWindow*)window
198 willPositionSheet:(NSWindow*)sheet
199        usingRect:(NSRect)defaultSheetRect {
200   // Position the sheet as follows:
201   //  - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
202   //    bookmark bar is disabled), position the sheet immediately below the
203   //    normal toolbar.
204   //  - If the bookmark bar is shown (attached to the normal toolbar), position
205   //    the sheet below the bookmark bar.
206   //  - If the bookmark bar is currently animating, position the sheet according
207   //    to where the bar will be when the animation ends.
208   switch ([bookmarkBarController_ currentState]) {
209     case BookmarkBar::SHOW: {
210       NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame];
211       defaultSheetRect.origin.y = bookmarkBarFrame.origin.y;
212       break;
213     }
214     case BookmarkBar::HIDDEN:
215     case BookmarkBar::DETACHED: {
216       if ([self hasToolbar]) {
217         NSRect toolbarFrame = [[toolbarController_ view] frame];
218         defaultSheetRect.origin.y = toolbarFrame.origin.y;
219       } else {
220         // The toolbar is not shown in application mode. The sheet should be
221         // located at the top of the window, under the title of the window.
222         defaultSheetRect.origin.y = NSHeight([[window contentView] frame]) -
223                                     defaultSheetRect.size.height;
224       }
225       break;
226     }
227   }
228   return defaultSheetRect;
231 - (void)layoutSubviews {
232   // Suppress title drawing if necessary.
233   if ([self.window respondsToSelector:@selector(setShouldHideTitle:)])
234     [(id)self.window setShouldHideTitle:![self hasTitleBar]];
236   [bookmarkBarController_ updateHiddenState];
237   [self updateSubviewZOrder];
239   base::scoped_nsobject<BrowserWindowLayout> layout(
240       [[BrowserWindowLayout alloc] init]);
241   [self updateLayoutParameters:layout];
242   [self applyLayout:layout];
244   [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
247 - (void)applyTabStripLayout:(const chrome::TabStripLayout&)layout {
248   // Update the presence of the window controls.
249   if (layout.addCustomWindowControls)
250     [tabStripController_ addCustomWindowControls];
251   else
252     [tabStripController_ removeCustomWindowControls];
254   // Update the layout of the avatar.
255   if (!NSIsEmptyRect(layout.avatarFrame)) {
256     NSView* avatarButton = [avatarButtonController_ view];
257     [avatarButton setFrame:layout.avatarFrame];
258     [avatarButton setHidden:NO];
259   }
261   // Check if the tab strip's frame has changed.
262   BOOL requiresRelayout =
263       !NSEqualRects([[self tabStripView] frame], layout.frame);
265   // Check if the left indent has changed.
266   if (layout.leftIndent != [tabStripController_ leftIndentForControls]) {
267     [tabStripController_ setLeftIndentForControls:layout.leftIndent];
268     requiresRelayout = YES;
269   }
271   // Check if the right indent has changed.
272   if (layout.rightIndent != [tabStripController_ rightIndentForControls]) {
273     [tabStripController_ setRightIndentForControls:layout.rightIndent];
274     requiresRelayout = YES;
275   }
277   // It is undesirable to force tabs relayout when the tap strip's frame did
278   // not change, because it will interrupt tab animations in progress.
279   // In addition, there appears to be an AppKit bug on <10.9 where interrupting
280   // a tab animation resulted in the tab frame being the animator's target
281   // frame instead of the interrupting setFrame. (See http://crbug.com/415093)
282   if (requiresRelayout) {
283     [[self tabStripView] setFrame:layout.frame];
284     [tabStripController_ layoutTabsWithoutAnimation];
285   }
288 - (BOOL)placeBookmarkBarBelowInfoBar {
289   // If we are currently displaying the NTP detached bookmark bar or animating
290   // to/from it (from/to anything else), we display the bookmark bar below the
291   // info bar.
292   return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
293          [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
294          [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
297 - (void)layoutTabContentArea:(NSRect)newFrame {
298   NSView* tabContentView = [self tabContentArea];
299   NSRect tabContentFrame = [tabContentView frame];
301   bool contentShifted =
302       NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
303       NSMinX(tabContentFrame) != NSMinX(newFrame);
305   tabContentFrame = newFrame;
306   [tabContentView setFrame:tabContentFrame];
308   // If the relayout shifts the content area up or down, let the renderer know.
309   if (contentShifted) {
310     if (WebContents* contents =
311             browser_->tab_strip_model()->GetActiveWebContents()) {
312       if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
313         rwhv->WindowFrameChanged();
314     }
315   }
318 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
319   CGFloat newHeight =
320       [toolbarController_ desiredHeightForCompression:compression];
321   NSRect toolbarFrame = [[toolbarController_ view] frame];
322   CGFloat deltaH = newHeight - toolbarFrame.size.height;
324   if (deltaH == 0)
325     return;
327   toolbarFrame.size.height = newHeight;
328   NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
329   bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
330   [[toolbarController_ view] setFrame:toolbarFrame];
331   [[bookmarkBarController_ view] setFrame:bookmarkFrame];
332   [self layoutSubviews];
335 // Fullscreen and presentation mode methods
337 - (void)moveViewsForImmersiveFullscreen:(BOOL)fullscreen
338                           regularWindow:(NSWindow*)regularWindow
339                        fullscreenWindow:(NSWindow*)fullscreenWindow {
340   NSWindow* sourceWindow = fullscreen ? regularWindow : fullscreenWindow;
341   NSWindow* destWindow = fullscreen ? fullscreenWindow : regularWindow;
343   // Close the bookmark bubble, if it's open.  Use |-ok:| instead of |-cancel:|
344   // or |-close| because that matches the behavior when the bubble loses key
345   // status.
346   [bookmarkBubbleController_ ok:self];
348   // Save the current first responder so we can restore after views are moved.
349   base::scoped_nsobject<FocusTracker> focusTracker(
350       [[FocusTracker alloc] initWithWindow:sourceWindow]);
352   // While we move views (and focus) around, disable any bar visibility changes.
353   [self disableBarVisibilityUpdates];
355   // Retain the tab strip view while we remove it from its superview.
356   base::scoped_nsobject<NSView> tabStripView;
357   if ([self hasTabStrip]) {
358     tabStripView.reset([[self tabStripView] retain]);
359     [tabStripView removeFromSuperview];
360   }
362   // Disable autoresizing of subviews while we move views around. This prevents
363   // spurious renderer resizes.
364   [self.chromeContentView setAutoresizesSubviews:NO];
365   [self.chromeContentView removeFromSuperview];
367   // Have to do this here, otherwise later calls can crash because the window
368   // has no delegate.
369   [sourceWindow setDelegate:nil];
370   [destWindow setDelegate:self];
372   // With this call, valgrind complains that a "Conditional jump or move depends
373   // on uninitialised value(s)".  The error happens in -[NSThemeFrame
374   // drawOverlayRect:].  I'm pretty convinced this is an Apple bug, but there is
375   // no visual impact.  I have been unable to tickle it away with other window
376   // or view manipulation Cocoa calls.  Stack added to suppressions_mac.txt.
377   [self.chromeContentView setAutoresizesSubviews:YES];
378   [[destWindow contentView] addSubview:self.chromeContentView
379                             positioned:NSWindowBelow
380                             relativeTo:nil];
381   [self.chromeContentView setFrame:[[destWindow contentView] bounds]];
383   // Move the incognito badge if present.
384   if ([self shouldShowAvatar]) {
385     NSView* avatarButtonView = [avatarButtonController_ view];
387     [avatarButtonView removeFromSuperview];
388     [avatarButtonView setHidden:YES];  // Will be shown in layout.
389     [[destWindow cr_windowView] addSubview:avatarButtonView];
390   }
392   // Add the tab strip after setting the content view and moving the incognito
393   // badge (if any), so that the tab strip will be on top (in the z-order).
394   if ([self hasTabStrip])
395     [self insertTabStripView:tabStripView intoWindow:destWindow];
397   [sourceWindow setWindowController:nil];
398   [self setWindow:destWindow];
399   [destWindow setWindowController:self];
401   // Move the status bubble over, if we have one.
402   if (statusBubble_)
403     statusBubble_->SwitchParentWindow(destWindow);
405   // Move the title over.
406   [destWindow setTitle:[sourceWindow title]];
408   // The window needs to be onscreen before we can set its first responder.
409   // Ordering the window to the front can change the active Space (either to
410   // the window's old Space or to the application's assigned Space). To prevent
411   // this by temporarily change the collectionBehavior.
412   NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior];
413   [destWindow setCollectionBehavior:
414       NSWindowCollectionBehaviorMoveToActiveSpace];
415   [destWindow makeKeyAndOrderFront:self];
416   [destWindow setCollectionBehavior:behavior];
418   if (![focusTracker restoreFocusInWindow:destWindow]) {
419     // During certain types of fullscreen transitions, the view that had focus
420     // may have gone away (e.g., the one for a Flash FS widget).  In this case,
421     // FocusTracker will fail to restore focus to anything, so we set the focus
422     // to the tab contents as a reasonable fall-back.
423     [self focusTabContents];
424   }
425   [sourceWindow orderOut:self];
427   // We're done moving focus, so re-enable bar visibility changes.
428   [self enableBarVisibilityUpdates];
431 - (void)permissionBubbleWindowWillClose:(NSNotification*)notification {
432   DCHECK(permissionBubbleCocoa_);
434   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
435   [center removeObserver:self
436                     name:NSWindowWillCloseNotification
437                   object:[notification object]];
438   [self releaseBarVisibilityForOwner:[notification object]
439                        withAnimation:YES
440                                delay:YES];
443 - (void)configurePresentationModeController {
444   BOOL fullscreen_for_tab =
445       browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
446   BOOL kiosk_mode =
447       CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
448   BOOL showDropdown =
449       !fullscreen_for_tab && !kiosk_mode && ([self floatingBarHasFocus]);
450   if (permissionBubbleCocoa_ && permissionBubbleCocoa_->IsVisible()) {
451     DCHECK(permissionBubbleCocoa_->window());
452     // A visible permission bubble will force the dropdown to remain visible.
453     [self lockBarVisibilityForOwner:permissionBubbleCocoa_->window()
454                       withAnimation:NO
455                               delay:NO];
456     showDropdown = YES;
457     // Register to be notified when the permission bubble is closed, to
458     // allow fullscreen to hide the dropdown.
459     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
460     [center addObserver:self
461                selector:@selector(permissionBubbleWindowWillClose:)
462                    name:NSWindowWillCloseNotification
463                  object:permissionBubbleCocoa_->window()];
464   }
465   if (showDropdown) {
466     // Turn on layered mode for the window's root view for the entry
467     // animation.  Without this, the OS fullscreen animation for entering
468     // fullscreen mode does not correctly draw the tab strip.
469     // It will be turned off (set back to NO) when the animation finishes,
470     // in -windowDidEnterFullScreen:.
471     // Leaving wantsLayer on for the duration of presentation mode causes
472     // performance issues when the dropdown is animated in/out.  It also does
473     // not seem to be required for the exit animation.
474     windowViewWantsLayer_ = [[[self window] cr_windowView] wantsLayer];
475     [[[self window] cr_windowView] setWantsLayer:YES];
476   }
478   NSView* contentView = [[self window] contentView];
479   [presentationModeController_
480       enterPresentationModeForContentView:contentView
481                              showDropdown:showDropdown];
484 - (void)adjustUIForExitingFullscreenAndStopOmniboxSliding {
485   [presentationModeController_ exitPresentationMode];
486   presentationModeController_.reset();
488   // Force the bookmark bar z-order to update.
489   [[bookmarkBarController_ view] removeFromSuperview];
490   [self layoutSubviews];
493 - (void)adjustUIForSlidingFullscreenStyle:(fullscreen_mac::SlidingStyle)style {
494   if (!presentationModeController_) {
495     presentationModeController_.reset(
496         [self newPresentationModeControllerWithStyle:style]);
497     [self configurePresentationModeController];
498   } else {
499     presentationModeController_.get().slidingStyle = style;
500   }
502   if (!floatingBarBackingView_.get() &&
503       ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
504     floatingBarBackingView_.reset(
505         [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
506     [floatingBarBackingView_
507         setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
508   }
510   // Force the bookmark bar z-order to update.
511   [[bookmarkBarController_ view] removeFromSuperview];
512   [self layoutSubviews];
515 - (PresentationModeController*)newPresentationModeControllerWithStyle:
516     (fullscreen_mac::SlidingStyle)style {
517   return [[PresentationModeController alloc] initWithBrowserController:self
518                                                                  style:style];
521 - (void)enterImmersiveFullscreen {
522   RecordFullscreenHistogram(IMMERSIVE_FULLSCREEN_MECHANISM, [self window]);
524   // Set to NO by |-windowDidEnterFullScreen:|.
525   enteringImmersiveFullscreen_ = YES;
527   // Fade to black.
528   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
529   Boolean didFadeOut = NO;
530   CGDisplayFadeReservationToken token;
531   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
532       == kCGErrorSuccess) {
533     didFadeOut = YES;
534     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
535         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
536   }
538   // Create the fullscreen window.
539   fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
540   savedRegularWindow_ = [[self window] retain];
541   savedRegularWindowFrame_ = [savedRegularWindow_ frame];
543   [self moveViewsForImmersiveFullscreen:YES
544                           regularWindow:[self window]
545                        fullscreenWindow:fullscreenWindow_.get()];
547   fullscreen_mac::SlidingStyle style = fullscreen_mac::OMNIBOX_TABS_HIDDEN;
548   [self adjustUIForSlidingFullscreenStyle:style];
550   // AppKit is helpful and prevents NSWindows from having the same height as
551   // the screen while the menu bar is showing. This only applies to windows on
552   // a secondary screen, in a separate space. Calling [NSWindow
553   // setFrame:display:] with the screen's height will always reduce the
554   // height by the height of the MenuBar. Calling the method with any other
555   // height works fine. The relevant method in the 10.10 AppKit SDK is called:
556   // _canAdjustSizeForScreensHaveSeparateSpacesIfFillingSecondaryScreen
557   //
558   // TODO(erikchen): Refactor the logic to allow the window to be shown after
559   // the menubar has been hidden. This would remove the need for this hack.
560   // http://crbug.com/403203
561   NSRect frame = [[[self window] screen] frame];
562   if (!NSEqualRects(frame, [fullscreenWindow_ frame]))
563     [fullscreenWindow_ setFrame:[[[self window] screen] frame] display:YES];
565   [self layoutSubviews];
567   [self windowDidEnterFullScreen:nil];
569   // Fade back in.
570   if (didFadeOut) {
571     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
572         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
573     CGReleaseDisplayFadeReservation(token);
574   }
577 - (void)exitImmersiveFullscreen {
578   // Fade to black.
579   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
580   Boolean didFadeOut = NO;
581   CGDisplayFadeReservationToken token;
582   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
583       == kCGErrorSuccess) {
584     didFadeOut = YES;
585     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
586         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
587   }
589   [self windowWillExitFullScreen:nil];
591   [self moveViewsForImmersiveFullscreen:NO
592                           regularWindow:savedRegularWindow_
593                        fullscreenWindow:fullscreenWindow_.get()];
595   // When exiting fullscreen mode, we need to call layoutSubviews manually.
596   [savedRegularWindow_ autorelease];
597   savedRegularWindow_ = nil;
598   fullscreenWindow_.reset();
599   [self layoutSubviews];
601   [self windowDidExitFullScreen:nil];
603   // Fade back in.
604   if (didFadeOut) {
605     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
606         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
607     CGReleaseDisplayFadeReservation(token);
608   }
611 - (void)showFullscreenExitBubbleIfNecessary {
612   // This method is called in response to
613   // |-updateFullscreenExitBubbleURL:bubbleType:|. If we're in the middle of the
614   // transition into fullscreen (i.e., using the AppKit Fullscreen API), do not
615   // show the bubble because it will cause visual jank
616   // (http://crbug.com/130649). This will be called again as part of
617   // |-windowDidEnterFullScreen:|, so arrange to do that work then instead.
618   if (enteringAppKitFullscreen_)
619     return;
621   [self hideOverlayIfPossibleWithAnimation:NO delay:NO];
623   if (fullscreenBubbleType_ == FEB_TYPE_NONE ||
624       fullscreenBubbleType_ == FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) {
625     // Show no exit instruction bubble on Mac when in Browser Fullscreen.
626     [self destroyFullscreenExitBubbleIfNecessary];
627   } else {
628     [fullscreenExitBubbleController_ closeImmediately];
629     fullscreenExitBubbleController_.reset(
630         [[FullscreenExitBubbleController alloc]
631             initWithOwner:self
632                   browser:browser_.get()
633                       url:fullscreenUrl_
634                bubbleType:fullscreenBubbleType_]);
635     [fullscreenExitBubbleController_ showWindow];
636   }
639 - (void)destroyFullscreenExitBubbleIfNecessary {
640   [fullscreenExitBubbleController_ closeImmediately];
641   fullscreenExitBubbleController_.reset();
644 - (void)contentViewDidResize:(NSNotification*)notification {
645   [self layoutSubviews];
648 - (void)registerForContentViewResizeNotifications {
649   [[NSNotificationCenter defaultCenter]
650       addObserver:self
651          selector:@selector(contentViewDidResize:)
652              name:NSViewFrameDidChangeNotification
653            object:[[self window] contentView]];
656 - (void)deregisterForContentViewResizeNotifications {
657   [[NSNotificationCenter defaultCenter]
658       removeObserver:self
659                 name:NSViewFrameDidChangeNotification
660               object:[[self window] contentView]];
663 - (NSSize)window:(NSWindow*)window
664     willUseFullScreenContentSize:(NSSize)proposedSize {
665   return proposedSize;
668 - (NSApplicationPresentationOptions)window:(NSWindow*)window
669     willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
670   return (opt |
671           NSApplicationPresentationAutoHideDock |
672           NSApplicationPresentationAutoHideMenuBar);
675 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
676   RecordFullscreenHistogram(APPKIT_FULLSCREEN_MECHANISM, [self window]);
678   if (notification)  // For System Fullscreen when non-nil.
679     [self registerForContentViewResizeNotifications];
681   NSWindow* window = [self window];
682   savedRegularWindowFrame_ = [window frame];
683   BOOL mode = enteringPresentationMode_ ||
684        browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
685   enteringAppKitFullscreen_ = YES;
686   enteringAppKitFullscreenOnPrimaryScreen_ =
687       [[[self window] screen] isEqual:[[NSScreen screens] objectAtIndex:0]];
689   fullscreen_mac::SlidingStyle style =
690       mode ? fullscreen_mac::OMNIBOX_TABS_HIDDEN
691            : fullscreen_mac::OMNIBOX_TABS_PRESENT;
693   [self adjustUIForSlidingFullscreenStyle:style];
696 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
697   // In Yosemite, some combination of the titlebar and toolbar always show in
698   // full-screen mode. We do not want either to show. Search for the window that
699   // contains the views, and hide it. There is no need to ever unhide the view.
700   // http://crbug.com/380235
701   if (base::mac::IsOSYosemiteOrLater()) {
702     for (NSWindow* window in [[NSApplication sharedApplication] windows]) {
703       if ([window
704               isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
705         [[window contentView] setHidden:YES];
706       }
707     }
708   }
710   if ([self shouldUseMavericksAppKitFullscreenHack]) {
711     // Apply a hack to fix the size of the window. This is the last run of the
712     // MessageLoop where the hack will not work, so dispatch the hack to the
713     // top of the MessageLoop.
714     base::Callback<void(void)> callback = base::BindBlock(^{
715         if (![self isInAppKitFullscreen])
716           return;
718         // The window's frame should be exactly 22 points too short.
719         CGFloat kExpectedHeightDifference = 22;
720         NSRect currentFrame = [[self window] frame];
721         NSRect expectedFrame = [[[self window] screen] frame];
722         if (!NSEqualPoints(currentFrame.origin, expectedFrame.origin))
723           return;
724         if (currentFrame.size.width != expectedFrame.size.width)
725           return;
726         CGFloat heightDelta =
727             expectedFrame.size.height - currentFrame.size.height;
728         if (fabs(heightDelta - kExpectedHeightDifference) > 0.01)
729           return;
731         [[self window] setFrame:expectedFrame display:YES];
732     });
733     base::MessageLoop::current()->PostTask(FROM_HERE, callback);
734   }
736   if (notification)  // For System Fullscreen when non-nil.
737     [self deregisterForContentViewResizeNotifications];
738   enteringAppKitFullscreen_ = NO;
739   enteringImmersiveFullscreen_ = NO;
740   enteringPresentationMode_ = NO;
742   [self showFullscreenExitBubbleIfNecessary];
743   browser_->WindowFullscreenStateChanged();
744   [[[self window] cr_windowView] setWantsLayer:windowViewWantsLayer_];
747 - (void)windowWillExitFullScreen:(NSNotification*)notification {
748   if (notification)  // For System Fullscreen when non-nil.
749     [self registerForContentViewResizeNotifications];
750   [self destroyFullscreenExitBubbleIfNecessary];
751   [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
754 - (void)windowDidExitFullScreen:(NSNotification*)notification {
755   if (notification)  // For System Fullscreen when non-nil.
756     [self deregisterForContentViewResizeNotifications];
757   browser_->WindowFullscreenStateChanged();
760 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
761   [self deregisterForContentViewResizeNotifications];
762   enteringAppKitFullscreen_ = NO;
763   [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
766 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
767   [self deregisterForContentViewResizeNotifications];
769   // Force a relayout to try and get the window back into a reasonable state.
770   [self layoutSubviews];
773 - (void)enableBarVisibilityUpdates {
774   // Early escape if there's nothing to do.
775   if (barVisibilityUpdatesEnabled_)
776     return;
778   barVisibilityUpdatesEnabled_ = YES;
780   if ([barVisibilityLocks_ count])
781     [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
782   else
783     [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
786 - (void)disableBarVisibilityUpdates {
787   // Early escape if there's nothing to do.
788   if (!barVisibilityUpdatesEnabled_)
789     return;
791   barVisibilityUpdatesEnabled_ = NO;
792   [presentationModeController_ cancelAnimationAndTimers];
795 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
796   if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
797     return;
798   [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
799                                                           delay:delay];
802 - (CGFloat)toolbarDividerOpacity {
803   return [bookmarkBarController_ toolbarDividerOpacity];
806 - (void)updateLayerOrdering:(NSView*)view {
807   // Hold a reference to the view so that it doesn't accidentally get
808   // dealloc'ed.
809   base::scoped_nsobject<NSView> reference([view retain]);
811   // If the superview has a layer, then this hack isn't required.
812   NSView* superview = [view superview];
813   if ([superview layer])
814     return;
816   // Get the current position of the view.
817   NSArray* subviews = [superview subviews];
818   NSInteger index = [subviews indexOfObject:view];
819   NSView* siblingBelow = nil;
820   if (index > 0)
821     siblingBelow = [subviews objectAtIndex:index - 1];
823   // Remove the view.
824   [view removeFromSuperview];
826   // Add it to the same position.
827   if (siblingBelow) {
828     [superview addSubview:view
829                positioned:NSWindowAbove
830                relativeTo:siblingBelow];
831   } else {
832     [superview addSubview:view
833                positioned:NSWindowBelow
834                relativeTo:nil];
835   }
838 - (void)updateInfoBarTipVisibility {
839   // If there's no toolbar then hide the infobar tip.
840   [infoBarContainerController_
841       setShouldSuppressTopInfoBarTip:![self hasToolbar]];
844 - (NSInteger)pageInfoBubblePointY {
845   LocationBarViewMac* locationBarView = [self locationBarBridge];
847   // The point, in window coordinates.
848   NSPoint iconBottom = locationBarView->GetPageInfoBubblePoint();
850   // The toolbar, in window coordinates.
851   NSView* toolbar = [toolbarController_ view];
852   CGFloat toolbarY = NSMinY([toolbar convertRect:[toolbar bounds] toView:nil]);
854   return iconBottom.y - toolbarY;
857 - (void)enterAppKitFullscreen {
858   DCHECK(base::mac::IsOSLionOrLater());
859   if (FramedBrowserWindow* framedBrowserWindow =
860           base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
861     [framedBrowserWindow toggleSystemFullScreen];
862   }
865 - (void)exitAppKitFullscreen {
866   DCHECK(base::mac::IsOSLionOrLater());
867   if (FramedBrowserWindow* framedBrowserWindow =
868           base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
869     [framedBrowserWindow toggleSystemFullScreen];
870   }
873 - (void)updateLayoutParameters:(BrowserWindowLayout*)layout {
874   [layout setContentViewSize:[[[self window] contentView] bounds].size];
875   [layout setWindowSize:[[self window] frame].size];
877   [layout setInAnyFullscreen:[self isInAnyFullscreenMode]];
878   [layout setFullscreenSlidingStyle:
879       presentationModeController_.get().slidingStyle];
880   [layout setFullscreenMenubarOffset:
881       [presentationModeController_ menubarOffset]];
882   [layout setFullscreenToolbarFraction:
883       [presentationModeController_ toolbarFraction]];
885   [layout setHasTabStrip:[self hasTabStrip]];
886   NSButton* fullScreenButton =
887       [[self window] standardWindowButton:NSWindowFullScreenButton];
888   [layout setFullscreenButtonFrame:fullScreenButton ? [fullScreenButton frame]
889                                                     : NSZeroRect];
890   if ([self shouldShowAvatar]) {
891     NSView* avatar = [avatarButtonController_ view];
892     [layout setShouldShowAvatar:YES];
893     [layout setShouldUseNewAvatar:[self shouldUseNewAvatarButton]];
894     [layout setAvatarSize:[avatar frame].size];
895     [layout setAvatarLineWidth:[[avatar superview] cr_lineWidth]];
896   }
898   [layout setHasToolbar:[self hasToolbar]];
899   [layout setToolbarHeight:NSHeight([[toolbarController_ view] bounds])];
901   [layout setHasLocationBar:[self hasLocationBar]];
903   [layout setPlaceBookmarkBarBelowInfoBar:[self placeBookmarkBarBelowInfoBar]];
904   [layout setBookmarkBarHidden:[bookmarkBarController_ view].isHidden];
905   [layout setBookmarkBarHeight:
906       NSHeight([[bookmarkBarController_ view] bounds])];
908   [layout setInfoBarHeight:[infoBarContainerController_ heightOfInfoBars]];
909   [layout setPageInfoBubblePointY:[self pageInfoBubblePointY]];
911   [layout setHasDownloadShelf:(downloadShelfController_.get() != nil)];
912   [layout setDownloadShelfHeight:
913       NSHeight([[downloadShelfController_ view] bounds])];
916 - (void)applyLayout:(BrowserWindowLayout*)layout {
917   chrome::LayoutOutput output = [layout computeLayout];
919   if (!NSIsEmptyRect(output.tabStripLayout.frame))
920     [self applyTabStripLayout:output.tabStripLayout];
922   if (!NSIsEmptyRect(output.toolbarFrame))
923     [[toolbarController_ view] setFrame:output.toolbarFrame];
925   if (!NSIsEmptyRect(output.bookmarkFrame)) {
926     NSView* bookmarkBarView = [bookmarkBarController_ view];
927     [bookmarkBarView setFrame:output.bookmarkFrame];
929     // Pin the bookmark bar to the top of the window and make the width
930     // flexible.
931     [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
933     [bookmarkBarController_ layoutSubviews];
934   }
936   // The info bar is never hidden. Sometimes it has zero effective height.
937   [[infoBarContainerController_ view] setFrame:output.infoBarFrame];
938   [infoBarContainerController_
939       setMaxTopArrowHeight:output.infoBarMaxTopArrowHeight];
941   if (!NSIsEmptyRect(output.downloadShelfFrame))
942     [[downloadShelfController_ view] setFrame:output.downloadShelfFrame];
944   [self layoutTabContentArea:output.contentAreaFrame];
946   if (!NSIsEmptyRect(output.fullscreenBackingBarFrame)) {
947     [floatingBarBackingView_ setFrame:output.fullscreenBackingBarFrame];
948     [presentationModeController_
949         overlayFrameChanged:output.fullscreenBackingBarFrame];
950   }
952   [findBarCocoaController_
953       positionFindBarViewAtMaxY:output.findBarMaxY
954                        maxWidth:NSWidth(output.contentAreaFrame)];
956   [fullscreenExitBubbleController_
957       positionInWindowAtTop:output.fullscreenExitButtonMaxY
958                       width:NSWidth(output.contentAreaFrame)];
961 - (void)updateSubviewZOrder {
962   if ([self isInAnyFullscreenMode])
963     [self updateSubviewZOrderFullscreen];
964   else
965     [self updateSubviewZOrderNormal];
967   [self updateSubviewZOrderHack];
970 - (void)updateSubviewZOrderNormal {
971   base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
972   if ([downloadShelfController_ view])
973     [subviews addObject:[downloadShelfController_ view]];
974   if ([bookmarkBarController_ view])
975     [subviews addObject:[bookmarkBarController_ view]];
976   if ([toolbarController_ view])
977     [subviews addObject:[toolbarController_ view]];
978   if ([infoBarContainerController_ view])
979     [subviews addObject:[infoBarContainerController_ view]];
980   if ([self tabContentArea])
981     [subviews addObject:[self tabContentArea]];
982   if ([findBarCocoaController_ view])
983     [subviews addObject:[findBarCocoaController_ view]];
985   [self setContentViewSubviews:subviews];
988 - (void)updateSubviewZOrderFullscreen {
989   base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
990   if ([downloadShelfController_ view])
991     [subviews addObject:[downloadShelfController_ view]];
992   if ([self tabContentArea])
993     [subviews addObject:[self tabContentArea]];
994   if ([self placeBookmarkBarBelowInfoBar]) {
995     if ([bookmarkBarController_ view])
996       [subviews addObject:[bookmarkBarController_ view]];
997     if (floatingBarBackingView_)
998       [subviews addObject:floatingBarBackingView_];
999   } else {
1000     if (floatingBarBackingView_)
1001       [subviews addObject:floatingBarBackingView_];
1002     if ([bookmarkBarController_ view])
1003       [subviews addObject:[bookmarkBarController_ view]];
1004   }
1005   if ([toolbarController_ view])
1006     [subviews addObject:[toolbarController_ view]];
1007   if ([infoBarContainerController_ view])
1008     [subviews addObject:[infoBarContainerController_ view]];
1009   if ([findBarCocoaController_ view])
1010     [subviews addObject:[findBarCocoaController_ view]];
1012   [self setContentViewSubviews:subviews];
1015 - (void)setContentViewSubviews:(NSArray*)subviews {
1016   // Subviews already match.
1017   if ([[self.chromeContentView subviews] isEqual:subviews])
1018     return;
1020   // The tabContentArea isn't a subview, so just set all the subviews.
1021   NSView* tabContentArea = [self tabContentArea];
1022   if (![[self.chromeContentView subviews] containsObject:tabContentArea]) {
1023     [self.chromeContentView setSubviews:subviews];
1024     return;
1025   }
1027   // Remove all subviews that aren't the tabContentArea.
1028   for (NSView* view in [[self.chromeContentView subviews] copy]) {
1029     if (view != tabContentArea)
1030       [view removeFromSuperview];
1031   }
1033   // Add in the subviews below the tabContentArea.
1034   NSInteger index = [subviews indexOfObject:tabContentArea];
1035   for (int i = index - 1; i >= 0; --i) {
1036     NSView* view = [subviews objectAtIndex:i];
1037     [self.chromeContentView addSubview:view
1038                             positioned:NSWindowBelow
1039                             relativeTo:nil];
1040   }
1042   // Add in the subviews above the tabContentArea.
1043   for (NSUInteger i = index + 1; i < [subviews count]; ++i) {
1044     NSView* view = [subviews objectAtIndex:i];
1045     [self.chromeContentView addSubview:view
1046                             positioned:NSWindowAbove
1047                             relativeTo:nil];
1048   }
1051 - (void)updateSubviewZOrderHack {
1052   // TODO(erikchen): Remove and then add the tabStripView to the root NSView.
1053   // This fixes a layer ordering problem that occurs between the contentView
1054   // and the tabStripView. This is a hack required because NSThemeFrame is not
1055   // layer backed, and because Chrome adds subviews directly to the
1056   // NSThemeFrame.
1057   // http://crbug.com/407921
1058   if (enteringAppKitFullscreen_) {
1059     // The tabstrip frequently lies outside the bounds of its superview.
1060     // Repeatedly adding/removing the tabstrip from its superview during the
1061     // AppKit Fullscreen transition causes graphical glitches on 10.10. The
1062     // correct solution is to use the AppKit fullscreen transition APIs added
1063     // in 10.7+.
1064     // http://crbug.com/408791
1065     if (!hasAdjustedTabStripWhileEnteringAppKitFullscreen_) {
1066       // Disable implicit animations.
1067       [CATransaction begin];
1068       [CATransaction setDisableActions:YES];
1070       [self updateLayerOrdering:[self tabStripView]];
1071       [self updateLayerOrdering:[avatarButtonController_ view]];
1073       [CATransaction commit];
1074       hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = YES;
1075     }
1076   } else {
1077     hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = NO;
1078   }
1081 - (BOOL)shouldUseMavericksAppKitFullscreenHack {
1082   if (!base::mac::IsOSMavericks())
1083     return NO;
1084   if (![NSScreen respondsToSelector:@selector(screensHaveSeparateSpaces)] ||
1085       ![NSScreen screensHaveSeparateSpaces]) {
1086     return NO;
1087   }
1088   if (!enteringAppKitFullscreen_)
1089     return NO;
1090   if (enteringAppKitFullscreenOnPrimaryScreen_)
1091     return NO;
1093   return YES;
1096 @end  // @implementation BrowserWindowController(Private)