1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
|
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "chrome/browser/ui/cocoa/tab_contents/tab_contents_controller.h"
#include <stdint.h>
#include <utility>
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/scoped_nsobject.h"
#include "base/macros.h"
#include "chrome/browser/devtools/devtools_window.h"
#import "chrome/browser/themes/theme_properties.h"
#import "chrome/browser/themes/theme_service.h"
#import "chrome/browser/ui/cocoa/themed_window.h"
#include "chrome/browser/ui/view_ids.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "skia/ext/skia_utils_mac.h"
#include "ui/base/cocoa/animation_utils.h"
#import "ui/base/cocoa/nscolor_additions.h"
#include "ui/gfx/geometry/rect.h"
using content::WebContents;
using content::WebContentsObserver;
// FullscreenObserver is used by TabContentsController to monitor for the
// showing/destruction of fullscreen render widgets. When notified,
// TabContentsController will alter its child view hierarchy to either embed a
// fullscreen render widget view or restore the normal WebContentsView render
// view. The embedded fullscreen render widget will fill the user's screen in
// the case where TabContentsController's NSView is a subview of a browser
// window that has been toggled into fullscreen mode (e.g., via
// FullscreenController).
class FullscreenObserver : public WebContentsObserver {
public:
explicit FullscreenObserver(TabContentsController* controller)
: controller_(controller) {}
void Observe(content::WebContents* new_web_contents) {
WebContentsObserver::Observe(new_web_contents);
}
WebContents* web_contents() const {
return WebContentsObserver::web_contents();
}
void DidShowFullscreenWidget(int routing_id) override {
[controller_ toggleFullscreenWidget:YES];
}
void DidDestroyFullscreenWidget(int routing_id) override {
[controller_ toggleFullscreenWidget:NO];
}
void DidToggleFullscreenModeForTab(bool entered_fullscreen,
bool will_cause_resize) override {
[controller_ toggleFullscreenWidget:YES];
}
private:
TabContentsController* const controller_;
DISALLOW_COPY_AND_ASSIGN(FullscreenObserver);
};
@interface TabContentsController (TabContentsContainerViewDelegate)
- (BOOL)contentsInFullscreenCaptureMode;
// Computes and returns the frame to use for the contents view within the
// container view.
- (NSRect)frameForContentsView;
// Returns YES if the content view should be resized.
- (BOOL)shouldResizeContentView;
@end
// An NSView with special-case handling for when the contents view does not
// expand to fill the entire tab contents area. See 'AutoEmbedFullscreen mode'
// in header file comments.
@interface TabContentsContainerView : NSView {
@private
TabContentsController* delegate_; // weak
}
- (void)updateBackgroundColor;
@end
@implementation TabContentsContainerView
- (id)initWithDelegate:(TabContentsController*)delegate {
if ((self = [super initWithFrame:NSZeroRect])) {
delegate_ = delegate;
ScopedCAActionDisabler disabler;
base::scoped_nsobject<CALayer> layer([[CALayer alloc] init]);
[self setLayer:layer];
[self setWantsLayer:YES];
[self updateBackgroundColor];
}
return self;
}
// Called by the delegate during dealloc to invalidate the pointer held by this
// view.
- (void)delegateDestroyed {
delegate_ = nil;
}
// Override auto-resizing logic to query the delegate for the exact frame to
// use for the contents view.
- (void)resizeSubviewsWithOldSize:(NSSize)oldBoundsSize {
NSView* const contentsView =
[[self subviews] count] > 0 ? [[self subviews] objectAtIndex:0] : nil;
if (!contentsView || [contentsView autoresizingMask] == NSViewNotSizable ||
!delegate_ || ![delegate_ shouldResizeContentView]) {
return;
}
ScopedCAActionDisabler disabler;
[contentsView setFrame:[delegate_ frameForContentsView]];
}
// Update the background layer's color whenever the view needs to repaint.
- (void)setNeedsDisplayInRect:(NSRect)rect {
[super setNeedsDisplayInRect:rect];
[self updateBackgroundColor];
}
- (void)updateBackgroundColor {
// This view is sometimes flashed into visibility (e.g, when closing
// windows or opening new tabs), so ensure that the flash be the theme
// background color in those cases.
SkColor skBackgroundColor = SK_ColorWHITE;
const ThemeProvider* theme = [[self window] themeProvider];
if (theme)
skBackgroundColor = theme->GetColor(ThemeProperties::COLOR_NTP_BACKGROUND);
// If the page is in fullscreen tab capture mode, change the background color
// to be a dark tint of the new tab page's background color.
if ([delegate_ contentsInFullscreenCaptureMode]) {
const int kBackgroundDivisor = 5;
skBackgroundColor = skBackgroundColor = SkColorSetARGB(
SkColorGetA(skBackgroundColor),
SkColorGetR(skBackgroundColor) / kBackgroundDivisor,
SkColorGetG(skBackgroundColor) / kBackgroundDivisor,
SkColorGetB(skBackgroundColor) / kBackgroundDivisor);
}
ScopedCAActionDisabler disabler;
base::ScopedCFTypeRef<CGColorRef> cgBackgroundColor(
skia::CGColorCreateFromSkColor(skBackgroundColor));
[[self layer] setBackgroundColor:cgBackgroundColor];
}
- (ViewID)viewID {
return VIEW_ID_TAB_CONTAINER;
}
- (BOOL)acceptsFirstResponder {
return [[self subviews] count] > 0 &&
[[[self subviews] objectAtIndex:0] acceptsFirstResponder];
}
// When receiving a click-to-focus in the solid color area surrounding the
// WebContents' native view, immediately transfer focus to WebContents' native
// view.
- (BOOL)becomeFirstResponder {
if (![self acceptsFirstResponder])
return NO;
return [[self window] makeFirstResponder:[[self subviews] objectAtIndex:0]];
}
- (BOOL)canBecomeKeyView {
return NO; // Tab/Shift-Tab should focus the subview, not this view.
}
@end // @implementation TabContentsContainerView
@implementation TabContentsController
@synthesize webContents = contents_;
@synthesize blockFullscreenResize = blockFullscreenResize_;
- (id)initWithContents:(WebContents*)contents {
if ((self = [super initWithNibName:nil bundle:nil])) {
fullscreenObserver_.reset(new FullscreenObserver(self));
[self changeWebContents:contents];
}
return self;
}
- (void)dealloc {
[static_cast<TabContentsContainerView*>([self view]) delegateDestroyed];
// Make sure the contents view has been removed from the container view to
// allow objects to be released.
[[self view] removeFromSuperview];
[super dealloc];
}
- (void)loadView {
base::scoped_nsobject<NSView> view(
[[TabContentsContainerView alloc] initWithDelegate:self]);
[view setAutoresizingMask:NSViewHeightSizable|NSViewWidthSizable];
[self setView:view];
}
- (void)ensureContentsSizeDoesNotChange {
NSView* contentsContainer = [self view];
NSArray* subviews = [contentsContainer subviews];
if ([subviews count] > 0) {
NSView* currentSubview = [subviews objectAtIndex:0];
[currentSubview setAutoresizingMask:NSViewNotSizable];
}
}
- (void)ensureContentsVisible {
if (!contents_)
return;
ScopedCAActionDisabler disabler;
NSView* contentsContainer = [self view];
NSArray* subviews = [contentsContainer subviews];
NSView* contentsNativeView;
content::RenderWidgetHostView* const fullscreenView =
isEmbeddingFullscreenWidget_ ?
contents_->GetFullscreenRenderWidgetHostView() : NULL;
if (fullscreenView) {
contentsNativeView = fullscreenView->GetNativeView();
} else {
isEmbeddingFullscreenWidget_ = NO;
contentsNativeView = contents_->GetNativeView();
}
if ([self shouldResizeContentView])
[contentsNativeView setFrame:[self frameForContentsView]];
if ([subviews count] == 0) {
[contentsContainer addSubview:contentsNativeView];
} else if ([subviews objectAtIndex:0] != contentsNativeView) {
[contentsContainer replaceSubview:[subviews objectAtIndex:0]
with:contentsNativeView];
}
[contentsNativeView setAutoresizingMask:NSViewWidthSizable|
NSViewHeightSizable];
[contentsContainer setNeedsDisplay:YES];
// Push the background color down to the RenderWidgetHostView, so that if
// there is a flash between contents appearing, it will be the theme's color,
// not white.
SkColor skBackgroundColor = SK_ColorWHITE;
const ThemeProvider* theme = [[[self view] window] themeProvider];
if (theme)
skBackgroundColor = theme->GetColor(ThemeProperties::COLOR_NTP_BACKGROUND);
content::RenderWidgetHostView* rwhv = contents_->GetRenderWidgetHostView();
if (rwhv)
rwhv->SetBackgroundColor(skBackgroundColor);
}
- (void)updateFullscreenWidgetFrame {
// This should only apply if a fullscreen widget is embedded.
if (!isEmbeddingFullscreenWidget_ || blockFullscreenResize_)
return;
content::RenderWidgetHostView* const fullscreenView =
contents_->GetFullscreenRenderWidgetHostView();
if (fullscreenView)
[fullscreenView->GetNativeView() setFrame:[self frameForContentsView]];
}
- (void)changeWebContents:(WebContents*)newContents {
contents_ = newContents;
fullscreenObserver_->Observe(contents_);
isEmbeddingFullscreenWidget_ =
contents_ && contents_->GetFullscreenRenderWidgetHostView();
}
// Returns YES if the tab represented by this controller is the front-most.
- (BOOL)isCurrentTab {
// We're the current tab if we're in the view hierarchy, otherwise some other
// tab is.
return [[self view] superview] ? YES : NO;
}
- (void)willBecomeUnselectedTab {
// The RWHV is ripped out of the view hierarchy on tab switches, so it never
// formally resigns first responder status. Handle this by explicitly sending
// a Blur() message to the renderer, but only if the RWHV currently has focus.
content::RenderViewHost* rvh = [self webContents]->GetRenderViewHost();
if (rvh) {
if (rvh->GetWidget()->GetView() &&
rvh->GetWidget()->GetView()->HasFocus()) {
rvh->GetWidget()->Blur();
return;
}
WebContents* devtools = DevToolsWindow::GetInTabWebContents(
[self webContents], NULL);
if (devtools) {
content::RenderViewHost* devtoolsView = devtools->GetRenderViewHost();
if (devtoolsView && devtoolsView->GetWidget()->GetView() &&
devtoolsView->GetWidget()->GetView()->HasFocus()) {
devtoolsView->GetWidget()->Blur();
}
}
}
}
- (void)willBecomeSelectedTab {
// Do not explicitly call Focus() here, as the RWHV may not actually have
// focus (for example, if the omnibox has focus instead). The WebContents
// logic will restore focus to the appropriate view.
}
- (void)tabDidChange:(WebContents*)updatedContents {
// Calling setContentView: here removes any first responder status
// the view may have, so avoid changing the view hierarchy unless
// the view is different.
if ([self webContents] != updatedContents) {
[self changeWebContents:updatedContents];
[self ensureContentsVisible];
}
}
- (void)toggleFullscreenWidget:(BOOL)enterFullscreen {
isEmbeddingFullscreenWidget_ = enterFullscreen &&
contents_ && contents_->GetFullscreenRenderWidgetHostView();
[self ensureContentsVisible];
}
- (BOOL)contentsInFullscreenCaptureMode {
// Note: Grab a known-valid WebContents pointer from |fullscreenObserver_|.
content::WebContents* const wc = fullscreenObserver_->web_contents();
if (!wc ||
wc->GetCapturerCount() == 0 ||
wc->GetPreferredSize().IsEmpty() ||
!(isEmbeddingFullscreenWidget_ ||
(wc->GetDelegate() &&
wc->GetDelegate()->IsFullscreenForTabOrPending(wc)))) {
return NO;
}
return YES;
}
- (NSRect)frameForContentsView {
const NSSize containerSize = [[self view] frame].size;
gfx::Rect rect;
rect.set_width(containerSize.width);
rect.set_height(containerSize.height);
// In most cases, the contents view is simply sized to fill the container
// view's bounds. Only WebContentses that are in fullscreen mode and being
// screen-captured will engage the special layout/sizing behavior.
if (![self contentsInFullscreenCaptureMode])
return NSRectFromCGRect(rect.ToCGRect());
// Size the contents view to the capture video resolution and center it. If
// the container view is not large enough to fit it at the preferred size,
// scale down to fit (preserving aspect ratio).
content::WebContents* const wc = fullscreenObserver_->web_contents();
const gfx::Size captureSize = wc->GetPreferredSize();
if (captureSize.width() <= rect.width() &&
captureSize.height() <= rect.height()) {
// No scaling, just centering.
rect.ClampToCenteredSize(captureSize);
} else {
// Scale down, preserving aspect ratio, and center.
// TODO(miu): This is basically media::ComputeLetterboxRegion(), and it
// looks like others have written this code elsewhere. Let's consolidate
// into a shared function ui/gfx/geometry or around there.
const int64_t x = static_cast<int64_t>(captureSize.width()) * rect.height();
const int64_t y = static_cast<int64_t>(captureSize.height()) * rect.width();
if (y < x) {
rect.ClampToCenteredSize(gfx::Size(
rect.width(), static_cast<int>(y / captureSize.width())));
} else {
rect.ClampToCenteredSize(gfx::Size(
static_cast<int>(x / captureSize.height()), rect.height()));
}
}
return NSRectFromCGRect(rect.ToCGRect());
}
- (BOOL)shouldResizeContentView {
return !isEmbeddingFullscreenWidget_ || !blockFullscreenResize_;
}
@end
|