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"
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 "content/public/browser/render_view_host.h"
16 #include "content/public/browser/render_widget_host_view.h"
17 #include "content/public/browser/web_contents.h"
18 #include "content/public/browser/web_contents_observer.h"
19 #include "ui/base/cocoa/animation_utils.h"
20 #include "ui/gfx/geometry/rect.h"
22 using content::WebContents;
23 using content::WebContentsObserver;
25 // FullscreenObserver is used by TabContentsController to monitor for the
26 // showing/destruction of fullscreen render widgets. When notified,
27 // TabContentsController will alter its child view hierarchy to either embed a
28 // fullscreen render widget view or restore the normal WebContentsView render
29 // view. The embedded fullscreen render widget will fill the user's screen in
30 // the case where TabContentsController's NSView is a subview of a browser
31 // window that has been toggled into fullscreen mode (e.g., via
32 // FullscreenController).
33 class FullscreenObserver : public WebContentsObserver {
35 explicit FullscreenObserver(TabContentsController* controller)
36 : controller_(controller) {}
38 void Observe(content::WebContents* new_web_contents) {
39 WebContentsObserver::Observe(new_web_contents);
42 WebContents* web_contents() const {
43 return WebContentsObserver::web_contents();
46 virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE {
47 [controller_ toggleFullscreenWidget:YES];
50 virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE {
51 [controller_ toggleFullscreenWidget:NO];
54 virtual void DidToggleFullscreenModeForTab(bool entered_fullscreen) OVERRIDE {
55 [controller_ toggleFullscreenWidget:YES];
59 TabContentsController* const controller_;
61 DISALLOW_COPY_AND_ASSIGN(FullscreenObserver);
64 @interface TabContentsController (TabContentsContainerViewDelegate)
65 - (BOOL)contentsInFullscreenCaptureMode;
66 // Computes and returns the frame to use for the contents view within the
68 - (NSRect)frameForContentsView;
71 // An NSView with special-case handling for when the contents view does not
72 // expand to fill the entire tab contents area. See 'AutoEmbedFullscreen mode'
73 // in header file comments.
74 @interface TabContentsContainerView : NSView {
76 TabContentsController* delegate_; // weak
79 - (NSColor*)computeBackgroundColor;
82 @implementation TabContentsContainerView
84 - (id)initWithDelegate:(TabContentsController*)delegate {
85 if ((self = [super initWithFrame:NSZeroRect])) {
87 ScopedCAActionDisabler disabler;
88 base::scoped_nsobject<CALayer> layer([[CALayer alloc] init]);
89 [layer setBackgroundColor:CGColorGetConstantColor(kCGColorWhite)];
90 [self setLayer:layer];
91 [self setWantsLayer:YES];
96 // Called by the delegate during dealloc to invalidate the pointer held by this
98 - (void)delegateDestroyed {
102 - (NSColor*)computeBackgroundColor {
103 // This view is sometimes flashed into visibility (e.g, when closing
104 // windows), so ensure that the flash be white in those cases.
105 if (![delegate_ contentsInFullscreenCaptureMode])
106 return [NSColor whiteColor];
108 // Fill with a dark tint of the new tab page's background color. This is
109 // only seen when the subview is sized specially for fullscreen tab capture.
110 NSColor* bgColor = nil;
111 ThemeService* const theme =
112 static_cast<ThemeService*>([[self window] themeProvider]);
114 bgColor = theme->GetNSColor(ThemeProperties::COLOR_NTP_BACKGROUND);
116 bgColor = [NSColor whiteColor];
117 const float kDarknessFraction = 0.80f;
118 return [bgColor blendedColorWithFraction:kDarknessFraction
119 ofColor:[NSColor blackColor]];
122 // Override -drawRect to fill the view with a solid color outside of the
125 // Note: This method is never called when CoreAnimation is enabled.
126 - (void)drawRect:(NSRect)dirtyRect {
127 NSView* const contentsView =
128 [[self subviews] count] > 0 ? [[self subviews] objectAtIndex:0] : nil;
129 if (!contentsView || !NSContainsRect([contentsView frame], dirtyRect)) {
130 [[self computeBackgroundColor] setFill];
131 NSRectFill(dirtyRect);
133 [super drawRect:dirtyRect];
136 // Override auto-resizing logic to query the delegate for the exact frame to
137 // use for the contents view.
138 - (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
139 NSView* const contentsView =
140 [[self subviews] count] > 0 ? [[self subviews] objectAtIndex:0] : nil;
141 if (!contentsView || [contentsView autoresizingMask] == NSViewNotSizable ||
146 ScopedCAActionDisabler disabler;
147 [contentsView setFrame:[delegate_ frameForContentsView]];
150 // Update the background layer's color whenever the view needs to repaint.
151 - (void)setNeedsDisplayInRect:(NSRect)rect {
152 [super setNeedsDisplayInRect:rect];
154 // Convert from an NSColor to a CGColorRef.
155 NSColor* nsBackgroundColor = [self computeBackgroundColor];
156 NSColorSpace* nsColorSpace = [nsBackgroundColor colorSpace];
157 CGColorSpaceRef cgColorSpace = [nsColorSpace CGColorSpace];
158 const NSInteger numberOfComponents = [nsBackgroundColor numberOfComponents];
159 CGFloat components[numberOfComponents];
160 [nsBackgroundColor getComponents:components];
161 base::ScopedCFTypeRef<CGColorRef> cgBackgroundColor(
162 CGColorCreate(cgColorSpace, components));
164 ScopedCAActionDisabler disabler;
165 [[self layer] setBackgroundColor:cgBackgroundColor];
168 @end // @implementation TabContentsContainerView
170 @implementation TabContentsController
171 @synthesize webContents = contents_;
173 - (id)initWithContents:(WebContents*)contents {
174 if ((self = [super initWithNibName:nil bundle:nil])) {
175 fullscreenObserver_.reset(new FullscreenObserver(self));
176 [self changeWebContents:contents];
182 [static_cast<TabContentsContainerView*>([self view]) delegateDestroyed];
183 // Make sure the contents view has been removed from the container view to
184 // allow objects to be released.
185 [[self view] removeFromSuperview];
190 base::scoped_nsobject<NSView> view(
191 [[TabContentsContainerView alloc] initWithDelegate:self]);
192 [view setAutoresizingMask:NSViewHeightSizable|NSViewWidthSizable];
196 - (void)ensureContentsSizeDoesNotChange {
197 NSView* contentsContainer = [self view];
198 NSArray* subviews = [contentsContainer subviews];
199 if ([subviews count] > 0) {
200 NSView* currentSubview = [subviews objectAtIndex:0];
201 [currentSubview setAutoresizingMask:NSViewNotSizable];
205 - (void)ensureContentsVisible {
208 ScopedCAActionDisabler disabler;
209 NSView* contentsContainer = [self view];
210 NSArray* subviews = [contentsContainer subviews];
211 NSView* contentsNativeView;
212 content::RenderWidgetHostView* const fullscreenView =
213 isEmbeddingFullscreenWidget_ ?
214 contents_->GetFullscreenRenderWidgetHostView() : NULL;
215 if (fullscreenView) {
216 contentsNativeView = fullscreenView->GetNativeView();
218 isEmbeddingFullscreenWidget_ = NO;
219 contentsNativeView = contents_->GetNativeView();
221 [contentsNativeView setFrame:[self frameForContentsView]];
222 if ([subviews count] == 0) {
223 [contentsContainer addSubview:contentsNativeView];
224 } else if ([subviews objectAtIndex:0] != contentsNativeView) {
225 [contentsContainer replaceSubview:[subviews objectAtIndex:0]
226 with:contentsNativeView];
228 [contentsNativeView setAutoresizingMask:NSViewWidthSizable|
229 NSViewHeightSizable];
231 [contentsContainer setNeedsDisplay:YES];
233 // The rendering path with overlapping views disabled causes bugs when
234 // transitioning between composited and non-composited mode.
235 // http://crbug.com/279472
237 contents_->SetAllowOverlappingViews(true);
240 - (void)changeWebContents:(WebContents*)newContents {
241 contents_ = newContents;
242 fullscreenObserver_->Observe(contents_);
243 isEmbeddingFullscreenWidget_ =
244 contents_ && contents_->GetFullscreenRenderWidgetHostView();
247 // Returns YES if the tab represented by this controller is the front-most.
248 - (BOOL)isCurrentTab {
249 // We're the current tab if we're in the view hierarchy, otherwise some other
251 return [[self view] superview] ? YES : NO;
254 - (void)willBecomeUnselectedTab {
255 // The RWHV is ripped out of the view hierarchy on tab switches, so it never
256 // formally resigns first responder status. Handle this by explicitly sending
257 // a Blur() message to the renderer, but only if the RWHV currently has focus.
258 content::RenderViewHost* rvh = [self webContents]->GetRenderViewHost();
260 if (rvh->GetView() && rvh->GetView()->HasFocus()) {
264 WebContents* devtools = DevToolsWindow::GetInTabWebContents(
265 [self webContents], NULL);
267 content::RenderViewHost* devtoolsView = devtools->GetRenderViewHost();
268 if (devtoolsView && devtoolsView->GetView() &&
269 devtoolsView->GetView()->HasFocus()) {
270 devtoolsView->Blur();
276 - (void)willBecomeSelectedTab {
277 // Do not explicitly call Focus() here, as the RWHV may not actually have
278 // focus (for example, if the omnibox has focus instead). The WebContents
279 // logic will restore focus to the appropriate view.
282 - (void)tabDidChange:(WebContents*)updatedContents {
283 // Calling setContentView: here removes any first responder status
284 // the view may have, so avoid changing the view hierarchy unless
285 // the view is different.
286 if ([self webContents] != updatedContents) {
287 [self changeWebContents:updatedContents];
288 [self ensureContentsVisible];
292 - (void)toggleFullscreenWidget:(BOOL)enterFullscreen {
293 isEmbeddingFullscreenWidget_ = enterFullscreen &&
294 contents_ && contents_->GetFullscreenRenderWidgetHostView();
295 [self ensureContentsVisible];
298 - (BOOL)contentsInFullscreenCaptureMode {
299 // Note: Grab a known-valid WebContents pointer from |fullscreenObserver_|.
300 content::WebContents* const wc = fullscreenObserver_->web_contents();
302 wc->GetCapturerCount() == 0 ||
303 wc->GetPreferredSize().IsEmpty() ||
304 !(isEmbeddingFullscreenWidget_ ||
305 (wc->GetDelegate() &&
306 wc->GetDelegate()->IsFullscreenForTabOrPending(wc)))) {
312 - (NSRect)frameForContentsView {
313 const NSSize containerSize = [[self view] frame].size;
315 rect.set_width(containerSize.width);
316 rect.set_height(containerSize.height);
318 // In most cases, the contents view is simply sized to fill the container
319 // view's bounds. Only WebContentses that are in fullscreen mode and being
320 // screen-captured will engage the special layout/sizing behavior.
321 if (![self contentsInFullscreenCaptureMode])
322 return NSRectFromCGRect(rect.ToCGRect());
324 // Size the contents view to the capture video resolution and center it. If
325 // the container view is not large enough to fit it at the preferred size,
326 // scale down to fit (preserving aspect ratio).
327 content::WebContents* const wc = fullscreenObserver_->web_contents();
328 const gfx::Size captureSize = wc->GetPreferredSize();
329 if (captureSize.width() <= rect.width() &&
330 captureSize.height() <= rect.height()) {
331 // No scaling, just centering.
332 rect.ClampToCenteredSize(captureSize);
334 // Scale down, preserving aspect ratio, and center.
335 // TODO(miu): This is basically media::ComputeLetterboxRegion(), and it
336 // looks like others have written this code elsewhere. Let's consolidate
337 // into a shared function ui/gfx/geometry or around there.
338 const int64 x = static_cast<int64>(captureSize.width()) * rect.height();
339 const int64 y = static_cast<int64>(captureSize.height()) * rect.width();
341 rect.ClampToCenteredSize(gfx::Size(
342 rect.width(), static_cast<int>(y / captureSize.width())));
344 rect.ClampToCenteredSize(gfx::Size(
345 static_cast<int>(x / captureSize.height()), rect.height()));
349 return NSRectFromCGRect(rect.ToCGRect());