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

Unified 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: todo for whitelist location, rewrote max_offscreen_tabs.js, and logic fix in tab_capture_registry.cc Created 5 years, 3 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/extensions/api/tab_capture/tab_capture_api.cc
diff --git a/chrome/browser/extensions/api/tab_capture/tab_capture_api.cc b/chrome/browser/extensions/api/tab_capture/tab_capture_api.cc
index 57605300a2b609207d9a78f950a03facd1e15ed9..e4b19c5d774d0ffe2200c2dca754f97d493375f1 100644
--- a/chrome/browser/extensions/api/tab_capture/tab_capture_api.cc
+++ b/chrome/browser/extensions/api/tab_capture/tab_capture_api.cc
@@ -6,6 +6,7 @@
#include "chrome/browser/extensions/api/tab_capture/tab_capture_api.h"
+#include <algorithm>
#include <set>
#include <string>
#include <vector>
@@ -13,6 +14,7 @@
#include "base/command_line.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
+#include "chrome/browser/extensions/api/tab_capture/offscreen_presentation.h"
#include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
#include "chrome/browser/extensions/extension_renderer_state.h"
#include "chrome/browser/profiles/profile.h"
@@ -20,8 +22,10 @@
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_switches.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
+#include "content/public/browser/web_contents.h"
#include "extensions/common/features/feature.h"
#include "extensions/common/features/feature_provider.h"
#include "extensions/common/features/simple_feature.h"
@@ -43,6 +47,18 @@ const char kGrantError[] =
"Extension has not been invoked for the current page (see activeTab "
"permission). Chrome pages cannot be captured.";
+const char kNotWhitelistedForOffscreenTabApi[] =
+ "Extension is not whitelisted for use of the unstable, in-development "
+ "chrome.tabCapture.captureOffscreenTab API.";
+const char kInvalidStartUrl[] =
+ "Invalid/Missing/Malformatted starting URL for off-screen tab.";
+const char kMissingExtensionPage[] =
+ "tabCapture API was not invoked from an extension background page.";
+const char kTooManyOffscreenTabs[] =
+ "Extension has already started too many off-screen tabs.";
+const char kCapturingSameOffscreenTab[] =
+ "Cannot capture the same off-screen tab more than once.";
+
// Keys/values for media stream constraints.
const char kMediaStreamSource[] = "chromeMediaSource";
const char kMediaStreamSourceId[] = "chromeMediaSourceId";
@@ -52,6 +68,70 @@ const char kMediaStreamSourceTab[] = "tab";
// throttling mode in the capture pipeline.
const char kEnableAutoThrottlingKey[] = "enableAutoThrottling";
+bool OptionsSpecifyAudioOrVideo(const TabCapture::CaptureOptions& options) {
+ return (options.audio && *options.audio) || (options.video && *options.video);
+}
+
+bool IsAcceptableOffscreenTabUrl(const GURL& url) {
+ return url.is_valid() && (url.SchemeIsHTTPOrHTTPS() || url.SchemeIs("data"));
+}
+
+// Add Chrome-specific source identifiers to the MediaStreamConstraints objects
+// in |options| to provide references to the |target_contents| to be captured.
+void AddMediaStreamSourceConstraints(content::WebContents* target_contents,
+ TabCapture::CaptureOptions* options) {
+ DCHECK(options);
+ DCHECK(target_contents);
+
+ MediaStreamConstraint* constraints_to_modify[2] = { nullptr, nullptr };
+
+ if (options->audio && *options->audio) {
+ if (!options->audio_constraints)
+ options->audio_constraints.reset(new MediaStreamConstraint);
+ constraints_to_modify[0] = options->audio_constraints.get();
+ }
+
+ bool enable_auto_throttling = false;
+ if (options->video && *options->video) {
+ if (options->video_constraints) {
+ // Check for the Tab Capture-specific video constraint for enabling
+ // automatic resolution/rate throttling mode in the capture pipeline. See
+ // implementation comments for content::WebContentsVideoCaptureDevice.
+ base::DictionaryValue& props =
+ options->video_constraints->mandatory.additional_properties;
+ if (!props.GetBooleanWithoutPathExpansion(
+ kEnableAutoThrottlingKey, &enable_auto_throttling)) {
+ enable_auto_throttling = false;
+ }
+ // Remove the key from the properties to avoid an "unrecognized
+ // constraint" error in the renderer.
+ props.RemoveWithoutPathExpansion(kEnableAutoThrottlingKey, nullptr);
+ } else {
+ options->video_constraints.reset(new MediaStreamConstraint);
+ }
+ constraints_to_modify[1] = options->video_constraints.get();
+ }
+
+ // Format the device ID that references the target tab.
+ content::RenderFrameHost* const main_frame = target_contents->GetMainFrame();
+ // TODO(miu): We should instead use a "randomly generated device ID" scheme,
+ // like that employed by the desktop capture API. http://crbug.com/163100
+ const std::string device_id = base::StringPrintf(
+ "web-contents-media-stream://%i:%i%s",
+ main_frame->GetProcess()->GetID(),
+ main_frame->GetRoutingID(),
+ enable_auto_throttling ? "?throttling=auto" : "");
+
+ // Append chrome specific tab constraints.
+ for (MediaStreamConstraint* msc : constraints_to_modify) {
+ if (!msc)
+ continue;
+ base::DictionaryValue* constraint = &msc->mandatory.additional_properties;
+ constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab);
+ constraint->SetString(kMediaStreamSourceId, device_id);
+ }
+}
+
} // namespace
// Whitelisted extensions that do not check for a browser action grant because
@@ -78,7 +158,7 @@ const char* const kMediaRouterExtensionIds[] = {
bool TabCaptureCaptureFunction::RunSync() {
scoped_ptr<api::tab_capture::Capture::Params> params =
TabCapture::Capture::Params::Create(*args_);
- EXTENSION_FUNCTION_VALIDATE(params.get());
+ EXTENSION_FUNCTION_VALIDATE(params);
// Figure out the active WebContents and retrieve the needed ids.
Browser* target_browser = chrome::FindAnyBrowser(
@@ -112,76 +192,30 @@ bool TabCaptureCaptureFunction::RunSync() {
return false;
}
- // Create a constraints vector. We will modify all the constraints in this
- // vector to append our chrome specific constraints.
- std::vector<MediaStreamConstraint*> constraints;
- bool has_audio = params->options.audio.get() && *params->options.audio.get();
- bool has_video = params->options.video.get() && *params->options.video.get();
-
- if (!has_audio && !has_video) {
+ if (!OptionsSpecifyAudioOrVideo(params->options)) {
error_ = kNoAudioOrVideo;
return false;
}
- if (has_audio) {
- if (!params->options.audio_constraints.get())
- params->options.audio_constraints.reset(new MediaStreamConstraint);
-
- constraints.push_back(params->options.audio_constraints.get());
- }
-
- bool enable_auto_throttling = false;
- if (has_video) {
- if (params->options.video_constraints.get()) {
- // Check for the Tab Capture-specific video constraint for enabling
- // automatic resolution/rate throttling mode in the capture pipeline. See
- // implementation comments for content::WebContentsVideoCaptureDevice.
- base::DictionaryValue& props =
- params->options.video_constraints->mandatory.additional_properties;
- if (!props.GetBooleanWithoutPathExpansion(
- kEnableAutoThrottlingKey, &enable_auto_throttling)) {
- enable_auto_throttling = false;
- }
- // Remove the key from the properties to avoid an "unrecognized
- // constraint" error in the renderer.
- props.RemoveWithoutPathExpansion(kEnableAutoThrottlingKey, nullptr);
- } else {
- params->options.video_constraints.reset(new MediaStreamConstraint);
- }
-
- constraints.push_back(params->options.video_constraints.get());
- }
-
- // Device id we use for Tab Capture.
- content::RenderFrameHost* const main_frame = target_contents->GetMainFrame();
- // TODO(miu): We should instead use a "randomly generated device ID" scheme,
- // like that employed by the desktop capture API. http://crbug.com/163100
- const std::string device_id = base::StringPrintf(
- "web-contents-media-stream://%i:%i%s",
- main_frame->GetProcess()->GetID(),
- main_frame->GetRoutingID(),
- enable_auto_throttling ? "?throttling=auto" : "");
-
- // Append chrome specific tab constraints.
- for (std::vector<MediaStreamConstraint*>::iterator it = constraints.begin();
- it != constraints.end(); ++it) {
- base::DictionaryValue* constraint = &(*it)->mandatory.additional_properties;
- constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab);
- constraint->SetString(kMediaStreamSourceId, device_id);
- }
-
TabCaptureRegistry* registry = TabCaptureRegistry::Get(GetProfile());
- if (!registry->AddRequest(target_contents, extension_id)) {
+ if (!registry->AddRequest(target_contents, extension_id, false)) {
+ // TODO(miu): Allow multiple consumers of single tab capture.
+ // http://crbug.com/535336
error_ = kCapturingSameTab;
return false;
}
+ AddMediaStreamSourceConstraints(target_contents, &params->options);
- // Copy the result from our modified input parameters. This will be
- // intercepted by custom bindings which will build and send the special
- // WebRTC user media request.
+ // At this point, everything is set up in the browser process. It's now up to
+ // the custom JS bindings in the extension's render process to request a
+ // MediaStream using navigator.webkitGetUserMedia(). The result dictionary,
+ // passed to SetResult() here, contains the extra "hidden options" that will
+ // allow the Chrome platform implementation for getUserMedia() to start the
+ // virtual audio/video capture devices and set up all the data flows. The
+ // custom JS bindings can be found here:
+ // chrome/renderer/resources/extensions/tab_capture_custom_bindings.js
base::DictionaryValue* result = new base::DictionaryValue();
result->MergeDictionary(params->options.ToValue().get());
-
SetResult(result);
return true;
}
@@ -195,4 +229,124 @@ bool TabCaptureGetCapturedTabsFunction::RunSync() {
return true;
}
+bool TabCaptureCaptureOffscreenTabFunction::RunSync() {
+ scoped_ptr<TabCapture::CaptureOffscreenTab::Params> params =
+ TabCapture::CaptureOffscreenTab::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params);
+
+ // Make sure the extension is whitelisted for using this API, or this is a
+ // Canary/Dev-channel build of Chrome.
+ //
+ // TODO(miu): Use _api_features.json and extensions::Feature library instead.
+ // http://crbug.com/537732
not at google - send to devlin 2015/10/01 00:09:14 I don't understand your comment. This is new code?
miu 2015/10/01 22:34:46 The whitelist arrays themselves (kChromecastExtens
not at google - send to devlin 2015/10/02 01:17:25 I mean to just duplicate the IDs in the feature fi
miu 2015/10/02 18:58:21 Oh! I didn't realize they had been duplicated the
+ const bool is_whitelisted_extension =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kWhitelistedExtensionID) == extension()->id() ||
+ SimpleFeature::IsIdInArray(extension()->id(), kChromecastExtensionIds,
+ arraysize(kChromecastExtensionIds)) ||
+ SimpleFeature::IsIdInArray(extension()->id(), kMediaRouterExtensionIds,
+ arraysize(kMediaRouterExtensionIds));
+ if (!is_whitelisted_extension) {
+ error_ = kNotWhitelistedForOffscreenTabApi;
+ return false;
+ }
+
+ const GURL start_url(params->start_url);
+ if (!IsAcceptableOffscreenTabUrl(start_url)) {
+ SetError(kInvalidStartUrl);
+ return false;
+ }
+
+ if (!OptionsSpecifyAudioOrVideo(params->options)) {
+ SetError(kNoAudioOrVideo);
+ return false;
+ }
+
+ content::WebContents* const extension_web_contents = GetSenderWebContents();
+ if (!extension_web_contents) {
+ SetError(kMissingExtensionPage);
+ return false;
+ }
+
+ OffscreenPresentation* const offscreen_tab =
+ OffscreenPresentationsOwner::Get(extension_web_contents)
+ ->StartPresentation(
+ start_url,
+ (is_whitelisted_extension && params->options.presentation_id) ?
+ *params->options.presentation_id : std::string(),
not at google - send to devlin 2015/10/01 00:09:14 Huh, guess I didn't save the comment. It was: - W
miu 2015/10/01 22:34:46 Two ways to answer this: 1. It means the off-scre
+ DetermineInitialSize(params->options));
+ if (!offscreen_tab) {
+ SetError(kTooManyOffscreenTabs);
+ return false;
+ }
+
+ if (!TabCaptureRegistry::Get(browser_context())->AddRequest(
+ offscreen_tab->web_contents(), extension()->id(), true)) {
+ // TODO(miu): Allow multiple consumers of single tab capture.
+ // http://crbug.com/535336
+ SetError(kCapturingSameOffscreenTab);
+ return false;
+ }
+ AddMediaStreamSourceConstraints(offscreen_tab->web_contents(),
+ &params->options);
+
+ // At this point, everything is set up in the browser process. It's now up to
+ // the custom JS bindings in the extension's render process to complete the
+ // request. See the comment at end of TabCaptureCaptureFunction::RunSync()
+ // for more details.
+ base::DictionaryValue* const result = new base::DictionaryValue();
+ result->MergeDictionary(params->options.ToValue().get());
+ SetResult(result);
+ return true;
+}
+
+// static
+gfx::Size TabCaptureCaptureOffscreenTabFunction::DetermineInitialSize(
+ const TabCapture::CaptureOptions& options) {
+ static const int kDefaultWidth = 1280;
+ static const int kDefaultHeight = 720;
+
+ if (!options.video_constraints)
+ return gfx::Size(kDefaultWidth, kDefaultHeight);
+
+ gfx::Size min_size;
+ int width = -1;
+ int height = -1;
+ const base::DictionaryValue& mandatory_properties =
+ options.video_constraints->mandatory.additional_properties;
+ if (mandatory_properties.GetInteger("maxWidth", &width) && width >= 0 &&
+ mandatory_properties.GetInteger("maxHeight", &height) && height >= 0) {
+ return gfx::Size(width, height);
+ }
+ if (mandatory_properties.GetInteger("minWidth", &width) && width >= 0 &&
+ mandatory_properties.GetInteger("minHeight", &height) && height >= 0) {
+ min_size.SetSize(width, height);
+ }
+
+ // Use optional size constraints if no mandatory ones were provided.
+ if (options.video_constraints->optional) {
+ const base::DictionaryValue& optional_properties =
+ options.video_constraints->optional->additional_properties;
+ if (optional_properties.GetInteger("maxWidth", &width) && width >= 0 &&
+ optional_properties.GetInteger("maxHeight", &height) && height >= 0) {
+ if (min_size.IsEmpty()) {
+ return gfx::Size(width, height);
+ } else {
+ return gfx::Size(std::max(width, min_size.width()),
+ std::max(height, min_size.height()));
+ }
+ }
+ if (min_size.IsEmpty() &&
+ optional_properties.GetInteger("minWidth", &width) && width >= 0 &&
+ optional_properties.GetInteger("minHeight", &height) && height >= 0) {
+ min_size.SetSize(width, height);
+ }
+ }
+
+ // No maximum size was provided, so just return the default size bounded by
+ // the minimum size.
+ return gfx::Size(std::max(kDefaultWidth, min_size.width()),
+ std::max(kDefaultHeight, min_size.height()));
+}
+
} // namespace extensions

Powered by Google App Engine
This is Rietveld 408576698