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 #include "base/prefs/pref_service.h"
13 #include "base/prefs/scoped_user_pref_update.h"
14 #include "chrome/browser/browser_process.h"
15 #include "chrome/browser/fullscreen.h"
16 #include "chrome/browser/profiles/profile.h"
17 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
18 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/browser/ui/browser_window_state.h"
21 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
22 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
23 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
24 #import "chrome/browser/ui/cocoa/floating_bar_backing_view.h"
25 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
26 #import "chrome/browser/ui/cocoa/fullscreen_mode_controller.h"
27 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
28 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
29 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
30 #import "chrome/browser/ui/cocoa/nsview_additions.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 #include "ui/base/ui_base_types.h"
49 using content::RenderWidgetHostView;
50 using content::WebContents;
54 // Space between the incognito badge and the right edge of the window.
55 const CGFloat kAvatarRightOffset = 4;
57 // The amount by which to shrink the tab strip (on the right) when the
58 // incognito badge is present.
59 const CGFloat kAvatarTabStripShrink = 18;
61 // Width of the full screen icon. Used to position the AvatarButton to the
63 const CGFloat kFullscreenIconWidth = 32;
65 // Insets for the location bar, used when the full toolbar is hidden.
66 // TODO(viettrungluu): We can argue about the "correct" insetting; I like the
67 // following best, though arguably 0 inset is better/more correct.
68 const CGFloat kLocBarLeftRightInset = 1;
69 const CGFloat kLocBarTopInset = 0;
70 const CGFloat kLocBarBottomInset = 1;
74 @implementation BrowserWindowController(Private)
76 // Create the tab strip controller.
77 - (void)createTabStripController {
78 DCHECK([overlayableContentsController_ activeContainer]);
79 DCHECK([[overlayableContentsController_ activeContainer] window]);
80 tabStripController_.reset([[TabStripController alloc]
81 initWithView:[self tabStripView]
82 switchView:[overlayableContentsController_ activeContainer]
83 browser:browser_.get()
87 - (void)saveWindowPositionIfNeeded {
88 if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
91 // If we're in fullscreen mode, save the position of the regular window
93 NSWindow* window = [self isFullscreen] ? savedRegularWindow_ : [self window];
95 // Window positions are stored relative to the origin of the primary monitor.
96 NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
97 NSScreen* windowScreen = [window screen];
99 // Start with the window's frame, which is in virtual coordinates.
100 // Do some y twiddling to flip the coordinate system.
101 gfx::Rect bounds(NSRectToCGRect([window frame]));
102 bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
104 // Browser::SaveWindowPlacement saves information for session restore.
105 ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
106 if ([window isMiniaturized])
107 show_state = ui::SHOW_STATE_MINIMIZED;
108 else if ([self isFullscreen])
109 show_state = ui::SHOW_STATE_FULLSCREEN;
110 chrome::SaveWindowPlacement(browser_.get(), bounds, show_state);
112 // |windowScreen| can be nil (for example, if the monitor arrangement was
113 // changed while in fullscreen mode). If we see a nil screen, return without
115 // TODO(rohitrao): We should just not save anything for fullscreen windows.
116 // http://crbug.com/36479.
120 // Only save main window information to preferences.
121 PrefService* prefs = browser_->profile()->GetPrefs();
122 if (!prefs || browser_ != chrome::GetLastActiveBrowser())
125 // Save the current work area, in flipped coordinates.
126 gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
127 workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
129 DictionaryPrefUpdate update(
131 chrome::GetWindowPlacementKey(browser_.get()).c_str());
132 base::DictionaryValue* windowPreferences = update.Get();
133 windowPreferences->SetInteger("left", bounds.x());
134 windowPreferences->SetInteger("top", bounds.y());
135 windowPreferences->SetInteger("right", bounds.right());
136 windowPreferences->SetInteger("bottom", bounds.bottom());
137 windowPreferences->SetBoolean("maximized", false);
138 windowPreferences->SetBoolean("always_on_top", false);
139 windowPreferences->SetInteger("work_area_left", workArea.x());
140 windowPreferences->SetInteger("work_area_top", workArea.y());
141 windowPreferences->SetInteger("work_area_right", workArea.right());
142 windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
145 - (NSRect)window:(NSWindow*)window
146 willPositionSheet:(NSWindow*)sheet
147 usingRect:(NSRect)defaultSheetRect {
148 // Position the sheet as follows:
149 // - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
150 // bookmark bar is disabled), position the sheet immediately below the
152 // - If the bookmark bar is shown (attached to the normal toolbar), position
153 // the sheet below the bookmark bar.
154 // - If the bookmark bar is currently animating, position the sheet according
155 // to where the bar will be when the animation ends.
156 switch ([bookmarkBarController_ currentState]) {
157 case BookmarkBar::SHOW: {
158 NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame];
159 defaultSheetRect.origin.y = bookmarkBarFrame.origin.y;
162 case BookmarkBar::HIDDEN:
163 case BookmarkBar::DETACHED: {
164 if ([self hasToolbar]) {
165 NSRect toolbarFrame = [[toolbarController_ view] frame];
166 defaultSheetRect.origin.y = toolbarFrame.origin.y;
168 // The toolbar is not shown in application mode. The sheet should be
169 // located at the top of the window, under the title of the window.
170 defaultSheetRect.origin.y = NSHeight([[window contentView] frame]) -
171 defaultSheetRect.size.height;
176 return defaultSheetRect;
179 - (void)layoutSubviews {
180 // With the exception of the top tab strip, the subviews which we lay out are
181 // subviews of the content view, so we mainly work in the content view's
182 // coordinate system. Note, however, that the content view's coordinate system
183 // and the window's base coordinate system should coincide.
184 NSWindow* window = [self window];
185 NSView* contentView = [window contentView];
186 NSRect contentBounds = [contentView bounds];
187 CGFloat minX = NSMinX(contentBounds);
188 CGFloat minY = NSMinY(contentBounds);
189 CGFloat width = NSWidth(contentBounds);
191 BOOL useSimplifiedFullscreen = CommandLine::ForCurrentProcess()->HasSwitch(
192 switches::kEnableSimplifiedFullscreen);
194 // Suppress title drawing if necessary.
195 if ([window respondsToSelector:@selector(setShouldHideTitle:)])
196 [(id)window setShouldHideTitle:![self hasTitleBar]];
198 // Update z-order. The code below depends on this.
199 [self updateSubviewZOrder:[self inPresentationMode]];
201 BOOL inPresentationMode = [self inPresentationMode];
202 CGFloat floatingBarHeight = [self floatingBarHeight];
203 // In presentation mode, |yOffset| accounts for the sliding position of the
204 // floating bar and the extra offset needed to dodge the menu bar.
205 CGFloat yOffset = inPresentationMode && !useSimplifiedFullscreen ?
206 (std::floor((1 - floatingBarShownFraction_) * floatingBarHeight) -
207 [presentationModeController_ floatingBarVerticalOffset]) : 0;
208 CGFloat maxY = NSMaxY(contentBounds) + yOffset;
210 if ([self hasTabStrip]) {
211 // If we need to lay out the top tab strip, replace |maxY| with a higher
212 // value, and then lay out the tab strip.
213 NSRect windowFrame = [contentView convertRect:[window frame] fromView:nil];
214 maxY = NSHeight(windowFrame) + yOffset;
215 if (useSimplifiedFullscreen && [self isFullscreen]) {
216 CGFloat tabStripHeight = NSHeight([[self tabStripView] frame]);
217 CGFloat revealAmount = (1 - floatingBarShownFraction_) * tabStripHeight;
218 // In simplified fullscreen, only the toolbar is visible by default, and
219 // the tabstrip and menu bar come down (each separately) when the user
220 // mouses near the top of the window. Push the maxY of the toolbar up by
221 // the amount of the tabstrip that is revealed, while removing the amount
222 // of space needed by the menu bar.
224 revealAmount - [fullscreenModeController_ menuBarHeight]);
226 maxY = [self layoutTabStripAtMaxY:maxY
228 fullscreen:[self isFullscreen]];
231 // Sanity-check |maxY|.
232 DCHECK_GE(maxY, minY);
233 DCHECK_LE(maxY, NSMaxY(contentBounds) + yOffset);
235 // Place the toolbar at the top of the reserved area.
236 maxY = [self layoutToolbarAtMinX:minX maxY:maxY width:width];
238 // If we're not displaying the bookmark bar below the info bar, then it goes
239 // immediately below the toolbar.
240 BOOL placeBookmarkBarBelowInfoBar = [self placeBookmarkBarBelowInfoBar];
241 if (!placeBookmarkBarBelowInfoBar)
242 maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width];
244 // The floating bar backing view doesn't actually add any height.
245 NSRect floatingBarBackingRect =
246 NSMakeRect(minX, maxY, width, floatingBarHeight);
247 [self layoutFloatingBarBackingView:floatingBarBackingRect
248 presentationMode:inPresentationMode];
250 // Place the find bar immediately below the toolbar/attached bookmark bar. In
251 // presentation mode, it hangs off the top of the screen when the bar is
253 [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:width];
254 [fullscreenExitBubbleController_ positionInWindowAtTop:maxY width:width];
256 // If in presentation mode, reset |maxY| to top of screen, so that the
257 // floating bar slides over the things which appear to be in the content area.
258 if (inPresentationMode ||
259 (useSimplifiedFullscreen && !fullscreenUrl_.is_empty())) {
260 maxY = NSMaxY(contentBounds);
263 // Also place the info bar container immediate below the toolbar, except in
264 // presentation mode in which case it's at the top of the visual content area.
265 maxY = [self layoutInfoBarAtMinX:minX maxY:maxY width:width];
267 // If the bookmark bar is detached, place it next in the visual content area.
268 if (placeBookmarkBarBelowInfoBar)
269 maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width];
271 // Place the download shelf, if any, at the bottom of the view.
272 minY = [self layoutDownloadShelfAtMinX:minX minY:minY width:width];
274 // Finally, the content area takes up all of the remaining space.
275 NSRect contentAreaRect = NSMakeRect(minX, minY, width, maxY - minY);
276 [self layoutTabContentArea:contentAreaRect];
278 // Normally, we don't need to tell the toolbar whether or not to show the
279 // divider, but things break down during animation.
280 [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
283 - (CGFloat)floatingBarHeight {
284 if (![self inPresentationMode])
287 CGFloat totalHeight = [presentationModeController_ floatingBarVerticalOffset];
289 if ([self hasTabStrip])
290 totalHeight += NSHeight([[self tabStripView] frame]);
292 if ([self hasToolbar]) {
293 totalHeight += NSHeight([[toolbarController_ view] frame]);
294 } else if ([self hasLocationBar]) {
295 totalHeight += NSHeight([[toolbarController_ view] frame]) +
296 kLocBarTopInset + kLocBarBottomInset;
299 if (![self placeBookmarkBarBelowInfoBar])
300 totalHeight += NSHeight([[bookmarkBarController_ view] frame]);
305 - (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
307 fullscreen:(BOOL)fullscreen {
308 // Nothing to do if no tab strip.
309 if (![self hasTabStrip])
312 NSView* tabStripView = [self tabStripView];
313 CGFloat tabStripHeight = NSHeight([tabStripView frame]);
314 maxY -= tabStripHeight;
315 [tabStripView setFrame:NSMakeRect(0, maxY, width, tabStripHeight)];
317 // In Yosemite fullscreen, manually add the fullscreen controls to the tab
319 BOOL isInAppKitFullscreen =
320 [self isInSystemFullscreen] || enteringSystemFullscreen_;
321 BOOL addControlsInFullscreen =
322 isInAppKitFullscreen && base::mac::IsOSYosemiteOrLater();
324 // Set left indentation based on fullscreen mode status.
325 CGFloat leftIndent = 0;
326 if (!fullscreen || addControlsInFullscreen)
327 leftIndent = [[tabStripController_ class] defaultLeftIndentForControls];
328 [tabStripController_ setLeftIndentForControls:leftIndent];
330 if (addControlsInFullscreen)
331 [tabStripController_ addWindowControls];
333 [tabStripController_ removeWindowControls];
335 // Lay out the icognito/avatar badge because calculating the indentation on
336 // the right depends on it.
337 NSView* avatarButton = [avatarButtonController_ view];
338 if ([self shouldShowAvatar]) {
339 CGFloat badgeXOffset = -kAvatarRightOffset;
340 CGFloat badgeYOffset = 0;
341 CGFloat buttonHeight = NSHeight([avatarButton frame]);
343 if ([self shouldUseNewAvatarButton]) {
344 // The fullscreen icon is displayed to the right of the avatar button.
345 if (![self isFullscreen])
346 badgeXOffset -= kFullscreenIconWidth;
347 // Center the button vertically on the tabstrip.
348 badgeYOffset = (tabStripHeight - buttonHeight) / 2;
350 // Actually place the badge *above* |maxY|, by +2 to miss the divider.
351 badgeYOffset = 2 * [[avatarButton superview] cr_lineWidth];
354 [avatarButton setFrameSize:NSMakeSize(NSWidth([avatarButton frame]),
355 std::min(buttonHeight, tabStripHeight))];
357 NSMakePoint(width - NSWidth([avatarButton frame]) + badgeXOffset,
358 maxY + badgeYOffset);
359 [avatarButton setFrameOrigin:origin];
360 [avatarButton setHidden:NO]; // Make sure it's shown.
363 // Calculate the right indentation. The default indentation built into the
364 // tabstrip leaves enough room for the fullscreen button or presentation mode
365 // toggle button on Lion. On non-Lion systems, the right indent needs to be
366 // adjusted to make room for the new tab button when an avatar is present.
367 CGFloat rightIndent = 0;
368 if (base::mac::IsOSLionOrLater() &&
369 [[self window] isKindOfClass:[FramedBrowserWindow class]]) {
370 FramedBrowserWindow* window =
371 static_cast<FramedBrowserWindow*>([self window]);
372 rightIndent += -[window fullScreenButtonOriginAdjustment].x;
374 if ([self shouldUseNewAvatarButton]) {
375 // The new avatar is wider than the default indentation, so we need to
376 // account for its width.
377 rightIndent += NSWidth([avatarButton frame]) + kAvatarTabStripShrink;
379 // When the fullscreen icon is not displayed, return its width to the
381 if ([self isFullscreen])
382 rightIndent -= kFullscreenIconWidth;
384 } else if ([self shouldShowAvatar]) {
385 rightIndent += kAvatarTabStripShrink +
386 NSWidth([avatarButton frame]) + kAvatarRightOffset;
388 [tabStripController_ setRightIndentForControls:rightIndent];
390 // Go ahead and layout the tabs.
391 [tabStripController_ layoutTabsWithoutAnimation];
396 - (CGFloat)layoutToolbarAtMinX:(CGFloat)minX
398 width:(CGFloat)width {
399 NSView* toolbarView = [toolbarController_ view];
400 NSRect toolbarFrame = [toolbarView frame];
401 if ([self hasToolbar]) {
402 // The toolbar is present in the window, so we make room for it.
403 DCHECK(![toolbarView isHidden]);
404 toolbarFrame.origin.x = minX;
405 toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame);
406 toolbarFrame.size.width = width;
407 maxY -= NSHeight(toolbarFrame);
409 if ([self hasLocationBar]) {
410 // Location bar is present with no toolbar. Put a border of
411 // |kLocBar...Inset| pixels around the location bar.
412 // TODO(viettrungluu): This is moderately ridiculous. The toolbar should
413 // really be aware of what its height should be (the way the toolbar
414 // compression stuff is currently set up messes things up).
415 DCHECK(![toolbarView isHidden]);
416 toolbarFrame.origin.x = kLocBarLeftRightInset;
417 toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame) - kLocBarTopInset;
418 toolbarFrame.size.width = width - 2 * kLocBarLeftRightInset;
419 maxY -= kLocBarTopInset + NSHeight(toolbarFrame) + kLocBarBottomInset;
421 DCHECK([toolbarView isHidden]);
424 [toolbarView setFrame:toolbarFrame];
428 - (BOOL)placeBookmarkBarBelowInfoBar {
429 // If we are currently displaying the NTP detached bookmark bar or animating
430 // to/from it (from/to anything else), we display the bookmark bar below the
432 return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
433 [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
434 [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
437 - (CGFloat)layoutBookmarkBarAtMinX:(CGFloat)minX
439 width:(CGFloat)width {
440 [bookmarkBarController_ updateHiddenState];
442 NSView* bookmarkBarView = [bookmarkBarController_ view];
443 NSRect frame = [bookmarkBarView frame];
444 frame.origin.x = minX;
445 frame.origin.y = maxY - NSHeight(frame);
446 frame.size.width = width;
447 [bookmarkBarView setFrame:frame];
448 maxY -= NSHeight(frame);
450 // Pin the bookmark bar to the top of the window and make the width flexible.
451 [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
453 // TODO(viettrungluu): Does this really belong here? Calling it shouldn't be
454 // necessary in the non-NTP case.
455 [bookmarkBarController_ layoutSubviews];
460 - (void)layoutFloatingBarBackingView:(NSRect)frame
461 presentationMode:(BOOL)presentationMode {
462 // Only display when in presentation mode.
463 if (presentationMode) {
464 // For certain window types such as app windows (e.g., the dev tools
465 // window), there's no actual overlay. (Displaying one would result in an
466 // overly sliding in only under the menu, which gives an ugly effect.)
467 if (floatingBarBackingView_.get()) {
469 [floatingBarBackingView_ setFrame:frame];
472 // But we want the logic to work as usual (for show/hide/etc. purposes).
473 [presentationModeController_ overlayFrameChanged:frame];
475 // Okay to call even if |floatingBarBackingView_| is nil.
476 if ([floatingBarBackingView_ superview])
477 [floatingBarBackingView_ removeFromSuperview];
481 - (CGFloat)layoutInfoBarAtMinX:(CGFloat)minX
483 width:(CGFloat)width {
484 NSView* containerView = [infoBarContainerController_ view];
485 NSRect containerFrame = [containerView frame];
486 maxY -= NSHeight(containerFrame);
487 maxY += [infoBarContainerController_ overlappingTipHeight];
488 containerFrame.origin.x = minX;
489 containerFrame.origin.y = maxY;
490 containerFrame.size.width = width;
491 [containerView setFrame:containerFrame];
495 - (CGFloat)layoutDownloadShelfAtMinX:(CGFloat)minX
497 width:(CGFloat)width {
498 if (downloadShelfController_.get()) {
499 NSView* downloadView = [downloadShelfController_ view];
500 NSRect downloadFrame = [downloadView frame];
501 downloadFrame.origin.x = minX;
502 downloadFrame.origin.y = minY;
503 downloadFrame.size.width = width;
504 [downloadView setFrame:downloadFrame];
505 minY += NSHeight(downloadFrame);
510 - (void)layoutTabContentArea:(NSRect)newFrame {
511 NSView* tabContentView = [self tabContentArea];
512 NSRect tabContentFrame = [tabContentView frame];
514 bool contentShifted =
515 NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
516 NSMinX(tabContentFrame) != NSMinX(newFrame);
518 tabContentFrame = newFrame;
519 [tabContentView setFrame:tabContentFrame];
521 // If the relayout shifts the content area up or down, let the renderer know.
522 if (contentShifted) {
523 if (WebContents* contents =
524 browser_->tab_strip_model()->GetActiveWebContents()) {
525 if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
526 rwhv->WindowFrameChanged();
531 - (void)updateRoundedBottomCorners {
532 [[self tabContentArea] setRoundedBottomCorners:![self isFullscreen]];
535 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
537 [toolbarController_ desiredHeightForCompression:compression];
538 NSRect toolbarFrame = [[toolbarController_ view] frame];
539 CGFloat deltaH = newHeight - toolbarFrame.size.height;
544 toolbarFrame.size.height = newHeight;
545 NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
546 bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
547 [[toolbarController_ view] setFrame:toolbarFrame];
548 [[bookmarkBarController_ view] setFrame:bookmarkFrame];
549 [self layoutSubviews];
552 // Fullscreen and presentation mode methods
554 - (void)moveViewsForImmersiveFullscreen:(BOOL)fullscreen
555 regularWindow:(NSWindow*)regularWindow
556 fullscreenWindow:(NSWindow*)fullscreenWindow {
557 NSWindow* sourceWindow = fullscreen ? regularWindow : fullscreenWindow;
558 NSWindow* destWindow = fullscreen ? fullscreenWindow : regularWindow;
560 // Close the bookmark bubble, if it's open. Use |-ok:| instead of |-cancel:|
561 // or |-close| because that matches the behavior when the bubble loses key
563 [bookmarkBubbleController_ ok:self];
565 // Save the current first responder so we can restore after views are moved.
566 base::scoped_nsobject<FocusTracker> focusTracker(
567 [[FocusTracker alloc] initWithWindow:sourceWindow]);
569 // While we move views (and focus) around, disable any bar visibility changes.
570 [self disableBarVisibilityUpdates];
572 // Retain the tab strip view while we remove it from its superview.
573 base::scoped_nsobject<NSView> tabStripView;
574 if ([self hasTabStrip]) {
575 tabStripView.reset([[self tabStripView] retain]);
576 [tabStripView removeFromSuperview];
579 // Ditto for the content view.
580 base::scoped_nsobject<NSView> contentView(
581 [[sourceWindow contentView] retain]);
582 // Disable autoresizing of subviews while we move views around. This prevents
583 // spurious renderer resizes.
584 [contentView setAutoresizesSubviews:NO];
585 [contentView removeFromSuperview];
587 // Have to do this here, otherwise later calls can crash because the window
589 [sourceWindow setDelegate:nil];
590 [destWindow setDelegate:self];
592 // With this call, valgrind complains that a "Conditional jump or move depends
593 // on uninitialised value(s)". The error happens in -[NSThemeFrame
594 // drawOverlayRect:]. I'm pretty convinced this is an Apple bug, but there is
595 // no visual impact. I have been unable to tickle it away with other window
596 // or view manipulation Cocoa calls. Stack added to suppressions_mac.txt.
597 [contentView setAutoresizesSubviews:YES];
598 [destWindow setContentView:contentView];
600 // Move the incognito badge if present.
601 if ([self shouldShowAvatar]) {
602 NSView* avatarButtonView = [avatarButtonController_ view];
604 [avatarButtonView removeFromSuperview];
605 [avatarButtonView setHidden:YES]; // Will be shown in layout.
606 [[destWindow cr_windowView] addSubview:avatarButtonView];
609 // Add the tab strip after setting the content view and moving the incognito
610 // badge (if any), so that the tab strip will be on top (in the z-order).
611 if ([self hasTabStrip])
612 [[destWindow cr_windowView] addSubview:tabStripView];
614 [sourceWindow setWindowController:nil];
615 [self setWindow:destWindow];
616 [destWindow setWindowController:self];
618 // Move the status bubble over, if we have one.
620 statusBubble_->SwitchParentWindow(destWindow);
622 // Move the title over.
623 [destWindow setTitle:[sourceWindow title]];
625 // The window needs to be onscreen before we can set its first responder.
626 // Ordering the window to the front can change the active Space (either to
627 // the window's old Space or to the application's assigned Space). To prevent
628 // this by temporarily change the collectionBehavior.
629 NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior];
630 [destWindow setCollectionBehavior:
631 NSWindowCollectionBehaviorMoveToActiveSpace];
632 [destWindow makeKeyAndOrderFront:self];
633 [destWindow setCollectionBehavior:behavior];
635 if (![focusTracker restoreFocusInWindow:destWindow]) {
636 // During certain types of fullscreen transitions, the view that had focus
637 // may have gone away (e.g., the one for a Flash FS widget). In this case,
638 // FocusTracker will fail to restore focus to anything, so we set the focus
639 // to the tab contents as a reasonable fall-back.
640 [self focusTabContents];
642 [sourceWindow orderOut:self];
644 // We're done moving focus, so re-enable bar visibility changes.
645 [self enableBarVisibilityUpdates];
648 - (void)permissionBubbleWindowWillClose:(NSNotification*)notification {
649 DCHECK(permissionBubbleCocoa_);
651 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
652 [center removeObserver:self
653 name:NSWindowWillCloseNotification
654 object:[notification object]];
655 [self releaseBarVisibilityForOwner:[notification object]
660 - (void)setPresentationModeInternal:(BOOL)presentationMode
661 forceDropdown:(BOOL)forceDropdown {
662 if (presentationMode == [self inPresentationMode])
665 if (presentationMode) {
666 BOOL fullscreen_for_tab =
667 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
669 CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
670 BOOL showDropdown = !fullscreen_for_tab &&
672 (forceDropdown || [self floatingBarHasFocus]);
673 presentationModeController_.reset(
674 [[PresentationModeController alloc] initWithBrowserController:self]);
676 if (permissionBubbleCocoa_ && permissionBubbleCocoa_->IsVisible()) {
677 DCHECK(permissionBubbleCocoa_->window());
678 // A visible permission bubble will force the dropdown to remain visible.
679 [self lockBarVisibilityForOwner:permissionBubbleCocoa_->window()
683 // Register to be notified when the permission bubble is closed, to
684 // allow fullscreen to hide the dropdown.
685 NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
686 [center addObserver:self
687 selector:@selector(permissionBubbleWindowWillClose:)
688 name:NSWindowWillCloseNotification
689 object:permissionBubbleCocoa_->window()];
692 // Turn on layered mode for the window's root view for the entry
693 // animation. Without this, the OS fullscreen animation for entering
694 // fullscreen mode does not correctly draw the tab strip.
695 // It will be turned off (set back to NO) when the animation finishes,
696 // in -windowDidEnterFullScreen:.
697 // Leaving wantsLayer on for the duration of presentation mode causes
698 // performance issues when the dropdown is animated in/out. It also does
699 // not seem to be required for the exit animation.
700 [[[self window] cr_windowView] setWantsLayer:YES];
702 NSView* contentView = [[self window] contentView];
703 [presentationModeController_ enterPresentationModeForContentView:contentView
704 showDropdown:showDropdown];
706 [presentationModeController_ exitPresentationMode];
707 presentationModeController_.reset();
710 [self adjustUIForPresentationMode:presentationMode];
711 [self layoutSubviews];
714 - (void)enterImmersiveFullscreen {
715 // |-isFullscreen:| will return YES from here onwards.
716 enteringFullscreen_ = YES; // Set to NO by |-windowDidEnterFullScreen:|.
719 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
720 Boolean didFadeOut = NO;
721 CGDisplayFadeReservationToken token;
722 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
723 == kCGErrorSuccess) {
725 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
726 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
729 // Create the fullscreen window.
730 fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
731 savedRegularWindow_ = [[self window] retain];
732 savedRegularWindowFrame_ = [savedRegularWindow_ frame];
734 [self moveViewsForImmersiveFullscreen:YES
735 regularWindow:[self window]
736 fullscreenWindow:fullscreenWindow_.get()];
738 // When simplified fullscreen is enabled, do not enter presentation mode.
739 const CommandLine* command_line = CommandLine::ForCurrentProcess();
740 if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)) {
741 // TODO(rohitrao): Add code to manage the menubar here.
743 [self adjustUIForPresentationMode:YES];
744 [self setPresentationModeInternal:YES forceDropdown:NO];
747 [self layoutSubviews];
749 [self windowDidEnterFullScreen:nil];
753 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
754 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
755 CGReleaseDisplayFadeReservation(token);
759 - (void)exitImmersiveFullscreen {
761 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
762 Boolean didFadeOut = NO;
763 CGDisplayFadeReservationToken token;
764 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
765 == kCGErrorSuccess) {
767 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
768 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
771 // When simplified fullscreen is enabled, the menubar status is managed
773 const CommandLine* command_line = CommandLine::ForCurrentProcess();
774 if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)) {
775 // TODO(rohitrao): Add code to manage the menubar here.
778 [self windowWillExitFullScreen:nil];
780 [self moveViewsForImmersiveFullscreen:NO
781 regularWindow:savedRegularWindow_
782 fullscreenWindow:fullscreenWindow_.get()];
784 // When exiting fullscreen mode, we need to call layoutSubviews manually.
785 [savedRegularWindow_ autorelease];
786 savedRegularWindow_ = nil;
787 fullscreenWindow_.reset();
788 [self layoutSubviews];
790 [self windowDidExitFullScreen:nil];
794 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
795 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
796 CGReleaseDisplayFadeReservation(token);
800 // TODO(rohitrao): This function has shrunk into uselessness, and
801 // |-setFullscreen:| has grown rather large. Find a good way to break up
802 // |-setFullscreen:| into smaller pieces. http://crbug.com/36449
803 - (void)adjustUIForPresentationMode:(BOOL)fullscreen {
804 // Create the floating bar backing view if necessary.
805 if (fullscreen && !floatingBarBackingView_.get() &&
806 ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
807 floatingBarBackingView_.reset(
808 [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
809 [floatingBarBackingView_ setAutoresizingMask:(NSViewWidthSizable |
813 // Force the bookmark bar z-order to update.
814 [[bookmarkBarController_ view] removeFromSuperview];
815 [self updateSubviewZOrder:fullscreen];
816 [self updateAllowOverlappingViews:fullscreen];
819 - (void)showFullscreenExitBubbleIfNecessary {
820 // This method is called in response to
821 // |-updateFullscreenExitBubbleURL:bubbleType:|. If we're in the middle of the
822 // transition into fullscreen (i.e., using the System Fullscreen API), do not
823 // show the bubble because it will cause visual jank
824 // (http://crbug.com/130649). This will be called again as part of
825 // |-windowDidEnterFullScreen:|, so arrange to do that work then instead.
826 if (enteringFullscreen_)
829 [self hideOverlayIfPossibleWithAnimation:NO delay:NO];
831 if (fullscreenBubbleType_ == FEB_TYPE_NONE ||
832 fullscreenBubbleType_ == FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) {
833 // Show no exit instruction bubble on Mac when in Browser Fullscreen.
834 [self destroyFullscreenExitBubbleIfNecessary];
836 [fullscreenExitBubbleController_ closeImmediately];
837 fullscreenExitBubbleController_.reset(
838 [[FullscreenExitBubbleController alloc]
840 browser:browser_.get()
842 bubbleType:fullscreenBubbleType_]);
843 [fullscreenExitBubbleController_ showWindow];
847 - (void)destroyFullscreenExitBubbleIfNecessary {
848 [fullscreenExitBubbleController_ closeImmediately];
849 fullscreenExitBubbleController_.reset();
852 - (void)contentViewDidResize:(NSNotification*)notification {
853 [self layoutSubviews];
856 - (void)registerForContentViewResizeNotifications {
857 [[NSNotificationCenter defaultCenter]
859 selector:@selector(contentViewDidResize:)
860 name:NSViewFrameDidChangeNotification
861 object:[[self window] contentView]];
864 - (void)deregisterForContentViewResizeNotifications {
865 [[NSNotificationCenter defaultCenter]
867 name:NSViewFrameDidChangeNotification
868 object:[[self window] contentView]];
871 - (NSSize)window:(NSWindow*)window
872 willUseFullScreenContentSize:(NSSize)proposedSize {
876 - (NSApplicationPresentationOptions)window:(NSWindow*)window
877 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
879 NSApplicationPresentationAutoHideDock |
880 NSApplicationPresentationAutoHideMenuBar);
883 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
884 if (notification) // For System Fullscreen when non-nil.
885 [self registerForContentViewResizeNotifications];
887 NSWindow* window = [self window];
888 savedRegularWindowFrame_ = [window frame];
889 BOOL mode = enteringPresentationMode_ ||
890 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
891 enteringFullscreen_ = YES;
892 enteringSystemFullscreen_ = YES;
893 [self setPresentationModeInternal:mode forceDropdown:NO];
896 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
897 // In Yosemite, some combination of the titlebar and toolbar always show in
898 // full-screen mode. We do not want either to show. Search for the window that
899 // contains the views, and hide it. There is no need to ever unhide the view.
900 // http://crbug.com/380235
901 if (base::mac::IsOSYosemiteOrLater()) {
902 for (NSWindow* window in [[NSApplication sharedApplication] windows]) {
904 isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
905 [window.contentView setHidden:YES];
910 if (notification) // For System Fullscreen when non-nil.
911 [self deregisterForContentViewResizeNotifications];
912 enteringFullscreen_ = NO;
913 enteringSystemFullscreen_ = NO;
914 enteringPresentationMode_ = NO;
916 const CommandLine* command_line = CommandLine::ForCurrentProcess();
917 if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen) &&
918 fullscreenUrl_.is_empty()) {
919 fullscreenModeController_.reset([[FullscreenModeController alloc]
920 initWithBrowserWindowController:self]);
923 [self showFullscreenExitBubbleIfNecessary];
924 browser_->WindowFullscreenStateChanged();
925 [[[self window] cr_windowView] setWantsLayer:NO];
926 [self updateRoundedBottomCorners];
929 - (void)windowWillExitFullScreen:(NSNotification*)notification {
930 if (notification) // For System Fullscreen when non-nil.
931 [self registerForContentViewResizeNotifications];
932 fullscreenModeController_.reset();
933 [self destroyFullscreenExitBubbleIfNecessary];
934 [self setPresentationModeInternal:NO forceDropdown:NO];
937 - (void)windowDidExitFullScreen:(NSNotification*)notification {
938 if (notification) // For System Fullscreen when non-nil.
939 [self deregisterForContentViewResizeNotifications];
940 browser_->WindowFullscreenStateChanged();
941 [self updateRoundedBottomCorners];
944 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
945 [self deregisterForContentViewResizeNotifications];
946 enteringFullscreen_ = NO;
947 enteringSystemFullscreen_ = NO;
948 [self setPresentationModeInternal:NO forceDropdown:NO];
950 // Force a relayout to try and get the window back into a reasonable state.
951 [self layoutSubviews];
954 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
955 [self deregisterForContentViewResizeNotifications];
957 // Force a relayout to try and get the window back into a reasonable state.
958 [self layoutSubviews];
961 - (void)enableBarVisibilityUpdates {
962 // Early escape if there's nothing to do.
963 if (barVisibilityUpdatesEnabled_)
966 barVisibilityUpdatesEnabled_ = YES;
968 if ([barVisibilityLocks_ count])
969 [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
971 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
974 - (void)disableBarVisibilityUpdates {
975 // Early escape if there's nothing to do.
976 if (!barVisibilityUpdatesEnabled_)
979 barVisibilityUpdatesEnabled_ = NO;
980 [presentationModeController_ cancelAnimationAndTimers];
983 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
984 if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
986 [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
990 - (CGFloat)toolbarDividerOpacity {
991 return [bookmarkBarController_ toolbarDividerOpacity];
994 - (void)updateSubviewZOrder:(BOOL)inPresentationMode {
995 NSView* contentView = [[self window] contentView];
996 NSView* toolbarView = [toolbarController_ view];
998 if (inPresentationMode) {
999 // Toolbar is above tab contents so that it can slide down from top of
1001 [contentView cr_ensureSubview:toolbarView
1002 isPositioned:NSWindowAbove
1003 relativeTo:[self tabContentArea]];
1005 // Toolbar is below tab contents so that the info bar arrow can appear above
1007 [contentView cr_ensureSubview:toolbarView
1008 isPositioned:NSWindowBelow
1009 relativeTo:[self tabContentArea]];
1012 // The bookmark bar is always below the toolbar.
1013 [contentView cr_ensureSubview:[bookmarkBarController_ view]
1014 isPositioned:NSWindowBelow
1015 relativeTo:toolbarView];
1017 if (inPresentationMode) {
1018 // In presentation mode the info bar is below all other views.
1019 [contentView cr_ensureSubview:[infoBarContainerController_ view]
1020 isPositioned:NSWindowBelow
1021 relativeTo:[self tabContentArea]];
1023 // Above the toolbar but still below tab contents. Similar to the bookmark
1024 // bar, this allows Instant results to be above the info bar.
1025 [contentView cr_ensureSubview:[infoBarContainerController_ view]
1026 isPositioned:NSWindowAbove
1027 relativeTo:toolbarView];
1030 // The find bar is above everything.
1031 if (findBarCocoaController_) {
1032 NSView* relativeView = nil;
1033 if (inPresentationMode)
1034 relativeView = toolbarView;
1036 relativeView = [self tabContentArea];
1037 [contentView cr_ensureSubview:[findBarCocoaController_ view]
1038 isPositioned:NSWindowAbove
1039 relativeTo:relativeView];
1042 if (floatingBarBackingView_) {
1043 if ([floatingBarBackingView_ cr_isBelowView:[self tabContentArea]])
1044 [floatingBarBackingView_ removeFromSuperview];
1045 if ([self placeBookmarkBarBelowInfoBar]) {
1046 [contentView cr_ensureSubview:floatingBarBackingView_
1047 isPositioned:NSWindowAbove
1048 relativeTo:[bookmarkBarController_ view]];
1050 [contentView cr_ensureSubview:floatingBarBackingView_
1051 isPositioned:NSWindowBelow
1052 relativeTo:[bookmarkBarController_ view]];
1057 - (BOOL)shouldAllowOverlappingViews:(BOOL)inPresentationMode {
1058 if (inPresentationMode)
1061 if (findBarCocoaController_ &&
1062 ![[findBarCocoaController_ findBarView] isHidden]) {
1066 if (overlappedViewCount_)
1072 - (void)updateAllowOverlappingViews:(BOOL)inPresentationMode {
1073 WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
1077 BOOL allowOverlappingViews =
1078 [self shouldAllowOverlappingViews:inPresentationMode];
1080 // The rendering path with overlapping views disabled causes bugs when
1081 // transitioning between composited and non-composited mode.
1082 // http://crbug.com/279472
1083 allowOverlappingViews = YES;
1084 contents->SetAllowOverlappingViews(allowOverlappingViews);
1086 WebContents* devTools = DevToolsWindow::GetInTabWebContents(contents, NULL);
1088 devTools->SetAllowOverlappingViews(allowOverlappingViews);
1091 - (void)updateInfoBarTipVisibility {
1092 // If there's no toolbar then hide the infobar tip.
1093 [infoBarContainerController_
1094 setShouldSuppressTopInfoBarTip:![self hasToolbar]];
1097 @end // @implementation BrowserWindowController(Private)