Chromium Code Reviews| 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 |