Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / tab_contents / tab_contents_controller.mm
blobd3ba29709c1265165918370f1af7b7289fbe098b
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/tab_contents/tab_contents_controller.h"
7 #include <utility>
9 #include "base/mac/scoped_cftyperef.h"
10 #include "base/mac/scoped_nsobject.h"
11 #include "chrome/browser/devtools/devtools_window.h"
12 #import "chrome/browser/themes/theme_properties.h"
13 #import "chrome/browser/themes/theme_service.h"
14 #import "chrome/browser/ui/cocoa/themed_window.h"
15 #include "chrome/browser/ui/view_ids.h"
16 #include "content/public/browser/render_view_host.h"
17 #include "content/public/browser/render_widget_host_view.h"
18 #include "content/public/browser/web_contents.h"
19 #include "content/public/browser/web_contents_observer.h"
20 #include "skia/ext/skia_utils_mac.h"
21 #include "ui/base/cocoa/animation_utils.h"
22 #import "ui/base/cocoa/nscolor_additions.h"
23 #include "ui/gfx/geometry/rect.h"
25 using content::WebContents;
26 using content::WebContentsObserver;
28 // FullscreenObserver is used by TabContentsController to monitor for the
29 // showing/destruction of fullscreen render widgets.  When notified,
30 // TabContentsController will alter its child view hierarchy to either embed a
31 // fullscreen render widget view or restore the normal WebContentsView render
32 // view.  The embedded fullscreen render widget will fill the user's screen in
33 // the case where TabContentsController's NSView is a subview of a browser
34 // window that has been toggled into fullscreen mode (e.g., via
35 // FullscreenController).
36 class FullscreenObserver : public WebContentsObserver {
37  public:
38   explicit FullscreenObserver(TabContentsController* controller)
39       : controller_(controller) {}
41   void Observe(content::WebContents* new_web_contents) {
42     WebContentsObserver::Observe(new_web_contents);
43   }
45   WebContents* web_contents() const {
46     return WebContentsObserver::web_contents();
47   }
49   void DidShowFullscreenWidget(int routing_id) override {
50     [controller_ toggleFullscreenWidget:YES];
51   }
53   void DidDestroyFullscreenWidget(int routing_id) override {
54     [controller_ toggleFullscreenWidget:NO];
55   }
57   void DidToggleFullscreenModeForTab(bool entered_fullscreen) override {
58     [controller_ toggleFullscreenWidget:YES];
59   }
61  private:
62   TabContentsController* const controller_;
64   DISALLOW_COPY_AND_ASSIGN(FullscreenObserver);
67 @interface TabContentsController (TabContentsContainerViewDelegate)
68 - (BOOL)contentsInFullscreenCaptureMode;
69 // Computes and returns the frame to use for the contents view within the
70 // container view.
71 - (NSRect)frameForContentsView;
72 @end
74 // An NSView with special-case handling for when the contents view does not
75 // expand to fill the entire tab contents area. See 'AutoEmbedFullscreen mode'
76 // in header file comments.
77 @interface TabContentsContainerView : NSView {
78  @private
79   TabContentsController* delegate_;  // weak
82 - (NSColor*)computeBackgroundColor;
83 - (void)updateBackgroundColor;
84 @end
86 @implementation TabContentsContainerView
88 - (id)initWithDelegate:(TabContentsController*)delegate {
89   if ((self = [super initWithFrame:NSZeroRect])) {
90     delegate_ = delegate;
91     ScopedCAActionDisabler disabler;
92     base::scoped_nsobject<CALayer> layer([[CALayer alloc] init]);
93     [self setLayer:layer];
94     [self setWantsLayer:YES];
95     [self updateBackgroundColor];
96   }
97   return self;
100 // Called by the delegate during dealloc to invalidate the pointer held by this
101 // view.
102 - (void)delegateDestroyed {
103   delegate_ = nil;
106 - (NSColor*)computeBackgroundColor {
107   // This view is sometimes flashed into visibility (e.g, when closing
108   // windows or opening new tabs), so ensure that the flash be the theme
109   // background color in those cases.
110   NSColor* backgroundColor = nil;
111   ThemeService* const theme =
112       static_cast<ThemeService*>([[self window] themeProvider]);
113   if (theme)
114     backgroundColor = theme->GetNSColor(ThemeProperties::COLOR_NTP_BACKGROUND);
115   if (!backgroundColor)
116     backgroundColor = [NSColor whiteColor];
118   // If the page is in fullscreen tab capture mode, change the background color
119   // to be a dark tint of the new tab page's background color.
120   if ([delegate_ contentsInFullscreenCaptureMode]) {
121     const float kDarknessFraction = 0.80f;
122     return [backgroundColor blendedColorWithFraction:kDarknessFraction
123                                              ofColor:[NSColor blackColor]];
124   } else {
125     return backgroundColor;
126   }
129 // Override auto-resizing logic to query the delegate for the exact frame to
130 // use for the contents view.
131 - (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
132   NSView* const contentsView =
133       [[self subviews] count] > 0 ? [[self subviews] objectAtIndex:0] : nil;
134   if (!contentsView || [contentsView autoresizingMask] == NSViewNotSizable ||
135       !delegate_) {
136     return;
137   }
139   ScopedCAActionDisabler disabler;
140   [contentsView setFrame:[delegate_ frameForContentsView]];
143 // Update the background layer's color whenever the view needs to repaint.
144 - (void)setNeedsDisplayInRect:(NSRect)rect {
145   [super setNeedsDisplayInRect:rect];
146   [self updateBackgroundColor];
149 - (void)updateBackgroundColor {
150   ScopedCAActionDisabler disabler;
151   [[self layer] setBackgroundColor:[[self computeBackgroundColor] cr_CGColor]];
154 - (ViewID)viewID {
155   return VIEW_ID_TAB_CONTAINER;
158 - (BOOL)acceptsFirstResponder {
159   return [[self subviews] count] > 0 &&
160       [[[self subviews] objectAtIndex:0] acceptsFirstResponder];
163 // When receiving a click-to-focus in the solid color area surrounding the
164 // WebContents' native view, immediately transfer focus to WebContents' native
165 // view.
166 - (BOOL)becomeFirstResponder {
167   if (![self acceptsFirstResponder])
168     return NO;
169   return [[self window] makeFirstResponder:[[self subviews] objectAtIndex:0]];
172 - (BOOL)canBecomeKeyView {
173   return NO;  // Tab/Shift-Tab should focus the subview, not this view.
176 @end  // @implementation TabContentsContainerView
178 @implementation TabContentsController
179 @synthesize webContents = contents_;
181 - (id)initWithContents:(WebContents*)contents {
182   if ((self = [super initWithNibName:nil bundle:nil])) {
183     fullscreenObserver_.reset(new FullscreenObserver(self));
184     [self changeWebContents:contents];
185   }
186   return self;
189 - (void)dealloc {
190   [static_cast<TabContentsContainerView*>([self view]) delegateDestroyed];
191   // Make sure the contents view has been removed from the container view to
192   // allow objects to be released.
193   [[self view] removeFromSuperview];
194   [super dealloc];
197 - (void)loadView {
198   base::scoped_nsobject<NSView> view(
199       [[TabContentsContainerView alloc] initWithDelegate:self]);
200   [view setAutoresizingMask:NSViewHeightSizable|NSViewWidthSizable];
201   [self setView:view];
204 - (void)ensureContentsSizeDoesNotChange {
205   NSView* contentsContainer = [self view];
206   NSArray* subviews = [contentsContainer subviews];
207   if ([subviews count] > 0) {
208     NSView* currentSubview = [subviews objectAtIndex:0];
209     [currentSubview setAutoresizingMask:NSViewNotSizable];
210   }
213 - (void)ensureContentsVisible {
214   if (!contents_)
215     return;
216   ScopedCAActionDisabler disabler;
217   NSView* contentsContainer = [self view];
218   NSArray* subviews = [contentsContainer subviews];
219   NSView* contentsNativeView;
220   content::RenderWidgetHostView* const fullscreenView =
221       isEmbeddingFullscreenWidget_ ?
222       contents_->GetFullscreenRenderWidgetHostView() : NULL;
223   if (fullscreenView) {
224     contentsNativeView = fullscreenView->GetNativeView();
225   } else {
226     isEmbeddingFullscreenWidget_ = NO;
227     contentsNativeView = contents_->GetNativeView();
228   }
229   [contentsNativeView setFrame:[self frameForContentsView]];
230   if ([subviews count] == 0) {
231     [contentsContainer addSubview:contentsNativeView];
232   } else if ([subviews objectAtIndex:0] != contentsNativeView) {
233     [contentsContainer replaceSubview:[subviews objectAtIndex:0]
234                                  with:contentsNativeView];
235   }
236   [contentsNativeView setAutoresizingMask:NSViewWidthSizable|
237                                           NSViewHeightSizable];
239   [contentsContainer setNeedsDisplay:YES];
242 - (void)changeWebContents:(WebContents*)newContents {
243   contents_ = newContents;
244   fullscreenObserver_->Observe(contents_);
245   isEmbeddingFullscreenWidget_ =
246       contents_ && contents_->GetFullscreenRenderWidgetHostView();
249 // Returns YES if the tab represented by this controller is the front-most.
250 - (BOOL)isCurrentTab {
251   // We're the current tab if we're in the view hierarchy, otherwise some other
252   // tab is.
253   return [[self view] superview] ? YES : NO;
256 - (void)willBecomeUnselectedTab {
257   // The RWHV is ripped out of the view hierarchy on tab switches, so it never
258   // formally resigns first responder status.  Handle this by explicitly sending
259   // a Blur() message to the renderer, but only if the RWHV currently has focus.
260   content::RenderViewHost* rvh = [self webContents]->GetRenderViewHost();
261   if (rvh) {
262     if (rvh->GetView() && rvh->GetView()->HasFocus()) {
263       rvh->Blur();
264       return;
265     }
266     WebContents* devtools = DevToolsWindow::GetInTabWebContents(
267         [self webContents], NULL);
268     if (devtools) {
269       content::RenderViewHost* devtoolsView = devtools->GetRenderViewHost();
270       if (devtoolsView && devtoolsView->GetView() &&
271           devtoolsView->GetView()->HasFocus()) {
272         devtoolsView->Blur();
273       }
274     }
275   }
278 - (void)willBecomeSelectedTab {
279   // Do not explicitly call Focus() here, as the RWHV may not actually have
280   // focus (for example, if the omnibox has focus instead).  The WebContents
281   // logic will restore focus to the appropriate view.
284 - (void)tabDidChange:(WebContents*)updatedContents {
285   // Calling setContentView: here removes any first responder status
286   // the view may have, so avoid changing the view hierarchy unless
287   // the view is different.
288   if ([self webContents] != updatedContents) {
289     [self changeWebContents:updatedContents];
290     [self ensureContentsVisible];
291   }
294 - (void)toggleFullscreenWidget:(BOOL)enterFullscreen {
295   isEmbeddingFullscreenWidget_ = enterFullscreen &&
296       contents_ && contents_->GetFullscreenRenderWidgetHostView();
297   [self ensureContentsVisible];
300 - (BOOL)contentsInFullscreenCaptureMode {
301   // Note: Grab a known-valid WebContents pointer from |fullscreenObserver_|.
302   content::WebContents* const wc = fullscreenObserver_->web_contents();
303   if (!wc ||
304       wc->GetCapturerCount() == 0 ||
305       wc->GetPreferredSize().IsEmpty() ||
306       !(isEmbeddingFullscreenWidget_ ||
307         (wc->GetDelegate() &&
308          wc->GetDelegate()->IsFullscreenForTabOrPending(wc)))) {
309     return NO;
310   }
311   return YES;
314 - (NSRect)frameForContentsView {
315   const NSSize containerSize = [[self view] frame].size;
316   gfx::Rect rect;
317   rect.set_width(containerSize.width);
318   rect.set_height(containerSize.height);
320   // In most cases, the contents view is simply sized to fill the container
321   // view's bounds. Only WebContentses that are in fullscreen mode and being
322   // screen-captured will engage the special layout/sizing behavior.
323   if (![self contentsInFullscreenCaptureMode])
324     return NSRectFromCGRect(rect.ToCGRect());
326   // Size the contents view to the capture video resolution and center it. If
327   // the container view is not large enough to fit it at the preferred size,
328   // scale down to fit (preserving aspect ratio).
329   content::WebContents* const wc = fullscreenObserver_->web_contents();
330   const gfx::Size captureSize = wc->GetPreferredSize();
331   if (captureSize.width() <= rect.width() &&
332       captureSize.height() <= rect.height()) {
333     // No scaling, just centering.
334     rect.ClampToCenteredSize(captureSize);
335   } else {
336     // Scale down, preserving aspect ratio, and center.
337     // TODO(miu): This is basically media::ComputeLetterboxRegion(), and it
338     // looks like others have written this code elsewhere.  Let's consolidate
339     // into a shared function ui/gfx/geometry or around there.
340     const int64 x = static_cast<int64>(captureSize.width()) * rect.height();
341     const int64 y = static_cast<int64>(captureSize.height()) * rect.width();
342     if (y < x) {
343       rect.ClampToCenteredSize(gfx::Size(
344           rect.width(), static_cast<int>(y / captureSize.width())));
345     } else {
346       rect.ClampToCenteredSize(gfx::Size(
347           static_cast<int>(x / captureSize.height()), rect.height()));
348     }
349   }
351   return NSRectFromCGRect(rect.ToCGRect());
354 @end