• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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