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, ¶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 +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(), |
+ ¶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 |