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

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: 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 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 440c14fcc7168645c060e9b01107b36d63a8f39f..0670e7da0e81219def496d9a65d8637e2bf1f947 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,19 @@ const char kGrantError[] =
"Extension has not been invoked for the current page (see activeTab "
"permission). Chrome pages cannot be captured.";
+const char kCapturePresentationApiNotEnabled[] =
+ "Experimental capturePresentation API is not enabled.";
+const char kMissingTabCapturePermission[] =
+ "Extension manifest did not specify tabCapture permission.";
+const char kInvalidPresentationUrl[] =
+ "Invalid/Missing/Malformatted starting URL for presentation.";
+const char kMissingExtensionPage[] =
+ "Error finding extension page.";
+const char kTooManyPresentations[] =
+ "Extension has already started too many presentations.";
+const char kCapturingSamePresentation[] =
+ "Cannot capture the same presentation more than once.";
+
// Keys/values for media stream constraints.
const char kMediaStreamSource[] = "chromeMediaSource";
const char kMediaStreamSourceId[] = "chromeMediaSourceId";
@@ -52,6 +69,115 @@ 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 IsAcceptablePresentationUrl(const GURL& url) {
+ return url.is_valid() && (url.SchemeIsHTTPOrHTTPS() || url.SchemeIs("data"));
+}
+
+void AddMediaStreamSourceOptions(TabCapture::CaptureOptions* options,
+ content::WebContents* target_contents) {
+ DCHECK(options);
+ DCHECK(target_contents);
+
+ // Create a constraints vector. We will modify all the constraints in this
+ // vector to append our chrome specific constraints.
+ std::vector<MediaStreamConstraint*> constraints;
+ 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.
+ if (!options->audio_constraints.get())
+ options->audio_constraints.reset(new MediaStreamConstraint);
+ constraints.push_back(options->audio_constraints.get());
+ }
+ bool enable_auto_throttling = false;
+ if (options->video.get() && *options->video.get()) {
+ if (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 =
+ 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.push_back(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 (MediaStreamConstraint* msc : constraints) {
+ base::DictionaryValue* constraint = &msc->mandatory.additional_properties;
+ constraint->SetString(kMediaStreamSource, kMediaStreamSourceTab);
+ constraint->SetString(kMediaStreamSourceId, device_id);
+ }
+}
+
+// Examine the minimum and maximum size constraints in |options| to determine
+// the page size for an off-screen presentation.
+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
+ 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.get()) {
+ 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
// Whitelisted extensions that do not check for a browser action grant because
@@ -102,76 +228,28 @@ 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)) {
error_ = kCapturingSameTab;
return false;
}
+ AddMediaStreamSourceOptions(&params->options, target_contents);
- // 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 renderer 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;
}
@@ -185,4 +263,69 @@ bool TabCaptureGetCapturedTabsFunction::RunSync() {
return true;
}
+bool TabCaptureCapturePresentationFunction::RunSync() {
+ scoped_ptr<TabCapture::CapturePresentation::Params> params =
+ TabCapture::CapturePresentation::Params::Create(*args_);
+ EXTENSION_FUNCTION_VALIDATE(params.get());
+
+ // One or both of two possible switches will enable this API for use.
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ ::switches::kEnableCapturePresentationApi) &&
+ !::switches::MediaRouterEnabled()) {
+ SetError(kCapturePresentationApiNotEnabled);
+ return false;
+ }
+
+ if (!extension()->permissions_data()->HasAPIPermission(
+ APIPermission::kTabCapture) &&
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ switches::kWhitelistedExtensionID) != extension()->id() &&
+ !SimpleFeature::IsIdInArray(extension()->id(), kChromecastExtensionIds,
+ arraysize(kChromecastExtensionIds))) {
+ error_ = kMissingTabCapturePermission;
+ return false;
+ }
+
+ const GURL start_url(params->presentation_url);
+ if (!IsAcceptablePresentationUrl(start_url)) {
+ SetError(kInvalidPresentationUrl);
+ 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 presentation =
+ OffscreenPresentationsOwner::Get(extension_web_contents)
+ ->FindOrStartPresentation(start_url,
+ params->presentation_id,
+ DeterminePresentationSize(params->options));
+ if (!presentation) {
+ SetError(kTooManyPresentations);
+ return false;
+ }
+ if (!TabCaptureRegistry::Get(browser_context())->AddRequest(
+ presentation->web_contents(), extension()->id(), true)) {
+ SetError(kCapturingSamePresentation);
+ return false;
+ }
+ AddMediaStreamSourceOptions(&params->options, presentation->web_contents());
+
+ // At this point, everything is set up in the browser process. It's now up to
+ // the custom JS bindings in the renderer 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;
+}
+
} // namespace extensions

Powered by Google App Engine
This is Rietveld 408576698