| 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..2e76e2afb91a20ff6adfcb5497a1d473f02c776e 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,7 +47,17 @@ const char kGrantError[] =
|
| "Extension has not been invoked for the current page (see activeTab "
|
| "permission). Chrome pages cannot be captured.";
|
|
|
| -// Keys/values for media stream constraints.
|
| +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 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 passed to renderer-side JS bindings.
|
| const char kMediaStreamSource[] = "chromeMediaSource";
|
| const char kMediaStreamSourceId[] = "chromeMediaSourceId";
|
| const char kMediaStreamSourceTab[] = "tab";
|
| @@ -52,6 +66,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 +156,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 +190,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, ¶ms->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 +227,120 @@ 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, regardless of
|
| + // Chrome channel.
|
| + //
|
| + // TODO(miu): Use _api_features.json and extensions::Feature library instead.
|
| + // http://crbug.com/537732
|
| + 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();
|
| + EXTENSION_FUNCTION_VALIDATE(extension_web_contents);
|
| + 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(),
|
| + 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(),
|
| + ¶ms->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
|
|
|