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. 4 5#import "chrome/browser/ui/cocoa/browser_window_controller_private.h" 6 7#include <cmath> 8 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" 49 50using content::RenderWidgetHostView; 51using content::WebContents; 52 53namespace { 54 55// Space between the incognito badge and the right edge of the window. 56const CGFloat kAvatarRightOffset = 4; 57 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). 63const CGFloat kLocationBarRightOffset = 35; 64 65} // namespace 66 67@implementation BrowserWindowController(Private) 68 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() 77 delegate:self]); 78} 79 80- (void)saveWindowPositionIfNeeded { 81 if (!chrome::ShouldSaveWindowPlacement(browser_.get())) 82 return; 83 84 // If we're in fullscreen mode, save the position of the regular window 85 // instead. 86 NSWindow* window = 87 [self isInAnyFullscreenMode] ? savedRegularWindow_ : [self window]; 88 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]; 92 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()); 97 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); 105 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 108 // saving. 109 // TODO(rohitrao): We should just not save anything for fullscreen windows. 110 // http://crbug.com/36479. 111 if (!windowScreen) 112 return; 113 114 // Only save main window information to preferences. 115 PrefService* prefs = browser_->profile()->GetPrefs(); 116 if (!prefs || browser_ != chrome::GetLastActiveBrowser()) 117 return; 118 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()); 122 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()); 138} 139 140- (NSRect)window:(NSWindow*)window 141willPositionSheet:(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 146 // normal toolbar. 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; 156 break; 157 } 158 case BookmarkBar::HIDDEN: 159 case BookmarkBar::DETACHED: { 160 if ([self hasToolbar]) { 161 NSRect toolbarFrame = [[toolbarController_ view] frame]; 162 defaultSheetY = toolbarFrame.origin.y; 163 } else { 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; 168 } 169 break; 170 } 171 } 172 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. 177 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); 183 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); 187 188 defaultSheetRect.origin.y = defaultSheetY; 189 return defaultSheetRect; 190} 191 192- (void)layoutSubviews { 193 // Suppress title drawing if necessary. 194 if ([self.window respondsToSelector:@selector(setShouldHideTitle:)]) 195 [(id)self.window setShouldHideTitle:![self hasTitleBar]]; 196 197 [bookmarkBarController_ updateHiddenState]; 198 [self updateSubviewZOrder]; 199 200 base::scoped_nsobject<BrowserWindowLayout> layout( 201 [[BrowserWindowLayout alloc] init]); 202 [self updateLayoutParameters:layout]; 203 [self applyLayout:layout]; 204 205 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]]; 206} 207 208- (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY 209 width:(CGFloat)width 210 fullscreen:(BOOL)fullscreen { 211 // Nothing to do if no tab strip. 212 if (![self hasTabStrip]) 213 return maxY; 214 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]); 220 221 // In Yosemite fullscreen, manually add the fullscreen controls to the tab 222 // strip. 223 BOOL addControlsInFullscreen = 224 [self isInAppKitFullscreen] && base::mac::IsOSYosemiteOrLater(); 225 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; 233 } 234 235 if (addControlsInFullscreen) 236 [tabStripController_ addWindowControls]; 237 else 238 [tabStripController_ removeWindowControls]; 239 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]; 245 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]); 253 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; 260 } else { 261 // Actually place the badge *above* |maxY|, by +2 to miss the divider. 262 badgeYOffset = 2 * [[avatarButton superview] cr_lineWidth]; 263 } 264 265 [avatarButton setFrameSize:NSMakeSize(NSWidth([avatarButton frame]), 266 std::min(buttonHeight, tabStripHeight))]; 267 NSPoint origin = 268 NSMakePoint(width - NSWidth([avatarButton frame]) + badgeXOffset, 269 maxY + badgeYOffset); 270 [avatarButton setFrameOrigin:origin]; 271 [avatarButton setHidden:NO]; // Make sure it's shown. 272 } 273 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]); 284 } 285 if ([self shouldShowAvatar]) { 286 maxX = std::min(maxX, NSMinX([avatarButton frame])); 287 } 288 CGFloat rightIndent = width - maxX; 289 if (rightIndent != [tabStripController_ rightIndentForControls]) { 290 [tabStripController_ setRightIndentForControls:rightIndent]; 291 requiresRelayout = YES; 292 } 293 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]; 302 } 303 304 return maxY; 305} 306 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 310 // info bar. 311 return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] || 312 [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] || 313 [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED]; 314} 315 316- (void)layoutTabContentArea:(NSRect)newFrame { 317 NSView* tabContentView = [self tabContentArea]; 318 NSRect tabContentFrame = [tabContentView frame]; 319 320 bool contentShifted = 321 NSMaxY(tabContentFrame) != NSMaxY(newFrame) || 322 NSMinX(tabContentFrame) != NSMinX(newFrame); 323 324 tabContentFrame = newFrame; 325 [tabContentView setFrame:tabContentFrame]; 326 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(); 333 } 334 } 335} 336 337- (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression { 338 CGFloat newHeight = 339 [toolbarController_ desiredHeightForCompression:compression]; 340 NSRect toolbarFrame = [[toolbarController_ view] frame]; 341 CGFloat deltaH = newHeight - toolbarFrame.size.height; 342 343 if (deltaH == 0) 344 return; 345 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]; 352} 353 354// Fullscreen and presentation mode methods 355 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; 361 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 364 // status. 365 [bookmarkBubbleController_ ok:self]; 366 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]); 370 371 // While we move views (and focus) around, disable any bar visibility changes. 372 [self disableBarVisibilityUpdates]; 373 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]; 379 } 380 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]; 388 389 // Have to do this here, otherwise later calls can crash because the window 390 // has no delegate. 391 [sourceWindow setDelegate:nil]; 392 [destWindow setDelegate:self]; 393 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]; 402 403 // Move the incognito badge if present. 404 if ([self shouldShowAvatar]) { 405 NSView* avatarButtonView = [avatarButtonController_ view]; 406 407 [avatarButtonView removeFromSuperview]; 408 [avatarButtonView setHidden:YES]; // Will be shown in layout. 409 [[destWindow cr_windowView] addSubview:avatarButtonView]; 410 } 411 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]]; 416 417 [sourceWindow setWindowController:nil]; 418 [self setWindow:destWindow]; 419 [destWindow setWindowController:self]; 420 421 // Move the status bubble over, if we have one. 422 if (statusBubble_) 423 statusBubble_->SwitchParentWindow(destWindow); 424 425 // Move the title over. 426 [destWindow setTitle:[sourceWindow title]]; 427 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]; 437 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]; 444 } 445 [sourceWindow orderOut:self]; 446 447 // We're done moving focus, so re-enable bar visibility changes. 448 [self enableBarVisibilityUpdates]; 449} 450 451- (void)permissionBubbleWindowWillClose:(NSNotification*)notification { 452 DCHECK(permissionBubbleCocoa_); 453 454 NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; 455 [center removeObserver:self 456 name:NSWindowWillCloseNotification 457 object:[notification object]]; 458 [self releaseBarVisibilityForOwner:[notification object] 459 withAnimation:YES 460 delay:YES]; 461} 462 463- (void)configurePresentationModeController { 464 BOOL fullscreen_for_tab = 465 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending(); 466 BOOL kiosk_mode = 467 CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode); 468 BOOL showDropdown = 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() 474 withAnimation:NO 475 delay:NO]; 476 showDropdown = YES; 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()]; 484 } 485 if (showDropdown) { 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]; 496 } 497 498 NSView* contentView = [[self window] contentView]; 499 [presentationModeController_ 500 enterPresentationModeForContentView:contentView 501 showDropdown:showDropdown]; 502} 503 504- (void)adjustUIForExitingFullscreenAndStopOmniboxSliding { 505 [presentationModeController_ exitPresentationMode]; 506 presentationModeController_.reset(); 507 508 // Force the bookmark bar z-order to update. 509 [[bookmarkBarController_ view] removeFromSuperview]; 510 [self layoutSubviews]; 511} 512 513- (void)adjustUIForSlidingFullscreenStyle:(fullscreen_mac::SlidingStyle)style { 514 if (!presentationModeController_) { 515 presentationModeController_.reset( 516 [self newPresentationModeControllerWithStyle:style]); 517 [self configurePresentationModeController]; 518 } else { 519 presentationModeController_.get().slidingStyle = style; 520 } 521 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)]; 528 } 529 530 // Force the bookmark bar z-order to update. 531 [[bookmarkBarController_ view] removeFromSuperview]; 532 [self layoutSubviews]; 533} 534 535- (PresentationModeController*)newPresentationModeControllerWithStyle: 536 (fullscreen_mac::SlidingStyle)style { 537 return [[PresentationModeController alloc] initWithBrowserController:self 538 style:style]; 539} 540 541- (void)enterImmersiveFullscreen { 542 // Set to NO by |-windowDidEnterFullScreen:|. 543 enteringImmersiveFullscreen_ = YES; 544 545 // Fade to black. 546 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6; 547 Boolean didFadeOut = NO; 548 CGDisplayFadeReservationToken token; 549 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) 550 == kCGErrorSuccess) { 551 didFadeOut = YES; 552 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal, 553 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true); 554 } 555 556 // Create the fullscreen window. 557 fullscreenWindow_.reset([[self createFullscreenWindow] retain]); 558 savedRegularWindow_ = [[self window] retain]; 559 savedRegularWindowFrame_ = [savedRegularWindow_ frame]; 560 561 [self moveViewsForImmersiveFullscreen:YES 562 regularWindow:[self window] 563 fullscreenWindow:fullscreenWindow_.get()]; 564 565 fullscreen_mac::SlidingStyle style = fullscreen_mac::OMNIBOX_TABS_HIDDEN; 566 [self adjustUIForSlidingFullscreenStyle:style]; 567 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 575 // 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]; 582 583 [self layoutSubviews]; 584 585 [self windowDidEnterFullScreen:nil]; 586 587 // Fade back in. 588 if (didFadeOut) { 589 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor, 590 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false); 591 CGReleaseDisplayFadeReservation(token); 592 } 593} 594 595- (void)exitImmersiveFullscreen { 596 // Fade to black. 597 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6; 598 Boolean didFadeOut = NO; 599 CGDisplayFadeReservationToken token; 600 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) 601 == kCGErrorSuccess) { 602 didFadeOut = YES; 603 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal, 604 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true); 605 } 606 607 [self windowWillExitFullScreen:nil]; 608 609 [self moveViewsForImmersiveFullscreen:NO 610 regularWindow:savedRegularWindow_ 611 fullscreenWindow:fullscreenWindow_.get()]; 612 613 // When exiting fullscreen mode, we need to call layoutSubviews manually. 614 [savedRegularWindow_ autorelease]; 615 savedRegularWindow_ = nil; 616 fullscreenWindow_.reset(); 617 [self layoutSubviews]; 618 619 [self windowDidExitFullScreen:nil]; 620 621 // Fade back in. 622 if (didFadeOut) { 623 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor, 624 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false); 625 CGReleaseDisplayFadeReservation(token); 626 } 627} 628 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_) 637 return; 638 639 [self hideOverlayIfPossibleWithAnimation:NO delay:NO]; 640 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]; 645 } else { 646 [fullscreenExitBubbleController_ closeImmediately]; 647 fullscreenExitBubbleController_.reset( 648 [[FullscreenExitBubbleController alloc] 649 initWithOwner:self 650 browser:browser_.get() 651 url:fullscreenUrl_ 652 bubbleType:fullscreenBubbleType_]); 653 [fullscreenExitBubbleController_ showWindow]; 654 } 655} 656 657- (void)destroyFullscreenExitBubbleIfNecessary { 658 [fullscreenExitBubbleController_ closeImmediately]; 659 fullscreenExitBubbleController_.reset(); 660} 661 662- (void)contentViewDidResize:(NSNotification*)notification { 663 [self layoutSubviews]; 664} 665 666- (void)registerForContentViewResizeNotifications { 667 [[NSNotificationCenter defaultCenter] 668 addObserver:self 669 selector:@selector(contentViewDidResize:) 670 name:NSViewFrameDidChangeNotification 671 object:[[self window] contentView]]; 672} 673 674- (void)deregisterForContentViewResizeNotifications { 675 [[NSNotificationCenter defaultCenter] 676 removeObserver:self 677 name:NSViewFrameDidChangeNotification 678 object:[[self window] contentView]]; 679} 680 681- (NSSize)window:(NSWindow*)window 682 willUseFullScreenContentSize:(NSSize)proposedSize { 683 return proposedSize; 684} 685 686- (NSApplicationPresentationOptions)window:(NSWindow*)window 687 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt { 688 return (opt | 689 NSApplicationPresentationAutoHideDock | 690 NSApplicationPresentationAutoHideMenuBar); 691} 692 693- (void)windowWillEnterFullScreen:(NSNotification*)notification { 694 if (notification) // For System Fullscreen when non-nil. 695 [self registerForContentViewResizeNotifications]; 696 697 NSWindow* window = [self window]; 698 savedRegularWindowFrame_ = [window frame]; 699 BOOL mode = enteringPresentationMode_ || 700 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending(); 701 enteringAppKitFullscreen_ = YES; 702 703 fullscreen_mac::SlidingStyle style = 704 mode ? fullscreen_mac::OMNIBOX_TABS_HIDDEN 705 : fullscreen_mac::OMNIBOX_TABS_PRESENT; 706 707 [self adjustUIForSlidingFullscreenStyle:style]; 708} 709 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]) { 717 if ([window 718 isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) { 719 [window.contentView setHidden:YES]; 720 } 721 } 722 } 723 724 if (notification) // For System Fullscreen when non-nil. 725 [self deregisterForContentViewResizeNotifications]; 726 enteringAppKitFullscreen_ = NO; 727 enteringImmersiveFullscreen_ = NO; 728 enteringPresentationMode_ = NO; 729 730 [self showFullscreenExitBubbleIfNecessary]; 731 browser_->WindowFullscreenStateChanged(); 732 [[[self window] cr_windowView] setWantsLayer:windowViewWantsLayer_]; 733} 734 735- (void)windowWillExitFullScreen:(NSNotification*)notification { 736 if (notification) // For System Fullscreen when non-nil. 737 [self registerForContentViewResizeNotifications]; 738 [self destroyFullscreenExitBubbleIfNecessary]; 739 [self adjustUIForExitingFullscreenAndStopOmniboxSliding]; 740} 741 742- (void)windowDidExitFullScreen:(NSNotification*)notification { 743 if (notification) // For System Fullscreen when non-nil. 744 [self deregisterForContentViewResizeNotifications]; 745 browser_->WindowFullscreenStateChanged(); 746} 747 748- (void)windowDidFailToEnterFullScreen:(NSWindow*)window { 749 [self deregisterForContentViewResizeNotifications]; 750 enteringAppKitFullscreen_ = NO; 751 [self adjustUIForExitingFullscreenAndStopOmniboxSliding]; 752} 753 754- (void)windowDidFailToExitFullScreen:(NSWindow*)window { 755 [self deregisterForContentViewResizeNotifications]; 756 757 // Force a relayout to try and get the window back into a reasonable state. 758 [self layoutSubviews]; 759} 760 761- (void)enableBarVisibilityUpdates { 762 // Early escape if there's nothing to do. 763 if (barVisibilityUpdatesEnabled_) 764 return; 765 766 barVisibilityUpdatesEnabled_ = YES; 767 768 if ([barVisibilityLocks_ count]) 769 [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO]; 770 else 771 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO]; 772} 773 774- (void)disableBarVisibilityUpdates { 775 // Early escape if there's nothing to do. 776 if (!barVisibilityUpdatesEnabled_) 777 return; 778 779 barVisibilityUpdatesEnabled_ = NO; 780 [presentationModeController_ cancelAnimationAndTimers]; 781} 782 783- (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay { 784 if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count]) 785 return; 786 [presentationModeController_ ensureOverlayHiddenWithAnimation:animation 787 delay:delay]; 788} 789 790- (CGFloat)toolbarDividerOpacity { 791 return [bookmarkBarController_ toolbarDividerOpacity]; 792} 793 794- (void)updateLayerOrdering:(NSView*)view { 795 // Hold a reference to the view so that it doesn't accidentally get 796 // dealloc'ed. 797 base::scoped_nsobject<NSView> reference([view retain]); 798 799 // If the superview has a layer, then this hack isn't required. 800 NSView* superview = [view superview]; 801 if ([superview layer]) 802 return; 803 804 // Get the current position of the view. 805 NSArray* subviews = [superview subviews]; 806 NSInteger index = [subviews indexOfObject:view]; 807 NSView* siblingBelow = nil; 808 if (index > 0) 809 siblingBelow = [subviews objectAtIndex:index - 1]; 810 811 // Remove the view. 812 [view removeFromSuperview]; 813 814 // Add it to the same position. 815 if (siblingBelow) { 816 [superview addSubview:view 817 positioned:NSWindowAbove 818 relativeTo:siblingBelow]; 819 } else { 820 [superview addSubview:view 821 positioned:NSWindowBelow 822 relativeTo:nil]; 823 } 824} 825 826- (void)updateInfoBarTipVisibility { 827 // If there's no toolbar then hide the infobar tip. 828 [infoBarContainerController_ 829 setShouldSuppressTopInfoBarTip:![self hasToolbar]]; 830} 831 832- (NSInteger)pageInfoBubblePointY { 833 LocationBarViewMac* locationBarView = [self locationBarBridge]; 834 835 // The point, in window coordinates. 836 NSPoint iconBottom = locationBarView->GetPageInfoBubblePoint(); 837 838 // The toolbar, in window coordinates. 839 NSView* toolbar = [toolbarController_ view]; 840 CGFloat toolbarY = NSMinY([toolbar convertRect:[toolbar bounds] toView:nil]); 841 842 return iconBottom.y - toolbarY; 843} 844 845- (void)enterAppKitFullscreen { 846 DCHECK(base::mac::IsOSLionOrLater()); 847 if (FramedBrowserWindow* framedBrowserWindow = 848 base::mac::ObjCCast<FramedBrowserWindow>([self window])) { 849 [framedBrowserWindow toggleSystemFullScreen]; 850 } 851} 852 853- (void)exitAppKitFullscreen { 854 DCHECK(base::mac::IsOSLionOrLater()); 855 if (FramedBrowserWindow* framedBrowserWindow = 856 base::mac::ObjCCast<FramedBrowserWindow>([self window])) { 857 [framedBrowserWindow toggleSystemFullScreen]; 858 } 859} 860 861- (void)updateLayoutParameters:(BrowserWindowLayout*)layout { 862 [layout setContentViewSize:[[[self window] contentView] bounds].size]; 863 [layout setWindowSize:[[self window] frame].size]; 864 865 [layout setInAnyFullscreen:[self isInAnyFullscreenMode]]; 866 [layout setFullscreenSlidingStyle: 867 presentationModeController_.get().slidingStyle]; 868 [layout setFullscreenMenubarOffset: 869 [presentationModeController_ menubarOffset]]; 870 [layout setFullscreenToolbarFraction: 871 [presentationModeController_ toolbarFraction]]; 872 873 [layout setHasTabStrip:[self hasTabStrip]]; 874 875 [layout setHasToolbar:[self hasToolbar]]; 876 [layout setToolbarHeight:NSHeight([[toolbarController_ view] bounds])]; 877 878 [layout setHasLocationBar:[self hasLocationBar]]; 879 880 [layout setPlaceBookmarkBarBelowInfoBar:[self placeBookmarkBarBelowInfoBar]]; 881 [layout setBookmarkBarHidden:[bookmarkBarController_ view].isHidden]; 882 [layout setBookmarkBarHeight: 883 NSHeight([[bookmarkBarController_ view] bounds])]; 884 885 [layout setInfoBarHeight:[infoBarContainerController_ heightOfInfoBars]]; 886 [layout setPageInfoBubblePointY:[self pageInfoBubblePointY]]; 887 888 [layout setHasDownloadShelf:(downloadShelfController_.get() != nil)]; 889 [layout setDownloadShelfHeight: 890 NSHeight([[downloadShelfController_ view] bounds])]; 891} 892 893- (void)applyLayout:(BrowserWindowLayout*)layout { 894 chrome::LayoutOutput output = [layout computeLayout]; 895 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]]; 902 } 903 904 if (!NSIsEmptyRect(output.toolbarFrame)) { 905 [[toolbarController_ view] setFrame:output.toolbarFrame]; 906 } 907 908 if (!NSIsEmptyRect(output.bookmarkFrame)) { 909 NSView* bookmarkBarView = [bookmarkBarController_ view]; 910 [bookmarkBarView setFrame:output.bookmarkFrame]; 911 912 // Pin the bookmark bar to the top of the window and make the width 913 // flexible. 914 [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin]; 915 916 [bookmarkBarController_ layoutSubviews]; 917 } 918 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]; 923 924 if (!NSIsEmptyRect(output.downloadShelfFrame)) 925 [[downloadShelfController_ view] setFrame:output.downloadShelfFrame]; 926 927 [self layoutTabContentArea:output.contentAreaFrame]; 928 929 if (!NSIsEmptyRect(output.fullscreenBackingBarFrame)) { 930 [floatingBarBackingView_ setFrame:output.fullscreenBackingBarFrame]; 931 [presentationModeController_ 932 overlayFrameChanged:output.fullscreenBackingBarFrame]; 933 } 934 935 [findBarCocoaController_ 936 positionFindBarViewAtMaxY:output.findBarMaxY 937 maxWidth:NSWidth(output.contentAreaFrame)]; 938 939 [fullscreenExitBubbleController_ 940 positionInWindowAtTop:output.fullscreenExitButtonMaxY 941 width:NSWidth(output.contentAreaFrame)]; 942} 943 944- (void)updateSubviewZOrder { 945 if ([self isInAnyFullscreenMode]) 946 [self updateSubviewZOrderFullscreen]; 947 else 948 [self updateSubviewZOrderNormal]; 949 950 [self updateSubviewZOrderHack]; 951} 952 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]]; 967 968 [self setContentViewSubviews:subviews]; 969} 970 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_]; 982 } else { 983 if (floatingBarBackingView_) 984 [subviews addObject:floatingBarBackingView_]; 985 if ([bookmarkBarController_ view]) 986 [subviews addObject:[bookmarkBarController_ view]]; 987 } 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]]; 994 995 [self setContentViewSubviews:subviews]; 996} 997 998- (void)setContentViewSubviews:(NSArray*)subviews { 999 // Subviews already match. 1000 if ([[self.window.contentView subviews] isEqual:subviews]) 1001 return; 1002 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]; 1007 return; 1008 } 1009 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]; 1014 } 1015 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 1022 relativeTo:nil]; 1023 } 1024 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 1030 relativeTo:nil]; 1031 } 1032} 1033 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 1039 // NSThemeFrame. 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 1046 // in 10.7+. 1047 // http://crbug.com/408791 1048 if (!hasAdjustedTabStripWhileEnteringAppKitFullscreen_) { 1049 // Disable implicit animations. 1050 [CATransaction begin]; 1051 [CATransaction setDisableActions:YES]; 1052 1053 [self updateLayerOrdering:[self tabStripView]]; 1054 [self updateLayerOrdering:[avatarButtonController_ view]]; 1055 1056 [CATransaction commit]; 1057 hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = YES; 1058 } 1059 } else { 1060 hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = NO; 1061 } 1062} 1063 1064@end // @implementation BrowserWindowController(Private) 1065