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/foundation_util.h"
12 #include "base/mac/mac_util.h"
13 #import "base/mac/scoped_nsobject.h"
14 #import "base/mac/sdk_forward_declarations.h"
15 #include "base/metrics/histogram.h"
16 #include "base/prefs/pref_service.h"
17 #include "base/prefs/scoped_user_pref_update.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/fullscreen.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
22 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
23 #include "chrome/browser/ui/browser.h"
24 #include "chrome/browser/ui/browser_window_state.h"
25 #import "chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.h"
26 #import "chrome/browser/ui/cocoa/browser_window_layout.h"
27 #import "chrome/browser/ui/cocoa/custom_frame_view.h"
28 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
29 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
30 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
31 #import "chrome/browser/ui/cocoa/floating_bar_backing_view.h"
32 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
33 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
34 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
35 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
36 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
37 #import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h"
38 #import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h"
39 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
40 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
41 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
42 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
43 #import "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h"
44 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
45 #include "chrome/browser/ui/tabs/tab_strip_model.h"
46 #include "chrome/common/chrome_switches.h"
47 #include "content/public/browser/render_widget_host_view.h"
48 #include "content/public/browser/web_contents.h"
49 #import "ui/base/cocoa/focus_tracker.h"
50 #import "ui/base/cocoa/nsview_additions.h"
51 #include "ui/base/ui_base_types.h"
53 using content::RenderWidgetHostView;
54 using content::WebContents;
58 // The screen on which the window was fullscreened, and whether the device had
59 // multiple screens available.
61 PRIMARY_SINGLE_SCREEN = 0,
62 PRIMARY_MULTIPLE_SCREEN = 1,
63 SECONDARY_MULTIPLE_SCREEN = 2,
64 WINDOW_LOCATION_COUNT = 3
67 // There are 2 mechanisms for invoking fullscreen: AppKit and Immersive.
68 // There are 2 types of AppKit Fullscreen: Presentation Mode and Canonical
70 enum FullscreenStyle {
71 IMMERSIVE_FULLSCREEN = 0,
72 PRESENTATION_MODE = 1,
73 CANONICAL_FULLSCREEN = 2,
74 FULLSCREEN_STYLE_COUNT = 3
77 // Emits a histogram entry indicating the Fullscreen window location.
78 void RecordFullscreenWindowLocation(NSWindow* window) {
79 NSArray* screens = [NSScreen screens];
80 bool primary_screen = ([[window screen] isEqual:[screens objectAtIndex:0]]);
81 bool multiple_screens = [screens count] > 1;
83 WindowLocation location = PRIMARY_SINGLE_SCREEN;
84 if (multiple_screens) {
86 primary_screen ? PRIMARY_MULTIPLE_SCREEN : SECONDARY_MULTIPLE_SCREEN;
89 UMA_HISTOGRAM_ENUMERATION(
90 "OSX.Fullscreen.Enter.WindowLocation", location, WINDOW_LOCATION_COUNT);
93 // Emits a histogram entry indicating the Fullscreen style.
94 void RecordFullscreenStyle(FullscreenStyle style) {
95 UMA_HISTOGRAM_ENUMERATION(
96 "OSX.Fullscreen.Enter.Style", style, FULLSCREEN_STYLE_COUNT);
101 @implementation BrowserWindowController(Private)
103 // Create the tab strip controller.
104 - (void)createTabStripController {
105 DCHECK([overlayableContentsController_ activeContainer]);
106 DCHECK([[overlayableContentsController_ activeContainer] window]);
107 tabStripController_.reset([[TabStripController alloc]
108 initWithView:[self tabStripView]
109 switchView:[overlayableContentsController_ activeContainer]
110 browser:browser_.get()
114 - (void)saveWindowPositionIfNeeded {
115 if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
118 // If we're in fullscreen mode, save the position of the regular window
121 [self isInAnyFullscreenMode] ? savedRegularWindow_ : [self window];
123 // Window positions are stored relative to the origin of the primary monitor.
124 NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
125 NSScreen* windowScreen = [window screen];
127 // Start with the window's frame, which is in virtual coordinates.
128 // Do some y twiddling to flip the coordinate system.
129 gfx::Rect bounds(NSRectToCGRect([window frame]));
130 bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
132 // Browser::SaveWindowPlacement saves information for session restore.
133 ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
134 if ([window isMiniaturized])
135 show_state = ui::SHOW_STATE_MINIMIZED;
136 else if ([self isInAnyFullscreenMode])
137 show_state = ui::SHOW_STATE_FULLSCREEN;
138 chrome::SaveWindowPlacement(browser_.get(), bounds, show_state);
140 // |windowScreen| can be nil (for example, if the monitor arrangement was
141 // changed while in fullscreen mode). If we see a nil screen, return without
143 // TODO(rohitrao): We should just not save anything for fullscreen windows.
144 // http://crbug.com/36479.
148 // Only save main window information to preferences.
149 PrefService* prefs = browser_->profile()->GetPrefs();
150 if (!prefs || browser_ != chrome::GetLastActiveBrowser())
153 // Save the current work area, in flipped coordinates.
154 gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
155 workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
157 scoped_ptr<DictionaryPrefUpdate> update =
158 chrome::GetWindowPlacementDictionaryReadWrite(
159 chrome::GetWindowName(browser_.get()),
160 browser_->profile()->GetPrefs());
161 base::DictionaryValue* windowPreferences = update->Get();
162 windowPreferences->SetInteger("left", bounds.x());
163 windowPreferences->SetInteger("top", bounds.y());
164 windowPreferences->SetInteger("right", bounds.right());
165 windowPreferences->SetInteger("bottom", bounds.bottom());
166 windowPreferences->SetBoolean("maximized", false);
167 windowPreferences->SetBoolean("always_on_top", false);
168 windowPreferences->SetInteger("work_area_left", workArea.x());
169 windowPreferences->SetInteger("work_area_top", workArea.y());
170 windowPreferences->SetInteger("work_area_right", workArea.right());
171 windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
174 - (NSRect)window:(NSWindow*)window
175 willPositionSheet:(NSWindow*)sheet
176 usingRect:(NSRect)defaultSheetLocation {
177 // Position the sheet as follows:
178 // - If the bookmark bar is shown (attached to the normal toolbar), position
179 // the sheet below the bookmark bar.
180 // - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
181 // bookmark bar is disabled), position the sheet immediately below the
183 // - If the bookmark bar is currently animating, position the sheet according
184 // to where the bar will be when the animation ends.
185 CGFloat defaultSheetY = defaultSheetLocation.origin.y;
186 if ([self supportsBookmarkBar] &&
187 [bookmarkBarController_ currentState] == BookmarkBar::SHOW) {
188 defaultSheetY = NSMinY([[bookmarkBarController_ view] frame]);
189 } else if ([self hasToolbar]) {
190 defaultSheetY = NSMinY([[toolbarController_ view] frame]);
192 // The toolbar is not shown in popup and application modes. The sheet
193 // should be located at the top of the window, under the title of the
195 defaultSheetY = NSMaxY([[window contentView] frame]);
198 // AppKit may shift the window up to fit the sheet on screen, but it will
199 // never adjust the height of the sheet, or the origin of the sheet relative
200 // to the window. Adjust the origin to prevent sheets from extending past the
201 // bottom of the screen.
203 // Don't allow the sheet to extend past the bottom of the window. This logic
204 // intentionally ignores the size of the screens, since the window might span
205 // multiple screens, and AppKit may reposition the window.
206 CGFloat sheetHeight = NSHeight([sheet frame]);
207 defaultSheetY = std::max(defaultSheetY, sheetHeight);
209 // It doesn't make sense to provide a Y higher than the height of the window.
210 CGFloat windowHeight = NSHeight([window frame]);
211 defaultSheetY = std::min(defaultSheetY, windowHeight);
213 defaultSheetLocation.origin.y = defaultSheetY;
214 return defaultSheetLocation;
217 - (void)layoutSubviews {
218 // Suppress title drawing if necessary.
219 if ([self.window respondsToSelector:@selector(setShouldHideTitle:)])
220 [(id)self.window setShouldHideTitle:![self hasTitleBar]];
222 [bookmarkBarController_ updateHiddenState];
223 [self updateSubviewZOrder];
225 base::scoped_nsobject<BrowserWindowLayout> layout(
226 [[BrowserWindowLayout alloc] init]);
227 [self updateLayoutParameters:layout];
228 [self applyLayout:layout];
230 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
233 - (void)applyTabStripLayout:(const chrome::TabStripLayout&)layout {
234 // Update the presence of the window controls.
235 if (layout.addCustomWindowControls)
236 [tabStripController_ addCustomWindowControls];
238 [tabStripController_ removeCustomWindowControls];
240 // Update the layout of the avatar.
241 if (!NSIsEmptyRect(layout.avatarFrame)) {
242 NSView* avatarButton = [avatarButtonController_ view];
243 [avatarButton setFrame:layout.avatarFrame];
244 [avatarButton setHidden:NO];
247 // Check if the tab strip's frame has changed.
248 BOOL requiresRelayout =
249 !NSEqualRects([[self tabStripView] frame], layout.frame);
251 // Check if the left indent has changed.
252 if (layout.leftIndent != [tabStripController_ leftIndentForControls]) {
253 [tabStripController_ setLeftIndentForControls:layout.leftIndent];
254 requiresRelayout = YES;
257 // Check if the right indent has changed.
258 if (layout.rightIndent != [tabStripController_ rightIndentForControls]) {
259 [tabStripController_ setRightIndentForControls:layout.rightIndent];
260 requiresRelayout = YES;
263 // It is undesirable to force tabs relayout when the tap strip's frame did
264 // not change, because it will interrupt tab animations in progress.
265 // In addition, there appears to be an AppKit bug on <10.9 where interrupting
266 // a tab animation resulted in the tab frame being the animator's target
267 // frame instead of the interrupting setFrame. (See http://crbug.com/415093)
268 if (requiresRelayout) {
269 [[self tabStripView] setFrame:layout.frame];
270 [tabStripController_ layoutTabsWithoutAnimation];
274 - (BOOL)placeBookmarkBarBelowInfoBar {
275 // If we are currently displaying the NTP detached bookmark bar or animating
276 // to/from it (from/to anything else), we display the bookmark bar below the
278 return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
279 [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
280 [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
283 - (void)layoutTabContentArea:(NSRect)newFrame {
284 NSView* tabContentView = [self tabContentArea];
285 NSRect tabContentFrame = [tabContentView frame];
287 bool contentShifted =
288 NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
289 NSMinX(tabContentFrame) != NSMinX(newFrame);
291 tabContentFrame = newFrame;
292 [tabContentView setFrame:tabContentFrame];
294 // If the relayout shifts the content area up or down, let the renderer know.
295 if (contentShifted) {
296 if (WebContents* contents =
297 browser_->tab_strip_model()->GetActiveWebContents()) {
298 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
299 rwhv->WindowFrameChanged();
304 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
306 [toolbarController_ desiredHeightForCompression:compression];
307 NSRect toolbarFrame = [[toolbarController_ view] frame];
308 CGFloat deltaH = newHeight - toolbarFrame.size.height;
313 toolbarFrame.size.height = newHeight;
314 NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
315 bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
316 [[toolbarController_ view] setFrame:toolbarFrame];
317 [[bookmarkBarController_ view] setFrame:bookmarkFrame];
318 [self layoutSubviews];
321 // Fullscreen and presentation mode methods
323 - (void)moveViewsForImmersiveFullscreen:(BOOL)fullscreen
324 regularWindow:(NSWindow*)regularWindow
325 fullscreenWindow:(NSWindow*)fullscreenWindow {
326 NSWindow* sourceWindow = fullscreen ? regularWindow : fullscreenWindow;
327 NSWindow* destWindow = fullscreen ? fullscreenWindow : regularWindow;
329 // Close the bookmark bubble, if it's open. Use |-ok:| instead of |-cancel:|
330 // or |-close| because that matches the behavior when the bubble loses key
332 [bookmarkBubbleController_ ok:self];
334 // Save the current first responder so we can restore after views are moved.
335 base::scoped_nsobject<FocusTracker> focusTracker(
336 [[FocusTracker alloc] initWithWindow:sourceWindow]);
338 // While we move views (and focus) around, disable any bar visibility changes.
339 [self disableBarVisibilityUpdates];
341 // Retain the tab strip view while we remove it from its superview.
342 base::scoped_nsobject<NSView> tabStripView;
343 if ([self hasTabStrip]) {
344 tabStripView.reset([[self tabStripView] retain]);
345 [tabStripView removeFromSuperview];
348 // Disable autoresizing of subviews while we move views around. This prevents
349 // spurious renderer resizes.
350 [self.chromeContentView setAutoresizesSubviews:NO];
351 [self.chromeContentView removeFromSuperview];
353 // Have to do this here, otherwise later calls can crash because the window
355 [sourceWindow setDelegate:nil];
356 [destWindow setDelegate:self];
358 // With this call, valgrind complains that a "Conditional jump or move depends
359 // on uninitialised value(s)". The error happens in -[NSThemeFrame
360 // drawOverlayRect:]. I'm pretty convinced this is an Apple bug, but there is
361 // no visual impact. I have been unable to tickle it away with other window
362 // or view manipulation Cocoa calls. Stack added to suppressions_mac.txt.
363 [self.chromeContentView setAutoresizesSubviews:YES];
364 [[destWindow contentView] addSubview:self.chromeContentView
365 positioned:NSWindowBelow
367 [self.chromeContentView setFrame:[[destWindow contentView] bounds]];
369 // Move the incognito badge if present.
370 if ([self shouldShowAvatar]) {
371 NSView* avatarButtonView = [avatarButtonController_ view];
373 [avatarButtonView removeFromSuperview];
374 [avatarButtonView setHidden:YES]; // Will be shown in layout.
375 [[destWindow contentView] addSubview:avatarButtonView];
378 // Add the tab strip after setting the content view and moving the incognito
379 // badge (if any), so that the tab strip will be on top (in the z-order).
380 if ([self hasTabStrip])
381 [[destWindow contentView] addSubview:tabStripView];
383 [sourceWindow setWindowController:nil];
384 [self setWindow:destWindow];
385 [destWindow setWindowController:self];
387 // Move the status bubble over, if we have one.
389 statusBubble_->SwitchParentWindow(destWindow);
391 permissionBubbleCocoa_->SwitchParentWindow(destWindow);
393 // Move the title over.
394 [destWindow setTitle:[sourceWindow title]];
396 // The window needs to be onscreen before we can set its first responder.
397 // Ordering the window to the front can change the active Space (either to
398 // the window's old Space or to the application's assigned Space). To prevent
399 // this by temporarily change the collectionBehavior.
400 NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior];
401 [destWindow setCollectionBehavior:
402 NSWindowCollectionBehaviorMoveToActiveSpace];
403 [destWindow makeKeyAndOrderFront:self];
404 [destWindow setCollectionBehavior:behavior];
406 if (![focusTracker restoreFocusInWindow:destWindow]) {
407 // During certain types of fullscreen transitions, the view that had focus
408 // may have gone away (e.g., the one for a Flash FS widget). In this case,
409 // FocusTracker will fail to restore focus to anything, so we set the focus
410 // to the tab contents as a reasonable fall-back.
411 [self focusTabContents];
413 [sourceWindow orderOut:self];
415 // We're done moving focus, so re-enable bar visibility changes.
416 [self enableBarVisibilityUpdates];
419 - (void)permissionBubbleWindowWillClose:(NSNotification*)notification {
420 DCHECK(permissionBubbleCocoa_);
422 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
423 [center removeObserver:self
424 name:NSWindowWillCloseNotification
425 object:[notification object]];
426 [self releaseBarVisibilityForOwner:[notification object]
431 - (void)configurePresentationModeController {
432 BOOL fullscreen_for_tab = browser_->exclusive_access_manager()
433 ->fullscreen_controller()
434 ->IsWindowFullscreenForTabOrPending();
436 base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
438 !fullscreen_for_tab && !kiosk_mode && ([self floatingBarHasFocus]);
439 if (permissionBubbleCocoa_ && permissionBubbleCocoa_->IsVisible()) {
440 DCHECK(permissionBubbleCocoa_->window());
441 // A visible permission bubble will force the dropdown to remain visible.
442 [self lockBarVisibilityForOwner:permissionBubbleCocoa_->window()
446 // Register to be notified when the permission bubble is closed, to
447 // allow fullscreen to hide the dropdown.
448 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
449 [center addObserver:self
450 selector:@selector(permissionBubbleWindowWillClose:)
451 name:NSWindowWillCloseNotification
452 object:permissionBubbleCocoa_->window()];
455 // Turn on layered mode for the window's root view for the entry
456 // animation. Without this, the OS fullscreen animation for entering
457 // fullscreen mode does not correctly draw the tab strip.
458 // It will be turned off (set back to NO) when the animation finishes,
459 // in -windowDidEnterFullScreen:.
460 // Leaving wantsLayer on for the duration of presentation mode causes
461 // performance issues when the dropdown is animated in/out. It also does
462 // not seem to be required for the exit animation.
463 windowViewWantsLayer_ = [[[self window] contentView] wantsLayer];
464 [[[self window] contentView] setWantsLayer:YES];
467 NSView* contentView = [[self window] contentView];
468 [presentationModeController_
469 enterPresentationModeForContentView:contentView
470 showDropdown:showDropdown];
473 - (void)adjustUIForExitingFullscreenAndStopOmniboxSliding {
474 [presentationModeController_ exitPresentationMode];
475 presentationModeController_.reset();
477 // Force the bookmark bar z-order to update.
478 [[bookmarkBarController_ view] removeFromSuperview];
479 [self layoutSubviews];
482 - (void)adjustUIForSlidingFullscreenStyle:(fullscreen_mac::SlidingStyle)style {
483 if (!presentationModeController_) {
484 presentationModeController_.reset(
485 [self newPresentationModeControllerWithStyle:style]);
486 [self configurePresentationModeController];
488 presentationModeController_.get().slidingStyle = style;
491 if (!floatingBarBackingView_.get() &&
492 ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
493 floatingBarBackingView_.reset(
494 [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
495 [floatingBarBackingView_
496 setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
499 // Force the bookmark bar z-order to update.
500 [[bookmarkBarController_ view] removeFromSuperview];
501 [self layoutSubviews];
504 - (PresentationModeController*)newPresentationModeControllerWithStyle:
505 (fullscreen_mac::SlidingStyle)style {
506 return [[PresentationModeController alloc] initWithBrowserController:self
510 - (void)enterImmersiveFullscreen {
511 RecordFullscreenWindowLocation([self window]);
512 RecordFullscreenStyle(IMMERSIVE_FULLSCREEN);
514 // Set to NO by |-windowDidEnterFullScreen:|.
515 enteringImmersiveFullscreen_ = YES;
518 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
519 Boolean didFadeOut = NO;
520 CGDisplayFadeReservationToken token;
521 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
522 == kCGErrorSuccess) {
524 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
525 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
528 // Create the fullscreen window.
529 fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
530 savedRegularWindow_ = [[self window] retain];
531 savedRegularWindowFrame_ = [savedRegularWindow_ frame];
533 [self moveViewsForImmersiveFullscreen:YES
534 regularWindow:[self window]
535 fullscreenWindow:fullscreenWindow_.get()];
537 fullscreen_mac::SlidingStyle style = fullscreen_mac::OMNIBOX_TABS_HIDDEN;
538 [self adjustUIForSlidingFullscreenStyle:style];
540 // AppKit is helpful and prevents NSWindows from having the same height as
541 // the screen while the menu bar is showing. This only applies to windows on
542 // a secondary screen, in a separate space. Calling [NSWindow
543 // setFrame:display:] with the screen's height will always reduce the
544 // height by the height of the MenuBar. Calling the method with any other
545 // height works fine. The relevant method in the 10.10 AppKit SDK is called:
546 // _canAdjustSizeForScreensHaveSeparateSpacesIfFillingSecondaryScreen
548 // TODO(erikchen): Refactor the logic to allow the window to be shown after
549 // the menubar has been hidden. This would remove the need for this hack.
550 // http://crbug.com/403203
551 NSRect frame = [[[self window] screen] frame];
552 if (!NSEqualRects(frame, [fullscreenWindow_ frame]))
553 [fullscreenWindow_ setFrame:[[[self window] screen] frame] display:YES];
555 [self layoutSubviews];
557 [self windowDidEnterFullScreen:nil];
561 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
562 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
563 CGReleaseDisplayFadeReservation(token);
567 - (void)exitImmersiveFullscreen {
569 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
570 Boolean didFadeOut = NO;
571 CGDisplayFadeReservationToken token;
572 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
573 == kCGErrorSuccess) {
575 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
576 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
579 [self windowWillExitFullScreen:nil];
581 [self moveViewsForImmersiveFullscreen:NO
582 regularWindow:savedRegularWindow_
583 fullscreenWindow:fullscreenWindow_.get()];
585 // When exiting fullscreen mode, we need to call layoutSubviews manually.
586 [savedRegularWindow_ autorelease];
587 savedRegularWindow_ = nil;
588 fullscreenWindow_.reset();
589 [self layoutSubviews];
591 [self windowDidExitFullScreen:nil];
595 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
596 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
597 CGReleaseDisplayFadeReservation(token);
601 - (void)showFullscreenExitBubbleIfNecessary {
602 // This method is called in response to
603 // |-updateFullscreenExitBubbleURL:bubbleType:|. If we're in the middle of the
604 // transition into fullscreen (i.e., using the AppKit Fullscreen API), do not
605 // show the bubble because it will cause visual jank
606 // (http://crbug.com/130649). This will be called again as part of
607 // |-windowDidEnterFullScreen:|, so arrange to do that work then instead.
608 if (enteringAppKitFullscreen_)
611 [self hideOverlayIfPossibleWithAnimation:NO delay:NO];
613 if (exclusiveAccessBubbleType_ == EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE ||
614 exclusiveAccessBubbleType_ ==
615 EXCLUSIVE_ACCESS_BUBBLE_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) {
616 // Show no exit instruction bubble on Mac when in Browser Fullscreen.
617 [self destroyFullscreenExitBubbleIfNecessary];
619 [exclusiveAccessBubbleWindowController_ closeImmediately];
620 exclusiveAccessBubbleWindowController_.reset(
621 [[ExclusiveAccessBubbleWindowController alloc]
623 exclusive_access_manager:browser_.get()->exclusive_access_manager()
624 profile:browser_.get()->profile()
626 bubbleType:exclusiveAccessBubbleType_]);
627 [exclusiveAccessBubbleWindowController_ showWindow];
631 - (void)destroyFullscreenExitBubbleIfNecessary {
632 [exclusiveAccessBubbleWindowController_ closeImmediately];
633 exclusiveAccessBubbleWindowController_.reset();
636 - (void)contentViewDidResize:(NSNotification*)notification {
637 [self layoutSubviews];
640 - (void)registerForContentViewResizeNotifications {
641 [[NSNotificationCenter defaultCenter]
643 selector:@selector(contentViewDidResize:)
644 name:NSViewFrameDidChangeNotification
645 object:[[self window] contentView]];
648 - (void)deregisterForContentViewResizeNotifications {
649 [[NSNotificationCenter defaultCenter]
651 name:NSViewFrameDidChangeNotification
652 object:[[self window] contentView]];
655 - (NSSize)window:(NSWindow*)window
656 willUseFullScreenContentSize:(NSSize)proposedSize {
660 - (NSApplicationPresentationOptions)window:(NSWindow*)window
661 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
663 NSApplicationPresentationAutoHideDock |
664 NSApplicationPresentationAutoHideMenuBar);
667 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
668 RecordFullscreenWindowLocation([self window]);
669 RecordFullscreenStyle(enteringPresentationMode_ ? PRESENTATION_MODE
670 : CANONICAL_FULLSCREEN);
672 if (notification) // For System Fullscreen when non-nil.
673 [self registerForContentViewResizeNotifications];
675 NSWindow* window = [self window];
676 savedRegularWindowFrame_ = [window frame];
677 BOOL mode = enteringPresentationMode_ ||
678 browser_->exclusive_access_manager()
679 ->fullscreen_controller()
680 ->IsWindowFullscreenForTabOrPending();
681 enteringAppKitFullscreen_ = YES;
682 enteringAppKitFullscreenOnPrimaryScreen_ =
683 [[[self window] screen] isEqual:[[NSScreen screens] objectAtIndex:0]];
685 fullscreen_mac::SlidingStyle style =
686 mode ? fullscreen_mac::OMNIBOX_TABS_HIDDEN
687 : fullscreen_mac::OMNIBOX_TABS_PRESENT;
689 [self adjustUIForSlidingFullscreenStyle:style];
692 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
693 enterFullscreenTransition_.reset();
695 // In Yosemite, some combination of the titlebar and toolbar always show in
696 // full-screen mode. We do not want either to show. Search for the window that
697 // contains the views, and hide it. There is no need to ever unhide the view.
698 // http://crbug.com/380235
699 if (base::mac::IsOSYosemiteOrLater()) {
700 for (NSWindow* window in [[NSApplication sharedApplication] windows]) {
702 isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
703 [[window contentView] setHidden:YES];
708 if ([self shouldUseMavericksAppKitFullscreenHack]) {
709 // Apply a hack to fix the size of the window. This is the last run of the
710 // MessageLoop where the hack will not work, so dispatch the hack to the
711 // top of the MessageLoop.
712 base::Callback<void(void)> callback = base::BindBlock(^{
713 if (![self isInAppKitFullscreen])
716 // The window's frame should be exactly 22 points too short.
717 CGFloat kExpectedHeightDifference = 22;
718 NSRect currentFrame = [[self window] frame];
719 NSRect expectedFrame = [[[self window] screen] frame];
720 if (!NSEqualPoints(currentFrame.origin, expectedFrame.origin))
722 if (currentFrame.size.width != expectedFrame.size.width)
724 CGFloat heightDelta =
725 expectedFrame.size.height - currentFrame.size.height;
726 if (fabs(heightDelta - kExpectedHeightDifference) > 0.01)
729 [[self window] setFrame:expectedFrame display:YES];
731 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
734 if (notification) // For System Fullscreen when non-nil.
735 [self deregisterForContentViewResizeNotifications];
736 enteringAppKitFullscreen_ = NO;
737 enteringImmersiveFullscreen_ = NO;
738 enteringPresentationMode_ = NO;
740 [self showFullscreenExitBubbleIfNecessary];
741 browser_->WindowFullscreenStateChanged();
742 [[[self window] contentView] setWantsLayer:windowViewWantsLayer_];
745 - (void)windowWillExitFullScreen:(NSNotification*)notification {
746 if (notification) // For System Fullscreen when non-nil.
747 [self registerForContentViewResizeNotifications];
748 [self destroyFullscreenExitBubbleIfNecessary];
749 [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
752 - (void)windowDidExitFullScreen:(NSNotification*)notification {
753 if (notification) // For System Fullscreen when non-nil.
754 [self deregisterForContentViewResizeNotifications];
755 browser_->WindowFullscreenStateChanged();
758 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
759 [self deregisterForContentViewResizeNotifications];
760 enteringAppKitFullscreen_ = NO;
761 [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
764 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
765 [self deregisterForContentViewResizeNotifications];
767 // Force a relayout to try and get the window back into a reasonable state.
768 [self layoutSubviews];
771 - (void)enableBarVisibilityUpdates {
772 // Early escape if there's nothing to do.
773 if (barVisibilityUpdatesEnabled_)
776 barVisibilityUpdatesEnabled_ = YES;
778 if ([barVisibilityLocks_ count])
779 [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
781 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
784 - (void)disableBarVisibilityUpdates {
785 // Early escape if there's nothing to do.
786 if (!barVisibilityUpdatesEnabled_)
789 barVisibilityUpdatesEnabled_ = NO;
790 [presentationModeController_ cancelAnimationAndTimers];
793 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
794 if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
796 [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
800 - (CGFloat)toolbarDividerOpacity {
801 return [bookmarkBarController_ toolbarDividerOpacity];
804 - (void)updateInfoBarTipVisibility {
805 // If there's no toolbar then hide the infobar tip.
806 [infoBarContainerController_
807 setShouldSuppressTopInfoBarTip:![self hasToolbar]];
810 - (NSInteger)pageInfoBubblePointY {
811 LocationBarViewMac* locationBarView = [self locationBarBridge];
813 // The point, in window coordinates.
814 NSPoint iconBottom = locationBarView->GetPageInfoBubblePoint();
816 // The toolbar, in window coordinates.
817 NSView* toolbar = [toolbarController_ view];
818 CGFloat toolbarY = NSMinY([toolbar convertRect:[toolbar bounds] toView:nil]);
820 return iconBottom.y - toolbarY;
823 - (void)enterAppKitFullscreen {
824 DCHECK(base::mac::IsOSLionOrLater());
825 if (FramedBrowserWindow* framedBrowserWindow =
826 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
827 [framedBrowserWindow toggleSystemFullScreen];
831 - (void)exitAppKitFullscreen {
832 DCHECK(base::mac::IsOSLionOrLater());
833 if (FramedBrowserWindow* framedBrowserWindow =
834 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
835 [framedBrowserWindow toggleSystemFullScreen];
839 - (NSRect)fullscreenButtonFrame {
840 // NSWindowFullScreenButton is 10.7+ and results in log spam on 10.6 if used.
841 if (base::mac::IsOSSnowLeopard())
844 NSButton* fullscreenButton =
845 [[self window] standardWindowButton:NSWindowFullScreenButton];
846 if (!fullscreenButton)
849 NSRect buttonFrame = [fullscreenButton frame];
851 // When called from -windowWillExitFullScreen:, the button's frame may not
852 // be updated yet to match the new window size.
853 // We need to return where the button should be positioned.
854 NSView* rootView = [[[self window] contentView] superview];
855 if ([rootView respondsToSelector:@selector(_fullScreenButtonOrigin)])
856 buttonFrame.origin = [rootView _fullScreenButtonOrigin];
861 - (void)updateLayoutParameters:(BrowserWindowLayout*)layout {
862 [layout setContentViewSize:[[[self window] contentView] bounds].size];
863 [layout setWindowSize:[[self window] frame].size];
865 [layout setInAnyFullscreen:[self isInAnyFullscreenMode]];
866 [layout setFullscreenSlidingStyle:
867 presentationModeController_.get().slidingStyle];
868 [layout setFullscreenMenubarOffset:
869 [presentationModeController_ menubarOffset]];
870 [layout setFullscreenToolbarFraction:
871 [presentationModeController_ toolbarFraction]];
873 [layout setHasTabStrip:[self hasTabStrip]];
874 [layout setFullscreenButtonFrame:[self fullscreenButtonFrame]];
876 if ([self shouldShowAvatar]) {
877 NSView* avatar = [avatarButtonController_ view];
878 [layout setShouldShowAvatar:YES];
879 [layout setShouldUseNewAvatar:[self shouldUseNewAvatarButton]];
880 [layout setAvatarSize:[avatar frame].size];
881 [layout setAvatarLineWidth:[[avatar superview] cr_lineWidth]];
884 [layout setHasToolbar:[self hasToolbar]];
885 [layout setToolbarHeight:NSHeight([[toolbarController_ view] bounds])];
887 [layout setHasLocationBar:[self hasLocationBar]];
889 [layout setPlaceBookmarkBarBelowInfoBar:[self placeBookmarkBarBelowInfoBar]];
890 [layout setBookmarkBarHidden:[bookmarkBarController_ view].isHidden];
891 [layout setBookmarkBarHeight:
892 NSHeight([[bookmarkBarController_ view] bounds])];
894 [layout setInfoBarHeight:[infoBarContainerController_ heightOfInfoBars]];
895 [layout setPageInfoBubblePointY:[self pageInfoBubblePointY]];
897 [layout setHasDownloadShelf:(downloadShelfController_.get() != nil)];
898 [layout setDownloadShelfHeight:
899 NSHeight([[downloadShelfController_ view] bounds])];
902 - (void)applyLayout:(BrowserWindowLayout*)layout {
903 chrome::LayoutOutput output = [layout computeLayout];
905 if (!NSIsEmptyRect(output.tabStripLayout.frame))
906 [self applyTabStripLayout:output.tabStripLayout];
908 if (!NSIsEmptyRect(output.toolbarFrame))
909 [[toolbarController_ view] setFrame:output.toolbarFrame];
911 if (!NSIsEmptyRect(output.bookmarkFrame)) {
912 NSView* bookmarkBarView = [bookmarkBarController_ view];
913 [bookmarkBarView setFrame:output.bookmarkFrame];
915 // Pin the bookmark bar to the top of the window and make the width
917 [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
919 [bookmarkBarController_ layoutSubviews];
922 // The info bar is never hidden. Sometimes it has zero effective height.
923 [[infoBarContainerController_ view] setFrame:output.infoBarFrame];
924 [infoBarContainerController_
925 setMaxTopArrowHeight:output.infoBarMaxTopArrowHeight];
926 [infoBarContainerController_
927 setInfobarArrowX:[self locationBarBridge]->GetPageInfoBubblePoint().x];
929 if (!NSIsEmptyRect(output.downloadShelfFrame))
930 [[downloadShelfController_ view] setFrame:output.downloadShelfFrame];
932 [self layoutTabContentArea:output.contentAreaFrame];
934 if (!NSIsEmptyRect(output.fullscreenBackingBarFrame)) {
935 [floatingBarBackingView_ setFrame:output.fullscreenBackingBarFrame];
936 [presentationModeController_
937 overlayFrameChanged:output.fullscreenBackingBarFrame];
940 [findBarCocoaController_
941 positionFindBarViewAtMaxY:output.findBarMaxY
942 maxWidth:NSWidth(output.contentAreaFrame)];
944 [exclusiveAccessBubbleWindowController_
945 positionInWindowAtTop:output.fullscreenExitButtonMaxY
946 width:NSWidth(output.contentAreaFrame)];
949 - (void)updateSubviewZOrder {
950 if ([self isInAnyFullscreenMode])
951 [self updateSubviewZOrderFullscreen];
953 [self updateSubviewZOrderNormal];
956 - (void)updateSubviewZOrderNormal {
957 base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
958 if ([downloadShelfController_ view])
959 [subviews addObject:[downloadShelfController_ view]];
960 if ([bookmarkBarController_ view])
961 [subviews addObject:[bookmarkBarController_ view]];
962 if ([toolbarController_ view])
963 [subviews addObject:[toolbarController_ view]];
964 if ([infoBarContainerController_ view])
965 [subviews addObject:[infoBarContainerController_ view]];
966 if ([self tabContentArea])
967 [subviews addObject:[self tabContentArea]];
968 if ([findBarCocoaController_ view])
969 [subviews addObject:[findBarCocoaController_ view]];
971 [self setContentViewSubviews:subviews];
974 - (void)updateSubviewZOrderFullscreen {
975 base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
976 if ([downloadShelfController_ view])
977 [subviews addObject:[downloadShelfController_ view]];
978 if ([self tabContentArea])
979 [subviews addObject:[self tabContentArea]];
980 if ([self placeBookmarkBarBelowInfoBar]) {
981 if ([bookmarkBarController_ view])
982 [subviews addObject:[bookmarkBarController_ view]];
983 if (floatingBarBackingView_)
984 [subviews addObject:floatingBarBackingView_];
986 if (floatingBarBackingView_)
987 [subviews addObject:floatingBarBackingView_];
988 if ([bookmarkBarController_ view])
989 [subviews addObject:[bookmarkBarController_ view]];
991 if ([toolbarController_ view])
992 [subviews addObject:[toolbarController_ view]];
993 if ([infoBarContainerController_ view])
994 [subviews addObject:[infoBarContainerController_ view]];
995 if ([findBarCocoaController_ view])
996 [subviews addObject:[findBarCocoaController_ view]];
998 [self setContentViewSubviews:subviews];
1001 - (void)setContentViewSubviews:(NSArray*)subviews {
1002 // Subviews already match.
1003 if ([[self.chromeContentView subviews] isEqual:subviews])
1006 // The tabContentArea isn't a subview, so just set all the subviews.
1007 NSView* tabContentArea = [self tabContentArea];
1008 if (![[self.chromeContentView subviews] containsObject:tabContentArea]) {
1009 [self.chromeContentView setSubviews:subviews];
1013 // Remove all subviews that aren't the tabContentArea.
1014 for (NSView* view in [[[self.chromeContentView subviews] copy] autorelease]) {
1015 if (view != tabContentArea)
1016 [view removeFromSuperview];
1019 // Add in the subviews below the tabContentArea.
1020 NSInteger index = [subviews indexOfObject:tabContentArea];
1021 for (int i = index - 1; i >= 0; --i) {
1022 NSView* view = [subviews objectAtIndex:i];
1023 [self.chromeContentView addSubview:view
1024 positioned:NSWindowBelow
1028 // Add in the subviews above the tabContentArea.
1029 for (NSUInteger i = index + 1; i < [subviews count]; ++i) {
1030 NSView* view = [subviews objectAtIndex:i];
1031 [self.chromeContentView addSubview:view
1032 positioned:NSWindowAbove
1037 + (BOOL)systemSettingsRequireMavericksAppKitFullscreenHack {
1038 if (!base::mac::IsOSMavericks())
1040 return [NSScreen respondsToSelector:@selector(screensHaveSeparateSpaces)] &&
1041 [NSScreen screensHaveSeparateSpaces];
1044 - (BOOL)shouldUseMavericksAppKitFullscreenHack {
1045 if (![[self class] systemSettingsRequireMavericksAppKitFullscreenHack])
1047 if (!enteringAppKitFullscreen_)
1049 if (enteringAppKitFullscreenOnPrimaryScreen_)
1055 - (BOOL)shouldUseCustomAppKitFullscreenTransition {
1056 if (base::mac::IsOSMountainLionOrEarlier())
1059 NSView* root = [[self.window contentView] superview];
1063 // AppKit on OSX 10.9 has a bug for applications linked against OSX 10.8 SDK
1064 // and earlier. Under specific circumstances, it prevents the custom AppKit
1065 // transition from working well. See http://crbug.com/396980 for more
1067 if ([[self class] systemSettingsRequireMavericksAppKitFullscreenHack] &&
1068 ![[[self window] screen] isEqual:[[NSScreen screens] objectAtIndex:0]]) {
1075 - (NSArray*)customWindowsToEnterFullScreenForWindow:(NSWindow*)window {
1076 DCHECK([window isEqual:self.window]);
1078 if (![self shouldUseCustomAppKitFullscreenTransition])
1081 enterFullscreenTransition_.reset(
1082 [[BrowserWindowEnterFullscreenTransition alloc]
1083 initWithWindow:self.window]);
1084 return [enterFullscreenTransition_ customWindowsToEnterFullScreen];
1087 - (void)window:(NSWindow*)window
1088 startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration {
1089 DCHECK([window isEqual:self.window]);
1090 [enterFullscreenTransition_
1091 startCustomAnimationToEnterFullScreenWithDuration:duration];
1094 - (BOOL)shouldConstrainFrameRect {
1095 if ([enterFullscreenTransition_ shouldWindowBeUnconstrained])
1098 return [super shouldConstrainFrameRect];
1101 @end // @implementation BrowserWindowController(Private)