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

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: Created 5 years, 6 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 kCapturePresentationApiNotEnabled[] =
51 "Experimental capturePresentation API is not enabled.";
52 const char kMissingTabCapturePermission[] =
53 "Extension manifest did not specify tabCapture permission.";
54 const char kInvalidPresentationUrl[] =
55 "Invalid/Missing/Malformatted starting URL for presentation.";
56 const char kMissingExtensionPage[] =
57 "Error finding extension page.";
58 const char kTooManyPresentations[] =
59 "Extension has already started too many presentations.";
60 const char kCapturingSamePresentation[] =
61 "Cannot capture the same presentation more than once.";
62
46 // Keys/values for media stream constraints. 63 // Keys/values for media stream constraints.
47 const char kMediaStreamSource[] = "chromeMediaSource"; 64 const char kMediaStreamSource[] = "chromeMediaSource";
48 const char kMediaStreamSourceId[] = "chromeMediaSourceId"; 65 const char kMediaStreamSourceId[] = "chromeMediaSourceId";
49 const char kMediaStreamSourceTab[] = "tab"; 66 const char kMediaStreamSourceTab[] = "tab";
50 67
51 // Tab Capture-specific video constraint to enable automatic resolution/rate 68 // Tab Capture-specific video constraint to enable automatic resolution/rate
52 // throttling mode in the capture pipeline. 69 // throttling mode in the capture pipeline.
53 const char kEnableAutoThrottlingKey[] = "enableAutoThrottling"; 70 const char kEnableAutoThrottlingKey[] = "enableAutoThrottling";
54 71
72 bool OptionsSpecifyAudioOrVideo(const TabCapture::CaptureOptions& options) {
73 return (options.audio && *options.audio) || (options.video && *options.video);
74 }
75
76 bool IsAcceptablePresentationUrl(const GURL& url) {
77 return url.is_valid() && (url.SchemeIsHTTPOrHTTPS() || url.SchemeIs("data"));
78 }
79
80 void AddMediaStreamSourceOptions(TabCapture::CaptureOptions* options,
81 content::WebContents* target_contents) {
82 DCHECK(options);
83 DCHECK(target_contents);
84
85 // Create a constraints vector. We will modify all the constraints in this
86 // vector to append our chrome specific constraints.
87 std::vector<MediaStreamConstraint*> constraints;
88 if (options->audio.get() && *options->audio.get()) {
Kevin M 2015/06/29 22:17:02 .get() isn't needed here or elsewhere in the funct
miu 2015/06/30 04:09:59 Done. In some places, it was still required.
89 if (!options->audio_constraints.get())
90 options->audio_constraints.reset(new MediaStreamConstraint);
91 constraints.push_back(options->audio_constraints.get());
92 }
93 bool enable_auto_throttling = false;
94 if (options->video.get() && *options->video.get()) {
95 if (options->video_constraints.get()) {
96 // Check for the Tab Capture-specific video constraint for enabling
97 // automatic resolution/rate throttling mode in the capture pipeline. See
98 // implementation comments for content::WebContentsVideoCaptureDevice.
99 base::DictionaryValue& props =
100 options->video_constraints->mandatory.additional_properties;
101 if (!props.GetBooleanWithoutPathExpansion(
102 kEnableAutoThrottlingKey, &enable_auto_throttling)) {
103 enable_auto_throttling = false;
104 }
105 // Remove the key from the properties to avoid an "unrecognized
106 // constraint" error in the renderer.
107 props.RemoveWithoutPathExpansion(kEnableAutoThrottlingKey, nullptr);
108 } else {
109 options->video_constraints.reset(new MediaStreamConstraint);
110 }
111 constraints.push_back(options->video_constraints.get());
112 }
113
114 // Device id we use for Tab Capture.
115 content::RenderFrameHost* const main_frame = target_contents->GetMainFrame();
116 // TODO(miu): We should instead use a "randomly generated device ID" scheme,
117 // like that employed by the desktop capture API. http://crbug.com/163100
118 const std::string device_id = base::StringPrintf(
119 "web-contents-media-stream://%i:%i%s",
120 main_frame->GetProcess()->GetID(),
121 main_frame->GetRoutingID(),
122 enable_auto_throttling ? "?throttling=auto" : "");
123
124 // Append chrome specific tab constraints.
125 for (MediaStreamConstraint* msc : constraints) {
126 base::DictionaryValue* constraint = &msc->mandatory.additional_properties;
127 constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab);
128 constraint->SetString(kMediaStreamSourceId, device_id);
129 }
130 }
131
132 // Examine the minimum and maximum size constraints in |options| to determine
133 // the page size for an off-screen presentation.
134 gfx::Size DeterminePresentationSize(const TabCapture::CaptureOptions& options) {
Kevin M 2015/06/29 22:17:02 Could this be unit tested to ensure that all the c
miu 2015/06/30 04:09:59 Done. Made this function a static class member to
135 static const int kDefaultWidth = 1280;
136 static const int kDefaultHeight = 720;
137
138 if (!options.video_constraints)
139 return gfx::Size(kDefaultWidth, kDefaultHeight);
140
141 gfx::Size min_size;
142 int width = -1;
143 int height = -1;
144 const base::DictionaryValue& mandatory_properties =
145 options.video_constraints->mandatory.additional_properties;
146 if (mandatory_properties.GetInteger("maxWidth", &width) && width >= 0 &&
147 mandatory_properties.GetInteger("maxHeight", &height) && height >= 0) {
148 return gfx::Size(width, height);
149 }
150 if (mandatory_properties.GetInteger("minWidth", &width) && width >= 0 &&
151 mandatory_properties.GetInteger("minHeight", &height) && height >= 0) {
152 min_size.SetSize(width, height);
153 }
154
155 // Use optional size constraints if no mandatory ones were provided.
156 if (options.video_constraints->optional.get()) {
157 const base::DictionaryValue& optional_properties =
158 options.video_constraints->optional->additional_properties;
159 if (optional_properties.GetInteger("maxWidth", &width) && width >= 0 &&
160 optional_properties.GetInteger("maxHeight", &height) && height >= 0) {
161 if (min_size.IsEmpty()) {
162 return gfx::Size(width, height);
163 } else {
164 return gfx::Size(std::max(width, min_size.width()),
165 std::max(height, min_size.height()));
166 }
167 }
168 if (min_size.IsEmpty() &&
169 optional_properties.GetInteger("minWidth", &width) && width >= 0 &&
170 optional_properties.GetInteger("minHeight", &height) && height >= 0) {
171 min_size.SetSize(width, height);
172 }
173 }
174
175 // No maximum size was provided, so just return the default size bounded by
176 // the minimum size.
177 return gfx::Size(std::max(kDefaultWidth, min_size.width()),
178 std::max(kDefaultHeight, min_size.height()));
179 }
180
55 } // namespace 181 } // namespace
56 182
57 // Whitelisted extensions that do not check for a browser action grant because 183 // 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 184 // 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 185 // whitelisting and are *not* the Chromecast extension, add them to a new
60 // kWhitelist array. 186 // kWhitelist array.
61 const char* const kChromecastExtensionIds[] = { 187 const char* const kChromecastExtensionIds[] = {
62 "enhhojjnijigcajfphajepfemndkmdlo", // Dev 188 "enhhojjnijigcajfphajepfemndkmdlo", // Dev
63 "pkedcjkdefgpdelpbcmbmeomcjbeemfm", // Dogfood 189 "pkedcjkdefgpdelpbcmbmeomcjbeemfm", // Dogfood
64 "fmfcbgogabcbclcofgocippekhfcmgfj", // Staging 190 "fmfcbgogabcbclcofgocippekhfcmgfj", // Staging
(...skipping 30 matching lines...) Expand all
95 SessionTabHelper::IdForTab(target_contents), 221 SessionTabHelper::IdForTab(target_contents),
96 APIPermission::kTabCaptureForTab) && 222 APIPermission::kTabCaptureForTab) &&
97 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII( 223 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
98 switches::kWhitelistedExtensionID) != extension_id && 224 switches::kWhitelistedExtensionID) != extension_id &&
99 !SimpleFeature::IsIdInArray(extension_id, kChromecastExtensionIds, 225 !SimpleFeature::IsIdInArray(extension_id, kChromecastExtensionIds,
100 arraysize(kChromecastExtensionIds))) { 226 arraysize(kChromecastExtensionIds))) {
101 error_ = kGrantError; 227 error_ = kGrantError;
102 return false; 228 return false;
103 } 229 }
104 230
105 // Create a constraints vector. We will modify all the constraints in this 231 if (!OptionsSpecifyAudioOrVideo(params->options)) {
106 // vector to append our chrome specific constraints.
107 std::vector<MediaStreamConstraint*> constraints;
108 bool has_audio = params->options.audio.get() && *params->options.audio.get();
109 bool has_video = params->options.video.get() && *params->options.video.get();
110
111 if (!has_audio && !has_video) {
112 error_ = kNoAudioOrVideo; 232 error_ = kNoAudioOrVideo;
113 return false; 233 return false;
114 } 234 }
115 235
116 if (has_audio) {
117 if (!params->options.audio_constraints.get())
118 params->options.audio_constraints.reset(new MediaStreamConstraint);
119
120 constraints.push_back(params->options.audio_constraints.get());
121 }
122
123 bool enable_auto_throttling = false;
124 if (has_video) {
125 if (params->options.video_constraints.get()) {
126 // Check for the Tab Capture-specific video constraint for enabling
127 // automatic resolution/rate throttling mode in the capture pipeline. See
128 // implementation comments for content::WebContentsVideoCaptureDevice.
129 base::DictionaryValue& props =
130 params->options.video_constraints->mandatory.additional_properties;
131 if (!props.GetBooleanWithoutPathExpansion(
132 kEnableAutoThrottlingKey, &enable_auto_throttling)) {
133 enable_auto_throttling = false;
134 }
135 // Remove the key from the properties to avoid an "unrecognized
136 // constraint" error in the renderer.
137 props.RemoveWithoutPathExpansion(kEnableAutoThrottlingKey, nullptr);
138 } else {
139 params->options.video_constraints.reset(new MediaStreamConstraint);
140 }
141
142 constraints.push_back(params->options.video_constraints.get());
143 }
144
145 // Device id we use for Tab Capture.
146 content::RenderFrameHost* const main_frame = target_contents->GetMainFrame();
147 // TODO(miu): We should instead use a "randomly generated device ID" scheme,
148 // like that employed by the desktop capture API. http://crbug.com/163100
149 const std::string device_id = base::StringPrintf(
150 "web-contents-media-stream://%i:%i%s",
151 main_frame->GetProcess()->GetID(),
152 main_frame->GetRoutingID(),
153 enable_auto_throttling ? "?throttling=auto" : "");
154
155 // Append chrome specific tab constraints.
156 for (std::vector<MediaStreamConstraint*>::iterator it = constraints.begin();
157 it != constraints.end(); ++it) {
158 base::DictionaryValue* constraint = &(*it)->mandatory.additional_properties;
159 constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab);
160 constraint->SetString(kMediaStreamSourceId, device_id);
161 }
162
163 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile()); 236 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile());
164 if (!registry->AddRequest(target_contents, extension_id)) { 237 if (!registry->AddRequest(target_contents, extension_id, false)) {
165 error_ = kCapturingSameTab; 238 error_ = kCapturingSameTab;
166 return false; 239 return false;
167 } 240 }
241 AddMediaStreamSourceOptions(&params->options, target_contents);
168 242
169 // Copy the result from our modified input parameters. This will be 243 // At this point, everything is set up in the browser process. It's now up to
170 // intercepted by custom bindings which will build and send the special 244 // the custom JS bindings in the renderer to request a MediaStream using
171 // WebRTC user media request. 245 // navigator.webkitGetUserMedia(). The result dictionary, passed to
246 // SetResult() here, contains the extra "hidden options" that will allow the
247 // Chrome platform implementation for getUserMedia() to start the virtual
248 // audio/video capture devices and set up all the data flows. The custom JS
249 // bindings can be found here:
250 // chrome/renderer/resources/extensions/tab_capture_custom_bindings.js
172 base::DictionaryValue* result = new base::DictionaryValue(); 251 base::DictionaryValue* result = new base::DictionaryValue();
173 result->MergeDictionary(params->options.ToValue().get()); 252 result->MergeDictionary(params->options.ToValue().get());
174
175 SetResult(result); 253 SetResult(result);
176 return true; 254 return true;
177 } 255 }
178 256
179 bool TabCaptureGetCapturedTabsFunction::RunSync() { 257 bool TabCaptureGetCapturedTabsFunction::RunSync() {
180 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile()); 258 TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile());
181 base::ListValue* const list = new base::ListValue(); 259 base::ListValue* const list = new base::ListValue();
182 if (registry) 260 if (registry)
183 registry->GetCapturedTabs(extension()->id(), list); 261 registry->GetCapturedTabs(extension()->id(), list);
184 SetResult(list); 262 SetResult(list);
185 return true; 263 return true;
186 } 264 }
187 265
266 bool TabCaptureCapturePresentationFunction::RunSync() {
267 scoped_ptr<TabCapture::CapturePresentation::Params> params =
268 TabCapture::CapturePresentation::Params::Create(*args_);
269 EXTENSION_FUNCTION_VALIDATE(params.get());
270
271 // One or both of two possible switches will enable this API for use.
272 if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
273 ::switches::kEnableCapturePresentationApi) &&
274 !::switches::MediaRouterEnabled()) {
275 SetError(kCapturePresentationApiNotEnabled);
276 return false;
277 }
278
279 if (!extension()->permissions_data()->HasAPIPermission(
280 APIPermission::kTabCapture) &&
281 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
282 switches::kWhitelistedExtensionID) != extension()->id() &&
283 !SimpleFeature::IsIdInArray(extension()->id(), kChromecastExtensionIds,
284 arraysize(kChromecastExtensionIds))) {
285 error_ = kMissingTabCapturePermission;
286 return false;
287 }
288
289 const GURL start_url(params->presentation_url);
290 if (!IsAcceptablePresentationUrl(start_url)) {
291 SetError(kInvalidPresentationUrl);
292 return false;
293 }
294
295 if (!OptionsSpecifyAudioOrVideo(params->options)) {
296 SetError(kNoAudioOrVideo);
297 return false;
298 }
299
300 content::WebContents* const extension_web_contents = GetSenderWebContents();
301 if (!extension_web_contents) {
302 SetError(kMissingExtensionPage);
303 return false;
304 }
305
306 OffscreenPresentation* const presentation =
307 OffscreenPresentationsOwner::Get(extension_web_contents)
308 ->FindOrStartPresentation(start_url,
309 params->presentation_id,
310 DeterminePresentationSize(params->options));
311 if (!presentation) {
312 SetError(kTooManyPresentations);
313 return false;
314 }
315 if (!TabCaptureRegistry::Get(browser_context())->AddRequest(
316 presentation->web_contents(), extension()->id(), true)) {
317 SetError(kCapturingSamePresentation);
318 return false;
319 }
320 AddMediaStreamSourceOptions(&params->options, presentation->web_contents());
321
322 // At this point, everything is set up in the browser process. It's now up to
323 // the custom JS bindings in the renderer to complete the request. See the
324 // comment at end of TabCaptureCaptureFunction::RunSync() for more details.
325 base::DictionaryValue* const result = new base::DictionaryValue();
326 result->MergeDictionary(params->options.ToValue().get());
327 SetResult(result);
328 return true;
329 }
330
188 } // namespace extensions 331 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698