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#import "base/mac/scoped_nsobject.h" 11#include "base/prefs/pref_service.h" 12#include "base/prefs/scoped_user_pref_update.h" 13#include "chrome/browser/browser_process.h" 14#include "chrome/browser/fullscreen.h" 15#include "chrome/browser/profiles/profile.h" 16#include "chrome/browser/profiles/profile_avatar_icon_util.h" 17#include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h" 18#include "chrome/browser/ui/browser.h" 19#include "chrome/browser/ui/browser_window_state.h" 20#import "chrome/browser/ui/cocoa/dev_tools_controller.h" 21#import "chrome/browser/ui/cocoa/fast_resize_view.h" 22#import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h" 23#import "chrome/browser/ui/cocoa/floating_bar_backing_view.h" 24#import "chrome/browser/ui/cocoa/framed_browser_window.h" 25#import "chrome/browser/ui/cocoa/fullscreen_mode_controller.h" 26#import "chrome/browser/ui/cocoa/fullscreen_window.h" 27#import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h" 28#include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h" 29#import "chrome/browser/ui/cocoa/nsview_additions.h" 30#import "chrome/browser/ui/cocoa/presentation_mode_controller.h" 31#import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h" 32#import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h" 33#import "chrome/browser/ui/cocoa/status_bubble_mac.h" 34#import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h" 35#import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h" 36#import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h" 37#import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h" 38#include "chrome/browser/ui/fullscreen/fullscreen_controller.h" 39#include "chrome/browser/ui/tabs/tab_strip_model.h" 40#include "chrome/common/chrome_switches.h" 41#include "chrome/common/pref_names.h" 42#include "content/public/browser/render_widget_host_view.h" 43#include "content/public/browser/web_contents.h" 44#import "ui/base/cocoa/focus_tracker.h" 45#include "ui/base/ui_base_types.h" 46 47using content::RenderWidgetHostView; 48using content::WebContents; 49 50namespace { 51 52// Space between the incognito badge and the right edge of the window. 53const CGFloat kAvatarRightOffset = 4; 54 55// The amount by which to shrink the tab strip (on the right) when the 56// incognito badge is present. 57const CGFloat kAvatarTabStripShrink = 18; 58 59// Width of the full screen icon. Used to position the AvatarButton to the 60// left of the icon. 61const CGFloat kFullscreenIconWidth = 32; 62 63// Insets for the location bar, used when the full toolbar is hidden. 64// TODO(viettrungluu): We can argue about the "correct" insetting; I like the 65// following best, though arguably 0 inset is better/more correct. 66const CGFloat kLocBarLeftRightInset = 1; 67const CGFloat kLocBarTopInset = 0; 68const CGFloat kLocBarBottomInset = 1; 69 70} // namespace 71 72@implementation BrowserWindowController(Private) 73 74// Create the tab strip controller. 75- (void)createTabStripController { 76 DCHECK([overlayableContentsController_ activeContainer]); 77 DCHECK([[overlayableContentsController_ activeContainer] window]); 78 tabStripController_.reset([[TabStripController alloc] 79 initWithView:[self tabStripView] 80 switchView:[overlayableContentsController_ activeContainer] 81 browser:browser_.get() 82 delegate:self]); 83} 84 85- (void)saveWindowPositionIfNeeded { 86 if (!chrome::ShouldSaveWindowPlacement(browser_.get())) 87 return; 88 89 // If we're in fullscreen mode, save the position of the regular window 90 // instead. 91 NSWindow* window = [self isFullscreen] ? savedRegularWindow_ : [self window]; 92 93 // Window positions are stored relative to the origin of the primary monitor. 94 NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame]; 95 NSScreen* windowScreen = [window screen]; 96 97 // Start with the window's frame, which is in virtual coordinates. 98 // Do some y twiddling to flip the coordinate system. 99 gfx::Rect bounds(NSRectToCGRect([window frame])); 100 bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height()); 101 102 // Browser::SaveWindowPlacement saves information for session restore. 103 ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL; 104 if ([window isMiniaturized]) 105 show_state = ui::SHOW_STATE_MINIMIZED; 106 else if ([self isFullscreen]) 107 show_state = ui::SHOW_STATE_FULLSCREEN; 108 chrome::SaveWindowPlacement(browser_.get(), bounds, show_state); 109 110 // |windowScreen| can be nil (for example, if the monitor arrangement was 111 // changed while in fullscreen mode). If we see a nil screen, return without 112 // saving. 113 // TODO(rohitrao): We should just not save anything for fullscreen windows. 114 // http://crbug.com/36479. 115 if (!windowScreen) 116 return; 117 118 // Only save main window information to preferences. 119 PrefService* prefs = browser_->profile()->GetPrefs(); 120 if (!prefs || browser_ != chrome::GetLastActiveBrowser()) 121 return; 122 123 // Save the current work area, in flipped coordinates. 124 gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame])); 125 workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height()); 126 127 DictionaryPrefUpdate update( 128 prefs, 129 chrome::GetWindowPlacementKey(browser_.get()).c_str()); 130 base::DictionaryValue* windowPreferences = update.Get(); 131 windowPreferences->SetInteger("left", bounds.x()); 132 windowPreferences->SetInteger("top", bounds.y()); 133 windowPreferences->SetInteger("right", bounds.right()); 134 windowPreferences->SetInteger("bottom", bounds.bottom()); 135 windowPreferences->SetBoolean("maximized", false); 136 windowPreferences->SetBoolean("always_on_top", false); 137 windowPreferences->SetInteger("work_area_left", workArea.x()); 138 windowPreferences->SetInteger("work_area_top", workArea.y()); 139 windowPreferences->SetInteger("work_area_right", workArea.right()); 140 windowPreferences->SetInteger("work_area_bottom", workArea.bottom()); 141} 142 143- (NSRect)window:(NSWindow*)window 144willPositionSheet:(NSWindow*)sheet 145 usingRect:(NSRect)defaultSheetRect { 146 // Position the sheet as follows: 147 // - If the bookmark bar is hidden or shown as a bubble (on the NTP when the 148 // bookmark bar is disabled), position the sheet immediately below the 149 // normal toolbar. 150 // - If the bookmark bar is shown (attached to the normal toolbar), position 151 // the sheet below the bookmark bar. 152 // - If the bookmark bar is currently animating, position the sheet according 153 // to where the bar will be when the animation ends. 154 switch ([bookmarkBarController_ currentState]) { 155 case BookmarkBar::SHOW: { 156 NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame]; 157 defaultSheetRect.origin.y = bookmarkBarFrame.origin.y; 158 break; 159 } 160 case BookmarkBar::HIDDEN: 161 case BookmarkBar::DETACHED: { 162 if ([self hasToolbar]) { 163 NSRect toolbarFrame = [[toolbarController_ view] frame]; 164 defaultSheetRect.origin.y = toolbarFrame.origin.y; 165 } else { 166 // The toolbar is not shown in application mode. The sheet should be 167 // located at the top of the window, under the title of the window. 168 defaultSheetRect.origin.y = NSHeight([[window contentView] frame]) - 169 defaultSheetRect.size.height; 170 } 171 break; 172 } 173 } 174 return defaultSheetRect; 175} 176 177- (void)layoutSubviews { 178 // With the exception of the top tab strip, the subviews which we lay out are 179 // subviews of the content view, so we mainly work in the content view's 180 // coordinate system. Note, however, that the content view's coordinate system 181 // and the window's base coordinate system should coincide. 182 NSWindow* window = [self window]; 183 NSView* contentView = [window contentView]; 184 NSRect contentBounds = [contentView bounds]; 185 CGFloat minX = NSMinX(contentBounds); 186 CGFloat minY = NSMinY(contentBounds); 187 CGFloat width = NSWidth(contentBounds); 188 189 BOOL useSimplifiedFullscreen = CommandLine::ForCurrentProcess()->HasSwitch( 190 switches::kEnableSimplifiedFullscreen); 191 192 // Suppress title drawing if necessary. 193 if ([window respondsToSelector:@selector(setShouldHideTitle:)]) 194 [(id)window setShouldHideTitle:![self hasTitleBar]]; 195 196 // Update z-order. The code below depends on this. 197 [self updateSubviewZOrder:[self inPresentationMode]]; 198 199 BOOL inPresentationMode = [self inPresentationMode]; 200 CGFloat floatingBarHeight = [self floatingBarHeight]; 201 // In presentation mode, |yOffset| accounts for the sliding position of the 202 // floating bar and the extra offset needed to dodge the menu bar. 203 CGFloat yOffset = inPresentationMode && !useSimplifiedFullscreen ? 204 (std::floor((1 - floatingBarShownFraction_) * floatingBarHeight) - 205 [presentationModeController_ floatingBarVerticalOffset]) : 0; 206 CGFloat maxY = NSMaxY(contentBounds) + yOffset; 207 208 if ([self hasTabStrip]) { 209 // If we need to lay out the top tab strip, replace |maxY| with a higher 210 // value, and then lay out the tab strip. 211 NSRect windowFrame = [contentView convertRect:[window frame] fromView:nil]; 212 maxY = NSHeight(windowFrame) + yOffset; 213 if (useSimplifiedFullscreen && [self isFullscreen]) { 214 CGFloat tabStripHeight = NSHeight([[self tabStripView] frame]); 215 CGFloat revealAmount = (1 - floatingBarShownFraction_) * tabStripHeight; 216 // In simplified fullscreen, only the toolbar is visible by default, and 217 // the tabstrip and menu bar come down (each separately) when the user 218 // mouses near the top of the window. Push the maxY of the toolbar up by 219 // the amount of the tabstrip that is revealed, while removing the amount 220 // of space needed by the menu bar. 221 maxY += std::floor( 222 revealAmount - [fullscreenModeController_ menuBarHeight]); 223 } 224 maxY = [self layoutTabStripAtMaxY:maxY 225 width:width 226 fullscreen:[self isFullscreen]]; 227 } 228 229 // Sanity-check |maxY|. 230 DCHECK_GE(maxY, minY); 231 DCHECK_LE(maxY, NSMaxY(contentBounds) + yOffset); 232 233 // Place the toolbar at the top of the reserved area. 234 maxY = [self layoutToolbarAtMinX:minX maxY:maxY width:width]; 235 236 // If we're not displaying the bookmark bar below the info bar, then it goes 237 // immediately below the toolbar. 238 BOOL placeBookmarkBarBelowInfoBar = [self placeBookmarkBarBelowInfoBar]; 239 if (!placeBookmarkBarBelowInfoBar) 240 maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width]; 241 242 // The floating bar backing view doesn't actually add any height. 243 NSRect floatingBarBackingRect = 244 NSMakeRect(minX, maxY, width, floatingBarHeight); 245 [self layoutFloatingBarBackingView:floatingBarBackingRect 246 presentationMode:inPresentationMode]; 247 248 // Place the find bar immediately below the toolbar/attached bookmark bar. In 249 // presentation mode, it hangs off the top of the screen when the bar is 250 // hidden. 251 [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:width]; 252 [fullscreenExitBubbleController_ positionInWindowAtTop:maxY width:width]; 253 254 // If in presentation mode, reset |maxY| to top of screen, so that the 255 // floating bar slides over the things which appear to be in the content area. 256 if (inPresentationMode || 257 (useSimplifiedFullscreen && !fullscreenUrl_.is_empty())) { 258 maxY = NSMaxY(contentBounds); 259 } 260 261 // Also place the info bar container immediate below the toolbar, except in 262 // presentation mode in which case it's at the top of the visual content area. 263 maxY = [self layoutInfoBarAtMinX:minX maxY:maxY width:width]; 264 265 // If the bookmark bar is detached, place it next in the visual content area. 266 if (placeBookmarkBarBelowInfoBar) 267 maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width]; 268 269 // Place the download shelf, if any, at the bottom of the view. 270 minY = [self layoutDownloadShelfAtMinX:minX minY:minY width:width]; 271 272 // Finally, the content area takes up all of the remaining space. 273 NSRect contentAreaRect = NSMakeRect(minX, minY, width, maxY - minY); 274 [self layoutTabContentArea:contentAreaRect]; 275 276 // Normally, we don't need to tell the toolbar whether or not to show the 277 // divider, but things break down during animation. 278 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]]; 279 280 // Update the position of the active constrained window sheet. We force this 281 // here because the |sheetParentView| may not have been resized (e.g., to 282 // prevent jank during a fullscreen mode transition), but constrained window 283 // sheets also compute their position based on the bookmark bar and toolbar. 284 content::WebContents* const activeWebContents = 285 browser_->tab_strip_model()->GetActiveWebContents(); 286 NSView* const sheetParentView = activeWebContents ? 287 GetSheetParentViewForWebContents(activeWebContents) : nil; 288 if (sheetParentView) { 289 [[NSNotificationCenter defaultCenter] 290 postNotificationName:NSViewFrameDidChangeNotification 291 object:sheetParentView]; 292 } 293} 294 295- (CGFloat)floatingBarHeight { 296 if (![self inPresentationMode]) 297 return 0; 298 299 CGFloat totalHeight = [presentationModeController_ floatingBarVerticalOffset]; 300 301 if ([self hasTabStrip]) 302 totalHeight += NSHeight([[self tabStripView] frame]); 303 304 if ([self hasToolbar]) { 305 totalHeight += NSHeight([[toolbarController_ view] frame]); 306 } else if ([self hasLocationBar]) { 307 totalHeight += NSHeight([[toolbarController_ view] frame]) + 308 kLocBarTopInset + kLocBarBottomInset; 309 } 310 311 if (![self placeBookmarkBarBelowInfoBar]) 312 totalHeight += NSHeight([[bookmarkBarController_ view] frame]); 313 314 return totalHeight; 315} 316 317- (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY 318 width:(CGFloat)width 319 fullscreen:(BOOL)fullscreen { 320 // Nothing to do if no tab strip. 321 if (![self hasTabStrip]) 322 return maxY; 323 324 NSView* tabStripView = [self tabStripView]; 325 CGFloat tabStripHeight = NSHeight([tabStripView frame]); 326 maxY -= tabStripHeight; 327 [tabStripView setFrame:NSMakeRect(0, maxY, width, tabStripHeight)]; 328 329 // Set left indentation based on fullscreen mode status. 330 [tabStripController_ setLeftIndentForControls:(fullscreen ? 0 : 331 [[tabStripController_ class] defaultLeftIndentForControls])]; 332 333 // Lay out the icognito/avatar badge because calculating the indentation on 334 // the right depends on it. 335 NSView* avatarButton = [avatarButtonController_ view]; 336 if ([self shouldShowAvatar]) { 337 CGFloat badgeXOffset = -kAvatarRightOffset; 338 CGFloat badgeYOffset = 0; 339 CGFloat buttonHeight = NSHeight([avatarButton frame]); 340 341 if ([self shouldUseNewAvatarButton]) { 342 // The fullscreen icon is displayed to the right of the avatar button. 343 if (![self isFullscreen]) 344 badgeXOffset -= kFullscreenIconWidth; 345 // Center the button vertically on the tabstrip. 346 badgeYOffset = (tabStripHeight - buttonHeight) / 2; 347 } else { 348 // Actually place the badge *above* |maxY|, by +2 to miss the divider. 349 badgeYOffset = 2 * [[avatarButton superview] cr_lineWidth]; 350 } 351 352 [avatarButton setFrameSize:NSMakeSize(NSWidth([avatarButton frame]), 353 std::min(buttonHeight, tabStripHeight))]; 354 NSPoint origin = 355 NSMakePoint(width - NSWidth([avatarButton frame]) + badgeXOffset, 356 maxY + badgeYOffset); 357 [avatarButton setFrameOrigin:origin]; 358 [avatarButton setHidden:NO]; // Make sure it's shown. 359 } 360 361 // Calculate the right indentation. The default indentation built into the 362 // tabstrip leaves enough room for the fullscreen button or presentation mode 363 // toggle button on Lion. On non-Lion systems, the right indent needs to be 364 // adjusted to make room for the new tab button when an avatar is present. 365 CGFloat rightIndent = 0; 366 if (base::mac::IsOSLionOrLater() && 367 [[self window] isKindOfClass:[FramedBrowserWindow class]]) { 368 FramedBrowserWindow* window = 369 static_cast<FramedBrowserWindow*>([self window]); 370 rightIndent += -[window fullScreenButtonOriginAdjustment].x; 371 372 // The new avatar is wider than the default indentation, so we need to 373 // account for its width. 374 if ([self shouldUseNewAvatarButton]) 375 rightIndent += NSWidth([avatarButton frame]) + kAvatarTabStripShrink; 376 } else if ([self shouldShowAvatar]) { 377 rightIndent += kAvatarTabStripShrink + 378 NSWidth([avatarButton frame]) + kAvatarRightOffset; 379 } 380 [tabStripController_ setRightIndentForControls:rightIndent]; 381 382 // Go ahead and layout the tabs. 383 [tabStripController_ layoutTabsWithoutAnimation]; 384 385 return maxY; 386} 387 388- (CGFloat)layoutToolbarAtMinX:(CGFloat)minX 389 maxY:(CGFloat)maxY 390 width:(CGFloat)width { 391 NSView* toolbarView = [toolbarController_ view]; 392 NSRect toolbarFrame = [toolbarView frame]; 393 if ([self hasToolbar]) { 394 // The toolbar is present in the window, so we make room for it. 395 DCHECK(![toolbarView isHidden]); 396 toolbarFrame.origin.x = minX; 397 toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame); 398 toolbarFrame.size.width = width; 399 maxY -= NSHeight(toolbarFrame); 400 } else { 401 if ([self hasLocationBar]) { 402 // Location bar is present with no toolbar. Put a border of 403 // |kLocBar...Inset| pixels around the location bar. 404 // TODO(viettrungluu): This is moderately ridiculous. The toolbar should 405 // really be aware of what its height should be (the way the toolbar 406 // compression stuff is currently set up messes things up). 407 DCHECK(![toolbarView isHidden]); 408 toolbarFrame.origin.x = kLocBarLeftRightInset; 409 toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame) - kLocBarTopInset; 410 toolbarFrame.size.width = width - 2 * kLocBarLeftRightInset; 411 maxY -= kLocBarTopInset + NSHeight(toolbarFrame) + kLocBarBottomInset; 412 } else { 413 DCHECK([toolbarView isHidden]); 414 } 415 } 416 [toolbarView setFrame:toolbarFrame]; 417 return maxY; 418} 419 420- (BOOL)placeBookmarkBarBelowInfoBar { 421 // If we are currently displaying the NTP detached bookmark bar or animating 422 // to/from it (from/to anything else), we display the bookmark bar below the 423 // info bar. 424 return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] || 425 [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] || 426 [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED]; 427} 428 429- (CGFloat)layoutBookmarkBarAtMinX:(CGFloat)minX 430 maxY:(CGFloat)maxY 431 width:(CGFloat)width { 432 [bookmarkBarController_ updateHiddenState]; 433 434 NSView* bookmarkBarView = [bookmarkBarController_ view]; 435 NSRect frame = [bookmarkBarView frame]; 436 frame.origin.x = minX; 437 frame.origin.y = maxY - NSHeight(frame); 438 frame.size.width = width; 439 [bookmarkBarView setFrame:frame]; 440 maxY -= NSHeight(frame); 441 442 // Pin the bookmark bar to the top of the window and make the width flexible. 443 [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin]; 444 445 // TODO(viettrungluu): Does this really belong here? Calling it shouldn't be 446 // necessary in the non-NTP case. 447 [bookmarkBarController_ layoutSubviews]; 448 449 return maxY; 450} 451 452- (void)layoutFloatingBarBackingView:(NSRect)frame 453 presentationMode:(BOOL)presentationMode { 454 // Only display when in presentation mode. 455 if (presentationMode) { 456 // For certain window types such as app windows (e.g., the dev tools 457 // window), there's no actual overlay. (Displaying one would result in an 458 // overly sliding in only under the menu, which gives an ugly effect.) 459 if (floatingBarBackingView_.get()) { 460 // Set its frame. 461 [floatingBarBackingView_ setFrame:frame]; 462 } 463 464 // But we want the logic to work as usual (for show/hide/etc. purposes). 465 [presentationModeController_ overlayFrameChanged:frame]; 466 } else { 467 // Okay to call even if |floatingBarBackingView_| is nil. 468 if ([floatingBarBackingView_ superview]) 469 [floatingBarBackingView_ removeFromSuperview]; 470 } 471} 472 473- (CGFloat)layoutInfoBarAtMinX:(CGFloat)minX 474 maxY:(CGFloat)maxY 475 width:(CGFloat)width { 476 NSView* containerView = [infoBarContainerController_ view]; 477 NSRect containerFrame = [containerView frame]; 478 maxY -= NSHeight(containerFrame); 479 maxY += [infoBarContainerController_ overlappingTipHeight]; 480 containerFrame.origin.x = minX; 481 containerFrame.origin.y = maxY; 482 containerFrame.size.width = width; 483 [containerView setFrame:containerFrame]; 484 return maxY; 485} 486 487- (CGFloat)layoutDownloadShelfAtMinX:(CGFloat)minX 488 minY:(CGFloat)minY 489 width:(CGFloat)width { 490 if (downloadShelfController_.get()) { 491 NSView* downloadView = [downloadShelfController_ view]; 492 NSRect downloadFrame = [downloadView frame]; 493 downloadFrame.origin.x = minX; 494 downloadFrame.origin.y = minY; 495 downloadFrame.size.width = width; 496 [downloadView setFrame:downloadFrame]; 497 minY += NSHeight(downloadFrame); 498 } 499 return minY; 500} 501 502- (void)layoutTabContentArea:(NSRect)newFrame { 503 NSView* tabContentView = [self tabContentArea]; 504 NSRect tabContentFrame = [tabContentView frame]; 505 506 bool contentShifted = 507 NSMaxY(tabContentFrame) != NSMaxY(newFrame) || 508 NSMinX(tabContentFrame) != NSMinX(newFrame); 509 510 tabContentFrame = newFrame; 511 [tabContentView setFrame:tabContentFrame]; 512 513 // If the relayout shifts the content area up or down, let the renderer know. 514 if (contentShifted) { 515 if (WebContents* contents = 516 browser_->tab_strip_model()->GetActiveWebContents()) { 517 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView()) 518 rwhv->WindowFrameChanged(); 519 } 520 } 521} 522 523- (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression { 524 CGFloat newHeight = 525 [toolbarController_ desiredHeightForCompression:compression]; 526 NSRect toolbarFrame = [[toolbarController_ view] frame]; 527 CGFloat deltaH = newHeight - toolbarFrame.size.height; 528 529 if (deltaH == 0) 530 return; 531 532 toolbarFrame.size.height = newHeight; 533 NSRect bookmarkFrame = [[bookmarkBarController_ view] frame]; 534 bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH; 535 [[toolbarController_ view] setFrame:toolbarFrame]; 536 [[bookmarkBarController_ view] setFrame:bookmarkFrame]; 537 [self layoutSubviews]; 538} 539 540// Fullscreen and presentation mode methods 541 542- (void)moveViewsForImmersiveFullscreen:(BOOL)fullscreen 543 regularWindow:(NSWindow*)regularWindow 544 fullscreenWindow:(NSWindow*)fullscreenWindow { 545 NSWindow* sourceWindow = fullscreen ? regularWindow : fullscreenWindow; 546 NSWindow* destWindow = fullscreen ? fullscreenWindow : regularWindow; 547 548 // Close the bookmark bubble, if it's open. Use |-ok:| instead of |-cancel:| 549 // or |-close| because that matches the behavior when the bubble loses key 550 // status. 551 [bookmarkBubbleController_ ok:self]; 552 553 // Save the current first responder so we can restore after views are moved. 554 base::scoped_nsobject<FocusTracker> focusTracker( 555 [[FocusTracker alloc] initWithWindow:sourceWindow]); 556 557 // While we move views (and focus) around, disable any bar visibility changes. 558 [self disableBarVisibilityUpdates]; 559 560 // Retain the tab strip view while we remove it from its superview. 561 base::scoped_nsobject<NSView> tabStripView; 562 if ([self hasTabStrip]) { 563 tabStripView.reset([[self tabStripView] retain]); 564 [tabStripView removeFromSuperview]; 565 } 566 567 // Ditto for the content view. 568 base::scoped_nsobject<NSView> contentView( 569 [[sourceWindow contentView] retain]); 570 // Disable autoresizing of subviews while we move views around. This prevents 571 // spurious renderer resizes. 572 [contentView setAutoresizesSubviews:NO]; 573 [contentView removeFromSuperview]; 574 575 // Have to do this here, otherwise later calls can crash because the window 576 // has no delegate. 577 [sourceWindow setDelegate:nil]; 578 [destWindow setDelegate:self]; 579 580 // With this call, valgrind complains that a "Conditional jump or move depends 581 // on uninitialised value(s)". The error happens in -[NSThemeFrame 582 // drawOverlayRect:]. I'm pretty convinced this is an Apple bug, but there is 583 // no visual impact. I have been unable to tickle it away with other window 584 // or view manipulation Cocoa calls. Stack added to suppressions_mac.txt. 585 [contentView setAutoresizesSubviews:YES]; 586 [destWindow setContentView:contentView]; 587 588 // Move the incognito badge if present. 589 if ([self shouldShowAvatar]) { 590 NSView* avatarButtonView = [avatarButtonController_ view]; 591 592 [avatarButtonView removeFromSuperview]; 593 [avatarButtonView setHidden:YES]; // Will be shown in layout. 594 [[[destWindow contentView] superview] addSubview: avatarButtonView]; 595 } 596 597 // Add the tab strip after setting the content view and moving the incognito 598 // badge (if any), so that the tab strip will be on top (in the z-order). 599 if ([self hasTabStrip]) 600 [[[destWindow contentView] superview] addSubview:tabStripView]; 601 602 [sourceWindow setWindowController:nil]; 603 [self setWindow:destWindow]; 604 [destWindow setWindowController:self]; 605 606 // Move the status bubble over, if we have one. 607 if (statusBubble_) 608 statusBubble_->SwitchParentWindow(destWindow); 609 610 // Move the title over. 611 [destWindow setTitle:[sourceWindow title]]; 612 613 // The window needs to be onscreen before we can set its first responder. 614 // Ordering the window to the front can change the active Space (either to 615 // the window's old Space or to the application's assigned Space). To prevent 616 // this by temporarily change the collectionBehavior. 617 NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior]; 618 [destWindow setCollectionBehavior: 619 NSWindowCollectionBehaviorMoveToActiveSpace]; 620 [destWindow makeKeyAndOrderFront:self]; 621 [destWindow setCollectionBehavior:behavior]; 622 623 [focusTracker restoreFocusInWindow:destWindow]; 624 [sourceWindow orderOut:self]; 625 626 // We're done moving focus, so re-enable bar visibility changes. 627 [self enableBarVisibilityUpdates]; 628} 629 630- (void)setPresentationModeInternal:(BOOL)presentationMode 631 forceDropdown:(BOOL)forceDropdown { 632 if (presentationMode == [self inPresentationMode]) 633 return; 634 635 if (presentationMode) { 636 BOOL fullscreen_for_tab = 637 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending(); 638 BOOL kiosk_mode = 639 CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode); 640 BOOL showDropdown = !fullscreen_for_tab && 641 !kiosk_mode && 642 (forceDropdown || [self floatingBarHasFocus]); 643 NSView* contentView = [[self window] contentView]; 644 presentationModeController_.reset( 645 [[PresentationModeController alloc] initWithBrowserController:self]); 646 [presentationModeController_ enterPresentationModeForContentView:contentView 647 showDropdown:showDropdown]; 648 } else { 649 [presentationModeController_ exitPresentationMode]; 650 presentationModeController_.reset(); 651 } 652 653 [self adjustUIForPresentationMode:presentationMode]; 654 [self layoutSubviews]; 655} 656 657- (void)enterImmersiveFullscreen { 658 // |-isFullscreen:| will return YES from here onwards. 659 enteringFullscreen_ = YES; // Set to NO by |-windowDidEnterFullScreen:|. 660 661 // Fade to black. 662 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6; 663 Boolean didFadeOut = NO; 664 CGDisplayFadeReservationToken token; 665 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) 666 == kCGErrorSuccess) { 667 didFadeOut = YES; 668 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal, 669 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true); 670 } 671 672 // Create the fullscreen window. 673 fullscreenWindow_.reset([[self createFullscreenWindow] retain]); 674 savedRegularWindow_ = [[self window] retain]; 675 savedRegularWindowFrame_ = [savedRegularWindow_ frame]; 676 677 [self moveViewsForImmersiveFullscreen:YES 678 regularWindow:[self window] 679 fullscreenWindow:fullscreenWindow_.get()]; 680 681 // When simplified fullscreen is enabled, do not enter presentation mode. 682 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 683 if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)) { 684 // TODO(rohitrao): Add code to manage the menubar here. 685 } else { 686 [self adjustUIForPresentationMode:YES]; 687 [self setPresentationModeInternal:YES forceDropdown:NO]; 688 } 689 690 [self layoutSubviews]; 691 692 [self windowDidEnterFullScreen:nil]; 693 694 // Fade back in. 695 if (didFadeOut) { 696 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor, 697 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false); 698 CGReleaseDisplayFadeReservation(token); 699 } 700} 701 702- (void)exitImmersiveFullscreen { 703 // Fade to black. 704 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6; 705 Boolean didFadeOut = NO; 706 CGDisplayFadeReservationToken token; 707 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token) 708 == kCGErrorSuccess) { 709 didFadeOut = YES; 710 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal, 711 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true); 712 } 713 714 // When simplified fullscreen is enabled, the menubar status is managed 715 // directly by BWC. 716 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 717 if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)) { 718 // TODO(rohitrao): Add code to manage the menubar here. 719 } 720 721 [self windowWillExitFullScreen:nil]; 722 723 [self moveViewsForImmersiveFullscreen:NO 724 regularWindow:savedRegularWindow_ 725 fullscreenWindow:fullscreenWindow_.get()]; 726 727 // When exiting fullscreen mode, we need to call layoutSubviews manually. 728 [savedRegularWindow_ autorelease]; 729 savedRegularWindow_ = nil; 730 fullscreenWindow_.reset(); 731 [self layoutSubviews]; 732 733 [self windowDidExitFullScreen:nil]; 734 735 // Fade back in. 736 if (didFadeOut) { 737 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor, 738 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false); 739 CGReleaseDisplayFadeReservation(token); 740 } 741} 742 743// TODO(rohitrao): This function has shrunk into uselessness, and 744// |-setFullscreen:| has grown rather large. Find a good way to break up 745// |-setFullscreen:| into smaller pieces. http://crbug.com/36449 746- (void)adjustUIForPresentationMode:(BOOL)fullscreen { 747 // Create the floating bar backing view if necessary. 748 if (fullscreen && !floatingBarBackingView_.get() && 749 ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) { 750 floatingBarBackingView_.reset( 751 [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]); 752 [floatingBarBackingView_ setAutoresizingMask:(NSViewWidthSizable | 753 NSViewMinYMargin)]; 754 } 755 756 // Force the bookmark bar z-order to update. 757 [[bookmarkBarController_ view] removeFromSuperview]; 758 [self updateSubviewZOrder:fullscreen]; 759 [self updateAllowOverlappingViews:fullscreen]; 760} 761 762- (void)showFullscreenExitBubbleIfNecessary { 763 // This method is called in response to 764 // |-updateFullscreenExitBubbleURL:bubbleType:|. If we're in the middle of the 765 // transition into fullscreen (i.e., using the System Fullscreen API), do not 766 // show the bubble because it will cause visual jank 767 // (http://crbug.com/130649). This will be called again as part of 768 // |-windowDidEnterFullScreen:|, so arrange to do that work then instead. 769 if (enteringFullscreen_) 770 return; 771 772 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO]; 773 774 if (fullscreenBubbleType_ == FEB_TYPE_NONE || 775 fullscreenBubbleType_ == FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) { 776 // Show no exit instruction bubble on Mac when in Browser Fullscreen. 777 [self destroyFullscreenExitBubbleIfNecessary]; 778 } else { 779 [fullscreenExitBubbleController_ closeImmediately]; 780 fullscreenExitBubbleController_.reset( 781 [[FullscreenExitBubbleController alloc] 782 initWithOwner:self 783 browser:browser_.get() 784 url:fullscreenUrl_ 785 bubbleType:fullscreenBubbleType_]); 786 [fullscreenExitBubbleController_ showWindow]; 787 } 788} 789 790- (void)destroyFullscreenExitBubbleIfNecessary { 791 [fullscreenExitBubbleController_ closeImmediately]; 792 fullscreenExitBubbleController_.reset(); 793} 794 795- (void)contentViewDidResize:(NSNotification*)notification { 796 [self layoutSubviews]; 797} 798 799- (void)registerForContentViewResizeNotifications { 800 [[NSNotificationCenter defaultCenter] 801 addObserver:self 802 selector:@selector(contentViewDidResize:) 803 name:NSViewFrameDidChangeNotification 804 object:[[self window] contentView]]; 805} 806 807- (void)deregisterForContentViewResizeNotifications { 808 [[NSNotificationCenter defaultCenter] 809 removeObserver:self 810 name:NSViewFrameDidChangeNotification 811 object:[[self window] contentView]]; 812} 813 814- (NSSize)window:(NSWindow*)window 815 willUseFullScreenContentSize:(NSSize)proposedSize { 816 return proposedSize; 817} 818 819- (NSApplicationPresentationOptions)window:(NSWindow*)window 820 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt { 821 return (opt | 822 NSApplicationPresentationAutoHideDock | 823 NSApplicationPresentationAutoHideMenuBar); 824} 825 826- (void)windowWillEnterFullScreen:(NSNotification*)notification { 827 if (notification) // For System Fullscreen when non-nil. 828 [self registerForContentViewResizeNotifications]; 829 830 NSWindow* window = [self window]; 831 savedRegularWindowFrame_ = [window frame]; 832 BOOL mode = enteringPresentationMode_ || 833 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending(); 834 enteringFullscreen_ = YES; 835 [self setPresentationModeInternal:mode forceDropdown:NO]; 836} 837 838- (void)windowDidEnterFullScreen:(NSNotification*)notification { 839 if (notification) // For System Fullscreen when non-nil. 840 [self deregisterForContentViewResizeNotifications]; 841 enteringFullscreen_ = NO; 842 enteringPresentationMode_ = NO; 843 844 const CommandLine* command_line = CommandLine::ForCurrentProcess(); 845 if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen) && 846 fullscreenUrl_.is_empty()) { 847 fullscreenModeController_.reset([[FullscreenModeController alloc] 848 initWithBrowserWindowController:self]); 849 } 850 851 [self showFullscreenExitBubbleIfNecessary]; 852 browser_->WindowFullscreenStateChanged(); 853} 854 855- (void)windowWillExitFullScreen:(NSNotification*)notification { 856 if (notification) // For System Fullscreen when non-nil. 857 [self registerForContentViewResizeNotifications]; 858 fullscreenModeController_.reset(); 859 [self destroyFullscreenExitBubbleIfNecessary]; 860 [self setPresentationModeInternal:NO forceDropdown:NO]; 861} 862 863- (void)windowDidExitFullScreen:(NSNotification*)notification { 864 if (notification) // For System Fullscreen when non-nil. 865 [self deregisterForContentViewResizeNotifications]; 866 browser_->WindowFullscreenStateChanged(); 867} 868 869- (void)windowDidFailToEnterFullScreen:(NSWindow*)window { 870 [self deregisterForContentViewResizeNotifications]; 871 enteringFullscreen_ = NO; 872 [self setPresentationModeInternal:NO forceDropdown:NO]; 873 874 // Force a relayout to try and get the window back into a reasonable state. 875 [self layoutSubviews]; 876} 877 878- (void)windowDidFailToExitFullScreen:(NSWindow*)window { 879 [self deregisterForContentViewResizeNotifications]; 880 881 // Force a relayout to try and get the window back into a reasonable state. 882 [self layoutSubviews]; 883} 884 885- (void)enableBarVisibilityUpdates { 886 // Early escape if there's nothing to do. 887 if (barVisibilityUpdatesEnabled_) 888 return; 889 890 barVisibilityUpdatesEnabled_ = YES; 891 892 if ([barVisibilityLocks_ count]) 893 [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO]; 894 else 895 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO]; 896} 897 898- (void)disableBarVisibilityUpdates { 899 // Early escape if there's nothing to do. 900 if (!barVisibilityUpdatesEnabled_) 901 return; 902 903 barVisibilityUpdatesEnabled_ = NO; 904 [presentationModeController_ cancelAnimationAndTimers]; 905} 906 907- (CGFloat)toolbarDividerOpacity { 908 return [bookmarkBarController_ toolbarDividerOpacity]; 909} 910 911- (void)updateSubviewZOrder:(BOOL)inPresentationMode { 912 NSView* contentView = [[self window] contentView]; 913 NSView* toolbarView = [toolbarController_ view]; 914 915 if (inPresentationMode) { 916 // Toolbar is above tab contents so that it can slide down from top of 917 // screen. 918 [contentView cr_ensureSubview:toolbarView 919 isPositioned:NSWindowAbove 920 relativeTo:[self tabContentArea]]; 921 } else { 922 // Toolbar is below tab contents so that the info bar arrow can appear above 923 // it. 924 [contentView cr_ensureSubview:toolbarView 925 isPositioned:NSWindowBelow 926 relativeTo:[self tabContentArea]]; 927 } 928 929 // The bookmark bar is always below the toolbar. 930 [contentView cr_ensureSubview:[bookmarkBarController_ view] 931 isPositioned:NSWindowBelow 932 relativeTo:toolbarView]; 933 934 if (inPresentationMode) { 935 // In presentation mode the info bar is below all other views. 936 [contentView cr_ensureSubview:[infoBarContainerController_ view] 937 isPositioned:NSWindowBelow 938 relativeTo:[self tabContentArea]]; 939 } else { 940 // Above the toolbar but still below tab contents. Similar to the bookmark 941 // bar, this allows Instant results to be above the info bar. 942 [contentView cr_ensureSubview:[infoBarContainerController_ view] 943 isPositioned:NSWindowAbove 944 relativeTo:toolbarView]; 945 } 946 947 // The find bar is above everything. 948 if (findBarCocoaController_) { 949 NSView* relativeView = nil; 950 if (inPresentationMode) 951 relativeView = toolbarView; 952 else 953 relativeView = [self tabContentArea]; 954 [contentView cr_ensureSubview:[findBarCocoaController_ view] 955 isPositioned:NSWindowAbove 956 relativeTo:relativeView]; 957 } 958 959 if (floatingBarBackingView_) { 960 if ([floatingBarBackingView_ cr_isBelowView:[self tabContentArea]]) 961 [floatingBarBackingView_ removeFromSuperview]; 962 if ([self placeBookmarkBarBelowInfoBar]) { 963 [contentView cr_ensureSubview:floatingBarBackingView_ 964 isPositioned:NSWindowAbove 965 relativeTo:[bookmarkBarController_ view]]; 966 } else { 967 [contentView cr_ensureSubview:floatingBarBackingView_ 968 isPositioned:NSWindowBelow 969 relativeTo:[bookmarkBarController_ view]]; 970 } 971 } 972} 973 974- (BOOL)shouldAllowOverlappingViews:(BOOL)inPresentationMode { 975 if (inPresentationMode) 976 return YES; 977 978 if (findBarCocoaController_ && 979 ![[findBarCocoaController_ findBarView] isHidden]) { 980 return YES; 981 } 982 983 if (overlappedViewCount_) 984 return YES; 985 986 return NO; 987} 988 989- (void)updateAllowOverlappingViews:(BOOL)inPresentationMode { 990 WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents(); 991 if (!contents) 992 return; 993 994 BOOL allowOverlappingViews = 995 [self shouldAllowOverlappingViews:inPresentationMode]; 996 997 // The rendering path with overlapping views disabled causes bugs when 998 // transitioning between composited and non-composited mode. 999 // http://crbug.com/279472 1000 allowOverlappingViews = YES; 1001 contents->SetAllowOverlappingViews(allowOverlappingViews); 1002 1003 WebContents* devTools = DevToolsWindow::GetInTabWebContents(contents, NULL); 1004 if (devTools) 1005 devTools->SetAllowOverlappingViews(allowOverlappingViews); 1006} 1007 1008- (void)updateInfoBarTipVisibility { 1009 // If there's no toolbar then hide the infobar tip. 1010 [infoBarContainerController_ 1011 setShouldSuppressTopInfoBarTip:![self hasToolbar]]; 1012} 1013 1014@end // @implementation BrowserWindowController(Private) 1015