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 #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"
47 using content::RenderWidgetHostView;
48 using content::WebContents;
52 // Space between the incognito badge and the right edge of the window.
53 const CGFloat kAvatarRightOffset = 4;
55 // The amount by which to shrink the tab strip (on the right) when the
56 // incognito badge is present.
57 const CGFloat kAvatarTabStripShrink = 18;
59 // Width of the full screen icon. Used to position the AvatarButton to the
61 const CGFloat kFullscreenIconWidth = 32;
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.
66 const CGFloat kLocBarLeftRightInset = 1;
67 const CGFloat kLocBarTopInset = 0;
68 const CGFloat kLocBarBottomInset = 1;
72 @implementation BrowserWindowController(Private)
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()
85 - (void)saveWindowPositionIfNeeded {
86 if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
89 // If we're in fullscreen mode, save the position of the regular window
91 NSWindow* window = [self isFullscreen] ? savedRegularWindow_ : [self window];
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];
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());
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);
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
113 // TODO(rohitrao): We should just not save anything for fullscreen windows.
114 // http://crbug.com/36479.
118 // Only save main window information to preferences.
119 PrefService* prefs = browser_->profile()->GetPrefs();
120 if (!prefs || browser_ != chrome::GetLastActiveBrowser())
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());
127 DictionaryPrefUpdate update(
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());
143 - (NSRect)window:(NSWindow*)window
144 willPositionSheet:(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
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;
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;
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;
174 return defaultSheetRect;
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);
189 BOOL useSimplifiedFullscreen = CommandLine::ForCurrentProcess()->HasSwitch(
190 switches::kEnableSimplifiedFullscreen);
192 // Suppress title drawing if necessary.
193 if ([window respondsToSelector:@selector(setShouldHideTitle:)])
194 [(id)window setShouldHideTitle:![self hasTitleBar]];
196 // Update z-order. The code below depends on this.
197 [self updateSubviewZOrder:[self inPresentationMode]];
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;
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.
222 revealAmount - [fullscreenModeController_ menuBarHeight]);
224 maxY = [self layoutTabStripAtMaxY:maxY
226 fullscreen:[self isFullscreen]];
229 // Sanity-check |maxY|.
230 DCHECK_GE(maxY, minY);
231 DCHECK_LE(maxY, NSMaxY(contentBounds) + yOffset);
233 // Place the toolbar at the top of the reserved area.
234 maxY = [self layoutToolbarAtMinX:minX maxY:maxY width:width];
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];
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];
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
251 [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:width];
252 [fullscreenExitBubbleController_ positionInWindowAtTop:maxY width:width];
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);
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];
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];
269 // Place the download shelf, if any, at the bottom of the view.
270 minY = [self layoutDownloadShelfAtMinX:minX minY:minY width:width];
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];
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]];
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];
295 - (CGFloat)floatingBarHeight {
296 if (![self inPresentationMode])
299 CGFloat totalHeight = [presentationModeController_ floatingBarVerticalOffset];
301 if ([self hasTabStrip])
302 totalHeight += NSHeight([[self tabStripView] frame]);
304 if ([self hasToolbar]) {
305 totalHeight += NSHeight([[toolbarController_ view] frame]);
306 } else if ([self hasLocationBar]) {
307 totalHeight += NSHeight([[toolbarController_ view] frame]) +
308 kLocBarTopInset + kLocBarBottomInset;
311 if (![self placeBookmarkBarBelowInfoBar])
312 totalHeight += NSHeight([[bookmarkBarController_ view] frame]);
317 - (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
319 fullscreen:(BOOL)fullscreen {
320 // Nothing to do if no tab strip.
321 if (![self hasTabStrip])
324 NSView* tabStripView = [self tabStripView];
325 CGFloat tabStripHeight = NSHeight([tabStripView frame]);
326 maxY -= tabStripHeight;
327 [tabStripView setFrame:NSMakeRect(0, maxY, width, tabStripHeight)];
329 // Set left indentation based on fullscreen mode status.
330 [tabStripController_ setLeftIndentForControls:(fullscreen ? 0 :
331 [[tabStripController_ class] defaultLeftIndentForControls])];
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]);
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;
348 // Actually place the badge *above* |maxY|, by +2 to miss the divider.
349 badgeYOffset = 2 * [[avatarButton superview] cr_lineWidth];
352 [avatarButton setFrameSize:NSMakeSize(NSWidth([avatarButton frame]),
353 std::min(buttonHeight, tabStripHeight))];
355 NSMakePoint(width - NSWidth([avatarButton frame]) + badgeXOffset,
356 maxY + badgeYOffset);
357 [avatarButton setFrameOrigin:origin];
358 [avatarButton setHidden:NO]; // Make sure it's shown.
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;
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;
380 [tabStripController_ setRightIndentForControls:rightIndent];
382 // Go ahead and layout the tabs.
383 [tabStripController_ layoutTabsWithoutAnimation];
388 - (CGFloat)layoutToolbarAtMinX:(CGFloat)minX
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);
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;
413 DCHECK([toolbarView isHidden]);
416 [toolbarView setFrame:toolbarFrame];
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
424 return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
425 [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
426 [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
429 - (CGFloat)layoutBookmarkBarAtMinX:(CGFloat)minX
431 width:(CGFloat)width {
432 [bookmarkBarController_ updateHiddenState];
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);
442 // Pin the bookmark bar to the top of the window and make the width flexible.
443 [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
445 // TODO(viettrungluu): Does this really belong here? Calling it shouldn't be
446 // necessary in the non-NTP case.
447 [bookmarkBarController_ layoutSubviews];
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()) {
461 [floatingBarBackingView_ setFrame:frame];
464 // But we want the logic to work as usual (for show/hide/etc. purposes).
465 [presentationModeController_ overlayFrameChanged:frame];
467 // Okay to call even if |floatingBarBackingView_| is nil.
468 if ([floatingBarBackingView_ superview])
469 [floatingBarBackingView_ removeFromSuperview];
473 - (CGFloat)layoutInfoBarAtMinX:(CGFloat)minX
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];
487 - (CGFloat)layoutDownloadShelfAtMinX:(CGFloat)minX
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);
502 - (void)layoutTabContentArea:(NSRect)newFrame {
503 NSView* tabContentView = [self tabContentArea];
504 NSRect tabContentFrame = [tabContentView frame];
506 bool contentShifted =
507 NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
508 NSMinX(tabContentFrame) != NSMinX(newFrame);
510 tabContentFrame = newFrame;
511 [tabContentView setFrame:tabContentFrame];
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();
523 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
525 [toolbarController_ desiredHeightForCompression:compression];
526 NSRect toolbarFrame = [[toolbarController_ view] frame];
527 CGFloat deltaH = newHeight - toolbarFrame.size.height;
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];
540 // Fullscreen and presentation mode methods
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;
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
551 [bookmarkBubbleController_ ok:self];
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]);
557 // While we move views (and focus) around, disable any bar visibility changes.
558 [self disableBarVisibilityUpdates];
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];
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];
575 // Have to do this here, otherwise later calls can crash because the window
577 [sourceWindow setDelegate:nil];
578 [destWindow setDelegate:self];
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];
588 // Move the incognito badge if present.
589 if ([self shouldShowAvatar]) {
590 NSView* avatarButtonView = [avatarButtonController_ view];
592 [avatarButtonView removeFromSuperview];
593 [avatarButtonView setHidden:YES]; // Will be shown in layout.
594 [[[destWindow contentView] superview] addSubview: avatarButtonView];
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];
602 [sourceWindow setWindowController:nil];
603 [self setWindow:destWindow];
604 [destWindow setWindowController:self];
606 // Move the status bubble over, if we have one.
608 statusBubble_->SwitchParentWindow(destWindow);
610 // Move the title over.
611 [destWindow setTitle:[sourceWindow title]];
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];
623 [focusTracker restoreFocusInWindow:destWindow];
624 [sourceWindow orderOut:self];
626 // We're done moving focus, so re-enable bar visibility changes.
627 [self enableBarVisibilityUpdates];
630 - (void)setPresentationModeInternal:(BOOL)presentationMode
631 forceDropdown:(BOOL)forceDropdown {
632 if (presentationMode == [self inPresentationMode])
635 if (presentationMode) {
636 BOOL fullscreen_for_tab =
637 browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
639 CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
640 BOOL showDropdown = !fullscreen_for_tab &&
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];
649 [presentationModeController_ exitPresentationMode];
650 presentationModeController_.reset();
653 [self adjustUIForPresentationMode:presentationMode];
654 [self layoutSubviews];
657 - (void)enterImmersiveFullscreen {
658 // |-isFullscreen:| will return YES from here onwards.
659 enteringFullscreen_ = YES; // Set to NO by |-windowDidEnterFullScreen:|.
662 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
663 Boolean didFadeOut = NO;
664 CGDisplayFadeReservationToken token;
665 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
666 == kCGErrorSuccess) {
668 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
669 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
672 // Create the fullscreen window.
673 fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
674 savedRegularWindow_ = [[self window] retain];
675 savedRegularWindowFrame_ = [savedRegularWindow_ frame];
677 [self moveViewsForImmersiveFullscreen:YES
678 regularWindow:[self window]
679 fullscreenWindow:fullscreenWindow_.get()];
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.
686 [self adjustUIForPresentationMode:YES];
687 [self setPresentationModeInternal:YES forceDropdown:NO];
690 [self layoutSubviews];
692 [self windowDidEnterFullScreen:nil];
696 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
697 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
698 CGReleaseDisplayFadeReservation(token);
702 - (void)exitImmersiveFullscreen {
704 const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
705 Boolean didFadeOut = NO;
706 CGDisplayFadeReservationToken token;
707 if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
708 == kCGErrorSuccess) {
710 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
711 kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
714 // When simplified fullscreen is enabled, the menubar status is managed
716 const CommandLine* command_line = CommandLine::ForCurrentProcess();
717 if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)) {
718 // TODO(rohitrao): Add code to manage the menubar here.
721 [self windowWillExitFullScreen:nil];
723 [self moveViewsForImmersiveFullscreen:NO
724 regularWindow:savedRegularWindow_
725 fullscreenWindow:fullscreenWindow_.get()];
727 // When exiting fullscreen mode, we need to call layoutSubviews manually.
728 [savedRegularWindow_ autorelease];
729 savedRegularWindow_ = nil;
730 fullscreenWindow_.reset();
731 [self layoutSubviews];
733 [self windowDidExitFullScreen:nil];
737 CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
738 kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
739 CGReleaseDisplayFadeReservation(token);
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 |
756 // Force the bookmark bar z-order to update.
757 [[bookmarkBarController_ view] removeFromSuperview];
758 [self updateSubviewZOrder:fullscreen];
759 [self updateAllowOverlappingViews:fullscreen];
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_)
772 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
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];
779 [fullscreenExitBubbleController_ closeImmediately];
780 fullscreenExitBubbleController_.reset(
781 [[FullscreenExitBubbleController alloc]
783 browser:browser_.get()
785 bubbleType:fullscreenBubbleType_]);
786 [fullscreenExitBubbleController_ showWindow];
790 - (void)destroyFullscreenExitBubbleIfNecessary {
791 [fullscreenExitBubbleController_ closeImmediately];
792 fullscreenExitBubbleController_.reset();
795 - (void)contentViewDidResize:(NSNotification*)notification {
796 [self layoutSubviews];
799 - (void)registerForContentViewResizeNotifications {
800 [[NSNotificationCenter defaultCenter]
802 selector:@selector(contentViewDidResize:)
803 name:NSViewFrameDidChangeNotification
804 object:[[self window] contentView]];
807 - (void)deregisterForContentViewResizeNotifications {
808 [[NSNotificationCenter defaultCenter]
810 name:NSViewFrameDidChangeNotification
811 object:[[self window] contentView]];
814 - (NSSize)window:(NSWindow*)window
815 willUseFullScreenContentSize:(NSSize)proposedSize {
819 - (NSApplicationPresentationOptions)window:(NSWindow*)window
820 willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
822 NSApplicationPresentationAutoHideDock |
823 NSApplicationPresentationAutoHideMenuBar);
826 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
827 if (notification) // For System Fullscreen when non-nil.
828 [self registerForContentViewResizeNotifications];
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];
838 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
839 if (notification) // For System Fullscreen when non-nil.
840 [self deregisterForContentViewResizeNotifications];
841 enteringFullscreen_ = NO;
842 enteringPresentationMode_ = NO;
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]);
851 [self showFullscreenExitBubbleIfNecessary];
852 browser_->WindowFullscreenStateChanged();
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];
863 - (void)windowDidExitFullScreen:(NSNotification*)notification {
864 if (notification) // For System Fullscreen when non-nil.
865 [self deregisterForContentViewResizeNotifications];
866 browser_->WindowFullscreenStateChanged();
869 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
870 [self deregisterForContentViewResizeNotifications];
871 enteringFullscreen_ = NO;
872 [self setPresentationModeInternal:NO forceDropdown:NO];
874 // Force a relayout to try and get the window back into a reasonable state.
875 [self layoutSubviews];
878 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
879 [self deregisterForContentViewResizeNotifications];
881 // Force a relayout to try and get the window back into a reasonable state.
882 [self layoutSubviews];
885 - (void)enableBarVisibilityUpdates {
886 // Early escape if there's nothing to do.
887 if (barVisibilityUpdatesEnabled_)
890 barVisibilityUpdatesEnabled_ = YES;
892 if ([barVisibilityLocks_ count])
893 [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
895 [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
898 - (void)disableBarVisibilityUpdates {
899 // Early escape if there's nothing to do.
900 if (!barVisibilityUpdatesEnabled_)
903 barVisibilityUpdatesEnabled_ = NO;
904 [presentationModeController_ cancelAnimationAndTimers];
907 - (CGFloat)toolbarDividerOpacity {
908 return [bookmarkBarController_ toolbarDividerOpacity];
911 - (void)updateSubviewZOrder:(BOOL)inPresentationMode {
912 NSView* contentView = [[self window] contentView];
913 NSView* toolbarView = [toolbarController_ view];
915 if (inPresentationMode) {
916 // Toolbar is above tab contents so that it can slide down from top of
918 [contentView cr_ensureSubview:toolbarView
919 isPositioned:NSWindowAbove
920 relativeTo:[self tabContentArea]];
922 // Toolbar is below tab contents so that the info bar arrow can appear above
924 [contentView cr_ensureSubview:toolbarView
925 isPositioned:NSWindowBelow
926 relativeTo:[self tabContentArea]];
929 // The bookmark bar is always below the toolbar.
930 [contentView cr_ensureSubview:[bookmarkBarController_ view]
931 isPositioned:NSWindowBelow
932 relativeTo:toolbarView];
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]];
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];
947 // The find bar is above everything.
948 if (findBarCocoaController_) {
949 NSView* relativeView = nil;
950 if (inPresentationMode)
951 relativeView = toolbarView;
953 relativeView = [self tabContentArea];
954 [contentView cr_ensureSubview:[findBarCocoaController_ view]
955 isPositioned:NSWindowAbove
956 relativeTo:relativeView];
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]];
967 [contentView cr_ensureSubview:floatingBarBackingView_
968 isPositioned:NSWindowBelow
969 relativeTo:[bookmarkBarController_ view]];
974 - (BOOL)shouldAllowOverlappingViews:(BOOL)inPresentationMode {
975 if (inPresentationMode)
978 if (findBarCocoaController_ &&
979 ![[findBarCocoaController_ findBarView] isHidden]) {
983 if (overlappedViewCount_)
989 - (void)updateAllowOverlappingViews:(BOOL)inPresentationMode {
990 WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
994 BOOL allowOverlappingViews =
995 [self shouldAllowOverlappingViews:inPresentationMode];
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);
1003 DevToolsWindow* devToolsWindow =
1004 DevToolsWindow::GetDockedInstanceForInspectedTab(contents);
1005 if (devToolsWindow) {
1006 devToolsWindow->web_contents()->
1007 SetAllowOverlappingViews(allowOverlappingViews);
1011 - (void)updateInfoBarTipVisibility {
1012 // If there's no toolbar then hide the infobar tip.
1013 [infoBarContainerController_
1014 setShouldSuppressTopInfoBarTip:![self hasToolbar]];
1017 @end // @implementation BrowserWindowController(Private)