1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
9 #include "base/command_line.h"
10 #include "base/mac/mac_util.h"
11 #import "base/mac/scoped_nsobject.h"
12 #import "base/mac/sdk_forward_declarations.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/fullscreen.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
19 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_window_state.h"
22 #import "chrome/browser/ui/cocoa/browser_window_layout.h"
23 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
24 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
25 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
26 #import "chrome/browser/ui/cocoa/floating_bar_backing_view.h"
27 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
28 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
29 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
30 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
31 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
32 #import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h"
33 #import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h"
34 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
35 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
36 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
37 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
38 #import "chrome/browser/ui/cocoa/version_independent_window.h"
39 #import "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h"
40 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
41 #include "chrome/browser/ui/tabs/tab_strip_model.h"
42 #include "chrome/common/chrome_switches.h"
43 #include "chrome/common/pref_names.h"
44 #include "content/public/browser/render_widget_host_view.h"
45 #include "content/public/browser/web_contents.h"
46 #import "ui/base/cocoa/focus_tracker.h"
47 #import "ui/base/cocoa/nsview_additions.h"
48 #include "ui/base/ui_base_types.h"
50 using content::RenderWidgetHostView;
51 using content::WebContents;
55 // Space between the incognito badge and the right edge of the window.
56 const CGFloat kAvatarRightOffset = 4;
58 // Space between the location bar and the right edge of the window, when there
59 // are no extension buttons present.
60 // When there is a fullscreen button to the right of the new style profile
61 // button, we align the profile button with the location bar (although it won't
62 // be aligned when there are extension buttons).
63 const CGFloat kLocationBarRightOffset = 35;
67 @implementation BrowserWindowController(Private)
69 // Create the tab strip controller.
70 - (void)createTabStripController {
71 DCHECK([overlayableContentsController_ activeContainer]);
72 DCHECK([[overlayableContentsController_ activeContainer] window]);
73 tabStripController_.reset([[TabStripController alloc]
74 initWithView:[self tabStripView]
75 switchView:[overlayableContentsController_ activeContainer]
76 browser:browser_.get()
80 - (void)saveWindowPositionIfNeeded {
81 if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
84 // If we're in fullscreen mode, save the position of the regular window
87 [self isInAnyFullscreenMode] ? savedRegularWindow_ : [self window];
89 // Window positions are stored relative to the origin of the primary monitor.
90 NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
91 NSScreen* windowScreen = [window screen];
93 // Start with the window's frame, which is in virtual coordinates.
94 // Do some y twiddling to flip the coordinate system.
95 gfx::Rect bounds(NSRectToCGRect([window frame]));
96 bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
98 // Browser::SaveWindowPlacement saves information for session restore.
99 ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
100 if ([window isMiniaturized])
101 show_state = ui::SHOW_STATE_MINIMIZED;
102 else if ([self isInAnyFullscreenMode])
103 show_state = ui::SHOW_STATE_FULLSCREEN;
104 chrome::SaveWindowPlacement(browser_.get(), bounds, show_state);
106 // |windowScreen| can be nil (for example, if the monitor arrangement was
107 // changed while in fullscreen mode). If we see a nil screen, return without
109 // TODO(rohitrao): We should just not save anything for fullscreen windows.
110 // http://crbug.com/36479.
114 // Only save main window information to preferences.
115 PrefService* prefs = browser_->profile()->GetPrefs();
116 if (!prefs || browser_ != chrome::GetLastActiveBrowser())
119 // Save the current work area, in flipped coordinates.
120 gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
121 workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
123 scoped_ptr<DictionaryPrefUpdate> update =
124 chrome::GetWindowPlacementDictionaryReadWrite(
125 chrome::GetWindowName(browser_.get()),
126 browser_->profile()->GetPrefs());
127 base::DictionaryValue* windowPreferences = update->Get();
128 windowPreferences->SetInteger("left", bounds.x());
129 windowPreferences->SetInteger("top", bounds.y());
130 windowPreferences->SetInteger("right", bounds.right());
131 windowPreferences->SetInteger("bottom", bounds.bottom());
132 windowPreferences->SetBoolean("maximized", false);
133 windowPreferences->SetBoolean("always_on_top", false);
134 windowPreferences->SetInteger("work_area_left", workArea.x());
135 windowPreferences->SetInteger("work_area_top", workArea.y());
136 windowPreferences->SetInteger("work_area_right", workArea.right());
137 windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
140 - (NSRect)window:(NSWindow*)window
141 willPositionSheet:(NSWindow*)sheet
142 usingRect:(NSRect)defaultSheetRect {
143 // Position the sheet as follows:
144 // - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
145 // bookmark bar is disabled), position the sheet immediately below the
147 // - If the bookmark bar is shown (attached to the normal toolbar), position
148 // the sheet below the bookmark bar.
149 // - If the bookmark bar is currently animating, position the sheet according
150 // to where the bar will be when the animation ends.
151 CGFloat defaultSheetY = defaultSheetRect.origin.y;
152 switch ([bookmarkBarController_ currentState]) {
153 case BookmarkBar::SHOW: {
154 NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame];
155 defaultSheetY = bookmarkBarFrame.origin.y;
158 case BookmarkBar::HIDDEN:
159 case BookmarkBar::DETACHED: {
160 if ([self hasToolbar]) {
161 NSRect toolbarFrame = [[toolbarController_ view] frame];
162 defaultSheetY = toolbarFrame.origin.y;
164 // The toolbar is not shown in application mode. The sheet should be
165 // located at the top of the window, under the title of the window.
166 defaultSheetY = NSHeight([[window contentView] frame]) -
167 defaultSheetRect.size.height;
173 // AppKit may shift the window up to fit the sheet on screen, but it will
174 // never adjust the height of the sheet, or the origin of the sheet relative
175 // to the window. Adjust the origin to prevent sheets from extending past the
176 // bottom of the screen.
178 // Don't allow the sheet to extend past the bottom of the window. This logic
179 // intentionally ignores the size of the screens, since the window might span
180 // multiple screens, and AppKit may reposition the window.
181 CGFloat sheetHeight = NSHeight([sheet frame]);
182 defaultSheetY = std::max(defaultSheetY, sheetHeight);
184 // It doesn't make sense to provide a Y higher than the height of the window.
185 CGFloat windowHeight = NSHeight([window frame]);
186 defaultSheetY = std::min(defaultSheetY, windowHeight);
188 defaultSheetRect.origin.y = defaultSheetY;
189 return defaultSheetRect;
192 - (void)layoutSubviews {
193 // Suppress title drawing if necessary.
194 if ([self.window respondsToSelector:@selector(setShouldHideTitle:)])
195 [(id)self.window setShouldHideTitle:![self hasTitleBar]];
197 [bookmarkBarController_ updateHiddenState];
198 [self updateSubviewZOrder];
200 base::scoped_nsobject<BrowserWindowLayout> layout(
201 [[BrowserWindowLayout alloc] init]);
202 [self updateLayoutParameters:layout];
203 [self applyLayout:layout];
205 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
208 - (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
210 fullscreen:(BOOL)fullscreen {
211 // Nothing to do if no tab strip.
212 if (![self hasTabStrip])
215 NSView* tabStripView = [self tabStripView];
216 CGFloat tabStripHeight = NSHeight([tabStripView frame]);
217 maxY -= tabStripHeight;
218 NSRect tabStripFrame = NSMakeRect(0, maxY, width, tabStripHeight);
219 BOOL requiresRelayout = !NSEqualRects(tabStripFrame, [tabStripView frame]);
221 // In Yosemite fullscreen, manually add the fullscreen controls to the tab
223 BOOL addControlsInFullscreen =
224 [self isInAppKitFullscreen] && base::mac::IsOSYosemiteOrLater();
226 // Set left indentation based on fullscreen mode status.
227 CGFloat leftIndent = 0;
228 if (!fullscreen || addControlsInFullscreen)
229 leftIndent = [[tabStripController_ class] defaultLeftIndentForControls];
230 if (leftIndent != [tabStripController_ leftIndentForControls]) {
231 [tabStripController_ setLeftIndentForControls:leftIndent];
232 requiresRelayout = YES;
235 if (addControlsInFullscreen)
236 [tabStripController_ addWindowControls];
238 [tabStripController_ removeWindowControls];
240 // fullScreenButton is non-nil when isInAnyFullscreenMode is NO, and OS
241 // version is in the range 10.7 <= version <= 10.9. Starting with 10.10, the
242 // zoom/maximize button acts as the fullscreen button.
243 NSButton* fullScreenButton =
244 [[self window] standardWindowButton:NSWindowFullScreenButton];
246 // Lay out the icognito/avatar badge because calculating the indentation on
247 // the right depends on it.
248 NSView* avatarButton = [avatarButtonController_ view];
249 if ([self shouldShowAvatar]) {
250 CGFloat badgeXOffset = -kAvatarRightOffset;
251 CGFloat badgeYOffset = 0;
252 CGFloat buttonHeight = NSHeight([avatarButton frame]);
254 if ([self shouldUseNewAvatarButton]) {
255 // The fullscreen icon is displayed to the right of the avatar button.
256 if (fullScreenButton)
257 badgeXOffset = -kLocationBarRightOffset;
258 // Center the button vertically on the tabstrip.
259 badgeYOffset = (tabStripHeight - buttonHeight) / 2;
261 // Actually place the badge *above* |maxY|, by +2 to miss the divider.
262 badgeYOffset = 2 * [[avatarButton superview] cr_lineWidth];
265 [avatarButton setFrameSize:NSMakeSize(NSWidth([avatarButton frame]),
266 std::min(buttonHeight, tabStripHeight))];
268 NSMakePoint(width - NSWidth([avatarButton frame]) + badgeXOffset,
269 maxY + badgeYOffset);
270 [avatarButton setFrameOrigin:origin];
271 [avatarButton setHidden:NO]; // Make sure it's shown.
274 // Calculate the right indentation.
275 // On 10.7 Lion to 10.9 Mavericks, there will be a fullscreen button when not
276 // in fullscreen mode.
277 // There may also be a profile button, which can be on the right of the
278 // fullscreen button (old style), or to its left (new style).
279 // The right indentation is calculated to prevent the tab strip from
280 // overlapping these buttons.
281 CGFloat maxX = width;
282 if (fullScreenButton) {
283 maxX = NSMinX([fullScreenButton frame]);
285 if ([self shouldShowAvatar]) {
286 maxX = std::min(maxX, NSMinX([avatarButton frame]));
288 CGFloat rightIndent = width - maxX;
289 if (rightIndent != [tabStripController_ rightIndentForControls]) {
290 [tabStripController_ setRightIndentForControls:rightIndent];
291 requiresRelayout = YES;
294 // It is undesirable to force tabs relayout when the tap strip's frame did
295 // not change, because it will interrupt tab animations in progress.
296 // In addition, there appears to be an AppKit bug on <10.9 where interrupting
297 // a tab animation resulted in the tab frame being the animator's target
298 // frame instead of the interrupting setFrame. (See http://crbug.com/415093)
299 if (requiresRelayout) {
300 [tabStripView setFrame:tabStripFrame];
301 [tabStripController_ layoutTabsWithoutAnimation];
307 - (BOOL)placeBookmarkBarBelowInfoBar {
308 // If we are currently displaying the NTP detached bookmark bar or animating
309 // to/from it (from/to anything else), we display the bookmark bar below the
311 return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
312 [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
313 [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
316 - (void)layoutTabContentArea:(NSRect)newFrame {
317 NSView* tabContentView = [self tabContentArea];
318 NSRect tabContentFrame = [tabContentView frame];
320 bool contentShifted =
321 NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
322 NSMinX(tabContentFrame) != NSMinX(newFrame);
324 tabContentFrame = newFrame;
325 [tabContentView setFrame:tabContentFrame];
327 // If the relayout shifts the content area up or down, let the renderer know.
328 if (contentShifted) {
329 if (WebContents* contents =
330 browser_->tab_strip_model()->GetActiveWebContents()) {
331 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
332 rwhv->WindowFrameChanged();
337 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
339 [toolbarController_ desiredHeightForCompression:compression];
340 NSRect toolbarFrame = [[toolbarController_ view] frame];
341 CGFloat deltaH = newHeight - toolbarFrame.size.height;
346 toolbarFrame.size.height = newHeight;
347 NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
348 bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
349 [[toolbarController_ view] setFrame:toolbarFrame];
350 [[bookmarkBarController_ view] setFrame:bookmarkFrame];
351 [self layoutSubviews];
354 // Fullscreen and presentation mode methods
356 - (void)moveViewsForImmersiveFullscreen:(BOOL)fullscreen
357 regularWindow:(NSWindow*)regularWindow
358 fullscreenWindow:(NSWindow*)fullscreenWindow {
359 NSWindow* sourceWindow = fullscreen ? regularWindow : fullscreenWindow;
360 NSWindow* destWindow = fullscreen ? fullscreenWindow : regularWindow;
362 // Close the bookmark bubble, if it's open. Use |-ok:| instead of |-cancel:|
363 // or |-close| because that matches the behavior when the bubble loses key
365 [bookmarkBubbleController_ ok:self];
367 // Save the current first responder so we can restore after views are moved.
368 base::scoped_nsobject<FocusTracker> focusTracker(
369 [[FocusTracker alloc] initWithWindow:sourceWindow]);
371 // While we move views (and focus) around, disable any bar visibility changes.
372 [self disableBarVisibilityUpdates];
374 // Retain the tab strip view while we remove it from its superview.
375 base::scoped_nsobject<NSView> tabStripView;
376 if ([self hasTabStrip]) {
377 tabStripView.reset([[self tabStripView] retain]);
378 [tabStripView removeFromSuperview];
381 // Ditto for the content view.
382 base::scoped_nsobject<NSView> contentView(
383 [[sourceWindow contentView] retain]);
384 // Disable autoresizing of subviews while we move views around. This prevents
385 // spurious renderer resizes.
386 [contentView setAutoresizesSubviews:NO];
387 [contentView removeFromSuperview];
389 // Have to do this here, otherwise later calls can crash because the window
391 [sourceWindow setDelegate:nil];
392 [destWindow setDelegate:self];
394 // With this call, valgrind complains that a "Conditional jump or move depends
395 // on uninitialised value(s)". The error happens in -[NSThemeFrame
396 // drawOverlayRect:]. I'm pretty convinced this is an Apple bug, but there is
397 // no visual impact. I have been unable to tickle it away with other window
398 // or view manipulation Cocoa calls. Stack added to suppressions_mac.txt.
399 [contentView setAutoresizesSubviews:YES];
400 [destWindow setContentView:contentView];
401 [self moveContentViewToBack:contentView];
403 // Move the incognito badge if present.
404 if ([self shouldShowAvatar]) {
405 NSView* avatarButtonView = [avatarButtonController_ view];
407 [avatarButtonView removeFromSuperview];
408 [avatarButtonView setHidden:YES]; // Will be shown in layout.
409 [[destWindow cr_windowView] addSubview:avatarButtonView];
412 // Add the tab strip after setting the content view and moving the incognito
413 // badge (if any), so that the tab strip will be on top (in the z-order).
414 if ([self hasTabStrip])
415 [self insertTabStripView:tabStripView intoWindow:[self window]];
417 [sourceWindow setWindowController:nil];
418 [self setWindow:destWindow];
419 [destWindow setWindowController:self];
421 // Move the status bubble over, if we have one.
423 statusBubble_->SwitchParentWindow(destWindow);
425 // Move the title over.
426 [destWindow setTitle:[sourceWindow title]];
428 // The window needs to be onscreen before we can set its first responder.
429 // Ordering the window to the front can change the active Space (either to
430 // the window's old Space or to the application's assigned Space). To prevent
431 // this by temporarily change the collectionBehavior.
432 NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior];
433 [destWindow setCollectionBehavior:
434 NSWindowCollectionBehaviorMoveToActiveSpace];
435 [destWindow makeKeyAndOrderFront:self];
436 [destWindow setCollectionBehavior:behavior];
438 if (![focusTracker restoreFocusInWindow:destWindow]) {
439 // During certain types of fullscreen transitions, the view that had focus
440 // may have gone away (e.g., the one for a Flash FS widget). In this case,
441 // FocusTracker will fail to restore focus to anything, so we set the focus
442 // to the tab contents as a reasonable fall-back.
443 [self focusTabContents];
445 [sourceWindow orderOut:self];
447 // We're done moving focus, so re-enable bar visibility changes.
448 [self enableBarVisibilityUpdates];
451 - (void)permissionBubbleWindowWillClose:(NSNotification*)notification {
452 DCHECK(permissionBubbleCocoa_);
454 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
455 [center removeObserver:self
456 name:NSWindowWillCloseNotification
457 object:[notification object]];
458 [self releaseBarVisibilityForOwner:[notification object]
463 - (void)configurePresentationModeController {
464 BOOL fullscreen_for_tab =
465 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
467 CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
469 !fullscreen_for_tab && !kiosk_mode && ([self floatingBarHasFocus]);
470 if (permissionBubbleCocoa_ && permissionBubbleCocoa_->IsVisible()) {
471 DCHECK(permissionBubbleCocoa_->window());
472 // A visible permission bubble will force the dropdown to remain visible.
473 [self lockBarVisibilityForOwner:permissionBubbleCocoa_->window()
477 // Register to be notified when the permission bubble is closed, to
478 // allow fullscreen to hide the dropdown.
479 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
480 [center addObserver:self
481 selector:@selector(permissionBubbleWindowWillClose:)
482 name:NSWindowWillCloseNotification
483 object:permissionBubbleCocoa_->window()];
486 // Turn on layered mode for the window's root view for the entry
487 // animation. Without this, the OS fullscreen animation for entering
488 // fullscreen mode does not correctly draw the tab strip.
489 // It will be turned off (set back to NO) when the animation finishes,
490 // in -windowDidEnterFullScreen:.
491 // Leaving wantsLayer on for the duration of presentation mode causes
492 // performance issues when the dropdown is animated in/out. It also does
493 // not seem to be required for the exit animation.
494 windowViewWantsLayer_ = [[[self window] cr_windowView] wantsLayer];
495 [[[self window] cr_windowView] setWantsLayer:YES];
498 NSView* contentView = [[self window] contentView];
499 [presentationModeController_
500 enterPresentationModeForContentView:contentView
501 showDropdown:showDropdown];
504 - (void)adjustUIForExitingFullscreenAndStopOmniboxSliding {
505 [presentationModeController_ exitPresentationMode];
506 presentationModeController_.reset();
508 // Force the bookmark bar z-order to update.
509 [[bookmarkBarController_ view] removeFromSuperview];
510 [self layoutSubviews];
513 - (void)adjustUIForSlidingFullscreenStyle:(fullscreen_mac::SlidingStyle)style {
514 if (!presentationModeController_) {
515 presentationModeController_.reset(
516 [self newPresentationModeControllerWithStyle:style]);
517 [self configurePresentationModeController];
519 presentationModeController_.get().slidingStyle = style;
522 if (!floatingBarBackingView_.get() &&
523 ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
524 floatingBarBackingView_.reset(
525 [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
526 [floatingBarBackingView_
527 setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
530 // Force the bookmark bar z-order to update.
531 [[bookmarkBarController_ view] removeFromSuperview];
532 [self layoutSubviews];
535 - (PresentationModeController*)newPresentationModeControllerWithStyle:
536 (fullscreen_mac::SlidingStyle)style {
537 return [[PresentationModeController alloc] initWithBrowserController:self
541 - (void)enterImmersiveFullscreen {
542 // Set to NO by |-windowDidEnterFullScreen:|.
543 enteringImmersiveFullscreen_ = YES;
546 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
547 Boolean didFadeOut = NO;
548 CGDisplayFadeReservationToken token;
549 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
550 == kCGErrorSuccess) {
552 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
553 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
556 // Create the fullscreen window.
557 fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
558 savedRegularWindow_ = [[self window] retain];
559 savedRegularWindowFrame_ = [savedRegularWindow_ frame];
561 [self moveViewsForImmersiveFullscreen:YES
562 regularWindow:[self window]
563 fullscreenWindow:fullscreenWindow_.get()];
565 fullscreen_mac::SlidingStyle style = fullscreen_mac::OMNIBOX_TABS_HIDDEN;
566 [self adjustUIForSlidingFullscreenStyle:style];
568 // AppKit is helpful and prevents NSWindows from having the same height as
569 // the screen while the menu bar is showing. This only applies to windows on
570 // a secondary screen, in a separate space. Calling [NSWindow
571 // setFrame:display:] with the screen's height will always reduce the
572 // height by the height of the MenuBar. Calling the method with any other
573 // height works fine. The relevant method in the 10.10 AppKit SDK is called:
574 // _canAdjustSizeForScreensHaveSeparateSpacesIfFillingSecondaryScreen
576 // TODO(erikchen): Refactor the logic to allow the window to be shown after
577 // the menubar has been hidden. This would remove the need for this hack.
578 // http://crbug.com/403203
579 NSRect frame = [[[self window] screen] frame];
580 if (!NSEqualRects(frame, [fullscreenWindow_ frame]))
581 [fullscreenWindow_ setFrame:[[[self window] screen] frame] display:YES];
583 [self layoutSubviews];
585 [self windowDidEnterFullScreen:nil];
589 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
590 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
591 CGReleaseDisplayFadeReservation(token);
595 - (void)exitImmersiveFullscreen {
597 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
598 Boolean didFadeOut = NO;
599 CGDisplayFadeReservationToken token;
600 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
601 == kCGErrorSuccess) {
603 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
604 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
607 [self windowWillExitFullScreen:nil];
609 [self moveViewsForImmersiveFullscreen:NO
610 regularWindow:savedRegularWindow_
611 fullscreenWindow:fullscreenWindow_.get()];
613 // When exiting fullscreen mode, we need to call layoutSubviews manually.
614 [savedRegularWindow_ autorelease];
615 savedRegularWindow_ = nil;
616 fullscreenWindow_.reset();
617 [self layoutSubviews];
619 [self windowDidExitFullScreen:nil];
623 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
624 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
625 CGReleaseDisplayFadeReservation(token);
629 - (void)showFullscreenExitBubbleIfNecessary {
630 // This method is called in response to
631 // |-updateFullscreenExitBubbleURL:bubbleType:|. If we're in the middle of the
632 // transition into fullscreen (i.e., using the AppKit Fullscreen API), do not
633 // show the bubble because it will cause visual jank
634 // (http://crbug.com/130649). This will be called again as part of
635 // |-windowDidEnterFullScreen:|, so arrange to do that work then instead.
636 if (enteringAppKitFullscreen_)
639 [self hideOverlayIfPossibleWithAnimation:NO delay:NO];
641 if (fullscreenBubbleType_ == FEB_TYPE_NONE ||
642 fullscreenBubbleType_ == FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) {
643 // Show no exit instruction bubble on Mac when in Browser Fullscreen.
644 [self destroyFullscreenExitBubbleIfNecessary];
646 [fullscreenExitBubbleController_ closeImmediately];
647 fullscreenExitBubbleController_.reset(
648 [[FullscreenExitBubbleController alloc]
650 browser:browser_.get()
652 bubbleType:fullscreenBubbleType_]);
653 [fullscreenExitBubbleController_ showWindow];
657 - (void)destroyFullscreenExitBubbleIfNecessary {
658 [fullscreenExitBubbleController_ closeImmediately];
659 fullscreenExitBubbleController_.reset();
662 - (void)contentViewDidResize:(NSNotification*)notification {
663 [self layoutSubviews];
666 - (void)registerForContentViewResizeNotifications {
667 [[NSNotificationCenter defaultCenter]
669 selector:@selector(contentViewDidResize:)
670 name:NSViewFrameDidChangeNotification
671 object:[[self window] contentView]];
674 - (void)deregisterForContentViewResizeNotifications {
675 [[NSNotificationCenter defaultCenter]
677 name:NSViewFrameDidChangeNotification
678 object:[[self window] contentView]];
681 - (NSSize)window:(NSWindow*)window
682 willUseFullScreenContentSize:(NSSize)proposedSize {
686 - (NSApplicationPresentationOptions)window:(NSWindow*)window
687 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
689 NSApplicationPresentationAutoHideDock |
690 NSApplicationPresentationAutoHideMenuBar);
693 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
694 if (notification) // For System Fullscreen when non-nil.
695 [self registerForContentViewResizeNotifications];
697 NSWindow* window = [self window];
698 savedRegularWindowFrame_ = [window frame];
699 BOOL mode = enteringPresentationMode_ ||
700 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
701 enteringAppKitFullscreen_ = YES;
703 fullscreen_mac::SlidingStyle style =
704 mode ? fullscreen_mac::OMNIBOX_TABS_HIDDEN
705 : fullscreen_mac::OMNIBOX_TABS_PRESENT;
707 [self adjustUIForSlidingFullscreenStyle:style];
710 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
711 // In Yosemite, some combination of the titlebar and toolbar always show in
712 // full-screen mode. We do not want either to show. Search for the window that
713 // contains the views, and hide it. There is no need to ever unhide the view.
714 // http://crbug.com/380235
715 if (base::mac::IsOSYosemiteOrLater()) {
716 for (NSWindow* window in [[NSApplication sharedApplication] windows]) {
718 isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
719 [window.contentView setHidden:YES];
724 if (notification) // For System Fullscreen when non-nil.
725 [self deregisterForContentViewResizeNotifications];
726 enteringAppKitFullscreen_ = NO;
727 enteringImmersiveFullscreen_ = NO;
728 enteringPresentationMode_ = NO;
730 [self showFullscreenExitBubbleIfNecessary];
731 browser_->WindowFullscreenStateChanged();
732 [[[self window] cr_windowView] setWantsLayer:windowViewWantsLayer_];
735 - (void)windowWillExitFullScreen:(NSNotification*)notification {
736 if (notification) // For System Fullscreen when non-nil.
737 [self registerForContentViewResizeNotifications];
738 [self destroyFullscreenExitBubbleIfNecessary];
739 [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
742 - (void)windowDidExitFullScreen:(NSNotification*)notification {
743 if (notification) // For System Fullscreen when non-nil.
744 [self deregisterForContentViewResizeNotifications];
745 browser_->WindowFullscreenStateChanged();
748 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
749 [self deregisterForContentViewResizeNotifications];
750 enteringAppKitFullscreen_ = NO;
751 [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
754 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
755 [self deregisterForContentViewResizeNotifications];
757 // Force a relayout to try and get the window back into a reasonable state.
758 [self layoutSubviews];
761 - (void)enableBarVisibilityUpdates {
762 // Early escape if there's nothing to do.
763 if (barVisibilityUpdatesEnabled_)
766 barVisibilityUpdatesEnabled_ = YES;
768 if ([barVisibilityLocks_ count])
769 [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
771 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
774 - (void)disableBarVisibilityUpdates {
775 // Early escape if there's nothing to do.
776 if (!barVisibilityUpdatesEnabled_)
779 barVisibilityUpdatesEnabled_ = NO;
780 [presentationModeController_ cancelAnimationAndTimers];
783 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
784 if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
786 [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
790 - (CGFloat)toolbarDividerOpacity {
791 return [bookmarkBarController_ toolbarDividerOpacity];
794 - (void)updateLayerOrdering:(NSView*)view {
795 // Hold a reference to the view so that it doesn't accidentally get
797 base::scoped_nsobject<NSView> reference([view retain]);
799 // If the superview has a layer, then this hack isn't required.
800 NSView* superview = [view superview];
801 if ([superview layer])
804 // Get the current position of the view.
805 NSArray* subviews = [superview subviews];
806 NSInteger index = [subviews indexOfObject:view];
807 NSView* siblingBelow = nil;
809 siblingBelow = [subviews objectAtIndex:index - 1];
812 [view removeFromSuperview];
814 // Add it to the same position.
816 [superview addSubview:view
817 positioned:NSWindowAbove
818 relativeTo:siblingBelow];
820 [superview addSubview:view
821 positioned:NSWindowBelow
826 - (void)updateInfoBarTipVisibility {
827 // If there's no toolbar then hide the infobar tip.
828 [infoBarContainerController_
829 setShouldSuppressTopInfoBarTip:![self hasToolbar]];
832 - (NSInteger)pageInfoBubblePointY {
833 LocationBarViewMac* locationBarView = [self locationBarBridge];
835 // The point, in window coordinates.
836 NSPoint iconBottom = locationBarView->GetPageInfoBubblePoint();
838 // The toolbar, in window coordinates.
839 NSView* toolbar = [toolbarController_ view];
840 CGFloat toolbarY = NSMinY([toolbar convertRect:[toolbar bounds] toView:nil]);
842 return iconBottom.y - toolbarY;
845 - (void)enterAppKitFullscreen {
846 DCHECK(base::mac::IsOSLionOrLater());
847 if (FramedBrowserWindow* framedBrowserWindow =
848 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
849 [framedBrowserWindow toggleSystemFullScreen];
853 - (void)exitAppKitFullscreen {
854 DCHECK(base::mac::IsOSLionOrLater());
855 if (FramedBrowserWindow* framedBrowserWindow =
856 base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
857 [framedBrowserWindow toggleSystemFullScreen];
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]];
875 [layout setHasToolbar:[self hasToolbar]];
876 [layout setToolbarHeight:NSHeight([[toolbarController_ view] bounds])];
878 [layout setHasLocationBar:[self hasLocationBar]];
880 [layout setPlaceBookmarkBarBelowInfoBar:[self placeBookmarkBarBelowInfoBar]];
881 [layout setBookmarkBarHidden:[bookmarkBarController_ view].isHidden];
882 [layout setBookmarkBarHeight:
883 NSHeight([[bookmarkBarController_ view] bounds])];
885 [layout setInfoBarHeight:[infoBarContainerController_ heightOfInfoBars]];
886 [layout setPageInfoBubblePointY:[self pageInfoBubblePointY]];
888 [layout setHasDownloadShelf:(downloadShelfController_.get() != nil)];
889 [layout setDownloadShelfHeight:
890 NSHeight([[downloadShelfController_ view] bounds])];
893 - (void)applyLayout:(BrowserWindowLayout*)layout {
894 chrome::LayoutOutput output = [layout computeLayout];
896 if (!NSIsEmptyRect(output.tabStripFrame)) {
897 // Note: The fullscreen parameter passed to the method is different from
898 // the field in |parameters| with the similar name.
899 [self layoutTabStripAtMaxY:NSMaxY(output.tabStripFrame)
900 width:NSWidth(output.tabStripFrame)
901 fullscreen:[self isInAnyFullscreenMode]];
904 if (!NSIsEmptyRect(output.toolbarFrame)) {
905 [[toolbarController_ view] setFrame:output.toolbarFrame];
908 if (!NSIsEmptyRect(output.bookmarkFrame)) {
909 NSView* bookmarkBarView = [bookmarkBarController_ view];
910 [bookmarkBarView setFrame:output.bookmarkFrame];
912 // Pin the bookmark bar to the top of the window and make the width
914 [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
916 [bookmarkBarController_ layoutSubviews];
919 // The info bar is never hidden. Sometimes it has zero effective height.
920 [[infoBarContainerController_ view] setFrame:output.infoBarFrame];
921 [infoBarContainerController_
922 setMaxTopArrowHeight:output.infoBarMaxTopArrowHeight];
924 if (!NSIsEmptyRect(output.downloadShelfFrame))
925 [[downloadShelfController_ view] setFrame:output.downloadShelfFrame];
927 [self layoutTabContentArea:output.contentAreaFrame];
929 if (!NSIsEmptyRect(output.fullscreenBackingBarFrame)) {
930 [floatingBarBackingView_ setFrame:output.fullscreenBackingBarFrame];
931 [presentationModeController_
932 overlayFrameChanged:output.fullscreenBackingBarFrame];
935 [findBarCocoaController_
936 positionFindBarViewAtMaxY:output.findBarMaxY
937 maxWidth:NSWidth(output.contentAreaFrame)];
939 [fullscreenExitBubbleController_
940 positionInWindowAtTop:output.fullscreenExitButtonMaxY
941 width:NSWidth(output.contentAreaFrame)];
944 - (void)updateSubviewZOrder {
945 if ([self isInAnyFullscreenMode])
946 [self updateSubviewZOrderFullscreen];
948 [self updateSubviewZOrderNormal];
950 [self updateSubviewZOrderHack];
953 - (void)updateSubviewZOrderNormal {
954 base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
955 if ([downloadShelfController_ view])
956 [subviews addObject:[downloadShelfController_ view]];
957 if ([bookmarkBarController_ view])
958 [subviews addObject:[bookmarkBarController_ view]];
959 if ([toolbarController_ view])
960 [subviews addObject:[toolbarController_ view]];
961 if ([infoBarContainerController_ view])
962 [subviews addObject:[infoBarContainerController_ view]];
963 if ([self tabContentArea])
964 [subviews addObject:[self tabContentArea]];
965 if ([findBarCocoaController_ view])
966 [subviews addObject:[findBarCocoaController_ view]];
968 [self setContentViewSubviews:subviews];
971 - (void)updateSubviewZOrderFullscreen {
972 base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
973 if ([downloadShelfController_ view])
974 [subviews addObject:[downloadShelfController_ view]];
975 if ([self tabContentArea])
976 [subviews addObject:[self tabContentArea]];
977 if ([self placeBookmarkBarBelowInfoBar]) {
978 if ([bookmarkBarController_ view])
979 [subviews addObject:[bookmarkBarController_ view]];
980 if (floatingBarBackingView_)
981 [subviews addObject:floatingBarBackingView_];
983 if (floatingBarBackingView_)
984 [subviews addObject:floatingBarBackingView_];
985 if ([bookmarkBarController_ view])
986 [subviews addObject:[bookmarkBarController_ view]];
988 if ([toolbarController_ view])
989 [subviews addObject:[toolbarController_ view]];
990 if ([infoBarContainerController_ view])
991 [subviews addObject:[infoBarContainerController_ view]];
992 if ([findBarCocoaController_ view])
993 [subviews addObject:[findBarCocoaController_ view]];
995 [self setContentViewSubviews:subviews];
998 - (void)setContentViewSubviews:(NSArray*)subviews {
999 // Subviews already match.
1000 if ([[self.window.contentView subviews] isEqual:subviews])
1003 // The tabContentArea isn't a subview, so just set all the subviews.
1004 NSView* tabContentArea = [self tabContentArea];
1005 if (![[self.window.contentView subviews] containsObject:tabContentArea]) {
1006 [self.window.contentView setSubviews:subviews];
1010 // Remove all subviews that aren't the tabContentArea.
1011 for (NSView* view in [[self.window.contentView subviews] copy]) {
1012 if (view != tabContentArea)
1013 [view removeFromSuperview];
1016 // Add in the subviews below the tabContentArea.
1017 NSInteger index = [subviews indexOfObject:tabContentArea];
1018 for (int i = index - 1; i >= 0; --i) {
1019 NSView* view = [subviews objectAtIndex:i];
1020 [self.window.contentView addSubview:view
1021 positioned:NSWindowBelow
1025 // Add in the subviews above the tabContentArea.
1026 for (NSUInteger i = index + 1; i < [subviews count]; ++i) {
1027 NSView* view = [subviews objectAtIndex:i];
1028 [self.window.contentView addSubview:view
1029 positioned:NSWindowAbove
1034 - (void)updateSubviewZOrderHack {
1035 // TODO(erikchen): Remove and then add the tabStripView to the root NSView.
1036 // This fixes a layer ordering problem that occurs between the contentView
1037 // and the tabStripView. This is a hack required because NSThemeFrame is not
1038 // layer backed, and because Chrome adds subviews directly to the
1040 // http://crbug.com/407921
1041 if (enteringAppKitFullscreen_) {
1042 // The tabstrip frequently lies outside the bounds of its superview.
1043 // Repeatedly adding/removing the tabstrip from its superview during the
1044 // AppKit Fullscreen transition causes graphical glitches on 10.10. The
1045 // correct solution is to use the AppKit fullscreen transition APIs added
1047 // http://crbug.com/408791
1048 if (!hasAdjustedTabStripWhileEnteringAppKitFullscreen_) {
1049 // Disable implicit animations.
1050 [CATransaction begin];
1051 [CATransaction setDisableActions:YES];
1053 [self updateLayerOrdering:[self tabStripView]];
1054 [self updateLayerOrdering:[avatarButtonController_ view]];
1056 [CATransaction commit];
1057 hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = YES;
1060 hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = NO;
1064 @end // @implementation BrowserWindowController(Private)