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"
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;
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
69 // - Primary (value of bit: 1)
70 // - Secondary (value of bit: 0)
72 // + Displays have separate spaces: An option available in Mission Control in
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,
88 FULLSCREEN_MECHANISM_BIT = 0,
89 PRIMARY_SCREEN_BIT = 1,
90 DISPLAYS_SEPARATE_SPACES_BIT = 2,
91 MULTIPLE_SCREENS_BIT = 3,
95 // Emits a histogram entry indicating that |window| is being made fullscreen.
96 void RecordFullscreenHistogram(FullscreenMechanism mechanism,
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;
106 if (mechanism == APPKIT_FULLSCREEN_MECHANISM)
107 output += 1 << FULLSCREEN_MECHANISM_BIT;
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);
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()
137 - (void)saveWindowPositionIfNeeded {
138 if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
141 // If we're in fullscreen mode, save the position of the regular 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
166 // TODO(rohitrao): We should just not save anything for fullscreen windows.
167 // http://crbug.com/36479.
171 // Only save main window information to preferences.
172 PrefService* prefs = browser_->profile()->GetPrefs();
173 if (!prefs || browser_ != chrome::GetLastActiveBrowser())
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
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;
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;
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;
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];
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];
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;
271 // Check if the right indent has changed.
272 if (layout.rightIndent != [tabStripController_ rightIndentForControls]) {
273 [tabStripController_ setRightIndentForControls:layout.rightIndent];
274 requiresRelayout = YES;
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];
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
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();
318 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
320 [toolbarController_ desiredHeightForCompression:compression];
321 NSRect toolbarFrame = [[toolbarController_ view] frame];
322 CGFloat deltaH = newHeight - toolbarFrame.size.height;
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
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];
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
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
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];
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.
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];
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]
443 - (void)configurePresentationModeController {
444 BOOL fullscreen_for_tab =
445 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
447 CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
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()
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()];
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];
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];
499 presentationModeController_.get().slidingStyle = style;
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)];
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
521 - (void)enterImmersiveFullscreen {
522 RecordFullscreenHistogram(IMMERSIVE_FULLSCREEN_MECHANISM, [self window]);
524 // Set to NO by |-windowDidEnterFullScreen:|.
525 enteringImmersiveFullscreen_ = YES;
528 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
529 Boolean didFadeOut = NO;
530 CGDisplayFadeReservationToken token;
531 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
532 == kCGErrorSuccess) {
534 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
535 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
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
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];
571 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
572 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
573 CGReleaseDisplayFadeReservation(token);
577 - (void)exitImmersiveFullscreen {
579 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
580 Boolean didFadeOut = NO;
581 CGDisplayFadeReservationToken token;
582 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
583 == kCGErrorSuccess) {
585 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
586 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
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];
605 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
606 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
607 CGReleaseDisplayFadeReservation(token);
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_)
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];
628 [fullscreenExitBubbleController_ closeImmediately];
629 fullscreenExitBubbleController_.reset(
630 [[FullscreenExitBubbleController alloc]
632 browser:browser_.get()
634 bubbleType:fullscreenBubbleType_]);
635 [fullscreenExitBubbleController_ showWindow];
639 - (void)destroyFullscreenExitBubbleIfNecessary {
640 [fullscreenExitBubbleController_ closeImmediately];
641 fullscreenExitBubbleController_.reset();
644 - (void)contentViewDidResize:(NSNotification*)notification {
645 [self layoutSubviews];
648 - (void)registerForContentViewResizeNotifications {
649 [[NSNotificationCenter defaultCenter]
651 selector:@selector(contentViewDidResize:)
652 name:NSViewFrameDidChangeNotification
653 object:[[self window] contentView]];
656 - (void)deregisterForContentViewResizeNotifications {
657 [[NSNotificationCenter defaultCenter]
659 name:NSViewFrameDidChangeNotification
660 object:[[self window] contentView]];
663 - (NSSize)window:(NSWindow*)window
664 willUseFullScreenContentSize:(NSSize)proposedSize {
668 - (NSApplicationPresentationOptions)window:(NSWindow*)window
669 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)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]) {
704 isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
705 [[window contentView] setHidden:YES];
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])
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))
724 if (currentFrame.size.width != expectedFrame.size.width)
726 CGFloat heightDelta =
727 expectedFrame.size.height - currentFrame.size.height;
728 if (fabs(heightDelta - kExpectedHeightDifference) > 0.01)
731 [[self window] setFrame:expectedFrame display:YES];
733 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
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_)
778 barVisibilityUpdatesEnabled_ = YES;
780 if ([barVisibilityLocks_ count])
781 [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
783 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
786 - (void)disableBarVisibilityUpdates {
787 // Early escape if there's nothing to do.
788 if (!barVisibilityUpdatesEnabled_)
791 barVisibilityUpdatesEnabled_ = NO;
792 [presentationModeController_ cancelAnimationAndTimers];
795 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
796 if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
798 [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
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
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])
816 // Get the current position of the view.
817 NSArray* subviews = [superview subviews];
818 NSInteger index = [subviews indexOfObject:view];
819 NSView* siblingBelow = nil;
821 siblingBelow = [subviews objectAtIndex:index - 1];
824 [view removeFromSuperview];
826 // Add it to the same position.
828 [superview addSubview:view
829 positioned:NSWindowAbove
830 relativeTo:siblingBelow];
832 [superview addSubview:view
833 positioned:NSWindowBelow
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];
865 - (void)exitAppKitFullscreen {
866 DCHECK(base::mac::IsOSLionOrLater());
867 if (FramedBrowserWindow* framedBrowserWindow =
868 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
869 [framedBrowserWindow toggleSystemFullScreen];
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]
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]];
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
931 [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
933 [bookmarkBarController_ layoutSubviews];
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];
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];
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_];
1000 if (floatingBarBackingView_)
1001 [subviews addObject:floatingBarBackingView_];
1002 if ([bookmarkBarController_ view])
1003 [subviews addObject:[bookmarkBarController_ view]];
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])
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];
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];
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
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
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
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
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;
1077 hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = NO;
1081 - (BOOL)shouldUseMavericksAppKitFullscreenHack {
1082 if (!base::mac::IsOSMavericks())
1084 if (![NSScreen respondsToSelector:@selector(screensHaveSeparateSpaces)] ||
1085 ![NSScreen screensHaveSeparateSpaces]) {
1088 if (!enteringAppKitFullscreen_)
1090 if (enteringAppKitFullscreenOnPrimaryScreen_)
1096 @end // @implementation BrowserWindowController(Private)