Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(97)

Side by Side Diff: chrome/browser/extensions/api/tab_capture/tab_capture_api.cc

Issue 1221483002: New tabCapture.captureOffscreenTab API, initially for Presentation API 1UA mode (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Revert to only whitelisted extension use. Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 // Implements the Chrome Extensions Tab Capture API. 5 // Implements the Chrome Extensions Tab Capture API.
6 6
7 #include "chrome/browser/extensions/api/tab_capture/tab_capture_api.h" 7 #include "chrome/browser/extensions/api/tab_capture/tab_capture_api.h"
8 8
9 #include <algorithm>
9 #include <set> 10 #include <set>
10 #include <string> 11 #include <string>
11 #include <vector> 12 #include <vector>
12 13
13 #include "base/command_line.h" 14 #include "base/command_line.h"
14 #include "base/strings/stringprintf.h" 15 #include "base/strings/stringprintf.h"
15 #include "base/values.h" 16 #include "base/values.h"
17 #include "chrome/browser/extensions/api/tab_capture/offscreen_presentation.h"
16 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h" 18 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
17 #include "chrome/browser/extensions/extension_renderer_state.h" 19 #include "chrome/browser/extensions/extension_renderer_state.h"
18 #include "chrome/browser/profiles/profile.h" 20 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/sessions/session_tab_helper.h" 21 #include "chrome/browser/sessions/session_tab_helper.h"
20 #include "chrome/browser/ui/browser.h" 22 #include "chrome/browser/ui/browser.h"
21 #include "chrome/browser/ui/browser_finder.h" 23 #include "chrome/browser/ui/browser_finder.h"
22 #include "chrome/browser/ui/tabs/tab_strip_model.h" 24 #include "chrome/browser/ui/tabs/tab_strip_model.h"
25 #include "chrome/common/chrome_switches.h"
23 #include "content/public/browser/render_frame_host.h" 26 #include "content/public/browser/render_frame_host.h"
24 #include "content/public/browser/render_process_host.h" 27 #include "content/public/browser/render_process_host.h"
28 #include "content/public/browser/web_contents.h"
25 #include "extensions/common/features/feature.h" 29 #include "extensions/common/features/feature.h"
26 #include "extensions/common/features/feature_provider.h" 30 #include "extensions/common/features/feature_provider.h"
27 #include "extensions/common/features/simple_feature.h" 31 #include "extensions/common/features/simple_feature.h"
28 #include "extensions/common/permissions/permissions_data.h" 32 #include "extensions/common/permissions/permissions_data.h"
29 #include "extensions/common/switches.h" 33 #include "extensions/common/switches.h"
30 34
31 using extensions::api::tab_capture::MediaStreamConstraint; 35 using extensions::api::tab_capture::MediaStreamConstraint;
32 36
33 namespace TabCapture = extensions::api::tab_capture; 37 namespace TabCapture = extensions::api::tab_capture;
34 namespace GetCapturedTabs = TabCapture::GetCapturedTabs; 38 namespace GetCapturedTabs = TabCapture::GetCapturedTabs;
35 39
36 namespace extensions { 40 namespace extensions {
37 namespace { 41 namespace {
38 42
39 const char kCapturingSameTab[] = "Cannot capture a tab with an active stream."; 43 const char kCapturingSameTab[] = "Cannot capture a tab with an active stream.";
40 const char kFindingTabError[] = "Error finding tab to capture."; 44 const char kFindingTabError[] = "Error finding tab to capture.";
41 const char kNoAudioOrVideo[] = "Capture failed. No audio or video requested."; 45 const char kNoAudioOrVideo[] = "Capture failed. No audio or video requested.";
42 const char kGrantError[] = 46 const char kGrantError[] =
43 "Extension has not been invoked for the current page (see activeTab " 47 "Extension has not been invoked for the current page (see activeTab "
44 "permission). Chrome pages cannot be captured."; 48 "permission). Chrome pages cannot be captured.";
45 49
50 const char kNotWhitelistedForOffscreenTabApi[] =
51 "Extension is not whitelisted for use of the unstable, in-development "
52 "chrome.tabCapture.captureOffscreenTab API.";
53 const char kInvalidStartUrl[] =
54 "Invalid/Missing/Malformatted starting URL for off-screen tab.";
55 const char kMissingExtensionPage[] =
56 "tabCapture API was not invoked from an extension background page.";
57 const char kTooManyOffscreenTabs[] =
58 "Extension has already started too many off-screen tabs.";
59 const char kCapturingSameOffscreenTab[] =
60 "Cannot capture the same off-screen tab more than once.";
61
46 // Keys/values for media stream constraints. 62 // Keys/values for media stream constraints.
47 const char kMediaStreamSource[] = "chromeMediaSource"; 63 const char kMediaStreamSource[] = "chromeMediaSource";
48 const char kMediaStreamSourceId[] = "chromeMediaSourceId"; 64 const char kMediaStreamSourceId[] = "chromeMediaSourceId";
49 const char kMediaStreamSourceTab[] = "tab"; 65 const char kMediaStreamSourceTab[] = "tab";
50 66
51 // Tab Capture-specific video constraint to enable automatic resolution/rate 67 // Tab Capture-specific video constraint to enable automatic resolution/rate
52 // throttling mode in the capture pipeline. 68 // throttling mode in the capture pipeline.
53 const char kEnableAutoThrottlingKey[] = "enableAutoThrottling"; 69 const char kEnableAutoThrottlingKey[] = "enableAutoThrottling";
54 70
71 bool OptionsSpecifyAudioOrVideo(const TabCapture::CaptureOptions& options) {
72 return (options.audio && *options.audio) || (options.video && *options.video);
73 }
74
75 bool IsAcceptableOffscreenTabUrl(const GURL& url) {
76 return url.is_valid() && (url.SchemeIsHTTPOrHTTPS() || url.SchemeIs("data"));
77 }
78
79 // Add Chrome-specific source identifiers to the MediaStreamConstraints objects
80 // in |options| to provide references to the |target_contents| to be captured.
81 void AddMediaStreamSourceConstraints(content::WebContents* target_contents,
82 TabCapture::CaptureOptions* options) {
83 DCHECK(options);
84 DCHECK(target_contents);
85
86 MediaStreamConstraint* constraints_to_modify[2] = { nullptr, nullptr };
87
88 if (options->audio && *options->audio) {
89 if (!options->audio_constraints)
90 options->audio_constraints.reset(new MediaStreamConstraint);
91 constraints_to_modify[0] = options->audio_constraints.get();
92 }
93
94 bool enable_auto_throttling = false;
95 if (options->video && *options->video) {
96 if (options->video_constraints) {
97 // Check for the Tab Capture-specific video constraint for enabling
98 // automatic resolution/rate throttling mode in the capture pipeline. See
99 // implementation comments for content::WebContentsVideoCaptureDevice.
100 base::DictionaryValue& props =
101 options->video_constraints->mandatory.additional_properties;
102 if (!props.GetBooleanWithoutPathExpansion(
103 kEnableAutoThrottlingKey, &enable_auto_throttling)) {
104 enable_auto_throttling = false;
105 }
106 // Remove the key from the properties to avoid an "unrecognized
107 // constraint" error in the renderer.
108 props.RemoveWithoutPathExpansion(kEnableAutoThrottlingKey, nullptr);
109 } else {
110 options->video_constraints.reset(new MediaStreamConstraint);
111 }
112 constraints_to_modify[1] = options->video_constraints.get();
113 }
114
115 // Format the device ID that references the target tab.
116 content::RenderFrameHost* const main_frame = target_contents->GetMainFrame();
117 // TODO(miu): We should instead use a "randomly generated device ID" scheme,
118 // like that employed by the desktop capture API. http://crbug.com/163100
119 const std::string device_id = base::StringPrintf(
120 "web-contents-media-stream://%i:%i%s",
121 main_frame->GetProcess()->GetID(),
122 main_frame->GetRoutingID(),
123 enable_auto_throttling ? "?throttling=auto" : "");
124
125 // Append chrome specific tab constraints.
126 for (MediaStreamConstraint* msc : constraints_to_modify) {
127 if (!msc)
128 continue;
129 base::DictionaryValue* constraint = &msc->mandatory.additional_properties;
130 constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab);
131 constraint->SetString(kMediaStreamSourceId, device_id);
132 }
133 }
134
55 } // namespace 135 } // namespace
56 136
57 // Whitelisted extensions that do not check for a browser action grant because 137 // Whitelisted extensions that do not check for a browser action grant because
58 // they provide API's. If there are additional extension ids that need 138 // they provide API's. If there are additional extension ids that need
59 // whitelisting and are *not* the Chromecast extension, add them to a new 139 // whitelisting and are *not* the Chromecast extension, add them to a new
60 // kWhitelist array. 140 // kWhitelist array.
61 // 141 //
62 // This list is also used by CastConfigDelegateChromeos to find official Cast 142 // This list is also used by CastConfigDelegateChromeos to find official Cast
63 // extensions. 143 // extensions.
64 const char* const kChromecastExtensionIds[] = { 144 const char* const kChromecastExtensionIds[] = {
65 "enhhojjnijigcajfphajepfemndkmdlo", // Dev 145 "enhhojjnijigcajfphajepfemndkmdlo", // Dev
66 "fmfcbgogabcbclcofgocippekhfcmgfj", // Staging 146 "fmfcbgogabcbclcofgocippekhfcmgfj", // Staging
67 "hfaagokkkhdbgiakmmlclaapfelnkoah", // Canary 147 "hfaagokkkhdbgiakmmlclaapfelnkoah", // Canary
68 "dliochdbjfkdbacpmhlcpmleaejidimm", // Google Cast Beta 148 "dliochdbjfkdbacpmhlcpmleaejidimm", // Google Cast Beta
69 "boadgeojelhgndaghljhdicfkmllpafd", // Google Cast Stable 149 "boadgeojelhgndaghljhdicfkmllpafd", // Google Cast Stable
70 "hlgmmjhlnlapooncikdpiiokdjcdpjme", // Test cast extension 150 "hlgmmjhlnlapooncikdpiiokdjcdpjme", // Test cast extension
71 }; 151 };
72 152
73 const char* const kMediaRouterExtensionIds[] = { 153 const char* const kMediaRouterExtensionIds[] = {
74 "fjhoaacokmgbjemoflkofnenfaiekifl", // Stable 154 "fjhoaacokmgbjemoflkofnenfaiekifl", // Stable
75 "ekpaaapppgpmolpcldedioblbkmijaca", // Beta 155 "ekpaaapppgpmolpcldedioblbkmijaca", // Beta
76 }; 156 };
77 157
78 bool TabCaptureCaptureFunction::RunSync() { 158 bool TabCaptureCaptureFunction::RunSync() {
79 scoped_ptr<api::tab_capture::Capture::Params> params = 159 scoped_ptr<api::tab_capture::Capture::Params> params =
80 TabCapture::Capture::Params::Create(*args_); 160 TabCapture::Capture::Params::Create(*args_);
81 EXTENSION_FUNCTION_VALIDATE(params.get()); 161 EXTENSION_FUNCTION_VALIDATE(params);
82 162
83 // Figure out the active WebContents and retrieve the needed ids. 163 // Figure out the active WebContents and retrieve the needed ids.
84 Browser* target_browser = chrome::FindAnyBrowser( 164 Browser* target_browser = chrome::FindAnyBrowser(
85 GetProfile(), include_incognito(), chrome::GetActiveDesktop()); 165 GetProfile(), include_incognito(), chrome::GetActiveDesktop());
86 if (!target_browser) { 166 if (!target_browser) {
87 error_ = kFindingTabError; 167 error_ = kFindingTabError;
88 return false; 168 return false;
89 } 169 }
90 170
91 content::WebContents* target_contents = 171 content::WebContents* target_contents =
(...skipping 13 matching lines...) Expand all
105 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 185 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
106 switches::kWhitelistedExtensionID) != extension_id && 186 switches::kWhitelistedExtensionID) != extension_id &&
107 !SimpleFeature::IsIdInArray(extension_id, kChromecastExtensionIds, 187 !SimpleFeature::IsIdInArray(extension_id, kChromecastExtensionIds,
108 arraysize(kChromecastExtensionIds)) && 188 arraysize(kChromecastExtensionIds)) &&
109 !SimpleFeature::IsIdInArray(extension_id, kMediaRouterExtensionIds, 189 !SimpleFeature::IsIdInArray(extension_id, kMediaRouterExtensionIds,
110 arraysize(kMediaRouterExtensionIds))) { 190 arraysize(kMediaRouterExtensionIds))) {
111 error_ = kGrantError; 191 error_ = kGrantError;
112 return false; 192 return false;
113 } 193 }
114 194
115 // Create a constraints vector. We will modify all the constraints in this 195 if (!OptionsSpecifyAudioOrVideo(params->options)) {
116 // vector to append our chrome specific constraints.
117 std::vector<MediaStreamConstraint*> constraints;
118 bool has_audio = params->options.audio.get() && *params->options.audio.get();
119 bool has_video = params->options.video.get() && *params->options.video.get();
120
121 if (!has_audio && !has_video) {
122 error_ = kNoAudioOrVideo; 196 error_ = kNoAudioOrVideo;
123 return false; 197 return false;
124 } 198 }
125 199
126 if (has_audio) {
127 if (!params->options.audio_constraints.get())
128 params->options.audio_constraints.reset(new MediaStreamConstraint);
129
130 constraints.push_back(params->options.audio_constraints.get());
131 }
132
133 bool enable_auto_throttling = false;
134 if (has_video) {
135 if (params->options.video_constraints.get()) {
136 // Check for the Tab Capture-specific video constraint for enabling
137 // automatic resolution/rate throttling mode in the capture pipeline. See
138 // implementation comments for content::WebContentsVideoCaptureDevice.
139 base::DictionaryValue& props =
140 params->options.video_constraints->mandatory.additional_properties;
141 if (!props.GetBooleanWithoutPathExpansion(
142 kEnableAutoThrottlingKey, &enable_auto_throttling)) {
143 enable_auto_throttling = false;
144 }
145 // Remove the key from the properties to avoid an "unrecognized
146 // constraint" error in the renderer.
147 props.RemoveWithoutPathExpansion(kEnableAutoThrottlingKey, nullptr);
148 } else {
149 params->options.video_constraints.reset(new MediaStreamConstraint);
150 }
151
152 constraints.push_back(params->options.video_constraints.get());
153 }
154
155 // Device id we use for Tab Capture.
156 content::RenderFrameHost* const main_frame = target_contents->GetMainFrame();
157 // TODO(miu): We should instead use a "randomly generated device ID" scheme,
158 // like that employed by the desktop capture API. http://crbug.com/163100
159 const std::string device_id = base::StringPrintf(
160 "web-contents-media-stream://%i:%i%s",
161 main_frame->GetProcess()->GetID(),
162 main_frame->GetRoutingID(),
163 enable_auto_throttling ? "?throttling=auto" : "");
164
165 // Append chrome specific tab constraints.
166 for (std::vector<MediaStreamConstraint*>::iterator it = constraints.begin();
167 it != constraints.end(); ++it) {
168 base::DictionaryValue* constraint = &(*it)->mandatory.additional_properties;
169 constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab);
170 constraint->SetString(kMediaStreamSourceId, device_id);
171 }
172
173 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile()); 200 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile());
174 if (!registry->AddRequest(target_contents, extension_id)) { 201 if (!registry->AddRequest(target_contents, extension_id, false)) {
202 // TODO(miu): Allow multiple consumers of single tab capture.
203 // http://crbug.com/535336
175 error_ = kCapturingSameTab; 204 error_ = kCapturingSameTab;
176 return false; 205 return false;
177 } 206 }
207 AddMediaStreamSourceConstraints(target_contents, &params->options);
178 208
179 // Copy the result from our modified input parameters. This will be 209 // At this point, everything is set up in the browser process. It's now up to
180 // intercepted by custom bindings which will build and send the special 210 // the custom JS bindings in the extension's render process to request a
181 // WebRTC user media request. 211 // MediaStream using navigator.webkitGetUserMedia(). The result dictionary,
212 // passed to SetResult() here, contains the extra "hidden options" that will
213 // allow the Chrome platform implementation for getUserMedia() to start the
214 // virtual audio/video capture devices and set up all the data flows. The
215 // custom JS bindings can be found here:
216 // chrome/renderer/resources/extensions/tab_capture_custom_bindings.js
182 base::DictionaryValue* result = new base::DictionaryValue(); 217 base::DictionaryValue* result = new base::DictionaryValue();
183 result->MergeDictionary(params->options.ToValue().get()); 218 result->MergeDictionary(params->options.ToValue().get());
184
185 SetResult(result); 219 SetResult(result);
186 return true; 220 return true;
187 } 221 }
188 222
189 bool TabCaptureGetCapturedTabsFunction::RunSync() { 223 bool TabCaptureGetCapturedTabsFunction::RunSync() {
190 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile()); 224 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile());
191 base::ListValue* const list = new base::ListValue(); 225 base::ListValue* const list = new base::ListValue();
192 if (registry) 226 if (registry)
193 registry->GetCapturedTabs(extension()->id(), list); 227 registry->GetCapturedTabs(extension()->id(), list);
194 SetResult(list); 228 SetResult(list);
195 return true; 229 return true;
196 } 230 }
197 231
232 bool TabCaptureCaptureOffscreenTabFunction::RunSync() {
233 scoped_ptr<TabCapture::CaptureOffscreenTab::Params> params =
234 TabCapture::CaptureOffscreenTab::Params::Create(*args_);
235 EXTENSION_FUNCTION_VALIDATE(params);
236
237 // Make sure the extension is whitelisted for using this API, or this is a
238 // Canary/Dev-channel build of Chrome.
239 const bool is_whitelisted_extension =
not at google - send to devlin 2015/09/30 01:32:11 A better way to whitelist is by adding a rule for
miu 2015/09/30 19:46:38 Added a TODO for this. The reason is that there i
240 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
241 switches::kWhitelistedExtensionID) == extension()->id() ||
242 SimpleFeature::IsIdInArray(extension()->id(), kChromecastExtensionIds,
243 arraysize(kChromecastExtensionIds)) ||
244 SimpleFeature::IsIdInArray(extension()->id(), kMediaRouterExtensionIds,
245 arraysize(kMediaRouterExtensionIds));
246 if (!is_whitelisted_extension) {
247 error_ = kNotWhitelistedForOffscreenTabApi;
248 return false;
249 }
250
251 const GURL start_url(params->start_url);
252 if (!IsAcceptableOffscreenTabUrl(start_url)) {
253 SetError(kInvalidStartUrl);
254 return false;
255 }
256
257 if (!OptionsSpecifyAudioOrVideo(params->options)) {
258 SetError(kNoAudioOrVideo);
259 return false;
260 }
261
262 content::WebContents* const extension_web_contents = GetSenderWebContents();
263 if (!extension_web_contents) {
264 SetError(kMissingExtensionPage);
265 return false;
266 }
267
268 OffscreenPresentation* const offscreen_tab =
269 OffscreenPresentationsOwner::Get(extension_web_contents)
270 ->StartPresentation(
271 start_url,
272 (is_whitelisted_extension && params->options.presentation_id) ?
273 *params->options.presentation_id : std::string(),
274 DetermineInitialSize(params->options));
275 if (!offscreen_tab) {
276 SetError(kTooManyOffscreenTabs);
277 return false;
278 }
279
280 if (!TabCaptureRegistry::Get(browser_context())->AddRequest(
281 offscreen_tab->web_contents(), extension()->id(), true)) {
282 // TODO(miu): Allow multiple consumers of single tab capture.
283 // http://crbug.com/535336
284 SetError(kCapturingSameOffscreenTab);
285 return false;
286 }
287 AddMediaStreamSourceConstraints(offscreen_tab->web_contents(),
288 &params->options);
289
290 // At this point, everything is set up in the browser process. It's now up to
291 // the custom JS bindings in the extension's render process to complete the
292 // request. See the comment at end of TabCaptureCaptureFunction::RunSync()
293 // for more details.
294 base::DictionaryValue* const result = new base::DictionaryValue();
295 result->MergeDictionary(params->options.ToValue().get());
296 SetResult(result);
297 return true;
298 }
299
300 // static
301 gfx::Size TabCaptureCaptureOffscreenTabFunction::DetermineInitialSize(
302 const TabCapture::CaptureOptions& options) {
303 static const int kDefaultWidth = 1280;
304 static const int kDefaultHeight = 720;
305
306 if (!options.video_constraints)
307 return gfx::Size(kDefaultWidth, kDefaultHeight);
308
309 gfx::Size min_size;
310 int width = -1;
311 int height = -1;
312 const base::DictionaryValue& mandatory_properties =
313 options.video_constraints->mandatory.additional_properties;
314 if (mandatory_properties.GetInteger("maxWidth", &width) && width >= 0 &&
315 mandatory_properties.GetInteger("maxHeight", &height) && height >= 0) {
316 return gfx::Size(width, height);
317 }
318 if (mandatory_properties.GetInteger("minWidth", &width) && width >= 0 &&
319 mandatory_properties.GetInteger("minHeight", &height) && height >= 0) {
320 min_size.SetSize(width, height);
321 }
322
323 // Use optional size constraints if no mandatory ones were provided.
324 if (options.video_constraints->optional) {
325 const base::DictionaryValue& optional_properties =
326 options.video_constraints->optional->additional_properties;
327 if (optional_properties.GetInteger("maxWidth", &width) && width >= 0 &&
328 optional_properties.GetInteger("maxHeight", &height) && height >= 0) {
329 if (min_size.IsEmpty()) {
330 return gfx::Size(width, height);
331 } else {
332 return gfx::Size(std::max(width, min_size.width()),
333 std::max(height, min_size.height()));
334 }
335 }
336 if (min_size.IsEmpty() &&
337 optional_properties.GetInteger("minWidth", &width) && width >= 0 &&
338 optional_properties.GetInteger("minHeight", &height) && height >= 0) {
339 min_size.SetSize(width, height);
340 }
341 }
342
343 // No maximum size was provided, so just return the default size bounded by
344 // the minimum size.
345 return gfx::Size(std::max(kDefaultWidth, min_size.width()),
346 std::max(kDefaultHeight, min_size.height()));
347 }
348
198 } // namespace extensions 349 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698