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