feat: session.setDisplayMediaRequestHandler (#30702)

This commit is contained in:
Jeremy Rose 2022-08-22 14:15:32 -07:00 committed by GitHub
parent 0c04be502c
commit 221bb51326
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 711 additions and 11 deletions

View file

@ -52,6 +52,7 @@
#include "shell/browser/api/electron_api_net_log.h"
#include "shell/browser/api/electron_api_protocol.h"
#include "shell/browser/api/electron_api_service_worker_context.h"
#include "shell/browser/api/electron_api_web_frame_main.h"
#include "shell/browser/api/electron_api_web_request.h"
#include "shell/browser/browser.h"
#include "shell/browser/electron_browser_context.h"
@ -65,6 +66,7 @@
#include "shell/common/gin_converters/content_converter.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_converters/gurl_converter.h"
#include "shell/common/gin_converters/media_converter.h"
#include "shell/common/gin_converters/net_converter.h"
#include "shell/common/gin_converters/value_converter.h"
#include "shell/common/gin_helper/dictionary.h"
@ -73,6 +75,7 @@
#include "shell/common/options_switches.h"
#include "shell/common/process_util.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
@ -643,6 +646,22 @@ void Session::SetPermissionCheckHandler(v8::Local<v8::Value> val,
permission_manager->SetPermissionCheckHandler(handler);
}
void Session::SetDisplayMediaRequestHandler(v8::Isolate* isolate,
v8::Local<v8::Value> val) {
if (val->IsNull()) {
browser_context_->SetDisplayMediaRequestHandler(
DisplayMediaRequestHandler());
return;
}
DisplayMediaRequestHandler handler;
if (!gin::ConvertFromV8(isolate, val, &handler)) {
gin_helper::ErrorThrower(isolate).ThrowTypeError(
"Display media request handler must be null or a function");
return;
}
browser_context_->SetDisplayMediaRequestHandler(handler);
}
void Session::SetDevicePermissionHandler(v8::Local<v8::Value> val,
gin::Arguments* args) {
ElectronPermissionManager::DeviceCheckHandler handler;
@ -1198,6 +1217,8 @@ gin::ObjectTemplateBuilder Session::GetObjectTemplateBuilder(
&Session::SetPermissionRequestHandler)
.SetMethod("setPermissionCheckHandler",
&Session::SetPermissionCheckHandler)
.SetMethod("setDisplayMediaRequestHandler",
&Session::SetDisplayMediaRequestHandler)
.SetMethod("setDevicePermissionHandler",
&Session::SetDevicePermissionHandler)
.SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache)

View file

@ -179,6 +179,9 @@ class Session : public gin::Wrappable<Session>,
#endif
private:
void SetDisplayMediaRequestHandler(v8::Isolate* isolate,
v8::Local<v8::Value> val);
// Cached gin_helper::Wrappable objects.
v8::Global<v8::Value> cookies_;
v8::Global<v8::Value> protocol_;

View file

@ -31,8 +31,11 @@
#include "content/browser/blob_storage/chrome_blob_storage_context.h" // nogncheck
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/cors_origin_pattern_setter.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/shared_cors_origin_access_list.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents_media_capture_id.h"
#include "media/audio/audio_device_description.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
#include "services/network/public/mojom/network_context.mojom.h"
@ -51,7 +54,10 @@
#include "shell/browser/zoom_level_delegate.h"
#include "shell/common/application_info.h"
#include "shell/common/electron_paths.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_helper/error_thrower.h"
#include "shell/common/options_switches.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "extensions/browser/browser_context_keyed_service_factories.h"
@ -412,6 +418,131 @@ void ElectronBrowserContext::SetSSLConfigClient(
ssl_config_client_ = std::move(client);
}
void ElectronBrowserContext::SetDisplayMediaRequestHandler(
DisplayMediaRequestHandler handler) {
display_media_request_handler_ = handler;
}
void ElectronBrowserContext::DisplayMediaDeviceChosen(
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback,
gin::Arguments* args) {
blink::mojom::StreamDevicesSetPtr stream_devices_set =
blink::mojom::StreamDevicesSet::New();
v8::Local<v8::Value> result;
if (!args->GetNext(&result) || result->IsNullOrUndefined()) {
std::move(callback).Run(
blink::mojom::StreamDevicesSet(),
blink::mojom::MediaStreamRequestResult::CAPTURE_FAILURE, nullptr);
return;
}
gin_helper::Dictionary result_dict;
if (!gin::ConvertFromV8(args->isolate(), result, &result_dict)) {
gin_helper::ErrorThrower(args->isolate())
.ThrowTypeError(
"Display Media Request streams callback must be called with null "
"or a valid object");
std::move(callback).Run(
blink::mojom::StreamDevicesSet(),
blink::mojom::MediaStreamRequestResult::CAPTURE_FAILURE, nullptr);
return;
}
stream_devices_set->stream_devices.emplace_back(
blink::mojom::StreamDevices::New());
blink::mojom::StreamDevices& devices = *stream_devices_set->stream_devices[0];
bool video_requested =
request.video_type != blink::mojom::MediaStreamType::NO_SERVICE;
bool audio_requested =
request.audio_type != blink::mojom::MediaStreamType::NO_SERVICE;
bool has_video = false;
if (video_requested && result_dict.Has("video")) {
gin_helper::Dictionary video_dict;
std::string id;
std::string name;
content::RenderFrameHost* rfh;
if (result_dict.Get("video", &video_dict) && video_dict.Get("id", &id) &&
video_dict.Get("name", &name)) {
devices.video_device =
blink::MediaStreamDevice(request.video_type, id, name);
} else if (result_dict.Get("video", &rfh)) {
devices.video_device = blink::MediaStreamDevice(
request.video_type,
content::WebContentsMediaCaptureId(rfh->GetProcess()->GetID(),
rfh->GetRoutingID())
.ToString(),
base::UTF16ToUTF8(
content::WebContents::FromRenderFrameHost(rfh)->GetTitle()));
} else {
gin_helper::ErrorThrower(args->isolate())
.ThrowTypeError(
"video must be a WebFrameMain or DesktopCapturerSource");
std::move(callback).Run(
blink::mojom::StreamDevicesSet(),
blink::mojom::MediaStreamRequestResult::CAPTURE_FAILURE, nullptr);
return;
}
has_video = true;
}
if (audio_requested && result_dict.Has("audio")) {
gin_helper::Dictionary audio_dict;
std::string id;
std::string name;
content::RenderFrameHost* rfh;
// NB. this is not permitted by the documentation, but is left here as an
// "escape hatch" for providing an arbitrary name/id if needed in the
// future.
if (result_dict.Get("audio", &audio_dict) && audio_dict.Get("id", &id) &&
audio_dict.Get("name", &name)) {
devices.audio_device =
blink::MediaStreamDevice(request.audio_type, id, name);
} else if (result_dict.Get("audio", &rfh)) {
devices.audio_device = blink::MediaStreamDevice(
request.audio_type,
content::WebContentsMediaCaptureId(rfh->GetProcess()->GetID(),
rfh->GetRoutingID(),
/* disable_local_echo= */ true)
.ToString(),
"Tab audio");
} else if (result_dict.Get("audio", &id)) {
devices.audio_device =
blink::MediaStreamDevice(request.audio_type, id, "System audio");
} else {
gin_helper::ErrorThrower(args->isolate())
.ThrowTypeError(
"audio must be a WebFrameMain, \"loopback\" or "
"\"loopbackWithMute\"");
std::move(callback).Run(
blink::mojom::StreamDevicesSet(),
blink::mojom::MediaStreamRequestResult::CAPTURE_FAILURE, nullptr);
return;
}
}
if ((video_requested && !has_video)) {
gin_helper::ErrorThrower(args->isolate())
.ThrowTypeError(
"Video was requested, but no video stream was provided");
std::move(callback).Run(
blink::mojom::StreamDevicesSet(),
blink::mojom::MediaStreamRequestResult::CAPTURE_FAILURE, nullptr);
return;
}
std::move(callback).Run(*stream_devices_set,
blink::mojom::MediaStreamRequestResult::OK, nullptr);
}
bool ElectronBrowserContext::ChooseDisplayMediaDevice(
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback) {
if (!display_media_request_handler_)
return false;
DisplayMediaResponseCallbackJs callbackJs =
base::BindOnce(&DisplayMediaDeviceChosen, request, std::move(callback));
display_media_request_handler_.Run(request, std::move(callbackJs));
return true;
}
void ElectronBrowserContext::GrantDevicePermission(
const url::Origin& origin,
const base::Value& device,

View file

@ -13,8 +13,10 @@
#include "base/memory/weak_ptr.h"
#include "chrome/browser/predictors/preconnect_manager.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/media_stream_request.h"
#include "content/public/browser/resource_context.h"
#include "electron/buildflags/buildflags.h"
#include "gin/arguments.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/network/public/mojom/network_context.mojom.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
@ -38,6 +40,13 @@ class ElectronExtensionSystem;
}
#endif
namespace v8 {
template <typename T>
class Local;
class Isolate;
class Value;
} // namespace v8
namespace electron {
using DevicePermissionMap =
@ -51,6 +60,12 @@ class ResolveProxyHelper;
class WebViewManager;
class ProtocolRegistry;
using DisplayMediaResponseCallbackJs =
base::OnceCallback<void(gin::Arguments* args)>;
using DisplayMediaRequestHandler =
base::RepeatingCallback<void(const content::MediaStreamRequest&,
DisplayMediaResponseCallbackJs)>;
class ElectronBrowserContext : public content::BrowserContext {
public:
// disable copy
@ -150,6 +165,10 @@ class ElectronBrowserContext : public content::BrowserContext {
network::mojom::SSLConfigPtr GetSSLConfig();
void SetSSLConfigClient(mojo::Remote<network::mojom::SSLConfigClient> client);
bool ChooseDisplayMediaDevice(const content::MediaStreamRequest& request,
content::MediaResponseCallback callback);
void SetDisplayMediaRequestHandler(DisplayMediaRequestHandler handler);
~ElectronBrowserContext() override;
// Grants |origin| access to |device|.
@ -176,6 +195,11 @@ class ElectronBrowserContext : public content::BrowserContext {
bool in_memory,
base::Value::Dict options);
static void DisplayMediaDeviceChosen(
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback,
gin::Arguments* args);
// Initialize pref registry.
void InitPrefs();
@ -214,6 +238,8 @@ class ElectronBrowserContext : public content::BrowserContext {
network::mojom::SSLConfigPtr ssl_config_;
mojo::Remote<network::mojom::SSLConfigClient> ssl_config_client_;
DisplayMediaRequestHandler display_media_request_handler_;
// In-memory cache that holds objects that have been granted permissions.
DevicePermissionMap granted_devices_;

View file

@ -111,19 +111,43 @@ void MediaAccessAllowed(const content::MediaStreamRequest& request,
request.video_type ==
blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE ||
request.audio_type ==
blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE)
blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE) {
HandleUserMediaRequest(request, std::move(callback));
else if (request.video_type ==
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
request.audio_type ==
blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE)
} else if (request.video_type ==
blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE ||
request.audio_type ==
blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
webrtc::MediaStreamDevicesController::RequestPermissions(
request, MediaCaptureDevicesDispatcher::GetInstance(),
base::BindOnce(&OnMediaStreamRequestResponse, std::move(callback)));
else
} else if (request.video_type ==
blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE ||
request.video_type == blink::mojom::MediaStreamType::
DISPLAY_VIDEO_CAPTURE_THIS_TAB ||
request.video_type ==
blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_SET ||
request.audio_type ==
blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE) {
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
request.render_process_id, request.render_frame_id);
if (!rfh)
return;
content::BrowserContext* browser_context = rfh->GetBrowserContext();
ElectronBrowserContext* electron_browser_context =
static_cast<ElectronBrowserContext*>(browser_context);
auto split_callback = base::SplitOnceCallback(std::move(callback));
if (electron_browser_context->ChooseDisplayMediaDevice(
request, std::move(split_callback.second)))
return;
std::move(split_callback.first)
.Run(blink::mojom::StreamDevicesSet(),
blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
} else {
std::move(callback).Run(
blink::mojom::StreamDevicesSet(),
blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, nullptr);
}
} else {
std::move(callback).Run(
blink::mojom::StreamDevicesSet(),

View file

@ -26,6 +26,19 @@ v8::Local<v8::Value> Converter<content::RenderFrameHost*>::ToV8(
return electron::api::WebFrameMain::From(isolate, val).ToV8();
}
// static
bool Converter<content::RenderFrameHost*>::FromV8(
v8::Isolate* isolate,
v8::Local<v8::Value> val,
content::RenderFrameHost** out) {
electron::api::WebFrameMain* web_frame_main = nullptr;
if (!ConvertFromV8(isolate, val, &web_frame_main))
return false;
*out = web_frame_main->render_frame_host();
return true;
}
// static
v8::Local<v8::Value>
Converter<gin_helper::AccessorValue<content::RenderFrameHost*>>::ToV8(

View file

@ -18,6 +18,9 @@ template <>
struct Converter<content::RenderFrameHost*> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
content::RenderFrameHost* val);
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
content::RenderFrameHost** out);
};
template <>

View file

@ -0,0 +1,36 @@
// Copyright (c) 2021 Slack Technologies, LLC.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/common/gin_converters/media_converter.h"
#include <string>
#include <utility>
#include "content/public/browser/media_stream_request.h"
#include "content/public/browser/render_frame_host.h"
#include "gin/data_object_builder.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_converters/gurl_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
namespace gin {
v8::Local<v8::Value> Converter<content::MediaStreamRequest>::ToV8(
v8::Isolate* isolate,
const content::MediaStreamRequest& request) {
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
request.render_process_id, request.render_frame_id);
return gin::DataObjectBuilder(isolate)
.Set("frame", rfh)
.Set("securityOrigin", request.security_origin)
.Set("userGesture", request.user_gesture)
.Set("videoRequested",
request.video_type != blink::mojom::MediaStreamType::NO_SERVICE)
.Set("audioRequested",
request.audio_type != blink::mojom::MediaStreamType::NO_SERVICE)
.Build();
}
} // namespace gin

View file

@ -0,0 +1,26 @@
// Copyright (c) 2021 Slack Technologies, LLC.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_COMMON_GIN_CONVERTERS_MEDIA_CONVERTER_H_
#define ELECTRON_SHELL_COMMON_GIN_CONVERTERS_MEDIA_CONVERTER_H_
#include "gin/converter.h"
#include "third_party/blink/public/common/mediastream/media_stream_request.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom-forward.h"
namespace content {
struct MediaStreamRequest;
}
namespace gin {
template <>
struct Converter<content::MediaStreamRequest> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const content::MediaStreamRequest& request);
};
} // namespace gin
#endif // ELECTRON_SHELL_COMMON_GIN_CONVERTERS_MEDIA_CONVERTER_H_