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