[Metrics] Make MetricsStateManager take a callback param to check if UMA is enabled.
[chromium-blink-merge.git] / chrome / browser / ui / cocoa / tab_contents / tab_contents_controller.mm
blobb49b82d7545ae350c389253f92dcb4791a7ac561
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/command_line.h"
10 #include "base/mac/scoped_cftyperef.h"
11 #include "base/mac/scoped_nsobject.h"
12 #include "chrome/browser/devtools/devtools_window.h"
13 #import "chrome/browser/themes/theme_properties.h"
14 #import "chrome/browser/themes/theme_service.h"
15 #import "chrome/browser/ui/cocoa/themed_window.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 "ui/base/cocoa/animation_utils.h"
21 #include "ui/base/ui_base_switches.h"
22 #include "ui/gfx/geometry/rect.h"
24 using content::WebContents;
25 using content::WebContentsObserver;
27 // FullscreenObserver is used by TabContentsController to monitor for the
28 // showing/destruction of fullscreen render widgets.  When notified,
29 // TabContentsController will alter its child view hierarchy to either embed a
30 // fullscreen render widget view or restore the normal WebContentsView render
31 // view.  The embedded fullscreen render widget will fill the user's screen in
32 // the case where TabContentsController's NSView is a subview of a browser
33 // window that has been toggled into fullscreen mode (e.g., via
34 // FullscreenController).
35 class FullscreenObserver : public WebContentsObserver {
36  public:
37   explicit FullscreenObserver(TabContentsController* controller)
38       : controller_(controller) {}
40   void Observe(content::WebContents* new_web_contents) {
41     WebContentsObserver::Observe(new_web_contents);
42   }
44   WebContents* web_contents() const {
45     return WebContentsObserver::web_contents();
46   }
48   virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE {
49     [controller_ toggleFullscreenWidget:YES];
50   }
52   virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE {
53     [controller_ toggleFullscreenWidget:NO];
54   }
56   virtual void DidToggleFullscreenModeForTab(bool entered_fullscreen) OVERRIDE {
57     [controller_ toggleFullscreenWidget:YES];
58   }
60  private:
61   TabContentsController* const controller_;
63   DISALLOW_COPY_AND_ASSIGN(FullscreenObserver);
66 @interface TabContentsController (TabContentsContainerViewDelegate)
67 - (BOOL)contentsInFullscreenCaptureMode;
68 // Computes and returns the frame to use for the contents view within the
69 // container view.
70 - (NSRect)frameForContentsView;
71 @end
73 // An NSView with special-case handling for when the contents view does not
74 // expand to fill the entire tab contents area. See 'AutoEmbedFullscreen mode'
75 // in header file comments.
76 @interface TabContentsContainerView : NSView {
77  @private
78   TabContentsController* delegate_;  // weak
81 - (NSColor*)computeBackgroundColor;
82 @end
84 @implementation TabContentsContainerView
86 - (id)initWithDelegate:(TabContentsController*)delegate {
87   if ((self = [super initWithFrame:NSZeroRect])) {
88     delegate_ = delegate;
89     if (!CommandLine::ForCurrentProcess()->HasSwitch(
90             switches::kDisableCoreAnimation)) {
91       ScopedCAActionDisabler disabler;
92       base::scoped_nsobject<CALayer> layer([[CALayer alloc] init]);
93       [layer setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
94       [self setLayer:layer];
95       [self setWantsLayer:YES];
96     }
97   }
98   return self;
101 // Called by the delegate during dealloc to invalidate the pointer held by this
102 // view.
103 - (void)delegateDestroyed {
104   delegate_ = nil;
107 - (NSColor*)computeBackgroundColor {
108   // This view is sometimes flashed into visibility (e.g, when closing
109   // windows), so ensure that the flash be white in those cases.
110   if (![delegate_ contentsInFullscreenCaptureMode])
111     return [NSColor whiteColor];
113   // Fill with a dark tint of the new tab page's background color.  This is
114   // only seen when the subview is sized specially for fullscreen tab capture.
115   NSColor* bgColor = nil;
116   ThemeService* const theme =
117       static_cast<ThemeService*>([[self window] themeProvider]);
118   if (theme)
119     bgColor = theme->GetNSColor(ThemeProperties::COLOR_NTP_BACKGROUND);
120   if (!bgColor)
121     bgColor = [[self window] backgroundColor];
122   const float kDarknessFraction = 0.80f;
123   return [bgColor blendedColorWithFraction:kDarknessFraction
124                                    ofColor:[NSColor blackColor]];
127 // Override -drawRect to fill the view with a solid color outside of the
128 // subview's frame.
129 - (void)drawRect:(NSRect)dirtyRect {
130   NSView* const contentsView =
131       [[self subviews] count] > 0 ? [[self subviews] objectAtIndex:0] : nil;
132   if (!contentsView || !NSContainsRect([contentsView frame], dirtyRect)) {
133     [[self computeBackgroundColor] setFill];
134     NSRectFill(dirtyRect);
135   }
136   [super drawRect:dirtyRect];
139 // Override auto-resizing logic to query the delegate for the exact frame to
140 // use for the contents view.
141 - (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
142   NSView* const contentsView =
143       [[self subviews] count] > 0 ? [[self subviews] objectAtIndex:0] : nil;
144   if (!contentsView || [contentsView autoresizingMask] == NSViewNotSizable ||
145       !delegate_) {
146     return;
147   }
149   ScopedCAActionDisabler disabler;
150   [contentsView setFrame:[delegate_ frameForContentsView]];
153 // Update the background layer's color whenever the view needs to repaint.
154 - (void)setNeedsDisplayInRect:(NSRect)rect {
155   [super setNeedsDisplayInRect:rect];
157   // Convert from an NSColor to a CGColorRef.
158   NSColor* nsBackgroundColor = [self computeBackgroundColor];
159   NSColorSpace* nsColorSpace = [nsBackgroundColor colorSpace];
160   CGColorSpaceRef cgColorSpace = [nsColorSpace CGColorSpace];
161   const NSInteger numberOfComponents = [nsBackgroundColor numberOfComponents];
162   CGFloat components[numberOfComponents];
163   [nsBackgroundColor getComponents:components];
164   base::ScopedCFTypeRef<CGColorRef> cgBackgroundColor(
165       CGColorCreate(cgColorSpace, components));
167   ScopedCAActionDisabler disabler;
168   [[self layer] setBackgroundColor:cgBackgroundColor];
171 @end  // @implementation TabContentsContainerView
173 @implementation TabContentsController
174 @synthesize webContents = contents_;
176 - (id)initWithContents:(WebContents*)contents
177     andAutoEmbedFullscreen:(BOOL)enableEmbeddedFullscreen {
178   if ((self = [super initWithNibName:nil bundle:nil])) {
179     if (enableEmbeddedFullscreen)
180       fullscreenObserver_.reset(new FullscreenObserver(self));
181     [self changeWebContents:contents];
182   }
183   return self;
186 - (void)dealloc {
187   [static_cast<TabContentsContainerView*>([self view]) delegateDestroyed];
188   // Make sure the contents view has been removed from the container view to
189   // allow objects to be released.
190   [[self view] removeFromSuperview];
191   [super dealloc];
194 - (void)loadView {
195   base::scoped_nsobject<NSView> view(
196       [[TabContentsContainerView alloc] initWithDelegate:self]);
197   [view setAutoresizingMask:NSViewHeightSizable|NSViewWidthSizable];
198   [self setView:view];
201 - (void)ensureContentsSizeDoesNotChange {
202   NSView* contentsContainer = [self view];
203   NSArray* subviews = [contentsContainer subviews];
204   if ([subviews count] > 0) {
205     NSView* currentSubview = [subviews objectAtIndex:0];
206     [currentSubview setAutoresizingMask:NSViewNotSizable];
207   }
210 - (void)ensureContentsVisible {
211   if (!contents_)
212     return;
213   ScopedCAActionDisabler disabler;
214   NSView* contentsContainer = [self view];
215   NSArray* subviews = [contentsContainer subviews];
216   NSView* contentsNativeView;
217   content::RenderWidgetHostView* const fullscreenView =
218       isEmbeddingFullscreenWidget_ ?
219       contents_->GetFullscreenRenderWidgetHostView() : NULL;
220   if (fullscreenView) {
221     contentsNativeView = fullscreenView->GetNativeView();
222   } else {
223     isEmbeddingFullscreenWidget_ = NO;
224     contentsNativeView = contents_->GetNativeView();
225   }
226   [contentsNativeView setFrame:[self frameForContentsView]];
227   if ([subviews count] == 0) {
228     [contentsContainer addSubview:contentsNativeView];
229   } else if ([subviews objectAtIndex:0] != contentsNativeView) {
230     [contentsContainer replaceSubview:[subviews objectAtIndex:0]
231                                  with:contentsNativeView];
232   }
233   [contentsNativeView setAutoresizingMask:NSViewWidthSizable|
234                                           NSViewHeightSizable];
236   // TODO(miu): The following can be removed once we use a CALayer in
237   // TabContentsContainerView.  http://crbug.com/354598
238   [contentsContainer setNeedsDisplay:YES];
240   // The rendering path with overlapping views disabled causes bugs when
241   // transitioning between composited and non-composited mode.
242   // http://crbug.com/279472
243   if (!fullscreenView)
244     contents_->SetAllowOverlappingViews(true);
247 - (void)changeWebContents:(WebContents*)newContents {
248   contents_ = newContents;
249   if (fullscreenObserver_) {
250     fullscreenObserver_->Observe(contents_);
251     isEmbeddingFullscreenWidget_ =
252         contents_ && contents_->GetFullscreenRenderWidgetHostView();
253   } else {
254     isEmbeddingFullscreenWidget_ = NO;
255   }
258 // Returns YES if the tab represented by this controller is the front-most.
259 - (BOOL)isCurrentTab {
260   // We're the current tab if we're in the view hierarchy, otherwise some other
261   // tab is.
262   return [[self view] superview] ? YES : NO;
265 - (void)willBecomeUnselectedTab {
266   // The RWHV is ripped out of the view hierarchy on tab switches, so it never
267   // formally resigns first responder status.  Handle this by explicitly sending
268   // a Blur() message to the renderer, but only if the RWHV currently has focus.
269   content::RenderViewHost* rvh = [self webContents]->GetRenderViewHost();
270   if (rvh) {
271     if (rvh->GetView() && rvh->GetView()->HasFocus()) {
272       rvh->Blur();
273       return;
274     }
275     DevToolsWindow* devtoolsWindow =
276         DevToolsWindow::GetDockedInstanceForInspectedTab([self webContents]);
277     if (devtoolsWindow) {
278       content::RenderViewHost* devtoolsView =
279           devtoolsWindow->web_contents()->GetRenderViewHost();
280       if (devtoolsView && devtoolsView->GetView() &&
281           devtoolsView->GetView()->HasFocus()) {
282         devtoolsView->Blur();
283       }
284     }
285   }
288 - (void)willBecomeSelectedTab {
289   // Do not explicitly call Focus() here, as the RWHV may not actually have
290   // focus (for example, if the omnibox has focus instead).  The WebContents
291   // logic will restore focus to the appropriate view.
294 - (void)tabDidChange:(WebContents*)updatedContents {
295   // Calling setContentView: here removes any first responder status
296   // the view may have, so avoid changing the view hierarchy unless
297   // the view is different.
298   if ([self webContents] != updatedContents) {
299     [self changeWebContents:updatedContents];
300     [self ensureContentsVisible];
301   }
304 - (void)toggleFullscreenWidget:(BOOL)enterFullscreen {
305   isEmbeddingFullscreenWidget_ = enterFullscreen &&
306       contents_ && contents_->GetFullscreenRenderWidgetHostView();
307   [self ensureContentsVisible];
310 - (BOOL)contentsInFullscreenCaptureMode {
311   if (!fullscreenObserver_)
312     return NO;
313   // Note: Grab a known-valid WebContents pointer from |fullscreenObserver_|.
314   content::WebContents* const wc = fullscreenObserver_->web_contents();
315   if (!wc ||
316       wc->GetCapturerCount() == 0 ||
317       wc->GetPreferredSize().IsEmpty() ||
318       !(isEmbeddingFullscreenWidget_ ||
319         (wc->GetDelegate() &&
320          wc->GetDelegate()->IsFullscreenForTabOrPending(wc)))) {
321     return NO;
322   }
323   return YES;
326 - (NSRect)frameForContentsView {
327   const NSSize containerSize = [[self view] frame].size;
328   gfx::Rect rect;
329   rect.set_width(containerSize.width);
330   rect.set_height(containerSize.height);
332   // In most cases, the contents view is simply sized to fill the container
333   // view's bounds. Only WebContentses that are in fullscreen mode and being
334   // screen-captured will engage the special layout/sizing behavior.
335   if (![self contentsInFullscreenCaptureMode])
336     return NSRectFromCGRect(rect.ToCGRect());
338   // Size the contents view to the capture video resolution and center it. If
339   // the container view is not large enough to fit it at the preferred size,
340   // scale down to fit (preserving aspect ratio).
341   content::WebContents* const wc = fullscreenObserver_->web_contents();
342   const gfx::Size captureSize = wc->GetPreferredSize();
343   if (captureSize.width() <= rect.width() &&
344       captureSize.height() <= rect.height()) {
345     // No scaling, just centering.
346     rect.ClampToCenteredSize(captureSize);
347   } else {
348     // Scale down, preserving aspect ratio, and center.
349     // TODO(miu): This is basically media::ComputeLetterboxRegion(), and it
350     // looks like others have written this code elsewhere.  Let's consolidate
351     // into a shared function ui/gfx/geometry or around there.
352     const int64 x = static_cast<int64>(captureSize.width()) * rect.height();
353     const int64 y = static_cast<int64>(captureSize.height()) * rect.width();
354     if (y < x) {
355       rect.ClampToCenteredSize(gfx::Size(
356           rect.width(), static_cast<int>(y / captureSize.width())));
357     } else {
358       rect.ClampToCenteredSize(gfx::Size(
359           static_cast<int>(x / captureSize.height()), rect.height()));
360     }
361   }
363   return NSRectFromCGRect(rect.ToCGRect());
366 @end