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_NONE;
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];
683 enteringAppKitFullscreen_ = YES;
684 enteringAppKitFullscreenOnPrimaryScreen_ =
685 [[[self window] screen] isEqual:[[NSScreen screens] objectAtIndex:0]];
687 fullscreen_mac::SlidingStyle style;
688 if (browser_->exclusive_access_manager()
689 ->fullscreen_controller()
690 ->IsWindowFullscreenForTabOrPending()) {
691 style = fullscreen_mac::OMNIBOX_TABS_NONE;
692 } else if (enteringPresentationMode_) {
693 style = fullscreen_mac::OMNIBOX_TABS_HIDDEN;
695 style = fullscreen_mac::OMNIBOX_TABS_PRESENT;
698 [self adjustUIForSlidingFullscreenStyle:style];
701 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
702 fullscreenTransition_.reset();
704 // In Yosemite, some combination of the titlebar and toolbar always show in
705 // full-screen mode. We do not want either to show. Search for the window that
706 // contains the views, and hide it. There is no need to ever unhide the view.
707 // http://crbug.com/380235
708 if (base::mac::IsOSYosemiteOrLater()) {
709 for (NSWindow* window in [[NSApplication sharedApplication] windows]) {
711 isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
712 [[window contentView] setHidden:YES];
717 if ([self shouldUseMavericksAppKitFullscreenHack]) {
718 // Apply a hack to fix the size of the window. This is the last run of the
719 // MessageLoop where the hack will not work, so dispatch the hack to the
720 // top of the MessageLoop.
721 base::Callback<void(void)> callback = base::BindBlock(^{
722 if (![self isInAppKitFullscreen])
725 // The window's frame should be exactly 22 points too short.
726 CGFloat kExpectedHeightDifference = 22;
727 NSRect currentFrame = [[self window] frame];
728 NSRect expectedFrame = [[[self window] screen] frame];
729 if (!NSEqualPoints(currentFrame.origin, expectedFrame.origin))
731 if (currentFrame.size.width != expectedFrame.size.width)
733 CGFloat heightDelta =
734 expectedFrame.size.height - currentFrame.size.height;
735 if (fabs(heightDelta - kExpectedHeightDifference) > 0.01)
738 [[self window] setFrame:expectedFrame display:YES];
740 base::MessageLoop::current()->PostTask(FROM_HERE, callback);
743 if (notification) // For System Fullscreen when non-nil.
744 [self deregisterForContentViewResizeNotifications];
745 enteringAppKitFullscreen_ = NO;
746 enteringImmersiveFullscreen_ = NO;
747 enteringPresentationMode_ = NO;
749 [self showFullscreenExitBubbleIfNecessary];
750 browser_->WindowFullscreenStateChanged();
753 - (void)windowWillExitFullScreen:(NSNotification*)notification {
754 if (notification) // For System Fullscreen when non-nil.
755 [self registerForContentViewResizeNotifications];
756 exitingAppKitFullscreen_ = YES;
758 [self destroyFullscreenExitBubbleIfNecessary];
759 [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
762 - (void)windowDidExitFullScreen:(NSNotification*)notification {
763 DCHECK(exitingAppKitFullscreen_);
765 if (notification) // For System Fullscreen when non-nil.
766 [self deregisterForContentViewResizeNotifications];
768 // Since the content view was forcefully resized during the transition, we
769 // want to ensure that the subviews are layout correctly after it ended.
770 [self layoutSubviews];
771 browser_->WindowFullscreenStateChanged();
773 exitingAppKitFullscreen_ = NO;
774 fullscreenTransition_.reset();
777 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
778 [self deregisterForContentViewResizeNotifications];
779 enteringAppKitFullscreen_ = NO;
780 fullscreenTransition_.reset();
781 [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
784 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
785 [self deregisterForContentViewResizeNotifications];
786 exitingAppKitFullscreen_ = NO;
787 fullscreenTransition_.reset();
788 // Force a relayout to try and get the window back into a reasonable state.
789 [self layoutSubviews];
792 - (void)enableBarVisibilityUpdates {
793 // Early escape if there's nothing to do.
794 if (barVisibilityUpdatesEnabled_)
797 barVisibilityUpdatesEnabled_ = YES;
799 if ([barVisibilityLocks_ count])
800 [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
802 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
805 - (void)disableBarVisibilityUpdates {
806 // Early escape if there's nothing to do.
807 if (!barVisibilityUpdatesEnabled_)
810 barVisibilityUpdatesEnabled_ = NO;
811 [presentationModeController_ cancelAnimationAndTimers];
814 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
815 if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
817 [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
821 - (CGFloat)toolbarDividerOpacity {
822 return [bookmarkBarController_ toolbarDividerOpacity];
825 - (void)updateInfoBarTipVisibility {
826 // If there's no toolbar then hide the infobar tip.
827 [infoBarContainerController_
828 setShouldSuppressTopInfoBarTip:![self hasToolbar]];
831 - (NSInteger)pageInfoBubblePointY {
832 LocationBarViewMac* locationBarView = [self locationBarBridge];
834 // The point, in window coordinates.
835 NSPoint iconBottom = locationBarView->GetPageInfoBubblePoint();
837 // The toolbar, in window coordinates.
838 NSView* toolbar = [toolbarController_ view];
839 CGFloat toolbarY = NSMinY([toolbar convertRect:[toolbar bounds] toView:nil]);
841 return iconBottom.y - toolbarY;
844 - (void)enterAppKitFullscreen {
845 DCHECK(base::mac::IsOSLionOrLater());
846 if (FramedBrowserWindow* framedBrowserWindow =
847 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
848 [framedBrowserWindow toggleSystemFullScreen];
852 - (void)exitAppKitFullscreen {
853 DCHECK(base::mac::IsOSLionOrLater());
854 if (FramedBrowserWindow* framedBrowserWindow =
855 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
856 [framedBrowserWindow toggleSystemFullScreen];
860 - (NSRect)fullscreenButtonFrame {
861 // NSWindowFullScreenButton is 10.7+ and results in log spam on 10.6 if used.
862 if (base::mac::IsOSSnowLeopard())
865 NSButton* fullscreenButton =
866 [[self window] standardWindowButton:NSWindowFullScreenButton];
867 if (!fullscreenButton)
870 NSRect buttonFrame = [fullscreenButton frame];
872 // When called from -windowWillExitFullScreen:, the button's frame may not
873 // be updated yet to match the new window size.
874 // We need to return where the button should be positioned.
875 NSView* rootView = [[[self window] contentView] superview];
876 if ([rootView respondsToSelector:@selector(_fullScreenButtonOrigin)])
877 buttonFrame.origin = [rootView _fullScreenButtonOrigin];
882 - (void)updateLayoutParameters:(BrowserWindowLayout*)layout {
883 [layout setContentViewSize:[[[self window] contentView] bounds].size];
885 NSSize windowSize = (fullscreenTransition_.get())
886 ? [fullscreenTransition_ desiredWindowLayoutSize]
887 : [[self window] frame].size;
889 [layout setWindowSize:windowSize];
891 [layout setInAnyFullscreen:[self isInAnyFullscreenMode]];
892 [layout setFullscreenSlidingStyle:
893 presentationModeController_.get().slidingStyle];
894 [layout setFullscreenMenubarOffset:
895 [presentationModeController_ menubarOffset]];
896 [layout setFullscreenToolbarFraction:
897 [presentationModeController_ toolbarFraction]];
899 [layout setHasTabStrip:[self hasTabStrip]];
900 [layout setFullscreenButtonFrame:[self fullscreenButtonFrame]];
902 if ([self shouldShowAvatar]) {
903 NSView* avatar = [avatarButtonController_ view];
904 [layout setShouldShowAvatar:YES];
905 [layout setShouldUseNewAvatar:[self shouldUseNewAvatarButton]];
906 [layout setAvatarSize:[avatar frame].size];
907 [layout setAvatarLineWidth:[[avatar superview] cr_lineWidth]];
910 [layout setHasToolbar:[self hasToolbar]];
911 [layout setToolbarHeight:NSHeight([[toolbarController_ view] bounds])];
913 [layout setHasLocationBar:[self hasLocationBar]];
915 [layout setPlaceBookmarkBarBelowInfoBar:[self placeBookmarkBarBelowInfoBar]];
916 [layout setBookmarkBarHidden:[bookmarkBarController_ view].isHidden];
917 [layout setBookmarkBarHeight:
918 NSHeight([[bookmarkBarController_ view] bounds])];
920 [layout setInfoBarHeight:[infoBarContainerController_ heightOfInfoBars]];
921 [layout setPageInfoBubblePointY:[self pageInfoBubblePointY]];
923 [layout setHasDownloadShelf:(downloadShelfController_.get() != nil)];
924 [layout setDownloadShelfHeight:
925 NSHeight([[downloadShelfController_ view] bounds])];
928 - (void)applyLayout:(BrowserWindowLayout*)layout {
929 chrome::LayoutOutput output = [layout computeLayout];
931 if (!NSIsEmptyRect(output.tabStripLayout.frame))
932 [self applyTabStripLayout:output.tabStripLayout];
934 if (!NSIsEmptyRect(output.toolbarFrame))
935 [[toolbarController_ view] setFrame:output.toolbarFrame];
937 if (!NSIsEmptyRect(output.bookmarkFrame)) {
938 NSView* bookmarkBarView = [bookmarkBarController_ view];
939 [bookmarkBarView setFrame:output.bookmarkFrame];
941 // Pin the bookmark bar to the top of the window and make the width
943 [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
945 [bookmarkBarController_ layoutSubviews];
948 // The info bar is never hidden. Sometimes it has zero effective height.
949 [[infoBarContainerController_ view] setFrame:output.infoBarFrame];
950 [infoBarContainerController_
951 setMaxTopArrowHeight:output.infoBarMaxTopArrowHeight];
952 [infoBarContainerController_
953 setInfobarArrowX:[self locationBarBridge]->GetPageInfoBubblePoint().x];
955 if (!NSIsEmptyRect(output.downloadShelfFrame))
956 [[downloadShelfController_ view] setFrame:output.downloadShelfFrame];
958 [self layoutTabContentArea:output.contentAreaFrame];
960 if (!NSIsEmptyRect(output.fullscreenBackingBarFrame)) {
961 [floatingBarBackingView_ setFrame:output.fullscreenBackingBarFrame];
962 [presentationModeController_
963 overlayFrameChanged:output.fullscreenBackingBarFrame];
966 [findBarCocoaController_
967 positionFindBarViewAtMaxY:output.findBarMaxY
968 maxWidth:NSWidth(output.contentAreaFrame)];
970 [exclusiveAccessBubbleWindowController_
971 positionInWindowAtTop:output.fullscreenExitButtonMaxY
972 width:NSWidth(output.contentAreaFrame)];
975 - (void)updateSubviewZOrder {
976 if ([self isInAnyFullscreenMode])
977 [self updateSubviewZOrderFullscreen];
979 [self updateSubviewZOrderNormal];
982 - (void)updateSubviewZOrderNormal {
983 base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
984 if ([downloadShelfController_ view])
985 [subviews addObject:[downloadShelfController_ view]];
986 if ([bookmarkBarController_ view])
987 [subviews addObject:[bookmarkBarController_ view]];
988 if ([toolbarController_ view])
989 [subviews addObject:[toolbarController_ view]];
990 if ([infoBarContainerController_ view])
991 [subviews addObject:[infoBarContainerController_ view]];
992 if ([self tabContentArea])
993 [subviews addObject:[self tabContentArea]];
994 if ([findBarCocoaController_ view])
995 [subviews addObject:[findBarCocoaController_ view]];
997 [self setContentViewSubviews:subviews];
1000 - (void)updateSubviewZOrderFullscreen {
1001 base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
1002 if ([downloadShelfController_ view])
1003 [subviews addObject:[downloadShelfController_ view]];
1004 if ([self tabContentArea])
1005 [subviews addObject:[self tabContentArea]];
1006 if ([self placeBookmarkBarBelowInfoBar]) {
1007 if ([bookmarkBarController_ view])
1008 [subviews addObject:[bookmarkBarController_ view]];
1009 if (floatingBarBackingView_)
1010 [subviews addObject:floatingBarBackingView_];
1012 if (floatingBarBackingView_)
1013 [subviews addObject:floatingBarBackingView_];
1014 if ([bookmarkBarController_ view])
1015 [subviews addObject:[bookmarkBarController_ view]];
1017 if ([toolbarController_ view])
1018 [subviews addObject:[toolbarController_ view]];
1019 if ([infoBarContainerController_ view])
1020 [subviews addObject:[infoBarContainerController_ view]];
1021 if ([findBarCocoaController_ view])
1022 [subviews addObject:[findBarCocoaController_ view]];
1024 [self setContentViewSubviews:subviews];
1027 - (void)setContentViewSubviews:(NSArray*)subviews {
1028 // Subviews already match.
1029 if ([[self.chromeContentView subviews] isEqual:subviews])
1032 // The tabContentArea isn't a subview, so just set all the subviews.
1033 NSView* tabContentArea = [self tabContentArea];
1034 if (![[self.chromeContentView subviews] containsObject:tabContentArea]) {
1035 [self.chromeContentView setSubviews:subviews];
1039 // Remove all subviews that aren't the tabContentArea.
1040 for (NSView* view in [[[self.chromeContentView subviews] copy] autorelease]) {
1041 if (view != tabContentArea)
1042 [view removeFromSuperview];
1045 // Add in the subviews below the tabContentArea.
1046 NSInteger index = [subviews indexOfObject:tabContentArea];
1047 for (int i = index - 1; i >= 0; --i) {
1048 NSView* view = [subviews objectAtIndex:i];
1049 [self.chromeContentView addSubview:view
1050 positioned:NSWindowBelow
1054 // Add in the subviews above the tabContentArea.
1055 for (NSUInteger i = index + 1; i < [subviews count]; ++i) {
1056 NSView* view = [subviews objectAtIndex:i];
1057 [self.chromeContentView addSubview:view
1058 positioned:NSWindowAbove
1063 + (BOOL)systemSettingsRequireMavericksAppKitFullscreenHack {
1064 if (!base::mac::IsOSMavericks())
1066 return [NSScreen respondsToSelector:@selector(screensHaveSeparateSpaces)] &&
1067 [NSScreen screensHaveSeparateSpaces];
1070 - (BOOL)shouldUseMavericksAppKitFullscreenHack {
1071 if (![[self class] systemSettingsRequireMavericksAppKitFullscreenHack])
1073 if (!enteringAppKitFullscreen_)
1075 if (enteringAppKitFullscreenOnPrimaryScreen_)
1081 - (BOOL)shouldUseCustomAppKitFullscreenTransition:(BOOL)enterFullScreen {
1082 // We are temporary disabling exit fullscreen animation because it only
1083 // works on OSX 10.10.
1084 // TODO(spqchan): Fix exit fullscreen animation so that it works on all
1086 if (!enterFullScreen)
1089 if (base::mac::IsOSMountainLionOrEarlier())
1092 NSView* root = [[self.window contentView] superview];
1096 // AppKit on OSX 10.9 has a bug for applications linked against OSX 10.8 SDK
1097 // and earlier. Under specific circumstances, it prevents the custom AppKit
1098 // transition from working well. See http://crbug.com/396980 for more
1100 if ([[self class] systemSettingsRequireMavericksAppKitFullscreenHack] &&
1101 ![[[self window] screen] isEqual:[[NSScreen screens] objectAtIndex:0]]) {
1108 - (NSArray*)customWindowsToEnterFullScreenForWindow:(NSWindow*)window {
1109 DCHECK([window isEqual:self.window]);
1111 if (![self shouldUseCustomAppKitFullscreenTransition:YES])
1114 FramedBrowserWindow* framedBrowserWindow =
1115 base::mac::ObjCCast<FramedBrowserWindow>([self window]);
1116 fullscreenTransition_.reset([[BrowserWindowFullscreenTransition alloc]
1117 initEnterWithWindow:framedBrowserWindow]);
1118 return [fullscreenTransition_ customWindowsForFullScreenTransition];
1121 - (NSArray*)customWindowsToExitFullScreenForWindow:(NSWindow*)window {
1122 DCHECK([window isEqual:self.window]);
1124 if (![self shouldUseCustomAppKitFullscreenTransition:NO])
1127 FramedBrowserWindow* framedBrowserWindow =
1128 base::mac::ObjCCast<FramedBrowserWindow>([self window]);
1129 fullscreenTransition_.reset([[BrowserWindowFullscreenTransition alloc]
1130 initExitWithWindow:framedBrowserWindow
1131 frame:savedRegularWindowFrame_]);
1133 return [fullscreenTransition_ customWindowsForFullScreenTransition];
1136 - (void)window:(NSWindow*)window
1137 startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration {
1138 DCHECK([window isEqual:self.window]);
1139 [fullscreenTransition_ startCustomFullScreenAnimationWithDuration:duration];
1142 - (void)window:(NSWindow*)window
1143 startCustomAnimationToExitFullScreenWithDuration:(NSTimeInterval)duration {
1144 DCHECK([window isEqual:self.window]);
1146 [fullscreenTransition_ startCustomFullScreenAnimationWithDuration:duration];
1149 - (BOOL)shouldConstrainFrameRect {
1150 if ([fullscreenTransition_ shouldWindowBeUnconstrained])
1153 return [super shouldConstrainFrameRect];
1156 - (WebContents*)webContents {
1157 return browser_->tab_strip_model()->GetActiveWebContents();
1160 - (PermissionBubbleManager*)permissionBubbleManager {
1161 if (WebContents* contents = [self webContents])
1162 return PermissionBubbleManager::FromWebContents(contents);
1166 @end // @implementation BrowserWindowController(Private)