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_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/browser/ui/website_settings/permission_bubble_manager.h"
47 #include "chrome/common/chrome_switches.h"
48 #include "content/public/browser/render_widget_host_view.h"
49 #include "content/public/browser/web_contents.h"
50 #import "ui/base/cocoa/focus_tracker.h"
51 #import "ui/base/cocoa/nsview_additions.h"
52 #include "ui/base/ui_base_types.h"
54 using content::RenderWidgetHostView;
55 using content::WebContents;
59 // The screen on which the window was fullscreened, and whether the device had
60 // multiple screens available.
62 PRIMARY_SINGLE_SCREEN = 0,
63 PRIMARY_MULTIPLE_SCREEN = 1,
64 SECONDARY_MULTIPLE_SCREEN = 2,
65 WINDOW_LOCATION_COUNT = 3
68 // There are 2 mechanisms for invoking fullscreen: AppKit and Immersive.
69 // There are 2 types of AppKit Fullscreen: Presentation Mode and Canonical
71 enum FullscreenStyle {
72 IMMERSIVE_FULLSCREEN = 0,
73 PRESENTATION_MODE = 1,
74 CANONICAL_FULLSCREEN = 2,
75 FULLSCREEN_STYLE_COUNT = 3
78 // Emits a histogram entry indicating the Fullscreen window location.
79 void RecordFullscreenWindowLocation(NSWindow* window) {
80 NSArray* screens = [NSScreen screens];
81 bool primary_screen = ([[window screen] isEqual:[screens objectAtIndex:0]]);
82 bool multiple_screens = [screens count] > 1;
84 WindowLocation location = PRIMARY_SINGLE_SCREEN;
85 if (multiple_screens) {
87 primary_screen ? PRIMARY_MULTIPLE_SCREEN : SECONDARY_MULTIPLE_SCREEN;
90 UMA_HISTOGRAM_ENUMERATION(
91 "OSX.Fullscreen.Enter.WindowLocation", location, WINDOW_LOCATION_COUNT);
94 // Emits a histogram entry indicating the Fullscreen style.
95 void RecordFullscreenStyle(FullscreenStyle style) {
96 UMA_HISTOGRAM_ENUMERATION(
97 "OSX.Fullscreen.Enter.Style", style, FULLSCREEN_STYLE_COUNT);
102 @implementation BrowserWindowController(Private)
104 // Create the tab strip controller.
105 - (void)createTabStripController {
106 DCHECK([overlayableContentsController_ activeContainer]);
107 DCHECK([[overlayableContentsController_ activeContainer] window]);
108 tabStripController_.reset([[TabStripController alloc]
109 initWithView:[self tabStripView]
110 switchView:[overlayableContentsController_ activeContainer]
111 browser:browser_.get()
115 - (void)saveWindowPositionIfNeeded {
116 if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
119 // If we're in fullscreen mode, save the position of the regular window
122 [self isInAnyFullscreenMode] ? savedRegularWindow_ : [self window];
124 // Window positions are stored relative to the origin of the primary monitor.
125 NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
126 NSScreen* windowScreen = [window screen];
128 // Start with the window's frame, which is in virtual coordinates.
129 // Do some y twiddling to flip the coordinate system.
130 gfx::Rect bounds(NSRectToCGRect([window frame]));
131 bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
133 // Browser::SaveWindowPlacement saves information for session restore.
134 ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
135 if ([window isMiniaturized])
136 show_state = ui::SHOW_STATE_MINIMIZED;
137 else if ([self isInAnyFullscreenMode])
138 show_state = ui::SHOW_STATE_FULLSCREEN;
139 chrome::SaveWindowPlacement(browser_.get(), bounds, show_state);
141 // |windowScreen| can be nil (for example, if the monitor arrangement was
142 // changed while in fullscreen mode). If we see a nil screen, return without
144 // TODO(rohitrao): We should just not save anything for fullscreen windows.
145 // http://crbug.com/36479.
149 // Only save main window information to preferences.
150 PrefService* prefs = browser_->profile()->GetPrefs();
151 if (!prefs || browser_ != chrome::GetLastActiveBrowser())
154 // Save the current work area, in flipped coordinates.
155 gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
156 workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
158 scoped_ptr<DictionaryPrefUpdate> update =
159 chrome::GetWindowPlacementDictionaryReadWrite(
160 chrome::GetWindowName(browser_.get()),
161 browser_->profile()->GetPrefs());
162 base::DictionaryValue* windowPreferences = update->Get();
163 windowPreferences->SetInteger("left", bounds.x());
164 windowPreferences->SetInteger("top", bounds.y());
165 windowPreferences->SetInteger("right", bounds.right());
166 windowPreferences->SetInteger("bottom", bounds.bottom());
167 windowPreferences->SetBoolean("maximized", false);
168 windowPreferences->SetBoolean("always_on_top", false);
169 windowPreferences->SetInteger("work_area_left", workArea.x());
170 windowPreferences->SetInteger("work_area_top", workArea.y());
171 windowPreferences->SetInteger("work_area_right", workArea.right());
172 windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
175 - (NSRect)window:(NSWindow*)window
176 willPositionSheet:(NSWindow*)sheet
177 usingRect:(NSRect)defaultSheetLocation {
178 // Position the sheet as follows:
179 // - If the bookmark bar is shown (attached to the normal toolbar), position
180 // the sheet below the bookmark bar.
181 // - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
182 // bookmark bar is disabled), position the sheet immediately below the
184 // - If the bookmark bar is currently animating, position the sheet according
185 // to where the bar will be when the animation ends.
186 CGFloat defaultSheetY = defaultSheetLocation.origin.y;
187 if ([self supportsBookmarkBar] &&
188 [bookmarkBarController_ currentState] == BookmarkBar::SHOW) {
189 defaultSheetY = NSMinY([[bookmarkBarController_ view] frame]);
190 } else if ([self hasToolbar]) {
191 defaultSheetY = NSMinY([[toolbarController_ view] frame]);
193 // The toolbar is not shown in popup and application modes. The sheet
194 // should be located at the top of the window, under the title of the
196 defaultSheetY = NSMaxY([[window contentView] frame]);
199 // AppKit may shift the window up to fit the sheet on screen, but it will
200 // never adjust the height of the sheet, or the origin of the sheet relative
201 // to the window. Adjust the origin to prevent sheets from extending past the
202 // bottom of the screen.
204 // Don't allow the sheet to extend past the bottom of the window. This logic
205 // intentionally ignores the size of the screens, since the window might span
206 // multiple screens, and AppKit may reposition the window.
207 CGFloat sheetHeight = NSHeight([sheet frame]);
208 defaultSheetY = std::max(defaultSheetY, sheetHeight);
210 // It doesn't make sense to provide a Y higher than the height of the window.
211 CGFloat windowHeight = NSHeight([window frame]);
212 defaultSheetY = std::min(defaultSheetY, windowHeight);
214 defaultSheetLocation.origin.y = defaultSheetY;
215 return defaultSheetLocation;
218 - (void)layoutSubviews {
219 // Suppress title drawing if necessary.
220 if ([self.window respondsToSelector:@selector(setShouldHideTitle:)])
221 [(id)self.window setShouldHideTitle:![self hasTitleBar]];
223 [bookmarkBarController_ updateHiddenState];
224 [self updateSubviewZOrder];
226 base::scoped_nsobject<BrowserWindowLayout> layout(
227 [[BrowserWindowLayout alloc] init]);
228 [self updateLayoutParameters:layout];
229 [self applyLayout:layout];
231 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
233 // Will update the location of the permission bubble when showing/hiding the
234 // top level toolbar in fullscreen.
235 PermissionBubbleManager* manager = [self permissionBubbleManager];
237 manager->UpdateAnchorPosition();
239 browser_->GetBubbleManager()->UpdateAllBubbleAnchors();
242 - (void)applyTabStripLayout:(const chrome::TabStripLayout&)layout {
243 // Update the presence of the window controls.
244 if (layout.addCustomWindowControls)
245 [tabStripController_ addCustomWindowControls];
247 [tabStripController_ removeCustomWindowControls];
249 // Update the layout of the avatar.
250 if (!NSIsEmptyRect(layout.avatarFrame)) {
251 NSView* avatarButton = [avatarButtonController_ view];
252 [avatarButton setFrame:layout.avatarFrame];
253 [avatarButton setHidden:NO];
256 // Check if the tab strip's frame has changed.
257 BOOL requiresRelayout =
258 !NSEqualRects([[self tabStripView] frame], layout.frame);
260 // Check if the left indent has changed.
261 if (layout.leftIndent != [tabStripController_ leftIndentForControls]) {
262 [tabStripController_ setLeftIndentForControls:layout.leftIndent];
263 requiresRelayout = YES;
266 // Check if the right indent has changed.
267 if (layout.rightIndent != [tabStripController_ rightIndentForControls]) {
268 [tabStripController_ setRightIndentForControls:layout.rightIndent];
269 requiresRelayout = YES;
272 // It is undesirable to force tabs relayout when the tap strip's frame did
273 // not change, because it will interrupt tab animations in progress.
274 // In addition, there appears to be an AppKit bug on <10.9 where interrupting
275 // a tab animation resulted in the tab frame being the animator's target
276 // frame instead of the interrupting setFrame. (See http://crbug.com/415093)
277 if (requiresRelayout) {
278 [[self tabStripView] setFrame:layout.frame];
279 [tabStripController_ layoutTabsWithoutAnimation];
283 - (BOOL)placeBookmarkBarBelowInfoBar {
284 // If we are currently displaying the NTP detached bookmark bar or animating
285 // to/from it (from/to anything else), we display the bookmark bar below the
287 return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
288 [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
289 [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
292 - (void)layoutTabContentArea:(NSRect)newFrame {
293 NSView* tabContentView = [self tabContentArea];
294 NSRect tabContentFrame = [tabContentView frame];
296 bool contentShifted =
297 NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
298 NSMinX(tabContentFrame) != NSMinX(newFrame);
300 tabContentFrame = newFrame;
301 [tabContentView setFrame:tabContentFrame];
303 // If the relayout shifts the content area up or down, let the renderer know.
304 if (contentShifted) {
305 WebContents* contents = [self webContents];
307 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 // Disable autoresizing of subviews while we move views around. This prevents
359 // spurious renderer resizes.
360 [self.chromeContentView setAutoresizesSubviews:NO];
361 [self.chromeContentView removeFromSuperview];
363 // Have to do this here, otherwise later calls can crash because the window
365 [sourceWindow setDelegate:nil];
366 [destWindow setDelegate:self];
368 // With this call, valgrind complains that a "Conditional jump or move depends
369 // on uninitialised value(s)". The error happens in -[NSThemeFrame
370 // drawOverlayRect:]. I'm pretty convinced this is an Apple bug, but there is
371 // no visual impact. I have been unable to tickle it away with other window
372 // or view manipulation Cocoa calls. Stack added to suppressions_mac.txt.
373 [self.chromeContentView setAutoresizesSubviews:YES];
374 [[destWindow contentView] addSubview:self.chromeContentView
375 positioned:NSWindowBelow
377 [self.chromeContentView setFrame:[[destWindow contentView] bounds]];
379 // Move the incognito badge if present.
380 if ([self shouldShowAvatar]) {
381 NSView* avatarButtonView = [avatarButtonController_ view];
383 [avatarButtonView removeFromSuperview];
384 [avatarButtonView setHidden:YES]; // Will be shown in layout.
385 [[destWindow contentView] addSubview:avatarButtonView];
388 // Add the tab strip after setting the content view and moving the incognito
389 // badge (if any), so that the tab strip will be on top (in the z-order).
390 if ([self hasTabStrip])
391 [[destWindow contentView] addSubview:tabStripView];
393 [sourceWindow setWindowController:nil];
394 [self setWindow:destWindow];
395 [destWindow setWindowController:self];
397 // Move the status bubble over, if we have one.
399 statusBubble_->SwitchParentWindow(destWindow);
401 // Updates the bubble position.
402 PermissionBubbleManager* manager = [self permissionBubbleManager];
404 manager->UpdateAnchorPosition();
406 // Move the title over.
407 [destWindow setTitle:[sourceWindow title]];
409 // The window needs to be onscreen before we can set its first responder.
410 // Ordering the window to the front can change the active Space (either to
411 // the window's old Space or to the application's assigned Space). To prevent
412 // this by temporarily change the collectionBehavior.
413 NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior];
414 [destWindow setCollectionBehavior:
415 NSWindowCollectionBehaviorMoveToActiveSpace];
416 [destWindow makeKeyAndOrderFront:self];
417 [destWindow setCollectionBehavior:behavior];
419 if (![focusTracker restoreFocusInWindow:destWindow]) {
420 // During certain types of fullscreen transitions, the view that had focus
421 // may have gone away (e.g., the one for a Flash FS widget). In this case,
422 // FocusTracker will fail to restore focus to anything, so we set the focus
423 // to the tab contents as a reasonable fall-back.
424 [self focusTabContents];
426 [sourceWindow orderOut:self];
428 // We're done moving focus, so re-enable bar visibility changes.
429 [self enableBarVisibilityUpdates];
432 - (void)permissionBubbleWindowWillClose:(NSNotification*)notification {
433 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
434 [center removeObserver:self
435 name:NSWindowWillCloseNotification
436 object:[notification object]];
437 [self releaseBarVisibilityForOwner:[notification object]
442 - (void)configurePresentationModeController {
443 BOOL fullscreen_for_tab = browser_->exclusive_access_manager()
444 ->fullscreen_controller()
445 ->IsWindowFullscreenForTabOrPending();
447 base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
449 !fullscreen_for_tab && !kiosk_mode && ([self floatingBarHasFocus]);
451 PermissionBubbleManager* manager = [self permissionBubbleManager];
452 if (manager && manager->IsBubbleVisible()) {
453 NSWindow* bubbleWindow = manager->GetBubbleWindow();
454 DCHECK(bubbleWindow);
455 // A visible permission bubble will force the dropdown to remain
457 [self lockBarVisibilityForOwner:bubbleWindow withAnimation:NO delay:NO];
459 // Register to be notified when the permission bubble is closed, to
460 // allow fullscreen to hide the dropdown.
461 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
462 [center addObserver:self
463 selector:@selector(permissionBubbleWindowWillClose:)
464 name:NSWindowWillCloseNotification
465 object:bubbleWindow];
468 NSView* contentView = [[self window] contentView];
469 [presentationModeController_
470 enterPresentationModeForContentView:contentView
471 showDropdown:showDropdown];
474 - (void)adjustUIForExitingFullscreenAndStopOmniboxSliding {
475 [presentationModeController_ exitPresentationMode];
476 presentationModeController_.reset();
478 // Force the bookmark bar z-order to update.
479 [[bookmarkBarController_ view] removeFromSuperview];
480 [self layoutSubviews];
483 - (void)adjustUIForSlidingFullscreenStyle:(fullscreen_mac::SlidingStyle)style {
484 if (!presentationModeController_) {
485 presentationModeController_.reset(
486 [self newPresentationModeControllerWithStyle:style]);
487 [self configurePresentationModeController];
489 presentationModeController_.get().slidingStyle = style;
492 if (!floatingBarBackingView_.get() &&
493 ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
494 floatingBarBackingView_.reset(
495 [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
496 [floatingBarBackingView_
497 setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
500 // Force the bookmark bar z-order to update.
501 [[bookmarkBarController_ view] removeFromSuperview];
502 [self layoutSubviews];
505 - (PresentationModeController*)newPresentationModeControllerWithStyle:
506 (fullscreen_mac::SlidingStyle)style {
507 return [[PresentationModeController alloc] initWithBrowserController:self
511 - (void)enterImmersiveFullscreen {
512 RecordFullscreenWindowLocation([self window]);
513 RecordFullscreenStyle(IMMERSIVE_FULLSCREEN);
515 // Set to NO by |-windowDidEnterFullScreen:|.
516 enteringImmersiveFullscreen_ = YES;
519 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
520 Boolean didFadeOut = NO;
521 CGDisplayFadeReservationToken token;
522 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
523 == kCGErrorSuccess) {
525 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
526 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
529 // Create the fullscreen window.
530 fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
531 savedRegularWindow_ = [[self window] retain];
532 savedRegularWindowFrame_ = [savedRegularWindow_ frame];
534 [self moveViewsForImmersiveFullscreen:YES
535 regularWindow:[self window]
536 fullscreenWindow:fullscreenWindow_.get()];
538 fullscreen_mac::SlidingStyle style = fullscreen_mac::OMNIBOX_TABS_HIDDEN;
539 [self adjustUIForSlidingFullscreenStyle:style];
541 // AppKit is helpful and prevents NSWindows from having the same height as
542 // the screen while the menu bar is showing. This only applies to windows on
543 // a secondary screen, in a separate space. Calling [NSWindow
544 // setFrame:display:] with the screen's height will always reduce the
545 // height by the height of the MenuBar. Calling the method with any other
546 // height works fine. The relevant method in the 10.10 AppKit SDK is called:
547 // _canAdjustSizeForScreensHaveSeparateSpacesIfFillingSecondaryScreen
549 // TODO(erikchen): Refactor the logic to allow the window to be shown after
550 // the menubar has been hidden. This would remove the need for this hack.
551 // http://crbug.com/403203
552 NSRect frame = [[[self window] screen] frame];
553 if (!NSEqualRects(frame, [fullscreenWindow_ frame]))
554 [fullscreenWindow_ setFrame:[[[self window] screen] frame] display:YES];
556 [self layoutSubviews];
558 [self windowDidEnterFullScreen:nil];
562 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
563 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
564 CGReleaseDisplayFadeReservation(token);
568 - (void)exitImmersiveFullscreen {
570 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
571 Boolean didFadeOut = NO;
572 CGDisplayFadeReservationToken token;
573 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
574 == kCGErrorSuccess) {
576 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
577 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
580 [self windowWillExitFullScreen:nil];
582 [self moveViewsForImmersiveFullscreen:NO
583 regularWindow:savedRegularWindow_
584 fullscreenWindow:fullscreenWindow_.get()];
586 // When exiting fullscreen mode, we need to call layoutSubviews manually.
587 [savedRegularWindow_ autorelease];
588 savedRegularWindow_ = nil;
590 // No close event is thrown when a window is dealloc'd after orderOut.
591 // Explicitly close the window to notify bubbles.
592 [fullscreenWindow_.get() close];
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 (exclusiveAccessBubbleType_ == EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE ||
619 exclusiveAccessBubbleType_ ==
620 EXCLUSIVE_ACCESS_BUBBLE_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) {
621 // Show no exit instruction bubble on Mac when in Browser Fullscreen.
622 [self destroyFullscreenExitBubbleIfNecessary];
624 [exclusiveAccessBubbleWindowController_ closeImmediately];
625 exclusiveAccessBubbleWindowController_.reset(
626 [[ExclusiveAccessBubbleWindowController alloc]
628 exclusive_access_manager:browser_.get()->exclusive_access_manager()
629 profile:browser_.get()->profile()
631 bubbleType:exclusiveAccessBubbleType_]);
632 [exclusiveAccessBubbleWindowController_ showWindow];
636 - (void)destroyFullscreenExitBubbleIfNecessary {
637 [exclusiveAccessBubbleWindowController_ closeImmediately];
638 exclusiveAccessBubbleWindowController_.reset();
641 - (void)contentViewDidResize:(NSNotification*)notification {
642 [self layoutSubviews];
645 - (void)registerForContentViewResizeNotifications {
646 [[NSNotificationCenter defaultCenter]
648 selector:@selector(contentViewDidResize:)
649 name:NSViewFrameDidChangeNotification
650 object:[[self window] contentView]];
653 - (void)deregisterForContentViewResizeNotifications {
654 [[NSNotificationCenter defaultCenter]
656 name:NSViewFrameDidChangeNotification
657 object:[[self window] contentView]];
660 - (NSSize)window:(NSWindow*)window
661 willUseFullScreenContentSize:(NSSize)proposedSize {
665 - (NSApplicationPresentationOptions)window:(NSWindow*)window
666 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
668 NSApplicationPresentationAutoHideDock |
669 NSApplicationPresentationAutoHideMenuBar);
672 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
673 RecordFullscreenWindowLocation([self window]);
674 RecordFullscreenStyle(enteringPresentationMode_ ? PRESENTATION_MODE
675 : CANONICAL_FULLSCREEN);
677 if (notification) // For System Fullscreen when non-nil.
678 [self registerForContentViewResizeNotifications];
680 NSWindow* window = [self window];
681 savedRegularWindowFrame_ = [window frame];
682 BOOL mode = enteringPresentationMode_ ||
683 browser_->exclusive_access_manager()
684 ->fullscreen_controller()
685 ->IsWindowFullscreenForTabOrPending();
686 enteringAppKitFullscreen_ = YES;
687 enteringAppKitFullscreenOnPrimaryScreen_ =
688 [[[self window] screen] isEqual:[[NSScreen screens] objectAtIndex:0]];
690 fullscreen_mac::SlidingStyle style =
691 mode ? fullscreen_mac::OMNIBOX_TABS_HIDDEN
692 : fullscreen_mac::OMNIBOX_TABS_PRESENT;
694 [self adjustUIForSlidingFullscreenStyle:style];
697 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
698 fullscreenTransition_.reset();
700 // In Yosemite, some combination of the titlebar and toolbar always show in
701 // full-screen mode. We do not want either to show. Search for the window that
702 // contains the views, and hide it. There is no need to ever unhide the view.
703 // http://crbug.com/380235
704 if (base::mac::IsOSYosemiteOrLater()) {
705 for (NSWindow* window in [[NSApplication sharedApplication] windows]) {
707 isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
708 [[window contentView] setHidden:YES];
713 if ([self shouldUseMavericksAppKitFullscreenHack]) {
714 // Apply a hack to fix the size of the window. This is the last run of the
715 // MessageLoop where the hack will not work, so dispatch the hack to the
716 // top of the MessageLoop.
717 base::Callback<void(void)> callback = base::BindBlock(^{
718 if (![self isInAppKitFullscreen])
721 // The window's frame should be exactly 22 points too short.
722 CGFloat kExpectedHeightDifference = 22;
723 NSRect currentFrame = [[self window] frame];
724 NSRect expectedFrame = [[[self window] screen] frame];
725 if (!NSEqualPoints(currentFrame.origin, expectedFrame.origin))
727 if (currentFrame.size.width != expectedFrame.size.width)
729 CGFloat heightDelta =
730 expectedFrame.size.height - currentFrame.size.height;
731 if (fabs(heightDelta - kExpectedHeightDifference) > 0.01)
734 [[self window] setFrame:expectedFrame display:YES];
736 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
739 if (notification) // For System Fullscreen when non-nil.
740 [self deregisterForContentViewResizeNotifications];
741 enteringAppKitFullscreen_ = NO;
742 enteringImmersiveFullscreen_ = NO;
743 enteringPresentationMode_ = NO;
745 [self showFullscreenExitBubbleIfNecessary];
746 browser_->WindowFullscreenStateChanged();
749 - (void)windowWillExitFullScreen:(NSNotification*)notification {
750 if (notification) // For System Fullscreen when non-nil.
751 [self registerForContentViewResizeNotifications];
752 exitingAppKitFullscreen_ = YES;
754 [self destroyFullscreenExitBubbleIfNecessary];
755 [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
758 - (void)windowDidExitFullScreen:(NSNotification*)notification {
759 DCHECK(exitingAppKitFullscreen_);
761 if (notification) // For System Fullscreen when non-nil.
762 [self deregisterForContentViewResizeNotifications];
764 // Since the content view was forcefully resized during the transition, we
765 // want to ensure that the subviews are layout correctly after it ended.
766 [self layoutSubviews];
767 browser_->WindowFullscreenStateChanged();
769 exitingAppKitFullscreen_ = NO;
770 fullscreenTransition_.reset();
773 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
774 [self deregisterForContentViewResizeNotifications];
775 enteringAppKitFullscreen_ = NO;
776 fullscreenTransition_.reset();
777 [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
780 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
781 [self deregisterForContentViewResizeNotifications];
782 exitingAppKitFullscreen_ = NO;
783 fullscreenTransition_.reset();
784 // Force a relayout to try and get the window back into a reasonable state.
785 [self layoutSubviews];
788 - (void)enableBarVisibilityUpdates {
789 // Early escape if there's nothing to do.
790 if (barVisibilityUpdatesEnabled_)
793 barVisibilityUpdatesEnabled_ = YES;
795 if ([barVisibilityLocks_ count])
796 [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
798 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
801 - (void)disableBarVisibilityUpdates {
802 // Early escape if there's nothing to do.
803 if (!barVisibilityUpdatesEnabled_)
806 barVisibilityUpdatesEnabled_ = NO;
807 [presentationModeController_ cancelAnimationAndTimers];
810 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
811 if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
813 [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
817 - (CGFloat)toolbarDividerOpacity {
818 return [bookmarkBarController_ toolbarDividerOpacity];
821 - (void)updateInfoBarTipVisibility {
822 // If there's no toolbar then hide the infobar tip.
823 [infoBarContainerController_
824 setShouldSuppressTopInfoBarTip:![self hasToolbar]];
827 - (NSInteger)pageInfoBubblePointY {
828 LocationBarViewMac* locationBarView = [self locationBarBridge];
830 // The point, in window coordinates.
831 NSPoint iconBottom = locationBarView->GetPageInfoBubblePoint();
833 // The toolbar, in window coordinates.
834 NSView* toolbar = [toolbarController_ view];
835 CGFloat toolbarY = NSMinY([toolbar convertRect:[toolbar bounds] toView:nil]);
837 return iconBottom.y - toolbarY;
840 - (void)enterAppKitFullscreen {
841 DCHECK(base::mac::IsOSLionOrLater());
842 if (FramedBrowserWindow* framedBrowserWindow =
843 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
844 [framedBrowserWindow toggleSystemFullScreen];
848 - (void)exitAppKitFullscreen {
849 DCHECK(base::mac::IsOSLionOrLater());
850 if (FramedBrowserWindow* framedBrowserWindow =
851 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
852 [framedBrowserWindow toggleSystemFullScreen];
856 - (NSRect)fullscreenButtonFrame {
857 // NSWindowFullScreenButton is 10.7+ and results in log spam on 10.6 if used.
858 if (base::mac::IsOSSnowLeopard())
861 NSButton* fullscreenButton =
862 [[self window] standardWindowButton:NSWindowFullScreenButton];
863 if (!fullscreenButton)
866 NSRect buttonFrame = [fullscreenButton frame];
868 // When called from -windowWillExitFullScreen:, the button's frame may not
869 // be updated yet to match the new window size.
870 // We need to return where the button should be positioned.
871 NSView* rootView = [[[self window] contentView] superview];
872 if ([rootView respondsToSelector:@selector(_fullScreenButtonOrigin)])
873 buttonFrame.origin = [rootView _fullScreenButtonOrigin];
878 - (void)updateLayoutParameters:(BrowserWindowLayout*)layout {
879 [layout setContentViewSize:[[[self window] contentView] bounds].size];
881 NSSize windowSize = (fullscreenTransition_.get())
882 ? [fullscreenTransition_ desiredWindowLayoutSize]
883 : [[self window] frame].size;
885 [layout setWindowSize:windowSize];
887 [layout setInAnyFullscreen:[self isInAnyFullscreenMode]];
888 [layout setFullscreenSlidingStyle:
889 presentationModeController_.get().slidingStyle];
890 [layout setFullscreenMenubarOffset:
891 [presentationModeController_ menubarOffset]];
892 [layout setFullscreenToolbarFraction:
893 [presentationModeController_ toolbarFraction]];
895 [layout setHasTabStrip:[self hasTabStrip]];
896 [layout setFullscreenButtonFrame:[self fullscreenButtonFrame]];
898 if ([self shouldShowAvatar]) {
899 NSView* avatar = [avatarButtonController_ view];
900 [layout setShouldShowAvatar:YES];
901 [layout setShouldUseNewAvatar:[self shouldUseNewAvatarButton]];
902 [layout setAvatarSize:[avatar frame].size];
903 [layout setAvatarLineWidth:[[avatar superview] cr_lineWidth]];
906 [layout setHasToolbar:[self hasToolbar]];
907 [layout setToolbarHeight:NSHeight([[toolbarController_ view] bounds])];
909 [layout setHasLocationBar:[self hasLocationBar]];
911 [layout setPlaceBookmarkBarBelowInfoBar:[self placeBookmarkBarBelowInfoBar]];
912 [layout setBookmarkBarHidden:[bookmarkBarController_ view].isHidden];
913 [layout setBookmarkBarHeight:
914 NSHeight([[bookmarkBarController_ view] bounds])];
916 [layout setInfoBarHeight:[infoBarContainerController_ heightOfInfoBars]];
917 [layout setPageInfoBubblePointY:[self pageInfoBubblePointY]];
919 [layout setHasDownloadShelf:(downloadShelfController_.get() != nil)];
920 [layout setDownloadShelfHeight:
921 NSHeight([[downloadShelfController_ view] bounds])];
924 - (void)applyLayout:(BrowserWindowLayout*)layout {
925 chrome::LayoutOutput output = [layout computeLayout];
927 if (!NSIsEmptyRect(output.tabStripLayout.frame))
928 [self applyTabStripLayout:output.tabStripLayout];
930 if (!NSIsEmptyRect(output.toolbarFrame))
931 [[toolbarController_ view] setFrame:output.toolbarFrame];
933 if (!NSIsEmptyRect(output.bookmarkFrame)) {
934 NSView* bookmarkBarView = [bookmarkBarController_ view];
935 [bookmarkBarView setFrame:output.bookmarkFrame];
937 // Pin the bookmark bar to the top of the window and make the width
939 [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
941 [bookmarkBarController_ layoutSubviews];
944 // The info bar is never hidden. Sometimes it has zero effective height.
945 [[infoBarContainerController_ view] setFrame:output.infoBarFrame];
946 [infoBarContainerController_
947 setMaxTopArrowHeight:output.infoBarMaxTopArrowHeight];
948 [infoBarContainerController_
949 setInfobarArrowX:[self locationBarBridge]->GetPageInfoBubblePoint().x];
951 if (!NSIsEmptyRect(output.downloadShelfFrame))
952 [[downloadShelfController_ view] setFrame:output.downloadShelfFrame];
954 [self layoutTabContentArea:output.contentAreaFrame];
956 if (!NSIsEmptyRect(output.fullscreenBackingBarFrame)) {
957 [floatingBarBackingView_ setFrame:output.fullscreenBackingBarFrame];
958 [presentationModeController_
959 overlayFrameChanged:output.fullscreenBackingBarFrame];
962 [findBarCocoaController_
963 positionFindBarViewAtMaxY:output.findBarMaxY
964 maxWidth:NSWidth(output.contentAreaFrame)];
966 [exclusiveAccessBubbleWindowController_
967 positionInWindowAtTop:output.fullscreenExitButtonMaxY
968 width:NSWidth(output.contentAreaFrame)];
971 - (void)updateSubviewZOrder {
972 if ([self isInAnyFullscreenMode])
973 [self updateSubviewZOrderFullscreen];
975 [self updateSubviewZOrderNormal];
978 - (void)updateSubviewZOrderNormal {
979 base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
980 if ([downloadShelfController_ view])
981 [subviews addObject:[downloadShelfController_ view]];
982 if ([bookmarkBarController_ view])
983 [subviews addObject:[bookmarkBarController_ view]];
984 if ([toolbarController_ view])
985 [subviews addObject:[toolbarController_ view]];
986 if ([infoBarContainerController_ view])
987 [subviews addObject:[infoBarContainerController_ view]];
988 if ([self tabContentArea])
989 [subviews addObject:[self tabContentArea]];
990 if ([findBarCocoaController_ view])
991 [subviews addObject:[findBarCocoaController_ view]];
993 [self setContentViewSubviews:subviews];
996 - (void)updateSubviewZOrderFullscreen {
997 base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
998 if ([downloadShelfController_ view])
999 [subviews addObject:[downloadShelfController_ view]];
1000 if ([self tabContentArea])
1001 [subviews addObject:[self tabContentArea]];
1002 if ([self placeBookmarkBarBelowInfoBar]) {
1003 if ([bookmarkBarController_ view])
1004 [subviews addObject:[bookmarkBarController_ view]];
1005 if (floatingBarBackingView_)
1006 [subviews addObject:floatingBarBackingView_];
1008 if (floatingBarBackingView_)
1009 [subviews addObject:floatingBarBackingView_];
1010 if ([bookmarkBarController_ view])
1011 [subviews addObject:[bookmarkBarController_ view]];
1013 if ([toolbarController_ view])
1014 [subviews addObject:[toolbarController_ view]];
1015 if ([infoBarContainerController_ view])
1016 [subviews addObject:[infoBarContainerController_ view]];
1017 if ([findBarCocoaController_ view])
1018 [subviews addObject:[findBarCocoaController_ view]];
1020 [self setContentViewSubviews:subviews];
1023 - (void)setContentViewSubviews:(NSArray*)subviews {
1024 // Subviews already match.
1025 if ([[self.chromeContentView subviews] isEqual:subviews])
1028 // The tabContentArea isn't a subview, so just set all the subviews.
1029 NSView* tabContentArea = [self tabContentArea];
1030 if (![[self.chromeContentView subviews] containsObject:tabContentArea]) {
1031 [self.chromeContentView setSubviews:subviews];
1035 // Remove all subviews that aren't the tabContentArea.
1036 for (NSView* view in [[[self.chromeContentView subviews] copy] autorelease]) {
1037 if (view != tabContentArea)
1038 [view removeFromSuperview];
1041 // Add in the subviews below the tabContentArea.
1042 NSInteger index = [subviews indexOfObject:tabContentArea];
1043 for (int i = index - 1; i >= 0; --i) {
1044 NSView* view = [subviews objectAtIndex:i];
1045 [self.chromeContentView addSubview:view
1046 positioned:NSWindowBelow
1050 // Add in the subviews above the tabContentArea.
1051 for (NSUInteger i = index + 1; i < [subviews count]; ++i) {
1052 NSView* view = [subviews objectAtIndex:i];
1053 [self.chromeContentView addSubview:view
1054 positioned:NSWindowAbove
1059 + (BOOL)systemSettingsRequireMavericksAppKitFullscreenHack {
1060 if (!base::mac::IsOSMavericks())
1062 return [NSScreen respondsToSelector:@selector(screensHaveSeparateSpaces)] &&
1063 [NSScreen screensHaveSeparateSpaces];
1066 - (BOOL)shouldUseMavericksAppKitFullscreenHack {
1067 if (![[self class] systemSettingsRequireMavericksAppKitFullscreenHack])
1069 if (!enteringAppKitFullscreen_)
1071 if (enteringAppKitFullscreenOnPrimaryScreen_)
1077 - (BOOL)shouldUseCustomAppKitFullscreenTransition {
1078 if (base::mac::IsOSMountainLionOrEarlier())
1081 NSView* root = [[self.window contentView] superview];
1085 // AppKit on OSX 10.9 has a bug for applications linked against OSX 10.8 SDK
1086 // and earlier. Under specific circumstances, it prevents the custom AppKit
1087 // transition from working well. See http://crbug.com/396980 for more
1089 if ([[self class] systemSettingsRequireMavericksAppKitFullscreenHack] &&
1090 ![[[self window] screen] isEqual:[[NSScreen screens] objectAtIndex:0]]) {
1097 - (NSArray*)customWindowsToEnterFullScreenForWindow:(NSWindow*)window {
1098 DCHECK([window isEqual:self.window]);
1100 if (![self shouldUseCustomAppKitFullscreenTransition])
1103 FramedBrowserWindow* framedBrowserWindow =
1104 base::mac::ObjCCast<FramedBrowserWindow>([self window]);
1105 fullscreenTransition_.reset([[BrowserWindowFullscreenTransition alloc]
1106 initEnterWithWindow:framedBrowserWindow]);
1107 return [fullscreenTransition_ customWindowsForFullScreenTransition];
1110 - (NSArray*)customWindowsToExitFullScreenForWindow:(NSWindow*)window {
1111 DCHECK([window isEqual:self.window]);
1113 if (![self shouldUseCustomAppKitFullscreenTransition])
1116 FramedBrowserWindow* framedBrowserWindow =
1117 base::mac::ObjCCast<FramedBrowserWindow>([self window]);
1118 fullscreenTransition_.reset([[BrowserWindowFullscreenTransition alloc]
1119 initExitWithWindow:framedBrowserWindow
1120 frame:savedRegularWindowFrame_]);
1122 return [fullscreenTransition_ customWindowsForFullScreenTransition];
1125 - (void)window:(NSWindow*)window
1126 startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration {
1127 DCHECK([window isEqual:self.window]);
1128 [fullscreenTransition_ startCustomFullScreenAnimationWithDuration:duration];
1131 - (void)window:(NSWindow*)window
1132 startCustomAnimationToExitFullScreenWithDuration:(NSTimeInterval)duration {
1133 DCHECK([window isEqual:self.window]);
1135 [fullscreenTransition_ startCustomFullScreenAnimationWithDuration:duration];
1138 - (BOOL)shouldConstrainFrameRect {
1139 if ([fullscreenTransition_ shouldWindowBeUnconstrained])
1142 return [super shouldConstrainFrameRect];
1145 - (WebContents*)webContents {
1146 return browser_->tab_strip_model()->GetActiveWebContents();
1149 - (PermissionBubbleManager*)permissionBubbleManager {
1150 if (WebContents* contents = [self webContents])
1151 return PermissionBubbleManager::FromWebContents(contents);
1155 @end // @implementation BrowserWindowController(Private)