Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: session.setDisplayMediaRequestHandler #30702

Merged
merged 40 commits into from Aug 22, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
c599fc6
feat: session.setMediaRequestHandler
nornagon Aug 25, 2021
72fd6aa
address comments
nornagon Aug 26, 2021
5373a64
iwyu
nornagon Aug 28, 2021
fda34cf
Merge remote-tracking branch 'origin/main' into media-request-handler
nornagon Sep 2, 2021
31c10b6
review, bump typescript-definitions
nornagon Sep 2, 2021
d4edceb
fix some stuff
nornagon Sep 3, 2021
d474408
Merge remote-tracking branch 'origin/main' into media-request-handler
nornagon Jun 15, 2022
68f0bef
update for changes to media stream api
nornagon Jun 16, 2022
fc36ba4
include patches
nornagon Jun 20, 2022
3b87eb4
chore: update patches
patchup[bot] Jun 20, 2022
8cf7385
Merge branch 'main' into media-request-handler
nornagon Jun 20, 2022
4479e80
Merge remote-tracking branch 'origin/main' into media-request-handler
nornagon Jun 29, 2022
0ae0dc7
lint
nornagon Jun 29, 2022
36d405c
remove patches applied upstream
nornagon Jun 29, 2022
997a8dc
fix when no media handler installed
nornagon Jun 30, 2022
ecdc1ea
add getVideoCaptureDevices so gUM requests can be served
nornagon Jun 30, 2022
ba8c9d9
remove PEPPER_ONLY
nornagon Jun 30, 2022
057f4ee
camelCase
nornagon Jun 30, 2022
6dcde55
comments, tests
nornagon Jun 30, 2022
3801884
Merge remote-tracking branch 'origin/main' into media-request-handler
nornagon Jul 20, 2022
40a6674
new api
nornagon Jul 21, 2022
6ddbad4
lint
nornagon Jul 21, 2022
e6173c8
remove log
nornagon Jul 21, 2022
a6e8011
simplify request object
nornagon Jul 21, 2022
76834b0
setMediaRequestHandler => setDisplayMediaRequestHandler
nornagon Jul 21, 2022
ae328ba
remove unneeded code
nornagon Jul 21, 2022
856f3ae
allow passing WebFrameMain
nornagon Jul 21, 2022
93d2940
Update electron_api_desktop_capturer.cc
nornagon Jul 21, 2022
11e085c
Update electron_api_desktop_capturer.cc
nornagon Jul 21, 2022
a501c95
Update shell/browser/electron_browser_context.cc
nornagon Jul 25, 2022
5d2d7e0
review feedback
nornagon Jul 25, 2022
a745f3b
throw an error when setting display media request handler to an inval…
nornagon Jul 25, 2022
c147347
disable local echo for WebFrameMain audio streams
nornagon Jul 25, 2022
1ba104e
id/name on video streams come from DesktopCapturerSource
nornagon Jul 25, 2022
70ba06d
fix various crash cases and add better errors, improve docs
nornagon Jul 25, 2022
aead701
Merge remote-tracking branch 'origin/main' into media-request-handler
nornagon Aug 5, 2022
5269fba
Merge remote-tracking branch 'origin/main' into media-request-handler
nornagon Aug 15, 2022
716f79a
try skipping when screen source length is 0..?
nornagon Aug 15, 2022
f005525
skip screen tests on macOS / x64
nornagon Aug 16, 2022
8c8a464
Update docs/api/session.md
nornagon Aug 17, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 38 additions & 0 deletions docs/api/session.md
Expand Up @@ -553,6 +553,44 @@ session.fromPartition('some-partition').setPermissionCheckHandler((webContents,
})
```

#### `ses.setMediaRequestHandler(handler)`

* `handler` Function | null
* `request` Object
* `frame` [WebFrameMain](web-frame-main.md) - frame that is requesting access to media
* `type` String - Type of request. Can be 'deviceAccess', 'deviceUpdate', 'generateStream' or 'openDevicePepperOnly'.
* `audioType` String - can be 'deviceAudioCapture', 'displayAudioCapture' or 'noService'.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could these be 'device', 'display', and null? The 'AudioCapture' is redundant since the key is 'audioType'.

* `videoType` String - can be 'deviceVideoCapture', 'displayVideoCapture', 'displayVideoCaptureThisTab' or 'noService'.
nornagon marked this conversation as resolved.
Show resolved Hide resolved
* `requestedAudioDeviceId` String -
* `requestedVideoDeviceId` String -
* `userGesture` Boolean - whether a user gesture was active when this request was triggered.
* `callback` Function
* `devices` Object[]
nornagon marked this conversation as resolved.
Show resolved Hide resolved
* `id` String - the id of the device being granted
* `name` String - the name of the device being granted
* `type` String - the type of the device being granted
* `result` String - the result of the request. Can be 'ok',
'permissionDenied', 'permissionDismissed', 'noHardware', or 'notSupported'.
nornagon marked this conversation as resolved.
Show resolved Hide resolved

This handler will be called when web content requests access to media devices
via the `navigator.mediaDevices` API. Use the
[desktopCapturer](desktop-capturer.md) API to choose a device or devices to
grant access to.

```javascript
const { session, desktopCapturer } = require('electron')

