Upstream version 10.38.208.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / cocoa / browser_window_controller_private.mm
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
6
7 #include <cmath>
8
9 #include "base/command_line.h"
10 #include "base/mac/mac_util.h"
11 #import "base/mac/scoped_nsobject.h"
12 #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"
48
49 using content::RenderWidgetHostView;
50 using content::WebContents;
51
52 namespace {
53
54 // Space between the incognito badge and the right edge of the window.
55 const CGFloat kAvatarRightOffset = 4;
56
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;
60
61 // Width of the full screen icon. Used to position the AvatarButton to the
62 // left of the icon.
63 const CGFloat kFullscreenIconWidth = 32;
64
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;
71
72 }  // namespace
73
74 @implementation BrowserWindowController(Private)
75
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()
84           delegate:self]);
85 }
86
87 - (void)saveWindowPositionIfNeeded {
88   if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
89     return;
90
91   // If we're in fullscreen mode, save the position of the regular window
92   // instead.
93   NSWindow* window = [self isFullscreen] ? savedRegularWindow_ : [self window];
94
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];
98
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());
103
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);
111
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
114   // saving.
115   // TODO(rohitrao): We should just not save anything for fullscreen windows.
116   // http://crbug.com/36479.
117   if (!windowScreen)
118     return;
119
120   // Only save main window information to preferences.
121   PrefService* prefs = browser_->profile()->GetPrefs();
122   if (!prefs || browser_ != chrome::GetLastActiveBrowser())
123     return;
124
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());
128
129   DictionaryPrefUpdate update(
130       prefs,
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());
143 }
144
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
151   //    normal toolbar.
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;
160       break;
161     }
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;
167       } else {
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;
172       }
173       break;
174     }
175   }
176   return defaultSheetRect;
177 }
178
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);
190
191   BOOL useSimplifiedFullscreen = CommandLine::ForCurrentProcess()->HasSwitch(
192       switches::kEnableSimplifiedFullscreen);
193
194   // Suppress title drawing if necessary.
195   if ([window respondsToSelector:@selector(setShouldHideTitle:)])
196     [(id)window setShouldHideTitle:![self hasTitleBar]];
197
198   // Update z-order. The code below depends on this.
199   [self updateSubviewZOrder:[self inPresentationMode]];
200
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;
209
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.
223       maxY += std::floor(
224           revealAmount - [fullscreenModeController_ menuBarHeight]);
225     }
226     maxY = [self layoutTabStripAtMaxY:maxY
227                                 width:width
228                            fullscreen:[self isFullscreen]];
229   }
230
231   // Sanity-check |maxY|.
232   DCHECK_GE(maxY, minY);
233   DCHECK_LE(maxY, NSMaxY(contentBounds) + yOffset);
234
235   // Place the toolbar at the top of the reserved area.
236   maxY = [self layoutToolbarAtMinX:minX maxY:maxY width:width];
237
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];
243
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];
249
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
252   // hidden.
253   [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:width];
254   [fullscreenExitBubbleController_ positionInWindowAtTop:maxY width:width];
255
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);
261   }
262
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];
266
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];
270
271   // Place the download shelf, if any, at the bottom of the view.
272   minY = [self layoutDownloadShelfAtMinX:minX minY:minY width:width];
273
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];
277
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]];
281 }
282
283 - (CGFloat)floatingBarHeight {
284   if (![self inPresentationMode])
285     return 0;
286
287   CGFloat totalHeight = [presentationModeController_ floatingBarVerticalOffset];
288
289   if ([self hasTabStrip])
290     totalHeight += NSHeight([[self tabStripView] frame]);
291
292   if ([self hasToolbar]) {
293     totalHeight += NSHeight([[toolbarController_ view] frame]);
294   } else if ([self hasLocationBar]) {
295     totalHeight += NSHeight([[toolbarController_ view] frame]) +
296                    kLocBarTopInset + kLocBarBottomInset;
297   }
298
299   if (![self placeBookmarkBarBelowInfoBar])
300     totalHeight += NSHeight([[bookmarkBarController_ view] frame]);
301
302   return totalHeight;
303 }
304
305 - (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
306                           width:(CGFloat)width
307                      fullscreen:(BOOL)fullscreen {
308   // Nothing to do if no tab strip.
309   if (![self hasTabStrip])
310     return maxY;
311
312   NSView* tabStripView = [self tabStripView];
313   CGFloat tabStripHeight = NSHeight([tabStripView frame]);
314   maxY -= tabStripHeight;
315   [tabStripView setFrame:NSMakeRect(0, maxY, width, tabStripHeight)];
316
317   // In Yosemite fullscreen, manually add the fullscreen controls to the tab
318   // strip.
319   BOOL isInAppKitFullscreen =
320       [self isInSystemFullscreen] || enteringSystemFullscreen_;
321   BOOL addControlsInFullscreen =
322       isInAppKitFullscreen && base::mac::IsOSYosemiteOrLater();
323
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];
329
330   if (addControlsInFullscreen)
331     [tabStripController_ addWindowControls];
332   else
333     [tabStripController_ removeWindowControls];
334
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]);
342
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;
349     } else {
350       // Actually place the badge *above* |maxY|, by +2 to miss the divider.
351       badgeYOffset = 2 * [[avatarButton superview] cr_lineWidth];
352     }
353
354     [avatarButton setFrameSize:NSMakeSize(NSWidth([avatarButton frame]),
355         std::min(buttonHeight, tabStripHeight))];
356     NSPoint origin =
357         NSMakePoint(width - NSWidth([avatarButton frame]) + badgeXOffset,
358                     maxY + badgeYOffset);
359     [avatarButton setFrameOrigin:origin];
360     [avatarButton setHidden:NO];  // Make sure it's shown.
361   }
362
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;
373
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;
378
379       // When the fullscreen icon is not displayed, return its width to the
380       // tabstrip.
381       if ([self isFullscreen])
382         rightIndent -= kFullscreenIconWidth;
383     }
384   } else if ([self shouldShowAvatar]) {
385     rightIndent += kAvatarTabStripShrink +
386         NSWidth([avatarButton frame]) + kAvatarRightOffset;
387   }
388   [tabStripController_ setRightIndentForControls:rightIndent];
389
390   // Go ahead and layout the tabs.
391   [tabStripController_ layoutTabsWithoutAnimation];
392
393   return maxY;
394 }
395
396 - (CGFloat)layoutToolbarAtMinX:(CGFloat)minX
397                           maxY:(CGFloat)maxY
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);
408   } else {
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;
420     } else {
421       DCHECK([toolbarView isHidden]);
422     }
423   }
424   [toolbarView setFrame:toolbarFrame];
425   return maxY;
426 }
427
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
431   // info bar.
432   return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
433          [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
434          [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
435 }
436
437 - (CGFloat)layoutBookmarkBarAtMinX:(CGFloat)minX
438                               maxY:(CGFloat)maxY
439                              width:(CGFloat)width {
440   [bookmarkBarController_ updateHiddenState];
441
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);
449
450   // Pin the bookmark bar to the top of the window and make the width flexible.
451   [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
452
453   // TODO(viettrungluu): Does this really belong here? Calling it shouldn't be
454   // necessary in the non-NTP case.
455   [bookmarkBarController_ layoutSubviews];
456
457   return maxY;
458 }
459
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()) {
468       // Set its frame.
469       [floatingBarBackingView_ setFrame:frame];
470     }
471
472     // But we want the logic to work as usual (for show/hide/etc. purposes).
473     [presentationModeController_ overlayFrameChanged:frame];
474   } else {
475     // Okay to call even if |floatingBarBackingView_| is nil.
476     if ([floatingBarBackingView_ superview])
477       [floatingBarBackingView_ removeFromSuperview];
478   }
479 }
480
481 - (CGFloat)layoutInfoBarAtMinX:(CGFloat)minX
482                           maxY:(CGFloat)maxY
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];
492   return maxY;
493 }
494
495 - (CGFloat)layoutDownloadShelfAtMinX:(CGFloat)minX
496                                 minY:(CGFloat)minY
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);
506   }
507   return minY;
508 }
509
510 - (void)layoutTabContentArea:(NSRect)newFrame {
511   NSView* tabContentView = [self tabContentArea];
512   NSRect tabContentFrame = [tabContentView frame];
513
514   bool contentShifted =
515       NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
516       NSMinX(tabContentFrame) != NSMinX(newFrame);
517
518   tabContentFrame = newFrame;
519   [tabContentView setFrame:tabContentFrame];
520
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();
527     }
528   }
529 }
530
531 - (void)updateRoundedBottomCorners {
532   [[self tabContentArea] setRoundedBottomCorners:![self isFullscreen]];
533 }
534
535 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
536   CGFloat newHeight =
537       [toolbarController_ desiredHeightForCompression:compression];
538   NSRect toolbarFrame = [[toolbarController_ view] frame];
539   CGFloat deltaH = newHeight - toolbarFrame.size.height;
540
541   if (deltaH == 0)
542     return;
543
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];
550 }
551
552 // Fullscreen and presentation mode methods
553
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;
559
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
562   // status.
563   [bookmarkBubbleController_ ok:self];
564
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]);
568
569   // While we move views (and focus) around, disable any bar visibility changes.
570   [self disableBarVisibilityUpdates];
571
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];
577   }
578
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];
586
587   // Have to do this here, otherwise later calls can crash because the window
588   // has no delegate.
589   [sourceWindow setDelegate:nil];
590   [destWindow setDelegate:self];
591
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];
599
600   // Move the incognito badge if present.
601   if ([self shouldShowAvatar]) {
602     NSView* avatarButtonView = [avatarButtonController_ view];
603
604     [avatarButtonView removeFromSuperview];
605     [avatarButtonView setHidden:YES];  // Will be shown in layout.
606     [[destWindow cr_windowView] addSubview:avatarButtonView];
607   }
608
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];
613
614   [sourceWindow setWindowController:nil];
615   [self setWindow:destWindow];
616   [destWindow setWindowController:self];
617
618   // Move the status bubble over, if we have one.
619   if (statusBubble_)
620     statusBubble_->SwitchParentWindow(destWindow);
621
622   // Move the title over.
623   [destWindow setTitle:[sourceWindow title]];
624
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];
634
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];
641   }
642   [sourceWindow orderOut:self];
643
644   // We're done moving focus, so re-enable bar visibility changes.
645   [self enableBarVisibilityUpdates];
646 }
647
648 - (void)permissionBubbleWindowWillClose:(NSNotification*)notification {
649   DCHECK(permissionBubbleCocoa_);
650
651   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
652   [center removeObserver:self
653                     name:NSWindowWillCloseNotification
654                   object:[notification object]];
655   [self releaseBarVisibilityForOwner:[notification object]
656                        withAnimation:YES
657                                delay:YES];
658 }
659
660 - (void)setPresentationModeInternal:(BOOL)presentationMode
661                       forceDropdown:(BOOL)forceDropdown {
662   if (presentationMode == [self inPresentationMode])
663     return;
664
665   if (presentationMode) {
666     BOOL fullscreen_for_tab =
667         browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
668     BOOL kiosk_mode =
669         CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
670     BOOL showDropdown = !fullscreen_for_tab &&
671         !kiosk_mode &&
672         (forceDropdown || [self floatingBarHasFocus]);
673     presentationModeController_.reset(
674         [[PresentationModeController alloc] initWithBrowserController:self]);
675
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()
680                         withAnimation:NO
681                                 delay:NO];
682       showDropdown = YES;
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()];
690     }
691     if (showDropdown) {
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];
701     }
702     NSView* contentView = [[self window] contentView];
703     [presentationModeController_ enterPresentationModeForContentView:contentView
704                                  showDropdown:showDropdown];
705   } else {
706     [presentationModeController_ exitPresentationMode];
707     presentationModeController_.reset();
708   }
709
710   [self adjustUIForPresentationMode:presentationMode];
711   [self layoutSubviews];
712 }
713
714 - (void)enterImmersiveFullscreen {
715   // |-isFullscreen:| will return YES from here onwards.
716   enteringFullscreen_ = YES;  // Set to NO by |-windowDidEnterFullScreen:|.
717
718   // Fade to black.
719   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
720   Boolean didFadeOut = NO;
721   CGDisplayFadeReservationToken token;
722   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
723       == kCGErrorSuccess) {
724     didFadeOut = YES;
725     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
726         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
727   }
728
729   // Create the fullscreen window.
730   fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
731   savedRegularWindow_ = [[self window] retain];
732   savedRegularWindowFrame_ = [savedRegularWindow_ frame];
733
734   [self moveViewsForImmersiveFullscreen:YES
735                           regularWindow:[self window]
736                        fullscreenWindow:fullscreenWindow_.get()];
737
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.
742   } else {
743     [self adjustUIForPresentationMode:YES];
744     [self setPresentationModeInternal:YES forceDropdown:NO];
745   }
746
747   [self layoutSubviews];
748
749   [self windowDidEnterFullScreen:nil];
750
751   // Fade back in.
752   if (didFadeOut) {
753     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
754         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
755     CGReleaseDisplayFadeReservation(token);
756   }
757 }
758
759 - (void)exitImmersiveFullscreen {
760   // Fade to black.
761   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
762   Boolean didFadeOut = NO;
763   CGDisplayFadeReservationToken token;
764   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
765       == kCGErrorSuccess) {
766     didFadeOut = YES;
767     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
768         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
769   }
770
771   // When simplified fullscreen is enabled, the menubar status is managed
772   // directly by BWC.
773   const CommandLine* command_line = CommandLine::ForCurrentProcess();
774   if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)) {
775     // TODO(rohitrao): Add code to manage the menubar here.
776   }
777
778   [self windowWillExitFullScreen:nil];
779
780   [self moveViewsForImmersiveFullscreen:NO
781                           regularWindow:savedRegularWindow_
782                        fullscreenWindow:fullscreenWindow_.get()];
783
784   // When exiting fullscreen mode, we need to call layoutSubviews manually.
785   [savedRegularWindow_ autorelease];
786   savedRegularWindow_ = nil;
787   fullscreenWindow_.reset();
788   [self layoutSubviews];
789
790   [self windowDidExitFullScreen:nil];
791
792   // Fade back in.
793   if (didFadeOut) {
794     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
795         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
796     CGReleaseDisplayFadeReservation(token);
797   }
798 }
799
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 |
810                                                   NSViewMinYMargin)];
811   }
812
813   // Force the bookmark bar z-order to update.
814   [[bookmarkBarController_ view] removeFromSuperview];
815   [self updateSubviewZOrder:fullscreen];
816   [self updateAllowOverlappingViews:fullscreen];
817 }
818
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_)
827     return;
828
829   [self hideOverlayIfPossibleWithAnimation:NO delay:NO];
830
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];
835   } else {
836     [fullscreenExitBubbleController_ closeImmediately];
837     fullscreenExitBubbleController_.reset(
838         [[FullscreenExitBubbleController alloc]
839             initWithOwner:self
840                   browser:browser_.get()
841                       url:fullscreenUrl_
842                bubbleType:fullscreenBubbleType_]);
843     [fullscreenExitBubbleController_ showWindow];
844   }
845 }
846
847 - (void)destroyFullscreenExitBubbleIfNecessary {
848   [fullscreenExitBubbleController_ closeImmediately];
849   fullscreenExitBubbleController_.reset();
850 }
851
852 - (void)contentViewDidResize:(NSNotification*)notification {
853   [self layoutSubviews];
854 }
855
856 - (void)registerForContentViewResizeNotifications {
857   [[NSNotificationCenter defaultCenter]
858       addObserver:self
859          selector:@selector(contentViewDidResize:)
860              name:NSViewFrameDidChangeNotification
861            object:[[self window] contentView]];
862 }
863
864 - (void)deregisterForContentViewResizeNotifications {
865   [[NSNotificationCenter defaultCenter]
866       removeObserver:self
867                 name:NSViewFrameDidChangeNotification
868               object:[[self window] contentView]];
869 }
870
871 - (NSSize)window:(NSWindow*)window
872     willUseFullScreenContentSize:(NSSize)proposedSize {
873   return proposedSize;
874 }
875
876 - (NSApplicationPresentationOptions)window:(NSWindow*)window
877     willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
878   return (opt |
879           NSApplicationPresentationAutoHideDock |
880           NSApplicationPresentationAutoHideMenuBar);
881 }
882
883 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
884   if (notification)  // For System Fullscreen when non-nil.
885     [self registerForContentViewResizeNotifications];
886
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];
894 }
895
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]) {
903       if ([window
904               isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
905         [window.contentView setHidden:YES];
906       }
907     }
908   }
909
910   if (notification)  // For System Fullscreen when non-nil.
911     [self deregisterForContentViewResizeNotifications];
912   enteringFullscreen_ = NO;
913   enteringSystemFullscreen_ = NO;
914   enteringPresentationMode_ = NO;
915
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]);
921   }
922
923   [self showFullscreenExitBubbleIfNecessary];
924   browser_->WindowFullscreenStateChanged();
925   [[[self window] cr_windowView] setWantsLayer:NO];
926   [self updateRoundedBottomCorners];
927 }
928
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];
935 }
936
937 - (void)windowDidExitFullScreen:(NSNotification*)notification {
938   if (notification)  // For System Fullscreen when non-nil.
939     [self deregisterForContentViewResizeNotifications];
940   browser_->WindowFullscreenStateChanged();
941   [self updateRoundedBottomCorners];
942 }
943
944 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
945   [self deregisterForContentViewResizeNotifications];
946   enteringFullscreen_ = NO;
947   enteringSystemFullscreen_ = NO;
948   [self setPresentationModeInternal:NO forceDropdown:NO];
949
950   // Force a relayout to try and get the window back into a reasonable state.
951   [self layoutSubviews];
952 }
953
954 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
955   [self deregisterForContentViewResizeNotifications];
956
957   // Force a relayout to try and get the window back into a reasonable state.
958   [self layoutSubviews];
959 }
960
961 - (void)enableBarVisibilityUpdates {
962   // Early escape if there's nothing to do.
963   if (barVisibilityUpdatesEnabled_)
964     return;
965
966   barVisibilityUpdatesEnabled_ = YES;
967
968   if ([barVisibilityLocks_ count])
969     [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
970   else
971     [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
972 }
973
974 - (void)disableBarVisibilityUpdates {
975   // Early escape if there's nothing to do.
976   if (!barVisibilityUpdatesEnabled_)
977     return;
978
979   barVisibilityUpdatesEnabled_ = NO;
980   [presentationModeController_ cancelAnimationAndTimers];
981 }
982
983 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
984   if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
985     return;
986   [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
987                                                           delay:delay];
988 }
989
990 - (CGFloat)toolbarDividerOpacity {
991   return [bookmarkBarController_ toolbarDividerOpacity];
992 }
993
994 - (void)updateSubviewZOrder:(BOOL)inPresentationMode {
995   NSView* contentView = [[self window] contentView];
996   NSView* toolbarView = [toolbarController_ view];
997
998   if (inPresentationMode) {
999     // Toolbar is above tab contents so that it can slide down from top of
1000     // screen.
1001     [contentView cr_ensureSubview:toolbarView
1002                      isPositioned:NSWindowAbove
1003                        relativeTo:[self tabContentArea]];
1004   } else {
1005     // Toolbar is below tab contents so that the info bar arrow can appear above
1006     // it.
1007     [contentView cr_ensureSubview:toolbarView
1008                      isPositioned:NSWindowBelow
1009                        relativeTo:[self tabContentArea]];
1010   }
1011
1012   // The bookmark bar is always below the toolbar.
1013   [contentView cr_ensureSubview:[bookmarkBarController_ view]
1014                    isPositioned:NSWindowBelow
1015                      relativeTo:toolbarView];
1016
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]];
1022   } else {
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];
1028   }
1029
1030   // The find bar is above everything.
1031   if (findBarCocoaController_) {
1032     NSView* relativeView = nil;
1033     if (inPresentationMode)
1034       relativeView = toolbarView;
1035     else
1036       relativeView = [self tabContentArea];
1037     [contentView cr_ensureSubview:[findBarCocoaController_ view]
1038                      isPositioned:NSWindowAbove
1039                        relativeTo:relativeView];
1040   }
1041
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]];
1049     } else {
1050       [contentView cr_ensureSubview:floatingBarBackingView_
1051                        isPositioned:NSWindowBelow
1052                          relativeTo:[bookmarkBarController_ view]];
1053     }
1054   }
1055 }
1056
1057 - (BOOL)shouldAllowOverlappingViews:(BOOL)inPresentationMode {
1058   if (inPresentationMode)
1059     return YES;
1060
1061   if (findBarCocoaController_ &&
1062       ![[findBarCocoaController_ findBarView] isHidden]) {
1063     return YES;
1064   }
1065
1066   if (overlappedViewCount_)
1067     return YES;
1068
1069   return NO;
1070 }
1071
1072 - (void)updateAllowOverlappingViews:(BOOL)inPresentationMode {
1073   WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
1074   if (!contents)
1075     return;
1076
1077   BOOL allowOverlappingViews =
1078       [self shouldAllowOverlappingViews:inPresentationMode];
1079
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);
1085
1086   WebContents* devTools = DevToolsWindow::GetInTabWebContents(contents, NULL);
1087   if (devTools)
1088     devTools->SetAllowOverlappingViews(allowOverlappingViews);
1089 }
1090
1091 - (void)updateInfoBarTipVisibility {
1092   // If there's no toolbar then hide the infobar tip.
1093   [infoBarContainerController_
1094       setShouldSuppressTopInfoBarTip:![self hasToolbar]];
1095 }
1096
1097 @end  // @implementation BrowserWindowController(Private)