Upstream version 10.39.225.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/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"
21
22 using content::WebContents;
23 using content::WebContentsObserver;
24
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 {
34  public:
35   explicit FullscreenObserver(TabContentsController* controller)
36       : controller_(controller) {}
37
38   void Observe(content::WebContents* new_web_contents) {
39     WebContentsObserver::Observe(new_web_contents);
40   }
41
42   WebContents* web_contents() const {
43     return WebContentsObserver::web_contents();
44   }
45
46   virtual void DidShowFullscreenWidget(int routing_id) OVERRIDE {
47     [controller_ toggleFullscreenWidget:YES];
48   }
49
50   virtual void DidDestroyFullscreenWidget(int routing_id) OVERRIDE {
51     [controller_ toggleFullscreenWidget:NO];
52   }
53
54   virtual void DidToggleFullscreenModeForTab(bool entered_fullscreen) OVERRIDE {
55     [controller_ toggleFullscreenWidget:YES];
56   }
57
58  private:
59   TabContentsController* const controller_;
60
61   DISALLOW_COPY_AND_ASSIGN(FullscreenObserver);
62 };
63
64 @interface TabContentsController (TabContentsContainerViewDelegate)
65 - (BOOL)contentsInFullscreenCaptureMode;
66 // Computes and returns the frame to use for the contents view within the
67 // container view.
68 - (NSRect)frameForContentsView;
69 @end
70
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 {
75  @private
76   TabContentsController* delegate_;  // weak
77 }
78
79 - (NSColor*)computeBackgroundColor;
80 @end
81
82 @implementation TabContentsContainerView
83
84 - (id)initWithDelegate:(TabContentsController*)delegate {
85   if ((self = [super initWithFrame:NSZeroRect])) {
86     delegate_ = delegate;
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];
92   }
93   return self;
94 }
95
96 // Called by the delegate during dealloc to invalidate the pointer held by this
97 // view.
98 - (void)delegateDestroyed {
99   delegate_ = nil;
100 }
101
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];
107
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]);
113   if (theme)
114     bgColor = theme->GetNSColor(ThemeProperties::COLOR_NTP_BACKGROUND);
115   if (!bgColor)
116     bgColor = [NSColor whiteColor];
117   const float kDarknessFraction = 0.80f;
118   return [bgColor blendedColorWithFraction:kDarknessFraction
119                                    ofColor:[NSColor blackColor]];
120 }
121
122 // Override -drawRect to fill the view with a solid color outside of the
123 // subview's frame.
124 //
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);
132   }
133   [super drawRect:dirtyRect];
134 }
135
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 ||
142       !delegate_) {
143     return;
144   }
145
146   ScopedCAActionDisabler disabler;
147   [contentsView setFrame:[delegate_ frameForContentsView]];
148 }
149
150 // Update the background layer's color whenever the view needs to repaint.
151 - (void)setNeedsDisplayInRect:(NSRect)rect {
152   [super setNeedsDisplayInRect:rect];
153
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));
163
164   ScopedCAActionDisabler disabler;
165   [[self layer] setBackgroundColor:cgBackgroundColor];
166 }
167
168 @end  // @implementation TabContentsContainerView
169
170 @implementation TabContentsController
171 @synthesize webContents = contents_;
172
173 - (id)initWithContents:(WebContents*)contents {
174   if ((self = [super initWithNibName:nil bundle:nil])) {
175     fullscreenObserver_.reset(new FullscreenObserver(self));
176     [self changeWebContents:contents];
177   }
178   return self;
179 }
180
181 - (void)dealloc {
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];
186   [super dealloc];
187 }
188
189 - (void)loadView {
190   base::scoped_nsobject<NSView> view(
191       [[TabContentsContainerView alloc] initWithDelegate:self]);
192   [view setAutoresizingMask:NSViewHeightSizable|NSViewWidthSizable];
193   [self setView:view];
194 }
195
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];
202   }
203 }
204
205 - (void)ensureContentsVisible {
206   if (!contents_)
207     return;
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();
217   } else {
218     isEmbeddingFullscreenWidget_ = NO;
219     contentsNativeView = contents_->GetNativeView();
220   }
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];
227   }
228   [contentsNativeView setAutoresizingMask:NSViewWidthSizable|
229                                           NSViewHeightSizable];
230
231   [contentsContainer setNeedsDisplay:YES];
232 }
233
234 - (void)changeWebContents:(WebContents*)newContents {
235   contents_ = newContents;
236   fullscreenObserver_->Observe(contents_);
237   isEmbeddingFullscreenWidget_ =
238       contents_ && contents_->GetFullscreenRenderWidgetHostView();
239 }
240
241 // Returns YES if the tab represented by this controller is the front-most.
242 - (BOOL)isCurrentTab {
243   // We're the current tab if we're in the view hierarchy, otherwise some other
244   // tab is.
245   return [[self view] superview] ? YES : NO;
246 }
247
248 - (void)willBecomeUnselectedTab {
249   // The RWHV is ripped out of the view hierarchy on tab switches, so it never
250   // formally resigns first responder status.  Handle this by explicitly sending
251   // a Blur() message to the renderer, but only if the RWHV currently has focus.
252   content::RenderViewHost* rvh = [self webContents]->GetRenderViewHost();
253   if (rvh) {
254     if (rvh->GetView() && rvh->GetView()->HasFocus()) {
255       rvh->Blur();
256       return;
257     }
258     WebContents* devtools = DevToolsWindow::GetInTabWebContents(
259         [self webContents], NULL);
260     if (devtools) {
261       content::RenderViewHost* devtoolsView = devtools->GetRenderViewHost();
262       if (devtoolsView && devtoolsView->GetView() &&
263           devtoolsView->GetView()->HasFocus()) {
264         devtoolsView->Blur();
265       }
266     }
267   }
268 }
269
270 - (void)willBecomeSelectedTab {
271   // Do not explicitly call Focus() here, as the RWHV may not actually have
272   // focus (for example, if the omnibox has focus instead).  The WebContents
273   // logic will restore focus to the appropriate view.
274 }
275
276 - (void)tabDidChange:(WebContents*)updatedContents {
277   // Calling setContentView: here removes any first responder status
278   // the view may have, so avoid changing the view hierarchy unless
279   // the view is different.
280   if ([self webContents] != updatedContents) {
281     [self changeWebContents:updatedContents];
282     [self ensureContentsVisible];
283   }
284 }
285
286 - (void)toggleFullscreenWidget:(BOOL)enterFullscreen {
287   isEmbeddingFullscreenWidget_ = enterFullscreen &&
288       contents_ && contents_->GetFullscreenRenderWidgetHostView();
289   [self ensureContentsVisible];
290 }
291
292 - (BOOL)contentsInFullscreenCaptureMode {
293   // Note: Grab a known-valid WebContents pointer from |fullscreenObserver_|.
294   content::WebContents* const wc = fullscreenObserver_->web_contents();
295   if (!wc ||
296       wc->GetCapturerCount() == 0 ||
297       wc->GetPreferredSize().IsEmpty() ||
298       !(isEmbeddingFullscreenWidget_ ||
299         (wc->GetDelegate() &&
300          wc->GetDelegate()->IsFullscreenForTabOrPending(wc)))) {
301     return NO;
302   }
303   return YES;
304 }
305
306 - (NSRect)frameForContentsView {
307   const NSSize containerSize = [[self view] frame].size;
308   gfx::Rect rect;
309   rect.set_width(containerSize.width);
310   rect.set_height(containerSize.height);
311
312   // In most cases, the contents view is simply sized to fill the container
313   // view's bounds. Only WebContentses that are in fullscreen mode and being
314   // screen-captured will engage the special layout/sizing behavior.
315   if (![self contentsInFullscreenCaptureMode])
316     return NSRectFromCGRect(rect.ToCGRect());
317
318   // Size the contents view to the capture video resolution and center it. If
319   // the container view is not large enough to fit it at the preferred size,
320   // scale down to fit (preserving aspect ratio).
321   content::WebContents* const wc = fullscreenObserver_->web_contents();
322   const gfx::Size captureSize = wc->GetPreferredSize();
323   if (captureSize.width() <= rect.width() &&
324       captureSize.height() <= rect.height()) {
325     // No scaling, just centering.
326     rect.ClampToCenteredSize(captureSize);
327   } else {
328     // Scale down, preserving aspect ratio, and center.
329     // TODO(miu): This is basically media::ComputeLetterboxRegion(), and it
330     // looks like others have written this code elsewhere.  Let's consolidate
331     // into a shared function ui/gfx/geometry or around there.
332     const int64 x = static_cast<int64>(captureSize.width()) * rect.height();
333     const int64 y = static_cast<int64>(captureSize.height()) * rect.width();
334     if (y < x) {
335       rect.ClampToCenteredSize(gfx::Size(
336           rect.width(), static_cast<int>(y / captureSize.width())));
337     } else {
338       rect.ClampToCenteredSize(gfx::Size(
339           static_cast<int>(x / captureSize.height()), rect.height()));
340     }
341   }
342
343   return NSRectFromCGRect(rect.ToCGRect());
344 }
345
346 @end