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/mac_util.h"
11 #import "base/mac/scoped_nsobject.h"
12 #import "base/mac/sdk_forward_declarations.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/fullscreen.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
19 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_window_state.h"
22 #import "chrome/browser/ui/cocoa/browser_window_layout.h"
23 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
24 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
25 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
26 #import "chrome/browser/ui/cocoa/floating_bar_backing_view.h"
27 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
28 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
29 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
30 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
31 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
32 #import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h"
33 #import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h"
34 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
35 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
36 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
37 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
38 #import "chrome/browser/ui/cocoa/version_independent_window.h"
39 #import "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h"
40 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
41 #include "chrome/browser/ui/tabs/tab_strip_model.h"
42 #include "chrome/common/chrome_switches.h"
43 #include "chrome/common/pref_names.h"
44 #include "content/public/browser/render_widget_host_view.h"
45 #include "content/public/browser/web_contents.h"
46 #import "ui/base/cocoa/focus_tracker.h"
47 #import "ui/base/cocoa/nsview_additions.h"
48 #include "ui/base/ui_base_types.h"
50 using content::RenderWidgetHostView;
51 using content::WebContents;
55 // Space between the incognito badge and the right edge of the window.
56 const CGFloat kAvatarRightOffset = 4;
60 @implementation BrowserWindowController(Private)
62 // Create the tab strip controller.
63 - (void)createTabStripController {
64 DCHECK([overlayableContentsController_ activeContainer]);
65 DCHECK([[overlayableContentsController_ activeContainer] window]);
66 tabStripController_.reset([[TabStripController alloc]
67 initWithView:[self tabStripView]
68 switchView:[overlayableContentsController_ activeContainer]
69 browser:browser_.get()
73 - (void)saveWindowPositionIfNeeded {
74 if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
77 // If we're in fullscreen mode, save the position of the regular window
80 [self isInAnyFullscreenMode] ? savedRegularWindow_ : [self window];
82 // Window positions are stored relative to the origin of the primary monitor.
83 NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
84 NSScreen* windowScreen = [window screen];
86 // Start with the window's frame, which is in virtual coordinates.
87 // Do some y twiddling to flip the coordinate system.
88 gfx::Rect bounds(NSRectToCGRect([window frame]));
89 bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
91 // Browser::SaveWindowPlacement saves information for session restore.
92 ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
93 if ([window isMiniaturized])
94 show_state = ui::SHOW_STATE_MINIMIZED;
95 else if ([self isInAnyFullscreenMode])
96 show_state = ui::SHOW_STATE_FULLSCREEN;
97 chrome::SaveWindowPlacement(browser_.get(), bounds, show_state);
99 // |windowScreen| can be nil (for example, if the monitor arrangement was
100 // changed while in fullscreen mode). If we see a nil screen, return without
102 // TODO(rohitrao): We should just not save anything for fullscreen windows.
103 // http://crbug.com/36479.
107 // Only save main window information to preferences.
108 PrefService* prefs = browser_->profile()->GetPrefs();
109 if (!prefs || browser_ != chrome::GetLastActiveBrowser())
112 // Save the current work area, in flipped coordinates.
113 gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
114 workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
116 scoped_ptr<DictionaryPrefUpdate> update =
117 chrome::GetWindowPlacementDictionaryReadWrite(
118 chrome::GetWindowName(browser_.get()),
119 browser_->profile()->GetPrefs());
120 base::DictionaryValue* windowPreferences = update->Get();
121 windowPreferences->SetInteger("left", bounds.x());
122 windowPreferences->SetInteger("top", bounds.y());
123 windowPreferences->SetInteger("right", bounds.right());
124 windowPreferences->SetInteger("bottom", bounds.bottom());
125 windowPreferences->SetBoolean("maximized", false);
126 windowPreferences->SetBoolean("always_on_top", false);
127 windowPreferences->SetInteger("work_area_left", workArea.x());
128 windowPreferences->SetInteger("work_area_top", workArea.y());
129 windowPreferences->SetInteger("work_area_right", workArea.right());
130 windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
133 - (NSRect)window:(NSWindow*)window
134 willPositionSheet:(NSWindow*)sheet
135 usingRect:(NSRect)defaultSheetRect {
136 // Position the sheet as follows:
137 // - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
138 // bookmark bar is disabled), position the sheet immediately below the
140 // - If the bookmark bar is shown (attached to the normal toolbar), position
141 // the sheet below the bookmark bar.
142 // - If the bookmark bar is currently animating, position the sheet according
143 // to where the bar will be when the animation ends.
144 switch ([bookmarkBarController_ currentState]) {
145 case BookmarkBar::SHOW: {
146 NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame];
147 defaultSheetRect.origin.y = bookmarkBarFrame.origin.y;
150 case BookmarkBar::HIDDEN:
151 case BookmarkBar::DETACHED: {
152 if ([self hasToolbar]) {
153 NSRect toolbarFrame = [[toolbarController_ view] frame];
154 defaultSheetRect.origin.y = toolbarFrame.origin.y;
156 // The toolbar is not shown in application mode. The sheet should be
157 // located at the top of the window, under the title of the window.
158 defaultSheetRect.origin.y = NSHeight([[window contentView] frame]) -
159 defaultSheetRect.size.height;
164 return defaultSheetRect;
167 - (void)layoutSubviews {
168 // Suppress title drawing if necessary.
169 if ([self.window respondsToSelector:@selector(setShouldHideTitle:)])
170 [(id)self.window setShouldHideTitle:![self hasTitleBar]];
172 [bookmarkBarController_ updateHiddenState];
173 [self updateSubviewZOrder];
175 base::scoped_nsobject<BrowserWindowLayout> layout(
176 [[BrowserWindowLayout alloc] init]);
177 [self updateLayoutParameters:layout];
178 [self applyLayout:layout];
180 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
183 - (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
185 fullscreen:(BOOL)fullscreen {
186 // Nothing to do if no tab strip.
187 if (![self hasTabStrip])
190 NSView* tabStripView = [self tabStripView];
191 CGFloat tabStripHeight = NSHeight([tabStripView frame]);
192 maxY -= tabStripHeight;
193 NSRect tabStripFrame = NSMakeRect(0, maxY, width, tabStripHeight);
194 BOOL requiresRelayout = !NSEqualRects(tabStripFrame, [tabStripView frame]);
196 // In Yosemite fullscreen, manually add the fullscreen controls to the tab
198 BOOL addControlsInFullscreen =
199 [self isInAppKitFullscreen] && base::mac::IsOSYosemiteOrLater();
201 // Set left indentation based on fullscreen mode status.
202 CGFloat leftIndent = 0;
203 if (!fullscreen || addControlsInFullscreen)
204 leftIndent = [[tabStripController_ class] defaultLeftIndentForControls];
205 if (leftIndent != [tabStripController_ leftIndentForControls]) {
206 [tabStripController_ setLeftIndentForControls:leftIndent];
207 requiresRelayout = YES;
210 if (addControlsInFullscreen)
211 [tabStripController_ addWindowControls];
213 [tabStripController_ removeWindowControls];
215 // fullScreenButton is non-nil when isInAnyFullscreenMode is NO, and OS
216 // version is in the range 10.7 <= version <= 10.9. Starting with 10.10, the
217 // zoom/maximize button acts as the fullscreen button.
218 NSButton* fullScreenButton =
219 [[self window] standardWindowButton:NSWindowFullScreenButton];
221 // Lay out the icognito/avatar badge because calculating the indentation on
222 // the right depends on it.
223 NSView* avatarButton = [avatarButtonController_ view];
224 if ([self shouldShowAvatar]) {
225 CGFloat badgeXOffset = -kAvatarRightOffset;
226 CGFloat badgeYOffset = 0;
227 CGFloat buttonHeight = NSHeight([avatarButton frame]);
229 if ([self shouldUseNewAvatarButton]) {
230 // The fullscreen icon is displayed to the right of the avatar button.
231 if (![self isInAnyFullscreenMode] && fullScreenButton)
232 badgeXOffset -= width - NSMinX([fullScreenButton frame]);
233 // Center the button vertically on the tabstrip.
234 badgeYOffset = (tabStripHeight - buttonHeight) / 2;
236 // Actually place the badge *above* |maxY|, by +2 to miss the divider.
237 badgeYOffset = 2 * [[avatarButton superview] cr_lineWidth];
240 [avatarButton setFrameSize:NSMakeSize(NSWidth([avatarButton frame]),
241 std::min(buttonHeight, tabStripHeight))];
243 NSMakePoint(width - NSWidth([avatarButton frame]) + badgeXOffset,
244 maxY + badgeYOffset);
245 [avatarButton setFrameOrigin:origin];
246 [avatarButton setHidden:NO]; // Make sure it's shown.
249 // Calculate the right indentation. The default indentation built into the
250 // tabstrip leaves enough room for the fullscreen button on Lion (10.7) to
251 // Mavericks (10.9). On 10.6 and >=10.10, the right indent needs to be
252 // adjusted to make room for the new tab button when an avatar is present.
253 CGFloat rightIndent = 0;
254 if (![self isInAnyFullscreenMode] && fullScreenButton) {
255 rightIndent = width - NSMinX([fullScreenButton frame]);
257 if ([self shouldUseNewAvatarButton]) {
258 // The new avatar button is to the left of the fullscreen button.
259 // (The old avatar button is to the right).
260 rightIndent += NSWidth([avatarButton frame]) + kAvatarRightOffset;
262 } else if ([self shouldShowAvatar]) {
263 rightIndent += NSWidth([avatarButton frame]) + kAvatarRightOffset;
266 if (rightIndent != [tabStripController_ rightIndentForControls]) {
267 [tabStripController_ setRightIndentForControls:rightIndent];
268 requiresRelayout = YES;
271 // It is undesirable to force tabs relayout when the tap strip's frame did
272 // not change, because it will interrupt tab animations in progress.
273 // In addition, there appears to be an AppKit bug on <10.9 where interrupting
274 // a tab animation resulted in the tab frame being the animator's target
275 // frame instead of the interrupting setFrame. (See http://crbug.com/415093)
276 if (requiresRelayout) {
277 [tabStripView setFrame:tabStripFrame];
278 [tabStripController_ layoutTabsWithoutAnimation];
284 - (BOOL)placeBookmarkBarBelowInfoBar {
285 // If we are currently displaying the NTP detached bookmark bar or animating
286 // to/from it (from/to anything else), we display the bookmark bar below the
288 return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
289 [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
290 [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
293 - (void)layoutTabContentArea:(NSRect)newFrame {
294 NSView* tabContentView = [self tabContentArea];
295 NSRect tabContentFrame = [tabContentView frame];
297 bool contentShifted =
298 NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
299 NSMinX(tabContentFrame) != NSMinX(newFrame);
301 tabContentFrame = newFrame;
302 [tabContentView setFrame:tabContentFrame];
304 // If the relayout shifts the content area up or down, let the renderer know.
305 if (contentShifted) {
306 if (WebContents* contents =
307 browser_->tab_strip_model()->GetActiveWebContents()) {
308 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
309 rwhv->WindowFrameChanged();
314 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
316 [toolbarController_ desiredHeightForCompression:compression];
317 NSRect toolbarFrame = [[toolbarController_ view] frame];
318 CGFloat deltaH = newHeight - toolbarFrame.size.height;
323 toolbarFrame.size.height = newHeight;
324 NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
325 bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
326 [[toolbarController_ view] setFrame:toolbarFrame];
327 [[bookmarkBarController_ view] setFrame:bookmarkFrame];
328 [self layoutSubviews];
331 // Fullscreen and presentation mode methods
333 - (void)moveViewsForImmersiveFullscreen:(BOOL)fullscreen
334 regularWindow:(NSWindow*)regularWindow
335 fullscreenWindow:(NSWindow*)fullscreenWindow {
336 NSWindow* sourceWindow = fullscreen ? regularWindow : fullscreenWindow;
337 NSWindow* destWindow = fullscreen ? fullscreenWindow : regularWindow;
339 // Close the bookmark bubble, if it's open. Use |-ok:| instead of |-cancel:|
340 // or |-close| because that matches the behavior when the bubble loses key
342 [bookmarkBubbleController_ ok:self];
344 // Save the current first responder so we can restore after views are moved.
345 base::scoped_nsobject<FocusTracker> focusTracker(
346 [[FocusTracker alloc] initWithWindow:sourceWindow]);
348 // While we move views (and focus) around, disable any bar visibility changes.
349 [self disableBarVisibilityUpdates];
351 // Retain the tab strip view while we remove it from its superview.
352 base::scoped_nsobject<NSView> tabStripView;
353 if ([self hasTabStrip]) {
354 tabStripView.reset([[self tabStripView] retain]);
355 [tabStripView removeFromSuperview];
358 // Ditto for the content view.
359 base::scoped_nsobject<NSView> contentView(
360 [[sourceWindow contentView] retain]);
361 // Disable autoresizing of subviews while we move views around. This prevents
362 // spurious renderer resizes.
363 [contentView setAutoresizesSubviews:NO];
364 [contentView removeFromSuperview];
366 // Have to do this here, otherwise later calls can crash because the window
368 [sourceWindow setDelegate:nil];
369 [destWindow setDelegate:self];
371 // With this call, valgrind complains that a "Conditional jump or move depends
372 // on uninitialised value(s)". The error happens in -[NSThemeFrame
373 // drawOverlayRect:]. I'm pretty convinced this is an Apple bug, but there is
374 // no visual impact. I have been unable to tickle it away with other window
375 // or view manipulation Cocoa calls. Stack added to suppressions_mac.txt.
376 [contentView setAutoresizesSubviews:YES];
377 [destWindow setContentView:contentView];
378 [self moveContentViewToBack:contentView];
380 // Move the incognito badge if present.
381 if ([self shouldShowAvatar]) {
382 NSView* avatarButtonView = [avatarButtonController_ view];
384 [avatarButtonView removeFromSuperview];
385 [avatarButtonView setHidden:YES]; // Will be shown in layout.
386 [[destWindow cr_windowView] addSubview:avatarButtonView];
389 // Add the tab strip after setting the content view and moving the incognito
390 // badge (if any), so that the tab strip will be on top (in the z-order).
391 if ([self hasTabStrip])
392 [self insertTabStripView:tabStripView intoWindow:[self window]];
394 [sourceWindow setWindowController:nil];
395 [self setWindow:destWindow];
396 [destWindow setWindowController:self];
398 // Move the status bubble over, if we have one.
400 statusBubble_->SwitchParentWindow(destWindow);
402 // Move the title over.
403 [destWindow setTitle:[sourceWindow title]];
405 // The window needs to be onscreen before we can set its first responder.
406 // Ordering the window to the front can change the active Space (either to
407 // the window's old Space or to the application's assigned Space). To prevent
408 // this by temporarily change the collectionBehavior.
409 NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior];
410 [destWindow setCollectionBehavior:
411 NSWindowCollectionBehaviorMoveToActiveSpace];
412 [destWindow makeKeyAndOrderFront:self];
413 [destWindow setCollectionBehavior:behavior];
415 if (![focusTracker restoreFocusInWindow:destWindow]) {
416 // During certain types of fullscreen transitions, the view that had focus
417 // may have gone away (e.g., the one for a Flash FS widget). In this case,
418 // FocusTracker will fail to restore focus to anything, so we set the focus
419 // to the tab contents as a reasonable fall-back.
420 [self focusTabContents];
422 [sourceWindow orderOut:self];
424 // We're done moving focus, so re-enable bar visibility changes.
425 [self enableBarVisibilityUpdates];
428 - (void)permissionBubbleWindowWillClose:(NSNotification*)notification {
429 DCHECK(permissionBubbleCocoa_);
431 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
432 [center removeObserver:self
433 name:NSWindowWillCloseNotification
434 object:[notification object]];
435 [self releaseBarVisibilityForOwner:[notification object]
440 - (void)configurePresentationModeController {
441 BOOL fullscreen_for_tab =
442 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
444 CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
446 !fullscreen_for_tab && !kiosk_mode && ([self floatingBarHasFocus]);
447 if (permissionBubbleCocoa_ && permissionBubbleCocoa_->IsVisible()) {
448 DCHECK(permissionBubbleCocoa_->window());
449 // A visible permission bubble will force the dropdown to remain visible.
450 [self lockBarVisibilityForOwner:permissionBubbleCocoa_->window()
454 // Register to be notified when the permission bubble is closed, to
455 // allow fullscreen to hide the dropdown.
456 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
457 [center addObserver:self
458 selector:@selector(permissionBubbleWindowWillClose:)
459 name:NSWindowWillCloseNotification
460 object:permissionBubbleCocoa_->window()];
463 // Turn on layered mode for the window's root view for the entry
464 // animation. Without this, the OS fullscreen animation for entering
465 // fullscreen mode does not correctly draw the tab strip.
466 // It will be turned off (set back to NO) when the animation finishes,
467 // in -windowDidEnterFullScreen:.
468 // Leaving wantsLayer on for the duration of presentation mode causes
469 // performance issues when the dropdown is animated in/out. It also does
470 // not seem to be required for the exit animation.
471 windowViewWantsLayer_ = [[[self window] cr_windowView] wantsLayer];
472 [[[self window] cr_windowView] setWantsLayer:YES];
475 NSView* contentView = [[self window] contentView];
476 [presentationModeController_
477 enterPresentationModeForContentView:contentView
478 showDropdown:showDropdown];
481 - (void)adjustUIForExitingFullscreenAndStopOmniboxSliding {
482 [presentationModeController_ exitPresentationMode];
483 presentationModeController_.reset();
485 // Force the bookmark bar z-order to update.
486 [[bookmarkBarController_ view] removeFromSuperview];
487 [self layoutSubviews];
490 - (void)adjustUIForSlidingFullscreenStyle:(fullscreen_mac::SlidingStyle)style {
491 if (!presentationModeController_) {
492 presentationModeController_.reset(
493 [self newPresentationModeControllerWithStyle:style]);
494 [self configurePresentationModeController];
496 presentationModeController_.get().slidingStyle = style;
499 if (!floatingBarBackingView_.get() &&
500 ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
501 floatingBarBackingView_.reset(
502 [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
503 [floatingBarBackingView_
504 setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
507 // Force the bookmark bar z-order to update.
508 [[bookmarkBarController_ view] removeFromSuperview];
509 [self layoutSubviews];
512 - (PresentationModeController*)newPresentationModeControllerWithStyle:
513 (fullscreen_mac::SlidingStyle)style {
514 return [[PresentationModeController alloc] initWithBrowserController:self
518 - (void)enterImmersiveFullscreen {
519 // Set to NO by |-windowDidEnterFullScreen:|.
520 enteringImmersiveFullscreen_ = YES;
523 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
524 Boolean didFadeOut = NO;
525 CGDisplayFadeReservationToken token;
526 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
527 == kCGErrorSuccess) {
529 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
530 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
533 // Create the fullscreen window.
534 fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
535 savedRegularWindow_ = [[self window] retain];
536 savedRegularWindowFrame_ = [savedRegularWindow_ frame];
538 [self moveViewsForImmersiveFullscreen:YES
539 regularWindow:[self window]
540 fullscreenWindow:fullscreenWindow_.get()];
542 fullscreen_mac::SlidingStyle style = fullscreen_mac::OMNIBOX_TABS_HIDDEN;
543 [self adjustUIForSlidingFullscreenStyle:style];
545 // AppKit is helpful and prevents NSWindows from having the same height as
546 // the screen while the menu bar is showing. This only applies to windows on
547 // a secondary screen, in a separate space. Calling [NSWindow
548 // setFrame:display:] with the screen's height will always reduce the
549 // height by the height of the MenuBar. Calling the method with any other
550 // height works fine. The relevant method in the 10.10 AppKit SDK is called:
551 // _canAdjustSizeForScreensHaveSeparateSpacesIfFillingSecondaryScreen
553 // TODO(erikchen): Refactor the logic to allow the window to be shown after
554 // the menubar has been hidden. This would remove the need for this hack.
555 // http://crbug.com/403203
556 NSRect frame = [[[self window] screen] frame];
557 if (!NSEqualRects(frame, [fullscreenWindow_ frame]))
558 [fullscreenWindow_ setFrame:[[[self window] screen] frame] display:YES];
560 [self layoutSubviews];
562 [self windowDidEnterFullScreen:nil];
566 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
567 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
568 CGReleaseDisplayFadeReservation(token);
572 - (void)exitImmersiveFullscreen {
574 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
575 Boolean didFadeOut = NO;
576 CGDisplayFadeReservationToken token;
577 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
578 == kCGErrorSuccess) {
580 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
581 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
584 [self windowWillExitFullScreen:nil];
586 [self moveViewsForImmersiveFullscreen:NO
587 regularWindow:savedRegularWindow_
588 fullscreenWindow:fullscreenWindow_.get()];
590 // When exiting fullscreen mode, we need to call layoutSubviews manually.
591 [savedRegularWindow_ autorelease];
592 savedRegularWindow_ = nil;
593 fullscreenWindow_.reset();
594 [self layoutSubviews];
596 [self windowDidExitFullScreen:nil];
600 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
601 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
602 CGReleaseDisplayFadeReservation(token);
606 - (void)showFullscreenExitBubbleIfNecessary {
607 // This method is called in response to
608 // |-updateFullscreenExitBubbleURL:bubbleType:|. If we're in the middle of the
609 // transition into fullscreen (i.e., using the AppKit Fullscreen API), do not
610 // show the bubble because it will cause visual jank
611 // (http://crbug.com/130649). This will be called again as part of
612 // |-windowDidEnterFullScreen:|, so arrange to do that work then instead.
613 if (enteringAppKitFullscreen_)
616 [self hideOverlayIfPossibleWithAnimation:NO delay:NO];
618 if (fullscreenBubbleType_ == FEB_TYPE_NONE ||
619 fullscreenBubbleType_ == FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) {
620 // Show no exit instruction bubble on Mac when in Browser Fullscreen.
621 [self destroyFullscreenExitBubbleIfNecessary];
623 [fullscreenExitBubbleController_ closeImmediately];
624 fullscreenExitBubbleController_.reset(
625 [[FullscreenExitBubbleController alloc]
627 browser:browser_.get()
629 bubbleType:fullscreenBubbleType_]);
630 [fullscreenExitBubbleController_ showWindow];
634 - (void)destroyFullscreenExitBubbleIfNecessary {
635 [fullscreenExitBubbleController_ closeImmediately];
636 fullscreenExitBubbleController_.reset();
639 - (void)contentViewDidResize:(NSNotification*)notification {
640 [self layoutSubviews];
643 - (void)registerForContentViewResizeNotifications {
644 [[NSNotificationCenter defaultCenter]
646 selector:@selector(contentViewDidResize:)
647 name:NSViewFrameDidChangeNotification
648 object:[[self window] contentView]];
651 - (void)deregisterForContentViewResizeNotifications {
652 [[NSNotificationCenter defaultCenter]
654 name:NSViewFrameDidChangeNotification
655 object:[[self window] contentView]];
658 - (NSSize)window:(NSWindow*)window
659 willUseFullScreenContentSize:(NSSize)proposedSize {
663 - (NSApplicationPresentationOptions)window:(NSWindow*)window
664 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
666 NSApplicationPresentationAutoHideDock |
667 NSApplicationPresentationAutoHideMenuBar);
670 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
671 if (notification) // For System Fullscreen when non-nil.
672 [self registerForContentViewResizeNotifications];
674 NSWindow* window = [self window];
675 savedRegularWindowFrame_ = [window frame];
676 BOOL mode = enteringPresentationMode_ ||
677 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
678 enteringAppKitFullscreen_ = YES;
680 fullscreen_mac::SlidingStyle style =
681 mode ? fullscreen_mac::OMNIBOX_TABS_HIDDEN
682 : fullscreen_mac::OMNIBOX_TABS_PRESENT;
684 [self adjustUIForSlidingFullscreenStyle:style];
687 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
688 // In Yosemite, some combination of the titlebar and toolbar always show in
689 // full-screen mode. We do not want either to show. Search for the window that
690 // contains the views, and hide it. There is no need to ever unhide the view.
691 // http://crbug.com/380235
692 if (base::mac::IsOSYosemiteOrLater()) {
693 for (NSWindow* window in [[NSApplication sharedApplication] windows]) {
695 isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
696 [window.contentView setHidden:YES];
701 if (notification) // For System Fullscreen when non-nil.
702 [self deregisterForContentViewResizeNotifications];
703 enteringAppKitFullscreen_ = NO;
704 enteringImmersiveFullscreen_ = NO;
705 enteringPresentationMode_ = NO;
707 [self showFullscreenExitBubbleIfNecessary];
708 browser_->WindowFullscreenStateChanged();
709 [[[self window] cr_windowView] setWantsLayer:windowViewWantsLayer_];
712 - (void)windowWillExitFullScreen:(NSNotification*)notification {
713 if (notification) // For System Fullscreen when non-nil.
714 [self registerForContentViewResizeNotifications];
715 [self destroyFullscreenExitBubbleIfNecessary];
716 [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
719 - (void)windowDidExitFullScreen:(NSNotification*)notification {
720 if (notification) // For System Fullscreen when non-nil.
721 [self deregisterForContentViewResizeNotifications];
722 browser_->WindowFullscreenStateChanged();
725 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
726 [self deregisterForContentViewResizeNotifications];
727 enteringAppKitFullscreen_ = NO;
728 [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
731 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
732 [self deregisterForContentViewResizeNotifications];
734 // Force a relayout to try and get the window back into a reasonable state.
735 [self layoutSubviews];
738 - (void)enableBarVisibilityUpdates {
739 // Early escape if there's nothing to do.
740 if (barVisibilityUpdatesEnabled_)
743 barVisibilityUpdatesEnabled_ = YES;
745 if ([barVisibilityLocks_ count])
746 [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
748 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
751 - (void)disableBarVisibilityUpdates {
752 // Early escape if there's nothing to do.
753 if (!barVisibilityUpdatesEnabled_)
756 barVisibilityUpdatesEnabled_ = NO;
757 [presentationModeController_ cancelAnimationAndTimers];
760 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
761 if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
763 [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
767 - (CGFloat)toolbarDividerOpacity {
768 return [bookmarkBarController_ toolbarDividerOpacity];
771 - (void)updateLayerOrdering:(NSView*)view {
772 // Hold a reference to the view so that it doesn't accidentally get
774 base::scoped_nsobject<NSView> reference([view retain]);
776 // If the superview has a layer, then this hack isn't required.
777 NSView* superview = [view superview];
778 if ([superview layer])
781 // Get the current position of the view.
782 NSArray* subviews = [superview subviews];
783 NSInteger index = [subviews indexOfObject:view];
784 NSView* siblingBelow = nil;
786 siblingBelow = [subviews objectAtIndex:index - 1];
789 [view removeFromSuperview];
791 // Add it to the same position.
793 [superview addSubview:view
794 positioned:NSWindowAbove
795 relativeTo:siblingBelow];
797 [superview addSubview:view
798 positioned:NSWindowBelow
803 - (void)updateInfoBarTipVisibility {
804 // If there's no toolbar then hide the infobar tip.
805 [infoBarContainerController_
806 setShouldSuppressTopInfoBarTip:![self hasToolbar]];
809 - (NSInteger)pageInfoBubblePointY {
810 LocationBarViewMac* locationBarView = [self locationBarBridge];
812 // The point, in window coordinates.
813 NSPoint iconBottom = locationBarView->GetPageInfoBubblePoint();
815 // The toolbar, in window coordinates.
816 NSView* toolbar = [toolbarController_ view];
817 CGFloat toolbarY = NSMinY([toolbar convertRect:[toolbar bounds] toView:nil]);
819 return iconBottom.y - toolbarY;
822 - (void)enterAppKitFullscreen {
823 DCHECK(base::mac::IsOSLionOrLater());
824 if (FramedBrowserWindow* framedBrowserWindow =
825 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
826 [framedBrowserWindow toggleSystemFullScreen];
830 - (void)exitAppKitFullscreen {
831 DCHECK(base::mac::IsOSLionOrLater());
832 if (FramedBrowserWindow* framedBrowserWindow =
833 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
834 [framedBrowserWindow toggleSystemFullScreen];
838 - (void)updateLayoutParameters:(BrowserWindowLayout*)layout {
839 [layout setContentViewSize:[[[self window] contentView] bounds].size];
840 [layout setWindowSize:[[self window] frame].size];
842 [layout setInAnyFullscreen:[self isInAnyFullscreenMode]];
843 [layout setFullscreenSlidingStyle:
844 presentationModeController_.get().slidingStyle];
845 [layout setFullscreenMenubarOffset:
846 [presentationModeController_ menubarOffset]];
847 [layout setFullscreenToolbarFraction:
848 [presentationModeController_ toolbarFraction]];
850 [layout setHasTabStrip:[self hasTabStrip]];
852 [layout setHasToolbar:[self hasToolbar]];
853 [layout setToolbarHeight:NSHeight([[toolbarController_ view] bounds])];
855 [layout setHasLocationBar:[self hasLocationBar]];
857 [layout setPlaceBookmarkBarBelowInfoBar:[self placeBookmarkBarBelowInfoBar]];
858 [layout setBookmarkBarHidden:[bookmarkBarController_ view].isHidden];
859 [layout setBookmarkBarHeight:
860 NSHeight([[bookmarkBarController_ view] bounds])];
862 [layout setInfoBarHeight:[infoBarContainerController_ heightOfInfoBars]];
863 [layout setPageInfoBubblePointY:[self pageInfoBubblePointY]];
865 [layout setHasDownloadShelf:(downloadShelfController_.get() != nil)];
866 [layout setDownloadShelfHeight:
867 NSHeight([[downloadShelfController_ view] bounds])];
870 - (void)applyLayout:(BrowserWindowLayout*)layout {
871 chrome::LayoutOutput output = [layout computeLayout];
873 if (!NSIsEmptyRect(output.tabStripFrame)) {
874 // Note: The fullscreen parameter passed to the method is different from
875 // the field in |parameters| with the similar name.
876 [self layoutTabStripAtMaxY:NSMaxY(output.tabStripFrame)
877 width:NSWidth(output.tabStripFrame)
878 fullscreen:[self isInAnyFullscreenMode]];
881 if (!NSIsEmptyRect(output.toolbarFrame)) {
882 [[toolbarController_ view] setFrame:output.toolbarFrame];
885 if (!NSIsEmptyRect(output.bookmarkFrame)) {
886 NSView* bookmarkBarView = [bookmarkBarController_ view];
887 [bookmarkBarView setFrame:output.bookmarkFrame];
889 // Pin the bookmark bar to the top of the window and make the width
891 [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
893 [bookmarkBarController_ layoutSubviews];
896 // The info bar is never hidden. Sometimes it has zero effective height.
897 [[infoBarContainerController_ view] setFrame:output.infoBarFrame];
898 [infoBarContainerController_
899 setMaxTopArrowHeight:output.infoBarMaxTopArrowHeight];
901 if (!NSIsEmptyRect(output.downloadShelfFrame))
902 [[downloadShelfController_ view] setFrame:output.downloadShelfFrame];
904 [self layoutTabContentArea:output.contentAreaFrame];
906 if (!NSIsEmptyRect(output.fullscreenBackingBarFrame)) {
907 [floatingBarBackingView_ setFrame:output.fullscreenBackingBarFrame];
908 [presentationModeController_
909 overlayFrameChanged:output.fullscreenBackingBarFrame];
912 [findBarCocoaController_
913 positionFindBarViewAtMaxY:output.findBarMaxY
914 maxWidth:NSWidth(output.contentAreaFrame)];
916 [fullscreenExitBubbleController_
917 positionInWindowAtTop:output.fullscreenExitButtonMaxY
918 width:NSWidth(output.contentAreaFrame)];
921 - (void)updateSubviewZOrder {
922 if ([self isInAnyFullscreenMode])
923 [self updateSubviewZOrderFullscreen];
925 [self updateSubviewZOrderNormal];
927 [self updateSubviewZOrderHack];
930 - (void)updateSubviewZOrderNormal {
931 base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
932 if ([downloadShelfController_ view])
933 [subviews addObject:[downloadShelfController_ view]];
934 if ([bookmarkBarController_ view])
935 [subviews addObject:[bookmarkBarController_ view]];
936 if ([toolbarController_ view])
937 [subviews addObject:[toolbarController_ view]];
938 if ([infoBarContainerController_ view])
939 [subviews addObject:[infoBarContainerController_ view]];
940 if ([self tabContentArea])
941 [subviews addObject:[self tabContentArea]];
942 if ([findBarCocoaController_ view])
943 [subviews addObject:[findBarCocoaController_ view]];
945 [self setContentViewSubviews:subviews];
948 - (void)updateSubviewZOrderFullscreen {
949 base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
950 if ([downloadShelfController_ view])
951 [subviews addObject:[downloadShelfController_ view]];
952 if ([self tabContentArea])
953 [subviews addObject:[self tabContentArea]];
954 if ([self placeBookmarkBarBelowInfoBar]) {
955 if ([bookmarkBarController_ view])
956 [subviews addObject:[bookmarkBarController_ view]];
957 if (floatingBarBackingView_)
958 [subviews addObject:floatingBarBackingView_];
960 if (floatingBarBackingView_)
961 [subviews addObject:floatingBarBackingView_];
962 if ([bookmarkBarController_ view])
963 [subviews addObject:[bookmarkBarController_ view]];
965 if ([toolbarController_ view])
966 [subviews addObject:[toolbarController_ view]];
967 if ([infoBarContainerController_ view])
968 [subviews addObject:[infoBarContainerController_ view]];
969 if ([findBarCocoaController_ view])
970 [subviews addObject:[findBarCocoaController_ view]];
972 [self setContentViewSubviews:subviews];
975 - (void)setContentViewSubviews:(NSArray*)subviews {
976 // Subviews already match.
977 if ([[self.window.contentView subviews] isEqual:subviews])
980 // The tabContentArea isn't a subview, so just set all the subviews.
981 NSView* tabContentArea = [self tabContentArea];
982 if (![[self.window.contentView subviews] containsObject:tabContentArea]) {
983 [self.window.contentView setSubviews:subviews];
987 // Remove all subviews that aren't the tabContentArea.
988 for (NSView* view in [[self.window.contentView subviews] copy]) {
989 if (view != tabContentArea)
990 [view removeFromSuperview];
993 // Add in the subviews below the tabContentArea.
994 NSInteger index = [subviews indexOfObject:tabContentArea];
995 for (int i = index - 1; i >= 0; --i) {
996 NSView* view = [subviews objectAtIndex:i];
997 [self.window.contentView addSubview:view
998 positioned:NSWindowBelow
1002 // Add in the subviews above the tabContentArea.
1003 for (NSUInteger i = index + 1; i < [subviews count]; ++i) {
1004 NSView* view = [subviews objectAtIndex:i];
1005 [self.window.contentView addSubview:view
1006 positioned:NSWindowAbove
1011 - (void)updateSubviewZOrderHack {
1012 // TODO(erikchen): Remove and then add the tabStripView to the root NSView.
1013 // This fixes a layer ordering problem that occurs between the contentView
1014 // and the tabStripView. This is a hack required because NSThemeFrame is not
1015 // layer backed, and because Chrome adds subviews directly to the
1017 // http://crbug.com/407921
1018 if (enteringAppKitFullscreen_) {
1019 // The tabstrip frequently lies outside the bounds of its superview.
1020 // Repeatedly adding/removing the tabstrip from its superview during the
1021 // AppKit Fullscreen transition causes graphical glitches on 10.10. The
1022 // correct solution is to use the AppKit fullscreen transition APIs added
1024 // http://crbug.com/408791
1025 if (!hasAdjustedTabStripWhileEnteringAppKitFullscreen_) {
1026 // Disable implicit animations.
1027 [CATransaction begin];
1028 [CATransaction setDisableActions:YES];
1030 [self updateLayerOrdering:[self tabStripView]];
1031 [self updateLayerOrdering:[avatarButtonController_ view]];
1033 [CATransaction commit];
1034 hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = YES;
1037 hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = NO;
1041 @end // @implementation BrowserWindowController(Private)