session.defaultSession.setMediaRequestHandler((request, callback) => {
if (request.videoType === 'displayVideoCapture') {
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
// Grant access to the first screen found.
const { id, name } = sources[0]
callback([{ id, name, type: request.type }], 'ok')
})
}
})
```

#### `ses.clearHostResolverCache()`

Returns `Promise<void>` - Resolves when the operation is complete.
Expand Down
246 changes: 246 additions & 0 deletions shell/browser/api/electron_api_session.cc
Expand Up @@ -35,6 +35,7 @@
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/storage_partition.h"
#include "gin/arguments.h"
#include "gin/data_object_builder.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/completion_repeating_callback.h"
Expand All @@ -52,6 +53,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"
Expand All @@ -64,6 +66,7 @@
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/content_converter.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_converters/gurl_converter.h"
#include "shell/common/gin_converters/net_converter.h"
#include "shell/common/gin_converters/value_converter.h"
Expand Down Expand Up @@ -242,6 +245,229 @@ struct Converter<network::mojom::SSLConfigPtr> {
}
};

template <>
nornagon marked this conversation as resolved.
Show resolved Hide resolved
struct Converter<content::MediaStreamRequest> {
static v8::Local<v8::Value> 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("type", request.request_type)
.Set("requestedAudioDeviceId", request.requested_audio_device_id)
.Set("requestedVideoDeviceId", request.requested_video_device_id)
.Set("audioType", request.audio_type)
.Set("videoType", request.video_type)
.Set("disableLocalEcho", request.disable_local_echo)
.Set("requestPanTiltZoomPermission",
request.request_pan_tilt_zoom_permission)
.Build();
}
};

template <>
struct Converter<blink::MediaStreamDevice> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
blink::MediaStreamDevice* out) {
gin_helper::Dictionary dict;
if (!gin::ConvertFromV8(isolate, val, &dict))
return false;
blink::mojom::MediaStreamType type;
if (!dict.Get("type", &type))
return false;
std::string id;
if (!dict.Get("id", &id))
return false;
std::string name;
if (!dict.Get("name", &name))
return false;
*out = blink::MediaStreamDevice(type, id, name);
return true;
}
};

template <>
struct Converter<blink::mojom::MediaStreamRequestResult> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
blink::mojom::MediaStreamRequestResult* out) {
std::string type;
if (!gin::ConvertFromV8(isolate, val, &type))
return false;
if (type == "ok") {
*out = blink::mojom::MediaStreamRequestResult::OK;
return true;
}
if (type == "permissionDenied") {
*out = blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED;
return true;
}
if (type == "permissionDismissed") {
*out = blink::mojom::MediaStreamRequestResult::PERMISSION_DISMISSED;
return true;
}
if (type == "invalidState") {
*out = blink::mojom::MediaStreamRequestResult::INVALID_STATE;
return true;
}
if (type == "noHardware") {
*out = blink::mojom::MediaStreamRequestResult::NO_HARDWARE;
return true;
}
if (type == "invalidSecurityOrigin") {
*out = blink::mojom::MediaStreamRequestResult::INVALID_SECURITY_ORIGIN;
return true;
}
if (type == "tabCaptureFailure") {
*out = blink::mojom::MediaStreamRequestResult::TAB_CAPTURE_FAILURE;
return true;
}
if (type == "screenCaptureFailure") {
*out = blink::mojom::MediaStreamRequestResult::SCREEN_CAPTURE_FAILURE;
return true;
}
if (type == "captureFailure") {
*out = blink::mojom::MediaStreamRequestResult::CAPTURE_FAILURE;
return true;
}
if (type == "constraintNotSatisfied") {
*out = blink::mojom::MediaStreamRequestResult::CONSTRAINT_NOT_SATISFIED;
return true;
}
if (type == "trackStartFailureAudio") {
*out = blink::mojom::MediaStreamRequestResult::TRACK_START_FAILURE_AUDIO;
return true;
}
if (type == "trackStartFailureVideo") {
*out = blink::mojom::MediaStreamRequestResult::TRACK_START_FAILURE_VIDEO;
return true;
}
if (type == "notSupported") {
*out = blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED;
return true;
}
if (type == "failedDueToShutdown") {
*out = blink::mojom::MediaStreamRequestResult::FAILED_DUE_TO_SHUTDOWN;
return true;
}
if (type == "killSwitchOn") {
*out = blink::mojom::MediaStreamRequestResult::KILL_SWITCH_ON;
return true;
}
if (type == "systemPermissionDenied") {
*out = blink::mojom::MediaStreamRequestResult::SYSTEM_PERMISSION_DENIED;
return true;
}
if (type == "deviceInUse") {
*out = blink::mojom::MediaStreamRequestResult::DEVICE_IN_USE;
return true;
}
return false;
}
};

template <>
struct Converter<blink::MediaStreamRequestType> {
static v8::Local<v8::Value> ToV8(
v8::Isolate* isolate,
const blink::MediaStreamRequestType& request_type) {
switch (request_type) {
case blink::MEDIA_DEVICE_ACCESS:
return gin::StringToV8(isolate, "deviceAccess");
case blink::MEDIA_DEVICE_UPDATE:
return gin::StringToV8(isolate, "deviceUpdate");
case blink::MEDIA_GENERATE_STREAM:
return gin::StringToV8(isolate, "generateStream");
case blink::MEDIA_OPEN_DEVICE_PEPPER_ONLY:
return gin::StringToV8(isolate, "openDevicePepperOnly");
}
}
};

template <>
struct Converter<blink::mojom::MediaStreamType> {
static v8::Local<v8::Value> ToV8(
v8::Isolate* isolate,
const blink::mojom::MediaStreamType& stream_type) {
switch (stream_type) {
case blink::mojom::MediaStreamType::NO_SERVICE:
return gin::StringToV8(isolate, "noService");
case blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE:
return gin::StringToV8(isolate, "deviceAudioCapture");
case blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE:
return gin::StringToV8(isolate, "deviceVideoCapture");
case blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE:
return gin::StringToV8(isolate, "gumTabAudioCapture");
case blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE:
return gin::StringToV8(isolate, "gumTabVideoCapture");
case blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE:
return gin::StringToV8(isolate, "gumDesktopVideoCapture");
case blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE:
return gin::StringToV8(isolate, "gumDesktopAudioCapture");
case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE:
return gin::StringToV8(isolate, "displayVideoCapture");
case blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE:
return gin::StringToV8(isolate, "displayAudioCapture");
case blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB:
return gin::StringToV8(isolate, "displayVideoCaptureThisTab");
case blink::mojom::MediaStreamType::NUM_MEDIA_TYPES:
return v8::Null(isolate);
nornagon marked this conversation as resolved.
Show resolved Hide resolved
}
}

static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
blink::mojom::MediaStreamType* out) {
std::string type;
if (!gin::ConvertFromV8(isolate, val, &type))
return false;
if (type == "noService") {
*out = blink::mojom::MediaStreamType::NO_SERVICE;
return true;
}
if (type == "deviceAudioCapture") {
*out = blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE;
return true;
}
if (type == "deviceVideoCapture") {
*out = blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE;
return true;
}
if (type == "gumTabAudioCapture") {
*out = blink::mojom::MediaStreamType::GUM_TAB_AUDIO_CAPTURE;
return true;
}
if (type == "gumTabVideoCapture") {
*out = blink::mojom::MediaStreamType::GUM_TAB_VIDEO_CAPTURE;
return true;
}
if (type == "gumDesktopVideoCapture") {
*out = blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE;
return true;
}
if (type == "gumDesktopAudioCapture") {
*out = blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE;
return true;
}
if (type == "displayVideoCapture") {
*out = blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
return true;
}
if (type == "displayAudioCapture") {
*out = blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
return true;
}
if (type == "displayVideoCaptureThisTab") {
*out = blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE_THIS_TAB;
return true;
}
return false;
}
};

} // namespace gin

namespace electron {
Expand Down Expand Up @@ -644,6 +870,25 @@ void Session::SetPermissionCheckHandler(v8::Local<v8::Value> val,
permission_manager->SetPermissionCheckHandler(handler);
}

void Session::SetMediaRequestHandler(MediaRequestHandler handler) {
media_request_handler_ = handler;
}

bool Session::ChooseMediaDevice(const content::MediaStreamRequest& request,
content::MediaResponseCallback callback) {
if (!media_request_handler_)
return false;
MediaResponseCallbackJs callbackJs = base::BindOnce(
[](content::MediaResponseCallback callback,
const blink::MediaStreamDevices& devices,
blink::mojom::MediaStreamRequestResult result) {
std::move(callback).Run(devices, result, nullptr);
},
std::move(callback));
media_request_handler_.Run(request, std::move(callbackJs));
return true;
}

v8::Local<v8::Promise> Session::ClearHostResolverCache(gin::Arguments* args) {
v8::Isolate* isolate = args->isolate();
gin_helper::Promise<void> promise(isolate);
Expand Down Expand Up @@ -1147,6 +1392,7 @@ gin::ObjectTemplateBuilder Session::GetObjectTemplateBuilder(
&Session::SetPermissionRequestHandler)
.SetMethod("setPermissionCheckHandler",
&Session::SetPermissionCheckHandler)
.SetMethod("setMediaRequestHandler", &Session::SetMediaRequestHandler)
.SetMethod("clearHostResolverCache", &Session::ClearHostResolverCache)
.SetMethod("clearAuthCache", &Session::ClearAuthCache)
.SetMethod("allowNTLMCredentialsForDomains",
Expand Down
13 changes: 13 additions & 0 deletions shell/browser/api/electron_api_session.h
Expand Up @@ -10,6 +10,7 @@

#include "base/values.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/media_stream_request.h"
#include "electron/buildflags/buildflags.h"
#include "gin/handle.h"
#include "gin/wrappable.h"
Expand Down Expand Up @@ -153,6 +154,9 @@ class Session : public gin::Wrappable<Session>,
extensions::UnloadedExtensionReason reason) override;
#endif

bool ChooseMediaDevice(const content::MediaStreamRequest& request,
content::MediaResponseCallback callback);

protected:
Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context);
~Session() override;
Expand All @@ -179,6 +183,15 @@ class Session : public gin::Wrappable<Session>,
v8::Global<v8::Value> service_worker_context_;
v8::Global<v8::Value> web_request_;

using MediaResponseCallbackJs =
base::OnceCallback<void(const blink::MediaStreamDevices& devices,
blink::mojom::MediaStreamRequestResult result)>;
using MediaRequestHandler =
base::RepeatingCallback<void(const content::MediaStreamRequest&,
MediaResponseCallbackJs)>;
void SetMediaRequestHandler(MediaRequestHandler handler);
MediaRequestHandler media_request_handler_;

v8::Isolate* isolate_;

// The client id to enable the network throttler.
Expand Down