9e760f3ba992bd925a1e786928155d6bff014682
[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 #import "base/mac/sdk_forward_declarations.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/prefs/scoped_user_pref_update.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/fullscreen.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
19 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
20 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_window_state.h"
22 #import "chrome/browser/ui/cocoa/browser_window_layout.h"
23 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
24 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
25 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
26 #import "chrome/browser/ui/cocoa/floating_bar_backing_view.h"
27 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
28 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
29 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
30 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.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 #import "ui/base/cocoa/nsview_additions.h"
48 #include "ui/base/ui_base_types.h"
49
50 using content::RenderWidgetHostView;
51 using content::WebContents;
52
53 namespace {
54
55 // Space between the incognito badge and the right edge of the window.
56 const CGFloat kAvatarRightOffset = 4;
57
58 }  // namespace
59
60 @implementation BrowserWindowController(Private)
61
62 // Create the tab strip controller.
63 - (void)createTabStripController {
64   DCHECK([overlayableContentsController_ activeContainer]);
65   DCHECK([[overlayableContentsController_ activeContainer] window]);
66   tabStripController_.reset([[TabStripController alloc]
67       initWithView:[self tabStripView]
68         switchView:[overlayableContentsController_ activeContainer]
69            browser:browser_.get()
70           delegate:self]);
71 }
72
73 - (void)saveWindowPositionIfNeeded {
74   if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
75     return;
76
77   // If we're in fullscreen mode, save the position of the regular window
78   // instead.
79   NSWindow* window =
80       [self isInAnyFullscreenMode] ? savedRegularWindow_ : [self window];
81
82   // Window positions are stored relative to the origin of the primary monitor.
83   NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
84   NSScreen* windowScreen = [window screen];
85
86   // Start with the window's frame, which is in virtual coordinates.
87   // Do some y twiddling to flip the coordinate system.
88   gfx::Rect bounds(NSRectToCGRect([window frame]));
89   bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
90
91   // Browser::SaveWindowPlacement saves information for session restore.
92   ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
93   if ([window isMiniaturized])
94     show_state = ui::SHOW_STATE_MINIMIZED;
95   else if ([self isInAnyFullscreenMode])
96     show_state = ui::SHOW_STATE_FULLSCREEN;
97   chrome::SaveWindowPlacement(browser_.get(), bounds, show_state);
98
99   // |windowScreen| can be nil (for example, if the monitor arrangement was
100   // changed while in fullscreen mode).  If we see a nil screen, return without
101   // saving.
102   // TODO(rohitrao): We should just not save anything for fullscreen windows.
103   // http://crbug.com/36479.
104   if (!windowScreen)
105     return;
106
107   // Only save main window information to preferences.
108   PrefService* prefs = browser_->profile()->GetPrefs();
109   if (!prefs || browser_ != chrome::GetLastActiveBrowser())
110     return;
111
112   // Save the current work area, in flipped coordinates.
113   gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
114   workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
115
116   scoped_ptr<DictionaryPrefUpdate> update =
117       chrome::GetWindowPlacementDictionaryReadWrite(
118           chrome::GetWindowName(browser_.get()),
119           browser_->profile()->GetPrefs());
120   base::DictionaryValue* windowPreferences = update->Get();
121   windowPreferences->SetInteger("left", bounds.x());
122   windowPreferences->SetInteger("top", bounds.y());
123   windowPreferences->SetInteger("right", bounds.right());
124   windowPreferences->SetInteger("bottom", bounds.bottom());
125   windowPreferences->SetBoolean("maximized", false);
126   windowPreferences->SetBoolean("always_on_top", false);
127   windowPreferences->SetInteger("work_area_left", workArea.x());
128   windowPreferences->SetInteger("work_area_top", workArea.y());
129   windowPreferences->SetInteger("work_area_right", workArea.right());
130   windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
131 }
132
133 - (NSRect)window:(NSWindow*)window
134 willPositionSheet:(NSWindow*)sheet
135        usingRect:(NSRect)defaultSheetRect {
136   // Position the sheet as follows:
137   //  - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
138   //    bookmark bar is disabled), position the sheet immediately below the
139   //    normal toolbar.
140   //  - If the bookmark bar is shown (attached to the normal toolbar), position
141   //    the sheet below the bookmark bar.
142   //  - If the bookmark bar is currently animating, position the sheet according
143   //    to where the bar will be when the animation ends.
144   switch ([bookmarkBarController_ currentState]) {
145     case BookmarkBar::SHOW: {
146       NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame];
147       defaultSheetRect.origin.y = bookmarkBarFrame.origin.y;
148       break;
149     }
150     case BookmarkBar::HIDDEN:
151     case BookmarkBar::DETACHED: {
152       if ([self hasToolbar]) {
153         NSRect toolbarFrame = [[toolbarController_ view] frame];
154         defaultSheetRect.origin.y = toolbarFrame.origin.y;
155       } else {
156         // The toolbar is not shown in application mode. The sheet should be
157         // located at the top of the window, under the title of the window.
158         defaultSheetRect.origin.y = NSHeight([[window contentView] frame]) -
159                                     defaultSheetRect.size.height;
160       }
161       break;
162     }
163   }
164   return defaultSheetRect;
165 }
166
167 - (void)layoutSubviews {
168   // Suppress title drawing if necessary.
169   if ([self.window respondsToSelector:@selector(setShouldHideTitle:)])
170     [(id)self.window setShouldHideTitle:![self hasTitleBar]];
171
172   [bookmarkBarController_ updateHiddenState];
173   [self updateSubviewZOrder];
174
175   base::scoped_nsobject<BrowserWindowLayout> layout(
176       [[BrowserWindowLayout alloc] init]);
177   [self updateLayoutParameters:layout];
178   [self applyLayout:layout];
179
180   [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
181 }
182
183 - (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
184                           width:(CGFloat)width
185                      fullscreen:(BOOL)fullscreen {
186   // Nothing to do if no tab strip.
187   if (![self hasTabStrip])
188     return maxY;
189
190   NSView* tabStripView = [self tabStripView];
191   CGFloat tabStripHeight = NSHeight([tabStripView frame]);
192   maxY -= tabStripHeight;
193   NSRect tabStripFrame = NSMakeRect(0, maxY, width, tabStripHeight);
194   BOOL requiresRelayout = !NSEqualRects(tabStripFrame, [tabStripView frame]);
195
196   // In Yosemite fullscreen, manually add the fullscreen controls to the tab
197   // strip.
198   BOOL addControlsInFullscreen =
199       [self isInAppKitFullscreen] && base::mac::IsOSYosemiteOrLater();
200
201   // Set left indentation based on fullscreen mode status.
202   CGFloat leftIndent = 0;
203   if (!fullscreen || addControlsInFullscreen)
204     leftIndent = [[tabStripController_ class] defaultLeftIndentForControls];
205   if (leftIndent != [tabStripController_ leftIndentForControls]) {
206     [tabStripController_ setLeftIndentForControls:leftIndent];
207     requiresRelayout = YES;
208   }
209
210   if (addControlsInFullscreen)
211     [tabStripController_ addWindowControls];
212   else
213     [tabStripController_ removeWindowControls];
214
215   // fullScreenButton is non-nil when isInAnyFullscreenMode is NO, and OS
216   // version is in the range 10.7 <= version <= 10.9. Starting with 10.10, the
217   // zoom/maximize button acts as the fullscreen button.
218   NSButton* fullScreenButton =
219       [[self window] standardWindowButton:NSWindowFullScreenButton];
220
221   // Lay out the icognito/avatar badge because calculating the indentation on
222   // the right depends on it.
223   NSView* avatarButton = [avatarButtonController_ view];
224   if ([self shouldShowAvatar]) {
225     CGFloat badgeXOffset = -kAvatarRightOffset;
226     CGFloat badgeYOffset = 0;
227     CGFloat buttonHeight = NSHeight([avatarButton frame]);
228
229     if ([self shouldUseNewAvatarButton]) {
230       // The fullscreen icon is displayed to the right of the avatar button.
231       if (![self isInAnyFullscreenMode] && fullScreenButton)
232         badgeXOffset -= width - NSMinX([fullScreenButton frame]);
233       // Center the button vertically on the tabstrip.
234       badgeYOffset = (tabStripHeight - buttonHeight) / 2;
235     } else {
236       // Actually place the badge *above* |maxY|, by +2 to miss the divider.
237       badgeYOffset = 2 * [[avatarButton superview] cr_lineWidth];
238     }
239
240     [avatarButton setFrameSize:NSMakeSize(NSWidth([avatarButton frame]),
241         std::min(buttonHeight, tabStripHeight))];
242     NSPoint origin =
243         NSMakePoint(width - NSWidth([avatarButton frame]) + badgeXOffset,
244                     maxY + badgeYOffset);
245     [avatarButton setFrameOrigin:origin];
246     [avatarButton setHidden:NO];  // Make sure it's shown.
247   }
248
249   // Calculate the right indentation.  The default indentation built into the
250   // tabstrip leaves enough room for the fullscreen button on Lion (10.7) to
251   // Mavericks (10.9).  On 10.6 and >=10.10, the right indent needs to be
252   // adjusted to make room for the new tab button when an avatar is present.
253   CGFloat rightIndent = 0;
254   if (![self isInAnyFullscreenMode] && fullScreenButton) {
255     rightIndent = width - NSMinX([fullScreenButton frame]);
256
257     if ([self shouldUseNewAvatarButton]) {
258       // The new avatar button is to the left of the fullscreen button.
259       // (The old avatar button is to the right).
260       rightIndent += NSWidth([avatarButton frame]) + kAvatarRightOffset;
261     }
262   } else if ([self shouldShowAvatar]) {
263     rightIndent += NSWidth([avatarButton frame]) + kAvatarRightOffset;
264   }
265
266   if (rightIndent != [tabStripController_ rightIndentForControls]) {
267     [tabStripController_ setRightIndentForControls:rightIndent];
268     requiresRelayout = YES;
269   }
270
271   // It is undesirable to force tabs relayout when the tap strip's frame did
272   // not change, because it will interrupt tab animations in progress.
273   // In addition, there appears to be an AppKit bug on <10.9 where interrupting
274   // a tab animation resulted in the tab frame being the animator's target
275   // frame instead of the interrupting setFrame. (See http://crbug.com/415093)
276   if (requiresRelayout) {
277     [tabStripView setFrame:tabStripFrame];
278     [tabStripController_ layoutTabsWithoutAnimation];
279   }
280
281   return maxY;
282 }
283
284 - (BOOL)placeBookmarkBarBelowInfoBar {
285   // If we are currently displaying the NTP detached bookmark bar or animating
286   // to/from it (from/to anything else), we display the bookmark bar below the
287   // info bar.
288   return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
289          [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
290          [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
291 }
292
293 - (void)layoutTabContentArea:(NSRect)newFrame {
294   NSView* tabContentView = [self tabContentArea];
295   NSRect tabContentFrame = [tabContentView frame];
296
297   bool contentShifted =
298       NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
299       NSMinX(tabContentFrame) != NSMinX(newFrame);
300
301   tabContentFrame = newFrame;
302   [tabContentView setFrame:tabContentFrame];
303
304   // If the relayout shifts the content area up or down, let the renderer know.
305   if (contentShifted) {
306     if (WebContents* contents =
307             browser_->tab_strip_model()->GetActiveWebContents()) {
308       if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
309         rwhv->WindowFrameChanged();
310     }
311   }
312 }
313
314 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
315   CGFloat newHeight =
316       [toolbarController_ desiredHeightForCompression:compression];
317   NSRect toolbarFrame = [[toolbarController_ view] frame];
318   CGFloat deltaH = newHeight - toolbarFrame.size.height;
319
320   if (deltaH == 0)
321     return;
322
323   toolbarFrame.size.height = newHeight;
324   NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
325   bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
326   [[toolbarController_ view] setFrame:toolbarFrame];
327   [[bookmarkBarController_ view] setFrame:bookmarkFrame];
328   [self layoutSubviews];
329 }
330
331 // Fullscreen and presentation mode methods
332
333 - (void)moveViewsForImmersiveFullscreen:(BOOL)fullscreen
334                           regularWindow:(NSWindow*)regularWindow
335                        fullscreenWindow:(NSWindow*)fullscreenWindow {
336   NSWindow* sourceWindow = fullscreen ? regularWindow : fullscreenWindow;
337   NSWindow* destWindow = fullscreen ? fullscreenWindow : regularWindow;
338
339   // Close the bookmark bubble, if it's open.  Use |-ok:| instead of |-cancel:|
340   // or |-close| because that matches the behavior when the bubble loses key
341   // status.
342   [bookmarkBubbleController_ ok:self];
343
344   // Save the current first responder so we can restore after views are moved.
345   base::scoped_nsobject<FocusTracker> focusTracker(
346       [[FocusTracker alloc] initWithWindow:sourceWindow]);
347
348   // While we move views (and focus) around, disable any bar visibility changes.
349   [self disableBarVisibilityUpdates];
350
351   // Retain the tab strip view while we remove it from its superview.
352   base::scoped_nsobject<NSView> tabStripView;
353   if ([self hasTabStrip]) {
354     tabStripView.reset([[self tabStripView] retain]);
355     [tabStripView removeFromSuperview];
356   }
357
358   // Ditto for the content view.
359   base::scoped_nsobject<NSView> contentView(
360       [[sourceWindow contentView] retain]);
361   // Disable autoresizing of subviews while we move views around. This prevents
362   // spurious renderer resizes.
363   [contentView setAutoresizesSubviews:NO];
364   [contentView removeFromSuperview];
365
366   // Have to do this here, otherwise later calls can crash because the window
367   // has no delegate.
368   [sourceWindow setDelegate:nil];
369   [destWindow setDelegate:self];
370
371   // With this call, valgrind complains that a "Conditional jump or move depends
372   // on uninitialised value(s)".  The error happens in -[NSThemeFrame
373   // drawOverlayRect:].  I'm pretty convinced this is an Apple bug, but there is
374   // no visual impact.  I have been unable to tickle it away with other window
375   // or view manipulation Cocoa calls.  Stack added to suppressions_mac.txt.
376   [contentView setAutoresizesSubviews:YES];
377   [destWindow setContentView:contentView];
378   [self moveContentViewToBack:contentView];
379
380   // Move the incognito badge if present.
381   if ([self shouldShowAvatar]) {
382     NSView* avatarButtonView = [avatarButtonController_ view];
383
384     [avatarButtonView removeFromSuperview];
385     [avatarButtonView setHidden:YES];  // Will be shown in layout.
386     [[destWindow cr_windowView] addSubview:avatarButtonView];
387   }
388
389   // Add the tab strip after setting the content view and moving the incognito
390   // badge (if any), so that the tab strip will be on top (in the z-order).
391   if ([self hasTabStrip])
392     [self insertTabStripView:tabStripView intoWindow:[self window]];
393
394   [sourceWindow setWindowController:nil];
395   [self setWindow:destWindow];
396   [destWindow setWindowController:self];
397
398   // Move the status bubble over, if we have one.
399   if (statusBubble_)
400     statusBubble_->SwitchParentWindow(destWindow);
401
402   // Move the title over.
403   [destWindow setTitle:[sourceWindow title]];
404
405   // The window needs to be onscreen before we can set its first responder.
406   // Ordering the window to the front can change the active Space (either to
407   // the window's old Space or to the application's assigned Space). To prevent
408   // this by temporarily change the collectionBehavior.
409   NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior];
410   [destWindow setCollectionBehavior:
411       NSWindowCollectionBehaviorMoveToActiveSpace];
412   [destWindow makeKeyAndOrderFront:self];
413   [destWindow setCollectionBehavior:behavior];
414
415   if (![focusTracker restoreFocusInWindow:destWindow]) {
416     // During certain types of fullscreen transitions, the view that had focus
417     // may have gone away (e.g., the one for a Flash FS widget).  In this case,
418     // FocusTracker will fail to restore focus to anything, so we set the focus
419     // to the tab contents as a reasonable fall-back.
420     [self focusTabContents];
421   }
422   [sourceWindow orderOut:self];
423
424   // We're done moving focus, so re-enable bar visibility changes.
425   [self enableBarVisibilityUpdates];
426 }
427
428 - (void)permissionBubbleWindowWillClose:(NSNotification*)notification {
429   DCHECK(permissionBubbleCocoa_);
430
431   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
432   [center removeObserver:self
433                     name:NSWindowWillCloseNotification
434                   object:[notification object]];
435   [self releaseBarVisibilityForOwner:[notification object]
436                        withAnimation:YES
437                                delay:YES];
438 }
439
440 - (void)configurePresentationModeController {
441   BOOL fullscreen_for_tab =
442       browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
443   BOOL kiosk_mode =
444       CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
445   BOOL showDropdown =
446       !fullscreen_for_tab && !kiosk_mode && ([self floatingBarHasFocus]);
447   if (permissionBubbleCocoa_ && permissionBubbleCocoa_->IsVisible()) {
448     DCHECK(permissionBubbleCocoa_->window());
449     // A visible permission bubble will force the dropdown to remain visible.
450     [self lockBarVisibilityForOwner:permissionBubbleCocoa_->window()
451                       withAnimation:NO
452                               delay:NO];
453     showDropdown = YES;
454     // Register to be notified when the permission bubble is closed, to
455     // allow fullscreen to hide the dropdown.
456     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
457     [center addObserver:self
458                selector:@selector(permissionBubbleWindowWillClose:)
459                    name:NSWindowWillCloseNotification
460                  object:permissionBubbleCocoa_->window()];
461   }
462   if (showDropdown) {
463     // Turn on layered mode for the window's root view for the entry
464     // animation.  Without this, the OS fullscreen animation for entering
465     // fullscreen mode does not correctly draw the tab strip.
466     // It will be turned off (set back to NO) when the animation finishes,
467     // in -windowDidEnterFullScreen:.
468     // Leaving wantsLayer on for the duration of presentation mode causes
469     // performance issues when the dropdown is animated in/out.  It also does
470     // not seem to be required for the exit animation.
471     windowViewWantsLayer_ = [[[self window] cr_windowView] wantsLayer];
472     [[[self window] cr_windowView] setWantsLayer:YES];
473   }
474
475   NSView* contentView = [[self window] contentView];
476   [presentationModeController_
477       enterPresentationModeForContentView:contentView
478                              showDropdown:showDropdown];
479 }
480
481 - (void)adjustUIForExitingFullscreenAndStopOmniboxSliding {
482   [presentationModeController_ exitPresentationMode];
483   presentationModeController_.reset();
484
485   // Force the bookmark bar z-order to update.
486   [[bookmarkBarController_ view] removeFromSuperview];
487   [self layoutSubviews];
488 }
489
490 - (void)adjustUIForSlidingFullscreenStyle:(fullscreen_mac::SlidingStyle)style {
491   if (!presentationModeController_) {
492     presentationModeController_.reset(
493         [self newPresentationModeControllerWithStyle:style]);
494     [self configurePresentationModeController];
495   } else {
496     presentationModeController_.get().slidingStyle = style;
497   }
498
499   if (!floatingBarBackingView_.get() &&
500       ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
501     floatingBarBackingView_.reset(
502         [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
503     [floatingBarBackingView_
504         setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
505   }
506
507   // Force the bookmark bar z-order to update.
508   [[bookmarkBarController_ view] removeFromSuperview];
509   [self layoutSubviews];
510 }
511
512 - (PresentationModeController*)newPresentationModeControllerWithStyle:
513     (fullscreen_mac::SlidingStyle)style {
514   return [[PresentationModeController alloc] initWithBrowserController:self
515                                                                  style:style];
516 }
517
518 - (void)enterImmersiveFullscreen {
519   // Set to NO by |-windowDidEnterFullScreen:|.
520   enteringImmersiveFullscreen_ = YES;
521
522   // Fade to black.
523   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
524   Boolean didFadeOut = NO;
525   CGDisplayFadeReservationToken token;
526   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
527       == kCGErrorSuccess) {
528     didFadeOut = YES;
529     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
530         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
531   }
532
533   // Create the fullscreen window.
534   fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
535   savedRegularWindow_ = [[self window] retain];
536   savedRegularWindowFrame_ = [savedRegularWindow_ frame];
537
538   [self moveViewsForImmersiveFullscreen:YES
539                           regularWindow:[self window]
540                        fullscreenWindow:fullscreenWindow_.get()];
541
542   fullscreen_mac::SlidingStyle style = fullscreen_mac::OMNIBOX_TABS_HIDDEN;
543   [self adjustUIForSlidingFullscreenStyle:style];
544
545   // AppKit is helpful and prevents NSWindows from having the same height as
546   // the screen while the menu bar is showing. This only applies to windows on
547   // a secondary screen, in a separate space. Calling [NSWindow
548   // setFrame:display:] with the screen's height will always reduce the
549   // height by the height of the MenuBar. Calling the method with any other
550   // height works fine. The relevant method in the 10.10 AppKit SDK is called:
551   // _canAdjustSizeForScreensHaveSeparateSpacesIfFillingSecondaryScreen
552   //
553   // TODO(erikchen): Refactor the logic to allow the window to be shown after
554   // the menubar has been hidden. This would remove the need for this hack.
555   // http://crbug.com/403203
556   NSRect frame = [[[self window] screen] frame];
557   if (!NSEqualRects(frame, [fullscreenWindow_ frame]))
558     [fullscreenWindow_ setFrame:[[[self window] screen] frame] display:YES];
559
560   [self layoutSubviews];
561
562   [self windowDidEnterFullScreen:nil];
563
564   // Fade back in.
565   if (didFadeOut) {
566     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
567         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
568     CGReleaseDisplayFadeReservation(token);
569   }
570 }
571
572 - (void)exitImmersiveFullscreen {
573   // Fade to black.
574   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
575   Boolean didFadeOut = NO;
576   CGDisplayFadeReservationToken token;
577   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
578       == kCGErrorSuccess) {
579     didFadeOut = YES;
580     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
581         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
582   }
583
584   [self windowWillExitFullScreen:nil];
585
586   [self moveViewsForImmersiveFullscreen:NO
587                           regularWindow:savedRegularWindow_
588                        fullscreenWindow:fullscreenWindow_.get()];
589
590   // When exiting fullscreen mode, we need to call layoutSubviews manually.
591   [savedRegularWindow_ autorelease];
592   savedRegularWindow_ = nil;
593   fullscreenWindow_.reset();
594   [self layoutSubviews];
595
596   [self windowDidExitFullScreen:nil];
597
598   // Fade back in.
599   if (didFadeOut) {
600     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
601         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
602     CGReleaseDisplayFadeReservation(token);
603   }
604 }
605
606 - (void)showFullscreenExitBubbleIfNecessary {
607   // This method is called in response to
608   // |-updateFullscreenExitBubbleURL:bubbleType:|. If we're in the middle of the
609   // transition into fullscreen (i.e., using the AppKit Fullscreen API), do not
610   // show the bubble because it will cause visual jank
611   // (http://crbug.com/130649). This will be called again as part of
612   // |-windowDidEnterFullScreen:|, so arrange to do that work then instead.
613   if (enteringAppKitFullscreen_)
614     return;
615
616   [self hideOverlayIfPossibleWithAnimation:NO delay:NO];
617
618   if (fullscreenBubbleType_ == FEB_TYPE_NONE ||
619       fullscreenBubbleType_ == FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) {
620     // Show no exit instruction bubble on Mac when in Browser Fullscreen.
621     [self destroyFullscreenExitBubbleIfNecessary];
622   } else {
623     [fullscreenExitBubbleController_ closeImmediately];
624     fullscreenExitBubbleController_.reset(
625         [[FullscreenExitBubbleController alloc]
626             initWithOwner:self
627                   browser:browser_.get()
628                       url:fullscreenUrl_
629                bubbleType:fullscreenBubbleType_]);
630     [fullscreenExitBubbleController_ showWindow];
631   }
632 }
633
634 - (void)destroyFullscreenExitBubbleIfNecessary {
635   [fullscreenExitBubbleController_ closeImmediately];
636   fullscreenExitBubbleController_.reset();
637 }
638
639 - (void)contentViewDidResize:(NSNotification*)notification {
640   [self layoutSubviews];
641 }
642
643 - (void)registerForContentViewResizeNotifications {
644   [[NSNotificationCenter defaultCenter]
645       addObserver:self
646          selector:@selector(contentViewDidResize:)
647              name:NSViewFrameDidChangeNotification
648            object:[[self window] contentView]];
649 }
650
651 - (void)deregisterForContentViewResizeNotifications {
652   [[NSNotificationCenter defaultCenter]
653       removeObserver:self
654                 name:NSViewFrameDidChangeNotification
655               object:[[self window] contentView]];
656 }
657
658 - (NSSize)window:(NSWindow*)window
659     willUseFullScreenContentSize:(NSSize)proposedSize {
660   return proposedSize;
661 }
662
663 - (NSApplicationPresentationOptions)window:(NSWindow*)window
664     willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
665   return (opt |
666           NSApplicationPresentationAutoHideDock |
667           NSApplicationPresentationAutoHideMenuBar);
668 }
669
670 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
671   if (notification)  // For System Fullscreen when non-nil.
672     [self registerForContentViewResizeNotifications];
673
674   NSWindow* window = [self window];
675   savedRegularWindowFrame_ = [window frame];
676   BOOL mode = enteringPresentationMode_ ||
677        browser_->fullscreen_controller()->IsWindowFullscreenForTabOrPending();
678   enteringAppKitFullscreen_ = YES;
679
680   fullscreen_mac::SlidingStyle style =
681       mode ? fullscreen_mac::OMNIBOX_TABS_HIDDEN
682            : fullscreen_mac::OMNIBOX_TABS_PRESENT;
683
684   [self adjustUIForSlidingFullscreenStyle:style];
685 }
686
687 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
688   // In Yosemite, some combination of the titlebar and toolbar always show in
689   // full-screen mode. We do not want either to show. Search for the window that
690   // contains the views, and hide it. There is no need to ever unhide the view.
691   // http://crbug.com/380235
692   if (base::mac::IsOSYosemiteOrLater()) {
693     for (NSWindow* window in [[NSApplication sharedApplication] windows]) {
694       if ([window
695               isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
696         [window.contentView setHidden:YES];
697       }
698     }
699   }
700
701   if (notification)  // For System Fullscreen when non-nil.
702     [self deregisterForContentViewResizeNotifications];
703   enteringAppKitFullscreen_ = NO;
704   enteringImmersiveFullscreen_ = NO;
705   enteringPresentationMode_ = NO;
706
707   [self showFullscreenExitBubbleIfNecessary];
708   browser_->WindowFullscreenStateChanged();
709   [[[self window] cr_windowView] setWantsLayer:windowViewWantsLayer_];
710 }
711
712 - (void)windowWillExitFullScreen:(NSNotification*)notification {
713   if (notification)  // For System Fullscreen when non-nil.
714     [self registerForContentViewResizeNotifications];
715   [self destroyFullscreenExitBubbleIfNecessary];
716   [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
717 }
718
719 - (void)windowDidExitFullScreen:(NSNotification*)notification {
720   if (notification)  // For System Fullscreen when non-nil.
721     [self deregisterForContentViewResizeNotifications];
722   browser_->WindowFullscreenStateChanged();
723 }
724
725 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
726   [self deregisterForContentViewResizeNotifications];
727   enteringAppKitFullscreen_ = NO;
728   [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
729 }
730
731 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
732   [self deregisterForContentViewResizeNotifications];
733
734   // Force a relayout to try and get the window back into a reasonable state.
735   [self layoutSubviews];
736 }
737
738 - (void)enableBarVisibilityUpdates {
739   // Early escape if there's nothing to do.
740   if (barVisibilityUpdatesEnabled_)
741     return;
742
743   barVisibilityUpdatesEnabled_ = YES;
744
745   if ([barVisibilityLocks_ count])
746     [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
747   else
748     [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
749 }
750
751 - (void)disableBarVisibilityUpdates {
752   // Early escape if there's nothing to do.
753   if (!barVisibilityUpdatesEnabled_)
754     return;
755
756   barVisibilityUpdatesEnabled_ = NO;
757   [presentationModeController_ cancelAnimationAndTimers];
758 }
759
760 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
761   if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
762     return;
763   [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
764                                                           delay:delay];
765 }
766
767 - (CGFloat)toolbarDividerOpacity {
768   return [bookmarkBarController_ toolbarDividerOpacity];
769 }
770
771 - (void)updateLayerOrdering:(NSView*)view {
772   // Hold a reference to the view so that it doesn't accidentally get
773   // dealloc'ed.
774   base::scoped_nsobject<NSView> reference([view retain]);
775
776   // If the superview has a layer, then this hack isn't required.
777   NSView* superview = [view superview];
778   if ([superview layer])
779     return;
780
781   // Get the current position of the view.
782   NSArray* subviews = [superview subviews];
783   NSInteger index = [subviews indexOfObject:view];
784   NSView* siblingBelow = nil;
785   if (index > 0)
786     siblingBelow = [subviews objectAtIndex:index - 1];
787
788   // Remove the view.
789   [view removeFromSuperview];
790
791   // Add it to the same position.
792   if (siblingBelow) {
793     [superview addSubview:view
794                positioned:NSWindowAbove
795                relativeTo:siblingBelow];
796   } else {
797     [superview addSubview:view
798                positioned:NSWindowBelow
799                relativeTo:nil];
800   }
801 }
802
803 - (void)updateInfoBarTipVisibility {
804   // If there's no toolbar then hide the infobar tip.
805   [infoBarContainerController_
806       setShouldSuppressTopInfoBarTip:![self hasToolbar]];
807 }
808
809 - (NSInteger)pageInfoBubblePointY {
810   LocationBarViewMac* locationBarView = [self locationBarBridge];
811
812   // The point, in window coordinates.
813   NSPoint iconBottom = locationBarView->GetPageInfoBubblePoint();
814
815   // The toolbar, in window coordinates.
816   NSView* toolbar = [toolbarController_ view];
817   CGFloat toolbarY = NSMinY([toolbar convertRect:[toolbar bounds] toView:nil]);
818
819   return iconBottom.y - toolbarY;
820 }
821
822 - (void)enterAppKitFullscreen {
823   DCHECK(base::mac::IsOSLionOrLater());
824   if (FramedBrowserWindow* framedBrowserWindow =
825           base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
826     [framedBrowserWindow toggleSystemFullScreen];
827   }
828 }
829
830 - (void)exitAppKitFullscreen {
831   DCHECK(base::mac::IsOSLionOrLater());
832   if (FramedBrowserWindow* framedBrowserWindow =
833           base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
834     [framedBrowserWindow toggleSystemFullScreen];
835   }
836 }
837
838 - (void)updateLayoutParameters:(BrowserWindowLayout*)layout {
839   [layout setContentViewSize:[[[self window] contentView] bounds].size];
840   [layout setWindowSize:[[self window] frame].size];
841
842   [layout setInAnyFullscreen:[self isInAnyFullscreenMode]];
843   [layout setFullscreenSlidingStyle:
844       presentationModeController_.get().slidingStyle];
845   [layout setFullscreenMenubarOffset:
846       [presentationModeController_ menubarOffset]];
847   [layout setFullscreenToolbarFraction:
848       [presentationModeController_ toolbarFraction]];
849
850   [layout setHasTabStrip:[self hasTabStrip]];
851
852   [layout setHasToolbar:[self hasToolbar]];
853   [layout setToolbarHeight:NSHeight([[toolbarController_ view] bounds])];
854
855   [layout setHasLocationBar:[self hasLocationBar]];
856
857   [layout setPlaceBookmarkBarBelowInfoBar:[self placeBookmarkBarBelowInfoBar]];
858   [layout setBookmarkBarHidden:[bookmarkBarController_ view].isHidden];
859   [layout setBookmarkBarHeight:
860       NSHeight([[bookmarkBarController_ view] bounds])];
861
862   [layout setInfoBarHeight:[infoBarContainerController_ heightOfInfoBars]];
863   [layout setPageInfoBubblePointY:[self pageInfoBubblePointY]];
864
865   [layout setHasDownloadShelf:(downloadShelfController_.get() != nil)];
866   [layout setDownloadShelfHeight:
867       NSHeight([[downloadShelfController_ view] bounds])];
868 }
869
870 - (void)applyLayout:(BrowserWindowLayout*)layout {
871   chrome::LayoutOutput output = [layout computeLayout];
872
873   if (!NSIsEmptyRect(output.tabStripFrame)) {
874     // Note: The fullscreen parameter passed to the method is different from
875     // the field in |parameters| with the similar name.
876     [self layoutTabStripAtMaxY:NSMaxY(output.tabStripFrame)
877                          width:NSWidth(output.tabStripFrame)
878                     fullscreen:[self isInAnyFullscreenMode]];
879   }
880
881   if (!NSIsEmptyRect(output.toolbarFrame)) {
882     [[toolbarController_ view] setFrame:output.toolbarFrame];
883   }
884
885   if (!NSIsEmptyRect(output.bookmarkFrame)) {
886     NSView* bookmarkBarView = [bookmarkBarController_ view];
887     [bookmarkBarView setFrame:output.bookmarkFrame];
888
889     // Pin the bookmark bar to the top of the window and make the width
890     // flexible.
891     [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
892
893     [bookmarkBarController_ layoutSubviews];
894   }
895
896   // The info bar is never hidden. Sometimes it has zero effective height.
897   [[infoBarContainerController_ view] setFrame:output.infoBarFrame];
898   [infoBarContainerController_
899       setMaxTopArrowHeight:output.infoBarMaxTopArrowHeight];
900
901   if (!NSIsEmptyRect(output.downloadShelfFrame))
902     [[downloadShelfController_ view] setFrame:output.downloadShelfFrame];
903
904   [self layoutTabContentArea:output.contentAreaFrame];
905
906   if (!NSIsEmptyRect(output.fullscreenBackingBarFrame)) {
907     [floatingBarBackingView_ setFrame:output.fullscreenBackingBarFrame];
908     [presentationModeController_
909         overlayFrameChanged:output.fullscreenBackingBarFrame];
910   }
911
912   [findBarCocoaController_
913       positionFindBarViewAtMaxY:output.findBarMaxY
914                        maxWidth:NSWidth(output.contentAreaFrame)];
915
916   [fullscreenExitBubbleController_
917       positionInWindowAtTop:output.fullscreenExitButtonMaxY
918                       width:NSWidth(output.contentAreaFrame)];
919 }
920
921 - (void)updateSubviewZOrder {
922   if ([self isInAnyFullscreenMode])
923     [self updateSubviewZOrderFullscreen];
924   else
925     [self updateSubviewZOrderNormal];
926
927   [self updateSubviewZOrderHack];
928 }
929
930 - (void)updateSubviewZOrderNormal {
931   base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
932   if ([downloadShelfController_ view])
933     [subviews addObject:[downloadShelfController_ view]];
934   if ([bookmarkBarController_ view])
935     [subviews addObject:[bookmarkBarController_ view]];
936   if ([toolbarController_ view])
937     [subviews addObject:[toolbarController_ view]];
938   if ([infoBarContainerController_ view])
939     [subviews addObject:[infoBarContainerController_ view]];
940   if ([self tabContentArea])
941     [subviews addObject:[self tabContentArea]];
942   if ([findBarCocoaController_ view])
943     [subviews addObject:[findBarCocoaController_ view]];
944
945   [self setContentViewSubviews:subviews];
946 }
947
948 - (void)updateSubviewZOrderFullscreen {
949   base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
950   if ([downloadShelfController_ view])
951     [subviews addObject:[downloadShelfController_ view]];
952   if ([self tabContentArea])
953     [subviews addObject:[self tabContentArea]];
954   if ([self placeBookmarkBarBelowInfoBar]) {
955     if ([bookmarkBarController_ view])
956       [subviews addObject:[bookmarkBarController_ view]];
957     if (floatingBarBackingView_)
958       [subviews addObject:floatingBarBackingView_];
959   } else {
960     if (floatingBarBackingView_)
961       [subviews addObject:floatingBarBackingView_];
962     if ([bookmarkBarController_ view])
963       [subviews addObject:[bookmarkBarController_ view]];
964   }
965   if ([toolbarController_ view])
966     [subviews addObject:[toolbarController_ view]];
967   if ([infoBarContainerController_ view])
968     [subviews addObject:[infoBarContainerController_ view]];
969   if ([findBarCocoaController_ view])
970     [subviews addObject:[findBarCocoaController_ view]];
971
972   [self setContentViewSubviews:subviews];
973 }
974
975 - (void)setContentViewSubviews:(NSArray*)subviews {
976   // Subviews already match.
977   if ([[self.window.contentView subviews] isEqual:subviews])
978     return;
979
980   // The tabContentArea isn't a subview, so just set all the subviews.
981   NSView* tabContentArea = [self tabContentArea];
982   if (![[self.window.contentView subviews] containsObject:tabContentArea]) {
983     [self.window.contentView setSubviews:subviews];
984     return;
985   }
986
987   // Remove all subviews that aren't the tabContentArea.
988   for (NSView* view in [[self.window.contentView subviews] copy]) {
989     if (view != tabContentArea)
990       [view removeFromSuperview];
991   }
992
993   // Add in the subviews below the tabContentArea.
994   NSInteger index = [subviews indexOfObject:tabContentArea];
995   for (int i = index - 1; i >= 0; --i) {
996     NSView* view = [subviews objectAtIndex:i];
997     [self.window.contentView addSubview:view
998                              positioned:NSWindowBelow
999                              relativeTo:nil];
1000   }
1001
1002   // Add in the subviews above the tabContentArea.
1003   for (NSUInteger i = index + 1; i < [subviews count]; ++i) {
1004     NSView* view = [subviews objectAtIndex:i];
1005     [self.window.contentView addSubview:view
1006                              positioned:NSWindowAbove
1007                              relativeTo:nil];
1008   }
1009 }
1010
1011 - (void)updateSubviewZOrderHack {
1012   // TODO(erikchen): Remove and then add the tabStripView to the root NSView.
1013   // This fixes a layer ordering problem that occurs between the contentView
1014   // and the tabStripView. This is a hack required because NSThemeFrame is not
1015   // layer backed, and because Chrome adds subviews directly to the
1016   // NSThemeFrame.
1017   // http://crbug.com/407921
1018   if (enteringAppKitFullscreen_) {
1019     // The tabstrip frequently lies outside the bounds of its superview.
1020     // Repeatedly adding/removing the tabstrip from its superview during the
1021     // AppKit Fullscreen transition causes graphical glitches on 10.10. The
1022     // correct solution is to use the AppKit fullscreen transition APIs added
1023     // in 10.7+.
1024     // http://crbug.com/408791
1025     if (!hasAdjustedTabStripWhileEnteringAppKitFullscreen_) {
1026       // Disable implicit animations.
1027       [CATransaction begin];
1028       [CATransaction setDisableActions:YES];
1029
1030       [self updateLayerOrdering:[self tabStripView]];
1031       [self updateLayerOrdering:[avatarButtonController_ view]];
1032
1033       [CATransaction commit];
1034       hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = YES;
1035     }
1036   } else {
1037     hasAdjustedTabStripWhileEnteringAppKitFullscreen_ = NO;
1038   }
1039 }
1040
1041 @end  // @implementation BrowserWindowController(Private)