Check USB device path access when prompting users to select a device.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / browser_window_controller_private.mm
blob42403d30805736500d0e27ba0eb0b9424ffe2cf4
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #import "chrome/browser/ui/cocoa/browser_window_controller_private.h"
7 #include <cmath>
9 #include "base/command_line.h"
10 #include "base/mac/bind_objc_block.h"
11 #include "base/mac/foundation_util.h"
12 #include "base/mac/mac_util.h"
13 #import "base/mac/scoped_nsobject.h"
14 #import "base/mac/sdk_forward_declarations.h"
15 #include "base/metrics/histogram.h"
16 #include "base/prefs/pref_service.h"
17 #include "base/prefs/scoped_user_pref_update.h"
18 #include "chrome/browser/browser_process.h"
19 #include "chrome/browser/fullscreen.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/profiles/profile_avatar_icon_util.h"
22 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
23 #include "chrome/browser/ui/browser.h"
24 #include "chrome/browser/ui/browser_window_state.h"
25 #import "chrome/browser/ui/cocoa/browser_window_enter_fullscreen_transition.h"
26 #import "chrome/browser/ui/cocoa/browser_window_layout.h"
27 #import "chrome/browser/ui/cocoa/custom_frame_view.h"
28 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
29 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
30 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
31 #import "chrome/browser/ui/cocoa/floating_bar_backing_view.h"
32 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
33 #import "chrome/browser/ui/cocoa/fullscreen_window.h"
34 #import "chrome/browser/ui/cocoa/infobars/infobar_container_controller.h"
35 #include "chrome/browser/ui/cocoa/last_active_browser_cocoa.h"
36 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
37 #import "chrome/browser/ui/cocoa/profiles/avatar_button_controller.h"
38 #import "chrome/browser/ui/cocoa/profiles/avatar_icon_controller.h"
39 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
40 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
41 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
42 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
43 #import "chrome/browser/ui/cocoa/website_settings/permission_bubble_cocoa.h"
44 #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h"
45 #include "chrome/browser/ui/tabs/tab_strip_model.h"
46 #include "chrome/common/chrome_switches.h"
47 #include "content/public/browser/render_widget_host_view.h"
48 #include "content/public/browser/web_contents.h"
49 #import "ui/base/cocoa/focus_tracker.h"
50 #import "ui/base/cocoa/nsview_additions.h"
51 #include "ui/base/ui_base_types.h"
53 using content::RenderWidgetHostView;
54 using content::WebContents;
56 namespace {
58 // The screen on which the window was fullscreened, and whether the device had
59 // multiple screens available.
60 enum WindowLocation {
61   PRIMARY_SINGLE_SCREEN = 0,
62   PRIMARY_MULTIPLE_SCREEN = 1,
63   SECONDARY_MULTIPLE_SCREEN = 2,
64   WINDOW_LOCATION_COUNT = 3
67 // There are 2 mechanisms for invoking fullscreen: AppKit and Immersive.
68 // There are 2 types of AppKit Fullscreen: Presentation Mode and Canonical
69 // Fullscreen.
70 enum FullscreenStyle {
71   IMMERSIVE_FULLSCREEN = 0,
72   PRESENTATION_MODE = 1,
73   CANONICAL_FULLSCREEN = 2,
74   FULLSCREEN_STYLE_COUNT = 3
77 // Emits a histogram entry indicating the Fullscreen window location.
78 void RecordFullscreenWindowLocation(NSWindow* window) {
79   NSArray* screens = [NSScreen screens];
80   bool primary_screen = ([[window screen] isEqual:[screens objectAtIndex:0]]);
81   bool multiple_screens = [screens count] > 1;
83   WindowLocation location = PRIMARY_SINGLE_SCREEN;
84   if (multiple_screens) {
85     location =
86         primary_screen ? PRIMARY_MULTIPLE_SCREEN : SECONDARY_MULTIPLE_SCREEN;
87   }
89   UMA_HISTOGRAM_ENUMERATION(
90       "OSX.Fullscreen.Enter.WindowLocation", location, WINDOW_LOCATION_COUNT);
93 // Emits a histogram entry indicating the Fullscreen style.
94 void RecordFullscreenStyle(FullscreenStyle style) {
95   UMA_HISTOGRAM_ENUMERATION(
96       "OSX.Fullscreen.Enter.Style", style, FULLSCREEN_STYLE_COUNT);
99 }  // namespace
101 @implementation BrowserWindowController(Private)
103 // Create the tab strip controller.
104 - (void)createTabStripController {
105   DCHECK([overlayableContentsController_ activeContainer]);
106   DCHECK([[overlayableContentsController_ activeContainer] window]);
107   tabStripController_.reset([[TabStripController alloc]
108       initWithView:[self tabStripView]
109         switchView:[overlayableContentsController_ activeContainer]
110            browser:browser_.get()
111           delegate:self]);
114 - (void)saveWindowPositionIfNeeded {
115   if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
116     return;
118   // If we're in fullscreen mode, save the position of the regular window
119   // instead.
120   NSWindow* window =
121       [self isInAnyFullscreenMode] ? savedRegularWindow_ : [self window];
123   // Window positions are stored relative to the origin of the primary monitor.
124   NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
125   NSScreen* windowScreen = [window screen];
127   // Start with the window's frame, which is in virtual coordinates.
128   // Do some y twiddling to flip the coordinate system.
129   gfx::Rect bounds(NSRectToCGRect([window frame]));
130   bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
132   // Browser::SaveWindowPlacement saves information for session restore.
133   ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
134   if ([window isMiniaturized])
135     show_state = ui::SHOW_STATE_MINIMIZED;
136   else if ([self isInAnyFullscreenMode])
137     show_state = ui::SHOW_STATE_FULLSCREEN;
138   chrome::SaveWindowPlacement(browser_.get(), bounds, show_state);
140   // |windowScreen| can be nil (for example, if the monitor arrangement was
141   // changed while in fullscreen mode).  If we see a nil screen, return without
142   // saving.
143   // TODO(rohitrao): We should just not save anything for fullscreen windows.
144   // http://crbug.com/36479.
145   if (!windowScreen)
146     return;
148   // Only save main window information to preferences.
149   PrefService* prefs = browser_->profile()->GetPrefs();
150   if (!prefs || browser_ != chrome::GetLastActiveBrowser())
151     return;
153   // Save the current work area, in flipped coordinates.
154   gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
155   workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
157   scoped_ptr<DictionaryPrefUpdate> update =
158       chrome::GetWindowPlacementDictionaryReadWrite(
159           chrome::GetWindowName(browser_.get()),
160           browser_->profile()->GetPrefs());
161   base::DictionaryValue* windowPreferences = update->Get();
162   windowPreferences->SetInteger("left", bounds.x());
163   windowPreferences->SetInteger("top", bounds.y());
164   windowPreferences->SetInteger("right", bounds.right());
165   windowPreferences->SetInteger("bottom", bounds.bottom());
166   windowPreferences->SetBoolean("maximized", false);
167   windowPreferences->SetBoolean("always_on_top", false);
168   windowPreferences->SetInteger("work_area_left", workArea.x());
169   windowPreferences->SetInteger("work_area_top", workArea.y());
170   windowPreferences->SetInteger("work_area_right", workArea.right());
171   windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
174 - (NSRect)window:(NSWindow*)window
175 willPositionSheet:(NSWindow*)sheet
176        usingRect:(NSRect)defaultSheetLocation {
177   // Position the sheet as follows:
178   //  - If the bookmark bar is shown (attached to the normal toolbar), position
179   //    the sheet below the bookmark bar.
180   //  - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
181   //    bookmark bar is disabled), position the sheet immediately below the
182   //    normal toolbar.
183   //  - If the bookmark bar is currently animating, position the sheet according
184   //    to where the bar will be when the animation ends.
185   CGFloat defaultSheetY = defaultSheetLocation.origin.y;
186   if ([self supportsBookmarkBar] &&
187       [bookmarkBarController_ currentState] == BookmarkBar::SHOW) {
188     defaultSheetY = NSMinY([[bookmarkBarController_ view] frame]);
189   } else if ([self hasToolbar]) {
190     defaultSheetY = NSMinY([[toolbarController_ view] frame]);
191   } else {
192     // The toolbar is not shown in popup and application modes. The sheet
193     // should be located at the top of the window, under the title of the
194     // window.
195     defaultSheetY = NSMaxY([[window contentView] frame]);
196   }
198   // AppKit may shift the window up to fit the sheet on screen, but it will
199   // never adjust the height of the sheet, or the origin of the sheet relative
200   // to the window. Adjust the origin to prevent sheets from extending past the
201   // bottom of the screen.
203   // Don't allow the sheet to extend past the bottom of the window. This logic
204   // intentionally ignores the size of the screens, since the window might span
205   // multiple screens, and AppKit may reposition the window.
206   CGFloat sheetHeight = NSHeight([sheet frame]);
207   defaultSheetY = std::max(defaultSheetY, sheetHeight);
209   // It doesn't make sense to provide a Y higher than the height of the window.
210   CGFloat windowHeight = NSHeight([window frame]);
211   defaultSheetY = std::min(defaultSheetY, windowHeight);
213   defaultSheetLocation.origin.y = defaultSheetY;
214   return defaultSheetLocation;
217 - (void)layoutSubviews {
218   // Suppress title drawing if necessary.
219   if ([self.window respondsToSelector:@selector(setShouldHideTitle:)])
220     [(id)self.window setShouldHideTitle:![self hasTitleBar]];
222   [bookmarkBarController_ updateHiddenState];
223   [self updateSubviewZOrder];
225   base::scoped_nsobject<BrowserWindowLayout> layout(
226       [[BrowserWindowLayout alloc] init]);
227   [self updateLayoutParameters:layout];
228   [self applyLayout:layout];
230   [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
233 - (void)applyTabStripLayout:(const chrome::TabStripLayout&)layout {
234   // Update the presence of the window controls.
235   if (layout.addCustomWindowControls)
236     [tabStripController_ addCustomWindowControls];
237   else
238     [tabStripController_ removeCustomWindowControls];
240   // Update the layout of the avatar.
241   if (!NSIsEmptyRect(layout.avatarFrame)) {
242     NSView* avatarButton = [avatarButtonController_ view];
243     [avatarButton setFrame:layout.avatarFrame];
244     [avatarButton setHidden:NO];
245   }
247   // Check if the tab strip's frame has changed.
248   BOOL requiresRelayout =
249       !NSEqualRects([[self tabStripView] frame], layout.frame);
251   // Check if the left indent has changed.
252   if (layout.leftIndent != [tabStripController_ leftIndentForControls]) {
253     [tabStripController_ setLeftIndentForControls:layout.leftIndent];
254     requiresRelayout = YES;
255   }
257   // Check if the right indent has changed.
258   if (layout.rightIndent != [tabStripController_ rightIndentForControls]) {
259     [tabStripController_ setRightIndentForControls:layout.rightIndent];
260     requiresRelayout = YES;
261   }
263   // It is undesirable to force tabs relayout when the tap strip's frame did
264   // not change, because it will interrupt tab animations in progress.
265   // In addition, there appears to be an AppKit bug on <10.9 where interrupting
266   // a tab animation resulted in the tab frame being the animator's target
267   // frame instead of the interrupting setFrame. (See http://crbug.com/415093)
268   if (requiresRelayout) {
269     [[self tabStripView] setFrame:layout.frame];
270     [tabStripController_ layoutTabsWithoutAnimation];
271   }
274 - (BOOL)placeBookmarkBarBelowInfoBar {
275   // If we are currently displaying the NTP detached bookmark bar or animating
276   // to/from it (from/to anything else), we display the bookmark bar below the
277   // info bar.
278   return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
279          [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
280          [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
283 - (void)layoutTabContentArea:(NSRect)newFrame {
284   NSView* tabContentView = [self tabContentArea];
285   NSRect tabContentFrame = [tabContentView frame];
287   bool contentShifted =
288       NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
289       NSMinX(tabContentFrame) != NSMinX(newFrame);
291   tabContentFrame = newFrame;
292   [tabContentView setFrame:tabContentFrame];
294   // If the relayout shifts the content area up or down, let the renderer know.
295   if (contentShifted) {
296     if (WebContents* contents =
297             browser_->tab_strip_model()->GetActiveWebContents()) {
298       if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
299         rwhv->WindowFrameChanged();
300     }
301   }
304 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
305   CGFloat newHeight =
306       [toolbarController_ desiredHeightForCompression:compression];
307   NSRect toolbarFrame = [[toolbarController_ view] frame];
308   CGFloat deltaH = newHeight - toolbarFrame.size.height;
310   if (deltaH == 0)
311     return;
313   toolbarFrame.size.height = newHeight;
314   NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
315   bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
316   [[toolbarController_ view] setFrame:toolbarFrame];
317   [[bookmarkBarController_ view] setFrame:bookmarkFrame];
318   [self layoutSubviews];
321 // Fullscreen and presentation mode methods
323 - (void)moveViewsForImmersiveFullscreen:(BOOL)fullscreen
324                           regularWindow:(NSWindow*)regularWindow
325                        fullscreenWindow:(NSWindow*)fullscreenWindow {
326   NSWindow* sourceWindow = fullscreen ? regularWindow : fullscreenWindow;
327   NSWindow* destWindow = fullscreen ? fullscreenWindow : regularWindow;
329   // Close the bookmark bubble, if it's open.  Use |-ok:| instead of |-cancel:|
330   // or |-close| because that matches the behavior when the bubble loses key
331   // status.
332   [bookmarkBubbleController_ ok:self];
334   // Save the current first responder so we can restore after views are moved.
335   base::scoped_nsobject<FocusTracker> focusTracker(
336       [[FocusTracker alloc] initWithWindow:sourceWindow]);
338   // While we move views (and focus) around, disable any bar visibility changes.
339   [self disableBarVisibilityUpdates];
341   // Retain the tab strip view while we remove it from its superview.
342   base::scoped_nsobject<NSView> tabStripView;
343   if ([self hasTabStrip]) {
344     tabStripView.reset([[self tabStripView] retain]);
345     [tabStripView removeFromSuperview];
346   }
348   // Disable autoresizing of subviews while we move views around. This prevents
349   // spurious renderer resizes.
350   [self.chromeContentView setAutoresizesSubviews:NO];
351   [self.chromeContentView removeFromSuperview];
353   // Have to do this here, otherwise later calls can crash because the window
354   // has no delegate.
355   [sourceWindow setDelegate:nil];
356   [destWindow setDelegate:self];
358   // With this call, valgrind complains that a "Conditional jump or move depends
359   // on uninitialised value(s)".  The error happens in -[NSThemeFrame
360   // drawOverlayRect:].  I'm pretty convinced this is an Apple bug, but there is
361   // no visual impact.  I have been unable to tickle it away with other window
362   // or view manipulation Cocoa calls.  Stack added to suppressions_mac.txt.
363   [self.chromeContentView setAutoresizesSubviews:YES];
364   [[destWindow contentView] addSubview:self.chromeContentView
365                             positioned:NSWindowBelow
366                             relativeTo:nil];
367   [self.chromeContentView setFrame:[[destWindow contentView] bounds]];
369   // Move the incognito badge if present.
370   if ([self shouldShowAvatar]) {
371     NSView* avatarButtonView = [avatarButtonController_ view];
373     [avatarButtonView removeFromSuperview];
374     [avatarButtonView setHidden:YES];  // Will be shown in layout.
375     [[destWindow contentView] addSubview:avatarButtonView];
376   }
378   // Add the tab strip after setting the content view and moving the incognito
379   // badge (if any), so that the tab strip will be on top (in the z-order).
380   if ([self hasTabStrip])
381     [[destWindow contentView] addSubview:tabStripView];
383   [sourceWindow setWindowController:nil];
384   [self setWindow:destWindow];
385   [destWindow setWindowController:self];
387   // Move the status bubble over, if we have one.
388   if (statusBubble_)
389     statusBubble_->SwitchParentWindow(destWindow);
391   permissionBubbleCocoa_->SwitchParentWindow(destWindow);
393   // Move the title over.
394   [destWindow setTitle:[sourceWindow title]];
396   // The window needs to be onscreen before we can set its first responder.
397   // Ordering the window to the front can change the active Space (either to
398   // the window's old Space or to the application's assigned Space). To prevent
399   // this by temporarily change the collectionBehavior.
400   NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior];
401   [destWindow setCollectionBehavior:
402       NSWindowCollectionBehaviorMoveToActiveSpace];
403   [destWindow makeKeyAndOrderFront:self];
404   [destWindow setCollectionBehavior:behavior];
406   if (![focusTracker restoreFocusInWindow:destWindow]) {
407     // During certain types of fullscreen transitions, the view that had focus
408     // may have gone away (e.g., the one for a Flash FS widget).  In this case,
409     // FocusTracker will fail to restore focus to anything, so we set the focus
410     // to the tab contents as a reasonable fall-back.
411     [self focusTabContents];
412   }
413   [sourceWindow orderOut:self];
415   // We're done moving focus, so re-enable bar visibility changes.
416   [self enableBarVisibilityUpdates];
419 - (void)permissionBubbleWindowWillClose:(NSNotification*)notification {
420   DCHECK(permissionBubbleCocoa_);
422   NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
423   [center removeObserver:self
424                     name:NSWindowWillCloseNotification
425                   object:[notification object]];
426   [self releaseBarVisibilityForOwner:[notification object]
427                        withAnimation:YES
428                                delay:YES];
431 - (void)configurePresentationModeController {
432   BOOL fullscreen_for_tab = browser_->exclusive_access_manager()
433                                 ->fullscreen_controller()
434                                 ->IsWindowFullscreenForTabOrPending();
435   BOOL kiosk_mode =
436       base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
437   BOOL showDropdown =
438       !fullscreen_for_tab && !kiosk_mode && ([self floatingBarHasFocus]);
439   if (permissionBubbleCocoa_ && permissionBubbleCocoa_->IsVisible()) {
440     DCHECK(permissionBubbleCocoa_->window());
441     // A visible permission bubble will force the dropdown to remain visible.
442     [self lockBarVisibilityForOwner:permissionBubbleCocoa_->window()
443                       withAnimation:NO
444                               delay:NO];
445     showDropdown = YES;
446     // Register to be notified when the permission bubble is closed, to
447     // allow fullscreen to hide the dropdown.
448     NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
449     [center addObserver:self
450                selector:@selector(permissionBubbleWindowWillClose:)
451                    name:NSWindowWillCloseNotification
452                  object:permissionBubbleCocoa_->window()];
453   }
454   if (showDropdown) {
455     // Turn on layered mode for the window's root view for the entry
456     // animation.  Without this, the OS fullscreen animation for entering
457     // fullscreen mode does not correctly draw the tab strip.
458     // It will be turned off (set back to NO) when the animation finishes,
459     // in -windowDidEnterFullScreen:.
460     // Leaving wantsLayer on for the duration of presentation mode causes
461     // performance issues when the dropdown is animated in/out.  It also does
462     // not seem to be required for the exit animation.
463     windowViewWantsLayer_ = [[[self window] contentView] wantsLayer];
464     [[[self window] contentView] setWantsLayer:YES];
465   }
467   NSView* contentView = [[self window] contentView];
468   [presentationModeController_
469       enterPresentationModeForContentView:contentView
470                              showDropdown:showDropdown];
473 - (void)adjustUIForExitingFullscreenAndStopOmniboxSliding {
474   [presentationModeController_ exitPresentationMode];
475   presentationModeController_.reset();
477   // Force the bookmark bar z-order to update.
478   [[bookmarkBarController_ view] removeFromSuperview];
479   [self layoutSubviews];
482 - (void)adjustUIForSlidingFullscreenStyle:(fullscreen_mac::SlidingStyle)style {
483   if (!presentationModeController_) {
484     presentationModeController_.reset(
485         [self newPresentationModeControllerWithStyle:style]);
486     [self configurePresentationModeController];
487   } else {
488     presentationModeController_.get().slidingStyle = style;
489   }
491   if (!floatingBarBackingView_.get() &&
492       ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
493     floatingBarBackingView_.reset(
494         [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
495     [floatingBarBackingView_
496         setAutoresizingMask:(NSViewWidthSizable | NSViewMinYMargin)];
497   }
499   // Force the bookmark bar z-order to update.
500   [[bookmarkBarController_ view] removeFromSuperview];
501   [self layoutSubviews];
504 - (PresentationModeController*)newPresentationModeControllerWithStyle:
505     (fullscreen_mac::SlidingStyle)style {
506   return [[PresentationModeController alloc] initWithBrowserController:self
507                                                                  style:style];
510 - (void)enterImmersiveFullscreen {
511   RecordFullscreenWindowLocation([self window]);
512   RecordFullscreenStyle(IMMERSIVE_FULLSCREEN);
514   // Set to NO by |-windowDidEnterFullScreen:|.
515   enteringImmersiveFullscreen_ = YES;
517   // Fade to black.
518   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
519   Boolean didFadeOut = NO;
520   CGDisplayFadeReservationToken token;
521   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
522       == kCGErrorSuccess) {
523     didFadeOut = YES;
524     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
525         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
526   }
528   // Create the fullscreen window.
529   fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
530   savedRegularWindow_ = [[self window] retain];
531   savedRegularWindowFrame_ = [savedRegularWindow_ frame];
533   [self moveViewsForImmersiveFullscreen:YES
534                           regularWindow:[self window]
535                        fullscreenWindow:fullscreenWindow_.get()];
537   fullscreen_mac::SlidingStyle style = fullscreen_mac::OMNIBOX_TABS_HIDDEN;
538   [self adjustUIForSlidingFullscreenStyle:style];
540   // AppKit is helpful and prevents NSWindows from having the same height as
541   // the screen while the menu bar is showing. This only applies to windows on
542   // a secondary screen, in a separate space. Calling [NSWindow
543   // setFrame:display:] with the screen's height will always reduce the
544   // height by the height of the MenuBar. Calling the method with any other
545   // height works fine. The relevant method in the 10.10 AppKit SDK is called:
546   // _canAdjustSizeForScreensHaveSeparateSpacesIfFillingSecondaryScreen
547   //
548   // TODO(erikchen): Refactor the logic to allow the window to be shown after
549   // the menubar has been hidden. This would remove the need for this hack.
550   // http://crbug.com/403203
551   NSRect frame = [[[self window] screen] frame];
552   if (!NSEqualRects(frame, [fullscreenWindow_ frame]))
553     [fullscreenWindow_ setFrame:[[[self window] screen] frame] display:YES];
555   [self layoutSubviews];
557   [self windowDidEnterFullScreen:nil];
559   // Fade back in.
560   if (didFadeOut) {
561     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
562         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
563     CGReleaseDisplayFadeReservation(token);
564   }
567 - (void)exitImmersiveFullscreen {
568   // Fade to black.
569   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
570   Boolean didFadeOut = NO;
571   CGDisplayFadeReservationToken token;
572   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
573       == kCGErrorSuccess) {
574     didFadeOut = YES;
575     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
576         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
577   }
579   [self windowWillExitFullScreen:nil];
581   [self moveViewsForImmersiveFullscreen:NO
582                           regularWindow:savedRegularWindow_
583                        fullscreenWindow:fullscreenWindow_.get()];
585   // When exiting fullscreen mode, we need to call layoutSubviews manually.
586   [savedRegularWindow_ autorelease];
587   savedRegularWindow_ = nil;
588   fullscreenWindow_.reset();
589   [self layoutSubviews];
591   [self windowDidExitFullScreen:nil];
593   // Fade back in.
594   if (didFadeOut) {
595     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
596         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
597     CGReleaseDisplayFadeReservation(token);
598   }
601 - (void)showFullscreenExitBubbleIfNecessary {
602   // This method is called in response to
603   // |-updateFullscreenExitBubbleURL:bubbleType:|. If we're in the middle of the
604   // transition into fullscreen (i.e., using the AppKit Fullscreen API), do not
605   // show the bubble because it will cause visual jank
606   // (http://crbug.com/130649). This will be called again as part of
607   // |-windowDidEnterFullScreen:|, so arrange to do that work then instead.
608   if (enteringAppKitFullscreen_)
609     return;
611   [self hideOverlayIfPossibleWithAnimation:NO delay:NO];
613   if (exclusiveAccessBubbleType_ == EXCLUSIVE_ACCESS_BUBBLE_TYPE_NONE ||
614       exclusiveAccessBubbleType_ ==
615           EXCLUSIVE_ACCESS_BUBBLE_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) {
616     // Show no exit instruction bubble on Mac when in Browser Fullscreen.
617     [self destroyFullscreenExitBubbleIfNecessary];
618   } else {
619     [exclusiveAccessBubbleWindowController_ closeImmediately];
620     exclusiveAccessBubbleWindowController_.reset(
621         [[ExclusiveAccessBubbleWindowController alloc]
622                        initWithOwner:self
623             exclusive_access_manager:browser_.get()->exclusive_access_manager()
624                              profile:browser_.get()->profile()
625                                  url:fullscreenUrl_
626                           bubbleType:exclusiveAccessBubbleType_]);
627     [exclusiveAccessBubbleWindowController_ showWindow];
628   }
631 - (void)destroyFullscreenExitBubbleIfNecessary {
632   [exclusiveAccessBubbleWindowController_ closeImmediately];
633   exclusiveAccessBubbleWindowController_.reset();
636 - (void)contentViewDidResize:(NSNotification*)notification {
637   [self layoutSubviews];
640 - (void)registerForContentViewResizeNotifications {
641   [[NSNotificationCenter defaultCenter]
642       addObserver:self
643          selector:@selector(contentViewDidResize:)
644              name:NSViewFrameDidChangeNotification
645            object:[[self window] contentView]];
648 - (void)deregisterForContentViewResizeNotifications {
649   [[NSNotificationCenter defaultCenter]
650       removeObserver:self
651                 name:NSViewFrameDidChangeNotification
652               object:[[self window] contentView]];
655 - (NSSize)window:(NSWindow*)window
656     willUseFullScreenContentSize:(NSSize)proposedSize {
657   return proposedSize;
660 - (NSApplicationPresentationOptions)window:(NSWindow*)window
661     willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
662   return (opt |
663           NSApplicationPresentationAutoHideDock |
664           NSApplicationPresentationAutoHideMenuBar);
667 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
668   RecordFullscreenWindowLocation([self window]);
669   RecordFullscreenStyle(enteringPresentationMode_ ? PRESENTATION_MODE
670                                                   : CANONICAL_FULLSCREEN);
672   if (notification)  // For System Fullscreen when non-nil.
673     [self registerForContentViewResizeNotifications];
675   NSWindow* window = [self window];
676   savedRegularWindowFrame_ = [window frame];
677   BOOL mode = enteringPresentationMode_ ||
678               browser_->exclusive_access_manager()
679                   ->fullscreen_controller()
680                   ->IsWindowFullscreenForTabOrPending();
681   enteringAppKitFullscreen_ = YES;
682   enteringAppKitFullscreenOnPrimaryScreen_ =
683       [[[self window] screen] isEqual:[[NSScreen screens] objectAtIndex:0]];
685   fullscreen_mac::SlidingStyle style =
686       mode ? fullscreen_mac::OMNIBOX_TABS_HIDDEN
687            : fullscreen_mac::OMNIBOX_TABS_PRESENT;
689   [self adjustUIForSlidingFullscreenStyle:style];
692 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
693   enterFullscreenTransition_.reset();
695   // In Yosemite, some combination of the titlebar and toolbar always show in
696   // full-screen mode. We do not want either to show. Search for the window that
697   // contains the views, and hide it. There is no need to ever unhide the view.
698   // http://crbug.com/380235
699   if (base::mac::IsOSYosemiteOrLater()) {
700     for (NSWindow* window in [[NSApplication sharedApplication] windows]) {
701       if ([window
702               isKindOfClass:NSClassFromString(@"NSToolbarFullScreenWindow")]) {
703         [[window contentView] setHidden:YES];
704       }
705     }
706   }
708   if ([self shouldUseMavericksAppKitFullscreenHack]) {
709     // Apply a hack to fix the size of the window. This is the last run of the
710     // MessageLoop where the hack will not work, so dispatch the hack to the
711     // top of the MessageLoop.
712     base::Callback<void(void)> callback = base::BindBlock(^{
713         if (![self isInAppKitFullscreen])
714           return;
716         // The window's frame should be exactly 22 points too short.
717         CGFloat kExpectedHeightDifference = 22;
718         NSRect currentFrame = [[self window] frame];
719         NSRect expectedFrame = [[[self window] screen] frame];
720         if (!NSEqualPoints(currentFrame.origin, expectedFrame.origin))
721           return;
722         if (currentFrame.size.width != expectedFrame.size.width)
723           return;
724         CGFloat heightDelta =
725             expectedFrame.size.height - currentFrame.size.height;
726         if (fabs(heightDelta - kExpectedHeightDifference) > 0.01)
727           return;
729         [[self window] setFrame:expectedFrame display:YES];
730     });
731     base::MessageLoop::current()->PostTask(FROM_HERE, callback);
732   }
734   if (notification)  // For System Fullscreen when non-nil.
735     [self deregisterForContentViewResizeNotifications];
736   enteringAppKitFullscreen_ = NO;
737   enteringImmersiveFullscreen_ = NO;
738   enteringPresentationMode_ = NO;
740   [self showFullscreenExitBubbleIfNecessary];
741   browser_->WindowFullscreenStateChanged();
742   [[[self window] contentView] setWantsLayer:windowViewWantsLayer_];
745 - (void)windowWillExitFullScreen:(NSNotification*)notification {
746   if (notification)  // For System Fullscreen when non-nil.
747     [self registerForContentViewResizeNotifications];
748   [self destroyFullscreenExitBubbleIfNecessary];
749   [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
752 - (void)windowDidExitFullScreen:(NSNotification*)notification {
753   if (notification)  // For System Fullscreen when non-nil.
754     [self deregisterForContentViewResizeNotifications];
755   browser_->WindowFullscreenStateChanged();
758 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
759   [self deregisterForContentViewResizeNotifications];
760   enteringAppKitFullscreen_ = NO;
761   [self adjustUIForExitingFullscreenAndStopOmniboxSliding];
764 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
765   [self deregisterForContentViewResizeNotifications];
767   // Force a relayout to try and get the window back into a reasonable state.
768   [self layoutSubviews];
771 - (void)enableBarVisibilityUpdates {
772   // Early escape if there's nothing to do.
773   if (barVisibilityUpdatesEnabled_)
774     return;
776   barVisibilityUpdatesEnabled_ = YES;
778   if ([barVisibilityLocks_ count])
779     [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
780   else
781     [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
784 - (void)disableBarVisibilityUpdates {
785   // Early escape if there's nothing to do.
786   if (!barVisibilityUpdatesEnabled_)
787     return;
789   barVisibilityUpdatesEnabled_ = NO;
790   [presentationModeController_ cancelAnimationAndTimers];
793 - (void)hideOverlayIfPossibleWithAnimation:(BOOL)animation delay:(BOOL)delay {
794   if (!barVisibilityUpdatesEnabled_ || [barVisibilityLocks_ count])
795     return;
796   [presentationModeController_ ensureOverlayHiddenWithAnimation:animation
797                                                           delay:delay];
800 - (CGFloat)toolbarDividerOpacity {
801   return [bookmarkBarController_ toolbarDividerOpacity];
804 - (void)updateInfoBarTipVisibility {
805   // If there's no toolbar then hide the infobar tip.
806   [infoBarContainerController_
807       setShouldSuppressTopInfoBarTip:![self hasToolbar]];
810 - (NSInteger)pageInfoBubblePointY {
811   LocationBarViewMac* locationBarView = [self locationBarBridge];
813   // The point, in window coordinates.
814   NSPoint iconBottom = locationBarView->GetPageInfoBubblePoint();
816   // The toolbar, in window coordinates.
817   NSView* toolbar = [toolbarController_ view];
818   CGFloat toolbarY = NSMinY([toolbar convertRect:[toolbar bounds] toView:nil]);
820   return iconBottom.y - toolbarY;
823 - (void)enterAppKitFullscreen {
824   DCHECK(base::mac::IsOSLionOrLater());
825   if (FramedBrowserWindow* framedBrowserWindow =
826           base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
827     [framedBrowserWindow toggleSystemFullScreen];
828   }
831 - (void)exitAppKitFullscreen {
832   DCHECK(base::mac::IsOSLionOrLater());
833   if (FramedBrowserWindow* framedBrowserWindow =
834           base::mac::ObjCCast<FramedBrowserWindow>([self window])) {
835     [framedBrowserWindow toggleSystemFullScreen];
836   }
839 - (NSRect)fullscreenButtonFrame {
840   // NSWindowFullScreenButton is 10.7+ and results in log spam on 10.6 if used.
841   if (base::mac::IsOSSnowLeopard())
842     return NSZeroRect;
844   NSButton* fullscreenButton =
845       [[self window] standardWindowButton:NSWindowFullScreenButton];
846   if (!fullscreenButton)
847     return NSZeroRect;
849   NSRect buttonFrame = [fullscreenButton frame];
851   // When called from -windowWillExitFullScreen:, the button's frame may not
852   // be updated yet to match the new window size.
853   // We need to return where the button should be positioned.
854   NSView* rootView = [[[self window] contentView] superview];
855   if ([rootView respondsToSelector:@selector(_fullScreenButtonOrigin)])
856     buttonFrame.origin = [rootView _fullScreenButtonOrigin];
858   return buttonFrame;
861 - (void)updateLayoutParameters:(BrowserWindowLayout*)layout {
862   [layout setContentViewSize:[[[self window] contentView] bounds].size];
863   [layout setWindowSize:[[self window] frame].size];
865   [layout setInAnyFullscreen:[self isInAnyFullscreenMode]];
866   [layout setFullscreenSlidingStyle:
867       presentationModeController_.get().slidingStyle];
868   [layout setFullscreenMenubarOffset:
869       [presentationModeController_ menubarOffset]];
870   [layout setFullscreenToolbarFraction:
871       [presentationModeController_ toolbarFraction]];
873   [layout setHasTabStrip:[self hasTabStrip]];
874   [layout setFullscreenButtonFrame:[self fullscreenButtonFrame]];
876   if ([self shouldShowAvatar]) {
877     NSView* avatar = [avatarButtonController_ view];
878     [layout setShouldShowAvatar:YES];
879     [layout setShouldUseNewAvatar:[self shouldUseNewAvatarButton]];
880     [layout setAvatarSize:[avatar frame].size];
881     [layout setAvatarLineWidth:[[avatar superview] cr_lineWidth]];
882   }
884   [layout setHasToolbar:[self hasToolbar]];
885   [layout setToolbarHeight:NSHeight([[toolbarController_ view] bounds])];
887   [layout setHasLocationBar:[self hasLocationBar]];
889   [layout setPlaceBookmarkBarBelowInfoBar:[self placeBookmarkBarBelowInfoBar]];
890   [layout setBookmarkBarHidden:[bookmarkBarController_ view].isHidden];
891   [layout setBookmarkBarHeight:
892       NSHeight([[bookmarkBarController_ view] bounds])];
894   [layout setInfoBarHeight:[infoBarContainerController_ heightOfInfoBars]];
895   [layout setPageInfoBubblePointY:[self pageInfoBubblePointY]];
897   [layout setHasDownloadShelf:(downloadShelfController_.get() != nil)];
898   [layout setDownloadShelfHeight:
899       NSHeight([[downloadShelfController_ view] bounds])];
902 - (void)applyLayout:(BrowserWindowLayout*)layout {
903   chrome::LayoutOutput output = [layout computeLayout];
905   if (!NSIsEmptyRect(output.tabStripLayout.frame))
906     [self applyTabStripLayout:output.tabStripLayout];
908   if (!NSIsEmptyRect(output.toolbarFrame))
909     [[toolbarController_ view] setFrame:output.toolbarFrame];
911   if (!NSIsEmptyRect(output.bookmarkFrame)) {
912     NSView* bookmarkBarView = [bookmarkBarController_ view];
913     [bookmarkBarView setFrame:output.bookmarkFrame];
915     // Pin the bookmark bar to the top of the window and make the width
916     // flexible.
917     [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
919     [bookmarkBarController_ layoutSubviews];
920   }
922   // The info bar is never hidden. Sometimes it has zero effective height.
923   [[infoBarContainerController_ view] setFrame:output.infoBarFrame];
924   [infoBarContainerController_
925       setMaxTopArrowHeight:output.infoBarMaxTopArrowHeight];
926   [infoBarContainerController_
927       setInfobarArrowX:[self locationBarBridge]->GetPageInfoBubblePoint().x];
929   if (!NSIsEmptyRect(output.downloadShelfFrame))
930     [[downloadShelfController_ view] setFrame:output.downloadShelfFrame];
932   [self layoutTabContentArea:output.contentAreaFrame];
934   if (!NSIsEmptyRect(output.fullscreenBackingBarFrame)) {
935     [floatingBarBackingView_ setFrame:output.fullscreenBackingBarFrame];
936     [presentationModeController_
937         overlayFrameChanged:output.fullscreenBackingBarFrame];
938   }
940   [findBarCocoaController_
941       positionFindBarViewAtMaxY:output.findBarMaxY
942                        maxWidth:NSWidth(output.contentAreaFrame)];
944   [exclusiveAccessBubbleWindowController_
945       positionInWindowAtTop:output.fullscreenExitButtonMaxY
946                       width:NSWidth(output.contentAreaFrame)];
949 - (void)updateSubviewZOrder {
950   if ([self isInAnyFullscreenMode])
951     [self updateSubviewZOrderFullscreen];
952   else
953     [self updateSubviewZOrderNormal];
956 - (void)updateSubviewZOrderNormal {
957   base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
958   if ([downloadShelfController_ view])
959     [subviews addObject:[downloadShelfController_ view]];
960   if ([bookmarkBarController_ view])
961     [subviews addObject:[bookmarkBarController_ view]];
962   if ([toolbarController_ view])
963     [subviews addObject:[toolbarController_ view]];
964   if ([infoBarContainerController_ view])
965     [subviews addObject:[infoBarContainerController_ view]];
966   if ([self tabContentArea])
967     [subviews addObject:[self tabContentArea]];
968   if ([findBarCocoaController_ view])
969     [subviews addObject:[findBarCocoaController_ view]];
971   [self setContentViewSubviews:subviews];
974 - (void)updateSubviewZOrderFullscreen {
975   base::scoped_nsobject<NSMutableArray> subviews([[NSMutableArray alloc] init]);
976   if ([downloadShelfController_ view])
977     [subviews addObject:[downloadShelfController_ view]];
978   if ([self tabContentArea])
979     [subviews addObject:[self tabContentArea]];
980   if ([self placeBookmarkBarBelowInfoBar]) {
981     if ([bookmarkBarController_ view])
982       [subviews addObject:[bookmarkBarController_ view]];
983     if (floatingBarBackingView_)
984       [subviews addObject:floatingBarBackingView_];
985   } else {
986     if (floatingBarBackingView_)
987       [subviews addObject:floatingBarBackingView_];
988     if ([bookmarkBarController_ view])
989       [subviews addObject:[bookmarkBarController_ view]];
990   }
991   if ([toolbarController_ view])
992     [subviews addObject:[toolbarController_ view]];
993   if ([infoBarContainerController_ view])
994     [subviews addObject:[infoBarContainerController_ view]];
995   if ([findBarCocoaController_ view])
996     [subviews addObject:[findBarCocoaController_ view]];
998   [self setContentViewSubviews:subviews];
1001 - (void)setContentViewSubviews:(NSArray*)subviews {
1002   // Subviews already match.
1003   if ([[self.chromeContentView subviews] isEqual:subviews])
1004     return;
1006   // The tabContentArea isn't a subview, so just set all the subviews.
1007   NSView* tabContentArea = [self tabContentArea];
1008   if (![[self.chromeContentView subviews] containsObject:tabContentArea]) {
1009     [self.chromeContentView setSubviews:subviews];
1010     return;
1011   }
1013   // Remove all subviews that aren't the tabContentArea.
1014   for (NSView* view in [[[self.chromeContentView subviews] copy] autorelease]) {
1015     if (view != tabContentArea)
1016       [view removeFromSuperview];
1017   }
1019   // Add in the subviews below the tabContentArea.
1020   NSInteger index = [subviews indexOfObject:tabContentArea];
1021   for (int i = index - 1; i >= 0; --i) {
1022     NSView* view = [subviews objectAtIndex:i];
1023     [self.chromeContentView addSubview:view
1024                             positioned:NSWindowBelow
1025                             relativeTo:nil];
1026   }
1028   // Add in the subviews above the tabContentArea.
1029   for (NSUInteger i = index + 1; i < [subviews count]; ++i) {
1030     NSView* view = [subviews objectAtIndex:i];
1031     [self.chromeContentView addSubview:view
1032                             positioned:NSWindowAbove
1033                             relativeTo:nil];
1034   }
1037 + (BOOL)systemSettingsRequireMavericksAppKitFullscreenHack {
1038   if (!base::mac::IsOSMavericks())
1039     return NO;
1040   return [NSScreen respondsToSelector:@selector(screensHaveSeparateSpaces)] &&
1041          [NSScreen screensHaveSeparateSpaces];
1044 - (BOOL)shouldUseMavericksAppKitFullscreenHack {
1045   if (![[self class] systemSettingsRequireMavericksAppKitFullscreenHack])
1046     return NO;
1047   if (!enteringAppKitFullscreen_)
1048     return NO;
1049   if (enteringAppKitFullscreenOnPrimaryScreen_)
1050     return NO;
1052   return YES;
1055 - (BOOL)shouldUseCustomAppKitFullscreenTransition {
1056   if (base::mac::IsOSMountainLionOrEarlier())
1057     return NO;
1059   NSView* root = [[self.window contentView] superview];
1060   if (!root.layer)
1061     return NO;
1063   // AppKit on OSX 10.9 has a bug for applications linked against OSX 10.8 SDK
1064   // and earlier. Under specific circumstances, it prevents the custom AppKit
1065   // transition from working well. See http://crbug.com/396980 for more
1066   // details.
1067   if ([[self class] systemSettingsRequireMavericksAppKitFullscreenHack] &&
1068       ![[[self window] screen] isEqual:[[NSScreen screens] objectAtIndex:0]]) {
1069     return NO;
1070   }
1072   return YES;
1075 - (NSArray*)customWindowsToEnterFullScreenForWindow:(NSWindow*)window {
1076   DCHECK([window isEqual:self.window]);
1078   if (![self shouldUseCustomAppKitFullscreenTransition])
1079     return nil;
1081   enterFullscreenTransition_.reset(
1082       [[BrowserWindowEnterFullscreenTransition alloc]
1083           initWithWindow:self.window]);
1084   return [enterFullscreenTransition_ customWindowsToEnterFullScreen];
1087 - (void)window:(NSWindow*)window
1088     startCustomAnimationToEnterFullScreenWithDuration:(NSTimeInterval)duration {
1089   DCHECK([window isEqual:self.window]);
1090   [enterFullscreenTransition_
1091       startCustomAnimationToEnterFullScreenWithDuration:duration];
1094 - (BOOL)shouldConstrainFrameRect {
1095   if ([enterFullscreenTransition_ shouldWindowBeUnconstrained])
1096     return NO;
1098   return [super shouldConstrainFrameRect];
1101 @end  // @implementation BrowserWindowController(Private)