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(¶ms->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(¶ms->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 |