Upstream version 5.34.104.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 #import "base/mac/scoped_nsobject.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/prefs/scoped_user_pref_update.h"
13 #include "chrome/browser/browser_process.h"
14 #include "chrome/browser/fullscreen.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/profiles/profile_info_util.h"
17 #include "chrome/browser/ui/bookmarks/bookmark_tab_helper.h"
18 #include "chrome/browser/ui/browser.h"
19 #include "chrome/browser/ui/browser_window_state.h"
20 #import "chrome/browser/ui/cocoa/browser/avatar_button_controller.h"
21 #import "chrome/browser/ui/cocoa/browser/avatar_icon_controller.h"
22 #import "chrome/browser/ui/cocoa/dev_tools_controller.h"
23 #import "chrome/browser/ui/cocoa/fast_resize_view.h"
24 #import "chrome/browser/ui/cocoa/find_bar/find_bar_cocoa_controller.h"
25 #import "chrome/browser/ui/cocoa/floating_bar_backing_view.h"
26 #import "chrome/browser/ui/cocoa/framed_browser_window.h"
27 #import "chrome/browser/ui/cocoa/fullscreen_mode_controller.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/nsview_additions.h"
32 #import "chrome/browser/ui/cocoa/presentation_mode_controller.h"
33 #import "chrome/browser/ui/cocoa/status_bubble_mac.h"
34 #import "chrome/browser/ui/cocoa/tab_contents/overlayable_contents_controller.h"
35 #import "chrome/browser/ui/cocoa/tabs/tab_strip_controller.h"
36 #import "chrome/browser/ui/cocoa/tabs/tab_strip_view.h"
37 #import "chrome/browser/ui/cocoa/toolbar/toolbar_controller.h"
38 #include "chrome/browser/ui/fullscreen/fullscreen_controller.h"
39 #include "chrome/browser/ui/tabs/tab_strip_model.h"
40 #include "chrome/common/chrome_switches.h"
41 #include "chrome/common/pref_names.h"
42 #include "content/public/browser/render_widget_host_view.h"
43 #include "content/public/browser/web_contents.h"
44 #include "content/public/browser/web_contents_view.h"
45 #import "ui/base/cocoa/focus_tracker.h"
46 #include "ui/base/ui_base_types.h"
47
48 using content::RenderWidgetHostView;
49 using content::WebContents;
50
51 namespace {
52
53 // Space between the incognito badge and the right edge of the window.
54 const CGFloat kAvatarRightOffset = 4;
55
56 // The amount by which to shrink the tab strip (on the right) when the
57 // incognito badge is present.
58 const CGFloat kAvatarTabStripShrink = 18;
59
60 // Width of the full screen icon. Used to position the AvatarButton to the
61 // left of the icon.
62 const CGFloat kFullscreenIconWidth = 32;
63
64 // Insets for the location bar, used when the full toolbar is hidden.
65 // TODO(viettrungluu): We can argue about the "correct" insetting; I like the
66 // following best, though arguably 0 inset is better/more correct.
67 const CGFloat kLocBarLeftRightInset = 1;
68 const CGFloat kLocBarTopInset = 0;
69 const CGFloat kLocBarBottomInset = 1;
70
71 }  // namespace
72
73 @implementation BrowserWindowController(Private)
74
75 // Create the tab strip controller.
76 - (void)createTabStripController {
77   DCHECK([overlayableContentsController_ activeContainer]);
78   DCHECK([[overlayableContentsController_ activeContainer] window]);
79   tabStripController_.reset([[TabStripController alloc]
80       initWithView:[self tabStripView]
81         switchView:[overlayableContentsController_ activeContainer]
82            browser:browser_.get()
83           delegate:self]);
84 }
85
86 - (void)saveWindowPositionIfNeeded {
87   if (!chrome::ShouldSaveWindowPlacement(browser_.get()))
88     return;
89
90   // If we're in fullscreen mode, save the position of the regular window
91   // instead.
92   NSWindow* window = [self isFullscreen] ? savedRegularWindow_ : [self window];
93
94   // Window positions are stored relative to the origin of the primary monitor.
95   NSRect monitorFrame = [[[NSScreen screens] objectAtIndex:0] frame];
96   NSScreen* windowScreen = [window screen];
97
98   // Start with the window's frame, which is in virtual coordinates.
99   // Do some y twiddling to flip the coordinate system.
100   gfx::Rect bounds(NSRectToCGRect([window frame]));
101   bounds.set_y(monitorFrame.size.height - bounds.y() - bounds.height());
102
103   // Browser::SaveWindowPlacement saves information for session restore.
104   ui::WindowShowState show_state = ui::SHOW_STATE_NORMAL;
105   if ([window isMiniaturized])
106     show_state = ui::SHOW_STATE_MINIMIZED;
107   else if ([self isFullscreen])
108     show_state = ui::SHOW_STATE_FULLSCREEN;
109   chrome::SaveWindowPlacement(browser_.get(), bounds, show_state);
110
111   // |windowScreen| can be nil (for example, if the monitor arrangement was
112   // changed while in fullscreen mode).  If we see a nil screen, return without
113   // saving.
114   // TODO(rohitrao): We should just not save anything for fullscreen windows.
115   // http://crbug.com/36479.
116   if (!windowScreen)
117     return;
118
119   // Only save main window information to preferences.
120   PrefService* prefs = browser_->profile()->GetPrefs();
121   if (!prefs || browser_ != chrome::GetLastActiveBrowser())
122     return;
123
124   // Save the current work area, in flipped coordinates.
125   gfx::Rect workArea(NSRectToCGRect([windowScreen visibleFrame]));
126   workArea.set_y(monitorFrame.size.height - workArea.y() - workArea.height());
127
128   DictionaryPrefUpdate update(
129       prefs,
130       chrome::GetWindowPlacementKey(browser_.get()).c_str());
131   base::DictionaryValue* windowPreferences = update.Get();
132   windowPreferences->SetInteger("left", bounds.x());
133   windowPreferences->SetInteger("top", bounds.y());
134   windowPreferences->SetInteger("right", bounds.right());
135   windowPreferences->SetInteger("bottom", bounds.bottom());
136   windowPreferences->SetBoolean("maximized", false);
137   windowPreferences->SetBoolean("always_on_top", false);
138   windowPreferences->SetInteger("work_area_left", workArea.x());
139   windowPreferences->SetInteger("work_area_top", workArea.y());
140   windowPreferences->SetInteger("work_area_right", workArea.right());
141   windowPreferences->SetInteger("work_area_bottom", workArea.bottom());
142 }
143
144 - (NSRect)window:(NSWindow*)window
145 willPositionSheet:(NSWindow*)sheet
146        usingRect:(NSRect)defaultSheetRect {
147   // Position the sheet as follows:
148   //  - If the bookmark bar is hidden or shown as a bubble (on the NTP when the
149   //    bookmark bar is disabled), position the sheet immediately below the
150   //    normal toolbar.
151   //  - If the bookmark bar is shown (attached to the normal toolbar), position
152   //    the sheet below the bookmark bar.
153   //  - If the bookmark bar is currently animating, position the sheet according
154   //    to where the bar will be when the animation ends.
155   switch ([bookmarkBarController_ currentState]) {
156     case BookmarkBar::SHOW: {
157       NSRect bookmarkBarFrame = [[bookmarkBarController_ view] frame];
158       defaultSheetRect.origin.y = bookmarkBarFrame.origin.y;
159       break;
160     }
161     case BookmarkBar::HIDDEN:
162     case BookmarkBar::DETACHED: {
163       if ([self hasToolbar]) {
164         NSRect toolbarFrame = [[toolbarController_ view] frame];
165         defaultSheetRect.origin.y = toolbarFrame.origin.y;
166       } else {
167         // The toolbar is not shown in application mode. The sheet should be
168         // located at the top of the window, under the title of the window.
169         defaultSheetRect.origin.y = NSHeight([[window contentView] frame]) -
170                                     defaultSheetRect.size.height;
171       }
172       break;
173     }
174   }
175   return defaultSheetRect;
176 }
177
178 - (void)layoutSubviews {
179   // With the exception of the top tab strip, the subviews which we lay out are
180   // subviews of the content view, so we mainly work in the content view's
181   // coordinate system. Note, however, that the content view's coordinate system
182   // and the window's base coordinate system should coincide.
183   NSWindow* window = [self window];
184   NSView* contentView = [window contentView];
185   NSRect contentBounds = [contentView bounds];
186   CGFloat minX = NSMinX(contentBounds);
187   CGFloat minY = NSMinY(contentBounds);
188   CGFloat width = NSWidth(contentBounds);
189
190   BOOL useSimplifiedFullscreen = CommandLine::ForCurrentProcess()->HasSwitch(
191       switches::kEnableSimplifiedFullscreen);
192
193   // Suppress title drawing if necessary.
194   if ([window respondsToSelector:@selector(setShouldHideTitle:)])
195     [(id)window setShouldHideTitle:![self hasTitleBar]];
196
197   // Update z-order. The code below depends on this.
198   [self updateSubviewZOrder:[self inPresentationMode]];
199
200   BOOL inPresentationMode = [self inPresentationMode];
201   CGFloat floatingBarHeight = [self floatingBarHeight];
202   // In presentation mode, |yOffset| accounts for the sliding position of the
203   // floating bar and the extra offset needed to dodge the menu bar.
204   CGFloat yOffset = inPresentationMode && !useSimplifiedFullscreen ?
205       (std::floor((1 - floatingBarShownFraction_) * floatingBarHeight) -
206           [presentationModeController_ floatingBarVerticalOffset]) : 0;
207   CGFloat maxY = NSMaxY(contentBounds) + yOffset;
208
209   if ([self hasTabStrip]) {
210     // If we need to lay out the top tab strip, replace |maxY| with a higher
211     // value, and then lay out the tab strip.
212     NSRect windowFrame = [contentView convertRect:[window frame] fromView:nil];
213     maxY = NSHeight(windowFrame) + yOffset;
214     if (useSimplifiedFullscreen && [self isFullscreen]) {
215       CGFloat tabStripHeight = NSHeight([[self tabStripView] frame]);
216       CGFloat revealAmount = (1 - floatingBarShownFraction_) * tabStripHeight;
217       // In simplified fullscreen, only the toolbar is visible by default, and
218       // the tabstrip and menu bar come down (each separately) when the user
219       // mouses near the top of the window. Push the maxY of the toolbar up by
220       // the amount of the tabstrip that is revealed, while removing the amount
221       // of space needed by the menu bar.
222       maxY += std::floor(
223           revealAmount - [fullscreenModeController_ menuBarHeight]);
224     }
225     maxY = [self layoutTabStripAtMaxY:maxY
226                                 width:width
227                            fullscreen:[self isFullscreen]];
228   }
229
230   // Sanity-check |maxY|.
231   DCHECK_GE(maxY, minY);
232   DCHECK_LE(maxY, NSMaxY(contentBounds) + yOffset);
233
234   // Place the toolbar at the top of the reserved area.
235   maxY = [self layoutToolbarAtMinX:minX maxY:maxY width:width];
236
237   // If we're not displaying the bookmark bar below the info bar, then it goes
238   // immediately below the toolbar.
239   BOOL placeBookmarkBarBelowInfoBar = [self placeBookmarkBarBelowInfoBar];
240   if (!placeBookmarkBarBelowInfoBar)
241     maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width];
242
243   // The floating bar backing view doesn't actually add any height.
244   NSRect floatingBarBackingRect =
245       NSMakeRect(minX, maxY, width, floatingBarHeight);
246   [self layoutFloatingBarBackingView:floatingBarBackingRect
247                     presentationMode:inPresentationMode];
248
249   // Place the find bar immediately below the toolbar/attached bookmark bar. In
250   // presentation mode, it hangs off the top of the screen when the bar is
251   // hidden.
252   [findBarCocoaController_ positionFindBarViewAtMaxY:maxY maxWidth:width];
253   [fullscreenExitBubbleController_ positionInWindowAtTop:maxY width:width];
254
255   // If in presentation mode, reset |maxY| to top of screen, so that the
256   // floating bar slides over the things which appear to be in the content area.
257   if (inPresentationMode ||
258       (useSimplifiedFullscreen && !fullscreenUrl_.is_empty())) {
259     maxY = NSMaxY(contentBounds);
260   }
261
262   // Also place the info bar container immediate below the toolbar, except in
263   // presentation mode in which case it's at the top of the visual content area.
264   maxY = [self layoutInfoBarAtMinX:minX maxY:maxY width:width];
265
266   // If the bookmark bar is detached, place it next in the visual content area.
267   if (placeBookmarkBarBelowInfoBar)
268     maxY = [self layoutBookmarkBarAtMinX:minX maxY:maxY width:width];
269
270   // Place the download shelf, if any, at the bottom of the view.
271   minY = [self layoutDownloadShelfAtMinX:minX minY:minY width:width];
272
273   // Finally, the content area takes up all of the remaining space.
274   NSRect contentAreaRect = NSMakeRect(minX, minY, width, maxY - minY);
275   [self layoutTabContentArea:contentAreaRect];
276
277   // Normally, we don't need to tell the toolbar whether or not to show the
278   // divider, but things break down during animation.
279   [toolbarController_ setDividerOpacity:[self toolbarDividerOpacity]];
280 }
281
282 - (CGFloat)floatingBarHeight {
283   if (![self inPresentationMode])
284     return 0;
285
286   CGFloat totalHeight = [presentationModeController_ floatingBarVerticalOffset];
287
288   if ([self hasTabStrip])
289     totalHeight += NSHeight([[self tabStripView] frame]);
290
291   if ([self hasToolbar]) {
292     totalHeight += NSHeight([[toolbarController_ view] frame]);
293   } else if ([self hasLocationBar]) {
294     totalHeight += NSHeight([[toolbarController_ view] frame]) +
295                    kLocBarTopInset + kLocBarBottomInset;
296   }
297
298   if (![self placeBookmarkBarBelowInfoBar])
299     totalHeight += NSHeight([[bookmarkBarController_ view] frame]);
300
301   return totalHeight;
302 }
303
304 - (CGFloat)layoutTabStripAtMaxY:(CGFloat)maxY
305                           width:(CGFloat)width
306                      fullscreen:(BOOL)fullscreen {
307   // Nothing to do if no tab strip.
308   if (![self hasTabStrip])
309     return maxY;
310
311   NSView* tabStripView = [self tabStripView];
312   CGFloat tabStripHeight = NSHeight([tabStripView frame]);
313   maxY -= tabStripHeight;
314   [tabStripView setFrame:NSMakeRect(0, maxY, width, tabStripHeight)];
315
316   // Set left indentation based on fullscreen mode status.
317   [tabStripController_ setLeftIndentForControls:(fullscreen ? 0 :
318       [[tabStripController_ class] defaultLeftIndentForControls])];
319
320   // Lay out the icognito/avatar badge because calculating the indentation on
321   // the right depends on it.
322   NSView* avatarButton = [avatarButtonController_ view];
323   if ([self shouldShowAvatar]) {
324     CGFloat badgeXOffset = -kAvatarRightOffset;
325     CGFloat badgeYOffset = 0;
326     CGFloat buttonHeight = NSHeight([avatarButton frame]);
327
328     if ([self shouldUseNewAvatarButton]) {
329       // The fullscreen icon is displayed to the right of the avatar button.
330       if (![self isFullscreen])
331         badgeXOffset -= kFullscreenIconWidth;
332       // Center the button vertically on the tabstrip.
333       badgeYOffset = (tabStripHeight - buttonHeight) / 2;
334     } else {
335       // Actually place the badge *above* |maxY|, by +2 to miss the divider.
336       badgeYOffset = 2 * [[avatarButton superview] cr_lineWidth];
337     }
338
339     [avatarButton setFrameSize:NSMakeSize(NSWidth([avatarButton frame]),
340         std::min(buttonHeight, tabStripHeight))];
341     NSPoint origin =
342         NSMakePoint(width - NSWidth([avatarButton frame]) + badgeXOffset,
343                     maxY + badgeYOffset);
344     [avatarButton setFrameOrigin:origin];
345     [avatarButton setHidden:NO];  // Make sure it's shown.
346   }
347
348   // Calculate the right indentation.  The default indentation built into the
349   // tabstrip leaves enough room for the fullscreen button or presentation mode
350   // toggle button on Lion.  On non-Lion systems, the right indent needs to be
351   // adjusted to make room for the new tab button when an avatar is present.
352   CGFloat rightIndent = 0;
353   if (base::mac::IsOSLionOrLater() &&
354       [[self window] isKindOfClass:[FramedBrowserWindow class]]) {
355     FramedBrowserWindow* window =
356         static_cast<FramedBrowserWindow*>([self window]);
357     rightIndent += -[window fullScreenButtonOriginAdjustment].x;
358
359     // The new avatar is wider than the default indentation, so we need to
360     // account for its width.
361     if ([self shouldUseNewAvatarButton])
362       rightIndent += NSWidth([avatarButton frame]) + kAvatarTabStripShrink;
363   } else if ([self shouldShowAvatar]) {
364     rightIndent += kAvatarTabStripShrink +
365         NSWidth([avatarButton frame]) + kAvatarRightOffset;
366   }
367   [tabStripController_ setRightIndentForControls:rightIndent];
368
369   // Go ahead and layout the tabs.
370   [tabStripController_ layoutTabsWithoutAnimation];
371
372   return maxY;
373 }
374
375 - (CGFloat)layoutToolbarAtMinX:(CGFloat)minX
376                           maxY:(CGFloat)maxY
377                          width:(CGFloat)width {
378   NSView* toolbarView = [toolbarController_ view];
379   NSRect toolbarFrame = [toolbarView frame];
380   if ([self hasToolbar]) {
381     // The toolbar is present in the window, so we make room for it.
382     DCHECK(![toolbarView isHidden]);
383     toolbarFrame.origin.x = minX;
384     toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame);
385     toolbarFrame.size.width = width;
386     maxY -= NSHeight(toolbarFrame);
387   } else {
388     if ([self hasLocationBar]) {
389       // Location bar is present with no toolbar. Put a border of
390       // |kLocBar...Inset| pixels around the location bar.
391       // TODO(viettrungluu): This is moderately ridiculous. The toolbar should
392       // really be aware of what its height should be (the way the toolbar
393       // compression stuff is currently set up messes things up).
394       DCHECK(![toolbarView isHidden]);
395       toolbarFrame.origin.x = kLocBarLeftRightInset;
396       toolbarFrame.origin.y = maxY - NSHeight(toolbarFrame) - kLocBarTopInset;
397       toolbarFrame.size.width = width - 2 * kLocBarLeftRightInset;
398       maxY -= kLocBarTopInset + NSHeight(toolbarFrame) + kLocBarBottomInset;
399     } else {
400       DCHECK([toolbarView isHidden]);
401     }
402   }
403   [toolbarView setFrame:toolbarFrame];
404   return maxY;
405 }
406
407 - (BOOL)placeBookmarkBarBelowInfoBar {
408   // If we are currently displaying the NTP detached bookmark bar or animating
409   // to/from it (from/to anything else), we display the bookmark bar below the
410   // info bar.
411   return [bookmarkBarController_ isInState:BookmarkBar::DETACHED] ||
412          [bookmarkBarController_ isAnimatingToState:BookmarkBar::DETACHED] ||
413          [bookmarkBarController_ isAnimatingFromState:BookmarkBar::DETACHED];
414 }
415
416 - (CGFloat)layoutBookmarkBarAtMinX:(CGFloat)minX
417                               maxY:(CGFloat)maxY
418                              width:(CGFloat)width {
419   [bookmarkBarController_ updateHiddenState];
420
421   NSView* bookmarkBarView = [bookmarkBarController_ view];
422   NSRect frame = [bookmarkBarView frame];
423   frame.origin.x = minX;
424   frame.origin.y = maxY - NSHeight(frame);
425   frame.size.width = width;
426   [bookmarkBarView setFrame:frame];
427   maxY -= NSHeight(frame);
428
429   // Pin the bookmark bar to the top of the window and make the width flexible.
430   [bookmarkBarView setAutoresizingMask:NSViewWidthSizable | NSViewMinYMargin];
431
432   // TODO(viettrungluu): Does this really belong here? Calling it shouldn't be
433   // necessary in the non-NTP case.
434   [bookmarkBarController_ layoutSubviews];
435
436   return maxY;
437 }
438
439 - (void)layoutFloatingBarBackingView:(NSRect)frame
440                     presentationMode:(BOOL)presentationMode {
441   // Only display when in presentation mode.
442   if (presentationMode) {
443     // For certain window types such as app windows (e.g., the dev tools
444     // window), there's no actual overlay. (Displaying one would result in an
445     // overly sliding in only under the menu, which gives an ugly effect.)
446     if (floatingBarBackingView_.get()) {
447       // Set its frame.
448       [floatingBarBackingView_ setFrame:frame];
449     }
450
451     // But we want the logic to work as usual (for show/hide/etc. purposes).
452     [presentationModeController_ overlayFrameChanged:frame];
453   } else {
454     // Okay to call even if |floatingBarBackingView_| is nil.
455     if ([floatingBarBackingView_ superview])
456       [floatingBarBackingView_ removeFromSuperview];
457   }
458 }
459
460 - (CGFloat)layoutInfoBarAtMinX:(CGFloat)minX
461                           maxY:(CGFloat)maxY
462                          width:(CGFloat)width {
463   NSView* containerView = [infoBarContainerController_ view];
464   NSRect containerFrame = [containerView frame];
465   maxY -= NSHeight(containerFrame);
466   maxY += [infoBarContainerController_ overlappingTipHeight];
467   containerFrame.origin.x = minX;
468   containerFrame.origin.y = maxY;
469   containerFrame.size.width = width;
470   [containerView setFrame:containerFrame];
471   return maxY;
472 }
473
474 - (CGFloat)layoutDownloadShelfAtMinX:(CGFloat)minX
475                                 minY:(CGFloat)minY
476                                width:(CGFloat)width {
477   if (downloadShelfController_.get()) {
478     NSView* downloadView = [downloadShelfController_ view];
479     NSRect downloadFrame = [downloadView frame];
480     downloadFrame.origin.x = minX;
481     downloadFrame.origin.y = minY;
482     downloadFrame.size.width = width;
483     [downloadView setFrame:downloadFrame];
484     minY += NSHeight(downloadFrame);
485   }
486   return minY;
487 }
488
489 - (void)layoutTabContentArea:(NSRect)newFrame {
490   NSView* tabContentView = [self tabContentArea];
491   NSRect tabContentFrame = [tabContentView frame];
492
493   bool contentShifted =
494       NSMaxY(tabContentFrame) != NSMaxY(newFrame) ||
495       NSMinX(tabContentFrame) != NSMinX(newFrame);
496
497   tabContentFrame = newFrame;
498   [tabContentView setFrame:tabContentFrame];
499
500   // If the relayout shifts the content area up or down, let the renderer know.
501   if (contentShifted) {
502     if (WebContents* contents =
503             browser_->tab_strip_model()->GetActiveWebContents()) {
504       if (RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView())
505         rwhv->WindowFrameChanged();
506     }
507   }
508 }
509
510 - (void)adjustToolbarAndBookmarkBarForCompression:(CGFloat)compression {
511   CGFloat newHeight =
512       [toolbarController_ desiredHeightForCompression:compression];
513   NSRect toolbarFrame = [[toolbarController_ view] frame];
514   CGFloat deltaH = newHeight - toolbarFrame.size.height;
515
516   if (deltaH == 0)
517     return;
518
519   toolbarFrame.size.height = newHeight;
520   NSRect bookmarkFrame = [[bookmarkBarController_ view] frame];
521   bookmarkFrame.size.height = bookmarkFrame.size.height - deltaH;
522   [[toolbarController_ view] setFrame:toolbarFrame];
523   [[bookmarkBarController_ view] setFrame:bookmarkFrame];
524   [self layoutSubviews];
525 }
526
527 // Fullscreen and presentation mode methods
528
529 - (BOOL)shouldShowPresentationModeToggle {
530   return chrome::mac::SupportsSystemFullscreen() && [self isFullscreen];
531 }
532
533 - (void)moveViewsForFullscreenForSnowLeopard:(BOOL)fullscreen
534     regularWindow:(NSWindow*)regularWindow
535     fullscreenWindow:(NSWindow*)fullscreenWindow {
536   // This method is only for systems without fullscreen support.
537   DCHECK(!chrome::mac::SupportsSystemFullscreen());
538
539   NSWindow* sourceWindow = fullscreen ? regularWindow : fullscreenWindow;
540   NSWindow* destWindow = fullscreen ? fullscreenWindow : regularWindow;
541
542   // Close the bookmark bubble, if it's open.  Use |-ok:| instead of |-cancel:|
543   // or |-close| because that matches the behavior when the bubble loses key
544   // status.
545   [bookmarkBubbleController_ ok:self];
546
547   // Save the current first responder so we can restore after views are moved.
548   base::scoped_nsobject<FocusTracker> focusTracker(
549       [[FocusTracker alloc] initWithWindow:sourceWindow]);
550
551   // While we move views (and focus) around, disable any bar visibility changes.
552   [self disableBarVisibilityUpdates];
553
554   // Retain the tab strip view while we remove it from its superview.
555   base::scoped_nsobject<NSView> tabStripView;
556   if ([self hasTabStrip]) {
557     tabStripView.reset([[self tabStripView] retain]);
558     [tabStripView removeFromSuperview];
559   }
560
561   // Ditto for the content view.
562   base::scoped_nsobject<NSView> contentView(
563       [[sourceWindow contentView] retain]);
564   // Disable autoresizing of subviews while we move views around. This prevents
565   // spurious renderer resizes.
566   [contentView setAutoresizesSubviews:NO];
567   [contentView removeFromSuperview];
568
569   // Have to do this here, otherwise later calls can crash because the window
570   // has no delegate.
571   [sourceWindow setDelegate:nil];
572   [destWindow setDelegate:self];
573
574   // With this call, valgrind complains that a "Conditional jump or move depends
575   // on uninitialised value(s)".  The error happens in -[NSThemeFrame
576   // drawOverlayRect:].  I'm pretty convinced this is an Apple bug, but there is
577   // no visual impact.  I have been unable to tickle it away with other window
578   // or view manipulation Cocoa calls.  Stack added to suppressions_mac.txt.
579   [contentView setAutoresizesSubviews:YES];
580   [destWindow setContentView:contentView];
581
582   // Move the incognito badge if present.
583   if ([self shouldShowAvatar]) {
584     NSView* avatarButtonView = [avatarButtonController_ view];
585
586     [avatarButtonView removeFromSuperview];
587     [avatarButtonView setHidden:YES];  // Will be shown in layout.
588     [[[destWindow contentView] superview] addSubview: avatarButtonView];
589   }
590
591   // Add the tab strip after setting the content view and moving the incognito
592   // badge (if any), so that the tab strip will be on top (in the z-order).
593   if ([self hasTabStrip])
594     [[[destWindow contentView] superview] addSubview:tabStripView];
595
596   [sourceWindow setWindowController:nil];
597   [self setWindow:destWindow];
598   [destWindow setWindowController:self];
599
600   // Move the status bubble over, if we have one.
601   if (statusBubble_)
602     statusBubble_->SwitchParentWindow(destWindow);
603
604   // Move the title over.
605   [destWindow setTitle:[sourceWindow title]];
606
607   // The window needs to be onscreen before we can set its first responder.
608   // Ordering the window to the front can change the active Space (either to
609   // the window's old Space or to the application's assigned Space). To prevent
610   // this by temporarily change the collectionBehavior.
611   NSWindowCollectionBehavior behavior = [sourceWindow collectionBehavior];
612   [destWindow setCollectionBehavior:
613       NSWindowCollectionBehaviorMoveToActiveSpace];
614   [destWindow makeKeyAndOrderFront:self];
615   [destWindow setCollectionBehavior:behavior];
616
617   [focusTracker restoreFocusInWindow:destWindow];
618   [sourceWindow orderOut:self];
619
620   // We're done moving focus, so re-enable bar visibility changes.
621   [self enableBarVisibilityUpdates];
622 }
623
624 - (void)setPresentationModeInternal:(BOOL)presentationMode
625                       forceDropdown:(BOOL)forceDropdown {
626   if (presentationMode == [self inPresentationMode])
627     return;
628
629   if (presentationMode) {
630     BOOL fullscreen_for_tab =
631         browser_->fullscreen_controller()->IsFullscreenForTabOrPending();
632     BOOL kiosk_mode =
633         CommandLine::ForCurrentProcess()->HasSwitch(switches::kKioskMode);
634     BOOL showDropdown = !fullscreen_for_tab &&
635         !kiosk_mode &&
636         (forceDropdown || [self floatingBarHasFocus]);
637     NSView* contentView = [[self window] contentView];
638     presentationModeController_.reset(
639         [[PresentationModeController alloc] initWithBrowserController:self]);
640     [presentationModeController_ enterPresentationModeForContentView:contentView
641                                  showDropdown:showDropdown];
642   } else {
643     [presentationModeController_ exitPresentationMode];
644     presentationModeController_.reset();
645   }
646
647   [self adjustUIForPresentationMode:presentationMode];
648   [self layoutSubviews];
649 }
650
651 // TODO(rohitrao): This method is misnamed now, since there is a flag that
652 // enables 10.6-style fullscreen on newer OSes.
653 - (void)enterFullscreenForSnowLeopard {
654   // Fade to black.
655   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
656   Boolean didFadeOut = NO;
657   CGDisplayFadeReservationToken token;
658   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
659       == kCGErrorSuccess) {
660     didFadeOut = YES;
661     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
662         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
663   }
664
665   // Create the fullscreen window.  After this line, isFullscreen will return
666   // YES.
667   fullscreenWindow_.reset([[self createFullscreenWindow] retain]);
668   savedRegularWindow_ = [[self window] retain];
669   savedRegularWindowFrame_ = [savedRegularWindow_ frame];
670
671   [self moveViewsForFullscreenForSnowLeopard:YES
672                                regularWindow:[self window]
673                             fullscreenWindow:fullscreenWindow_.get()];
674
675   // When simplified fullscreen is enabled, do not enter presentation mode.
676   const CommandLine* command_line = CommandLine::ForCurrentProcess();
677   if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)) {
678     // TODO(rohitrao): Add code to manage the menubar here.
679   } else {
680     [self adjustUIForPresentationMode:YES];
681     [self setPresentationModeInternal:YES forceDropdown:NO];
682   }
683
684   [self layoutSubviews];
685
686   [self windowDidEnterFullScreen:nil];
687
688   // Fade back in.
689   if (didFadeOut) {
690     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
691         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
692     CGReleaseDisplayFadeReservation(token);
693   }
694 }
695
696 - (void)exitFullscreenForSnowLeopard {
697   // TODO(rohitrao): This method is misnamed now, since there is a flag that
698   // enables 10.6-style fullscreen on newer OSes.
699   DCHECK(!chrome::mac::SupportsSystemFullscreen());
700
701   // Fade to black.
702   const CGDisplayReservationInterval kFadeDurationSeconds = 0.6;
703   Boolean didFadeOut = NO;
704   CGDisplayFadeReservationToken token;
705   if (CGAcquireDisplayFadeReservation(kFadeDurationSeconds, &token)
706       == kCGErrorSuccess) {
707     didFadeOut = YES;
708     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendNormal,
709         kCGDisplayBlendSolidColor, 0.0, 0.0, 0.0, /*synchronous=*/true);
710   }
711
712   // When simplified fullscreen is enabled, the menubar status is managed
713   // directly by BWC.
714   const CommandLine* command_line = CommandLine::ForCurrentProcess();
715   if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen)) {
716     // TODO(rohitrao): Add code to manage the menubar here.
717   }
718
719   [self windowWillExitFullScreen:nil];
720
721   [self moveViewsForFullscreenForSnowLeopard:NO
722                                         regularWindow:savedRegularWindow_
723                                      fullscreenWindow:fullscreenWindow_.get()];
724
725   // When exiting fullscreen mode, we need to call layoutSubviews manually.
726   [savedRegularWindow_ autorelease];
727   savedRegularWindow_ = nil;
728   fullscreenWindow_.reset();
729   [self layoutSubviews];
730
731   [self windowDidExitFullScreen:nil];
732
733   // Fade back in.
734   if (didFadeOut) {
735     CGDisplayFade(token, kFadeDurationSeconds / 2, kCGDisplayBlendSolidColor,
736         kCGDisplayBlendNormal, 0.0, 0.0, 0.0, /*synchronous=*/false);
737     CGReleaseDisplayFadeReservation(token);
738   }
739 }
740
741 // TODO(rohitrao): This function has shrunk into uselessness, and
742 // |-setFullscreen:| has grown rather large.  Find a good way to break up
743 // |-setFullscreen:| into smaller pieces.  http://crbug.com/36449
744 - (void)adjustUIForPresentationMode:(BOOL)fullscreen {
745   // Create the floating bar backing view if necessary.
746   if (fullscreen && !floatingBarBackingView_.get() &&
747       ([self hasTabStrip] || [self hasToolbar] || [self hasLocationBar])) {
748     floatingBarBackingView_.reset(
749         [[FloatingBarBackingView alloc] initWithFrame:NSZeroRect]);
750     [floatingBarBackingView_ setAutoresizingMask:(NSViewWidthSizable |
751                                                   NSViewMinYMargin)];
752   }
753
754   // Force the bookmark bar z-order to update.
755   [[bookmarkBarController_ view] removeFromSuperview];
756   [self updateSubviewZOrder:fullscreen];
757   [self updateAllowOverlappingViews:fullscreen];
758 }
759
760 - (void)showFullscreenExitBubbleIfNecessary {
761   // This method is called in response to
762   // |-updateFullscreenExitBubbleURL:bubbleType:|. If on Lion the system is
763   // transitioning, do not show the bubble because it will cause visual jank
764   // <http://crbug.com/130649>. This will be called again as part of
765   // |-windowDidEnterFullScreen:|, so arrange to do that work then instead.
766   if (enteringFullscreen_)
767     return;
768
769   [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
770
771   if (fullscreenBubbleType_ == FEB_TYPE_NONE ||
772       fullscreenBubbleType_ == FEB_TYPE_BROWSER_FULLSCREEN_EXIT_INSTRUCTION) {
773     // Show no exit instruction bubble on Mac when in Browser Fullscreen.
774     [self destroyFullscreenExitBubbleIfNecessary];
775   } else {
776     [fullscreenExitBubbleController_ closeImmediately];
777     fullscreenExitBubbleController_.reset(
778         [[FullscreenExitBubbleController alloc]
779             initWithOwner:self
780                   browser:browser_.get()
781                       url:fullscreenUrl_
782                bubbleType:fullscreenBubbleType_]);
783     [fullscreenExitBubbleController_ showWindow];
784   }
785 }
786
787 - (void)destroyFullscreenExitBubbleIfNecessary {
788   [fullscreenExitBubbleController_ closeImmediately];
789   fullscreenExitBubbleController_.reset();
790 }
791
792 - (void)contentViewDidResize:(NSNotification*)notification {
793   [self layoutSubviews];
794 }
795
796 - (void)registerForContentViewResizeNotifications {
797   [[NSNotificationCenter defaultCenter]
798       addObserver:self
799          selector:@selector(contentViewDidResize:)
800              name:NSViewFrameDidChangeNotification
801            object:[[self window] contentView]];
802 }
803
804 - (void)deregisterForContentViewResizeNotifications {
805   [[NSNotificationCenter defaultCenter]
806       removeObserver:self
807                 name:NSViewFrameDidChangeNotification
808               object:[[self window] contentView]];
809 }
810
811 - (NSSize)window:(NSWindow*)window
812     willUseFullScreenContentSize:(NSSize)proposedSize {
813   return proposedSize;
814 }
815
816 - (NSApplicationPresentationOptions)window:(NSWindow*)window
817     willUseFullScreenPresentationOptions:(NSApplicationPresentationOptions)opt {
818   return (opt |
819           NSApplicationPresentationAutoHideDock |
820           NSApplicationPresentationAutoHideMenuBar);
821 }
822
823 - (void)windowWillEnterFullScreen:(NSNotification*)notification {
824   [self registerForContentViewResizeNotifications];
825
826   NSWindow* window = [self window];
827   savedRegularWindowFrame_ = [window frame];
828   BOOL mode = enteringPresentationMode_ ||
829        browser_->fullscreen_controller()->IsFullscreenForTabOrPending();
830   enteringFullscreen_ = YES;
831   [self setPresentationModeInternal:mode forceDropdown:NO];
832 }
833
834 - (void)windowDidEnterFullScreen:(NSNotification*)notification {
835   if (chrome::mac::SupportsSystemFullscreen())
836     [self deregisterForContentViewResizeNotifications];
837   enteringFullscreen_ = NO;
838   enteringPresentationMode_ = NO;
839
840   const CommandLine* command_line = CommandLine::ForCurrentProcess();
841   if (command_line->HasSwitch(switches::kEnableSimplifiedFullscreen) &&
842       fullscreenUrl_.is_empty()) {
843     fullscreenModeController_.reset([[FullscreenModeController alloc]
844         initWithBrowserWindowController:self]);
845   }
846
847   [self showFullscreenExitBubbleIfNecessary];
848   browser_->WindowFullscreenStateChanged();
849 }
850
851 - (void)windowWillExitFullScreen:(NSNotification*)notification {
852   if (chrome::mac::SupportsSystemFullscreen())
853     [self registerForContentViewResizeNotifications];
854   fullscreenModeController_.reset();
855   [self destroyFullscreenExitBubbleIfNecessary];
856   [self setPresentationModeInternal:NO forceDropdown:NO];
857 }
858
859 - (void)windowDidExitFullScreen:(NSNotification*)notification {
860   if (chrome::mac::SupportsSystemFullscreen())
861     [self deregisterForContentViewResizeNotifications];
862   browser_->WindowFullscreenStateChanged();
863 }
864
865 - (void)windowDidFailToEnterFullScreen:(NSWindow*)window {
866   [self deregisterForContentViewResizeNotifications];
867   enteringFullscreen_ = NO;
868   [self setPresentationModeInternal:NO forceDropdown:NO];
869
870   // Force a relayout to try and get the window back into a reasonable state.
871   [self layoutSubviews];
872 }
873
874 - (void)windowDidFailToExitFullScreen:(NSWindow*)window {
875   [self deregisterForContentViewResizeNotifications];
876
877   // Force a relayout to try and get the window back into a reasonable state.
878   [self layoutSubviews];
879 }
880
881 - (void)enableBarVisibilityUpdates {
882   // Early escape if there's nothing to do.
883   if (barVisibilityUpdatesEnabled_)
884     return;
885
886   barVisibilityUpdatesEnabled_ = YES;
887
888   if ([barVisibilityLocks_ count])
889     [presentationModeController_ ensureOverlayShownWithAnimation:NO delay:NO];
890   else
891     [presentationModeController_ ensureOverlayHiddenWithAnimation:NO delay:NO];
892 }
893
894 - (void)disableBarVisibilityUpdates {
895   // Early escape if there's nothing to do.
896   if (!barVisibilityUpdatesEnabled_)
897     return;
898
899   barVisibilityUpdatesEnabled_ = NO;
900   [presentationModeController_ cancelAnimationAndTimers];
901 }
902
903 - (CGFloat)toolbarDividerOpacity {
904   return [bookmarkBarController_ toolbarDividerOpacity];
905 }
906
907 - (void)updateSubviewZOrder:(BOOL)inPresentationMode {
908   NSView* contentView = [[self window] contentView];
909   NSView* toolbarView = [toolbarController_ view];
910
911   if (inPresentationMode) {
912     // Toolbar is above tab contents so that it can slide down from top of
913     // screen.
914     [contentView cr_ensureSubview:toolbarView
915                      isPositioned:NSWindowAbove
916                        relativeTo:[self tabContentArea]];
917   } else {
918     // Toolbar is below tab contents so that the info bar arrow can appear above
919     // it.
920     [contentView cr_ensureSubview:toolbarView
921                      isPositioned:NSWindowBelow
922                        relativeTo:[self tabContentArea]];
923   }
924
925   // The bookmark bar is always below the toolbar.
926   [contentView cr_ensureSubview:[bookmarkBarController_ view]
927                    isPositioned:NSWindowBelow
928                      relativeTo:toolbarView];
929
930   if (inPresentationMode) {
931     // In presentation mode the info bar is below all other views.
932     [contentView cr_ensureSubview:[infoBarContainerController_ view]
933                      isPositioned:NSWindowBelow
934                        relativeTo:[self tabContentArea]];
935   } else {
936     // Above the toolbar but still below tab contents. Similar to the bookmark
937     // bar, this allows Instant results to be above the info bar.
938     [contentView cr_ensureSubview:[infoBarContainerController_ view]
939                      isPositioned:NSWindowAbove
940                        relativeTo:toolbarView];
941   }
942
943   // The find bar is above everything.
944   if (findBarCocoaController_) {
945     NSView* relativeView = nil;
946     if (inPresentationMode)
947       relativeView = toolbarView;
948     else
949       relativeView = [self tabContentArea];
950     [contentView cr_ensureSubview:[findBarCocoaController_ view]
951                      isPositioned:NSWindowAbove
952                        relativeTo:relativeView];
953   }
954
955   if (floatingBarBackingView_) {
956     if ([floatingBarBackingView_ cr_isBelowView:[self tabContentArea]])
957       [floatingBarBackingView_ removeFromSuperview];
958     if ([self placeBookmarkBarBelowInfoBar]) {
959       [contentView cr_ensureSubview:floatingBarBackingView_
960                        isPositioned:NSWindowAbove
961                          relativeTo:[bookmarkBarController_ view]];
962     } else {
963       [contentView cr_ensureSubview:floatingBarBackingView_
964                        isPositioned:NSWindowBelow
965                          relativeTo:[bookmarkBarController_ view]];
966     }
967   }
968 }
969
970 - (BOOL)shouldAllowOverlappingViews:(BOOL)inPresentationMode {
971   if (inPresentationMode)
972     return YES;
973
974   if (findBarCocoaController_ &&
975       ![[findBarCocoaController_ findBarView] isHidden]) {
976     return YES;
977   }
978
979   if (overlappedViewCount_)
980     return YES;
981
982   return NO;
983 }
984
985 - (void)updateAllowOverlappingViews:(BOOL)inPresentationMode {
986   WebContents* contents = browser_->tab_strip_model()->GetActiveWebContents();
987   if (!contents)
988     return;
989
990   BOOL allowOverlappingViews =
991       [self shouldAllowOverlappingViews:inPresentationMode];
992
993   // The rendering path with overlapping views disabled causes bugs when
994   // transitioning between composited and non-composited mode.
995   // http://crbug.com/279472
996   allowOverlappingViews = YES;
997   contents->GetView()->SetAllowOverlappingViews(allowOverlappingViews);
998
999   DevToolsWindow* devToolsWindow =
1000       DevToolsWindow::GetDockedInstanceForInspectedTab(contents);
1001   if (devToolsWindow) {
1002     devToolsWindow->web_contents()->GetView()->
1003         SetAllowOverlappingViews(allowOverlappingViews);
1004   }
1005 }
1006
1007 - (void)updateInfoBarTipVisibility {
1008   // If there's no toolbar then hide the infobar tip.
1009   [infoBarContainerController_
1010       setShouldSuppressTopInfoBarTip:![self hasToolbar]];
1011 }
1012
1013 @end  // @implementation BrowserWindowController(Private)