fix: MediaDevices
missing DisplayMediaInformation
(#38390)
fix: MediaDevices missing DisplayMediaInformation
This commit is contained in:
parent
f07b040cb9
commit
16cd486356
2 changed files with 176 additions and 16 deletions
|
@ -56,6 +56,7 @@
|
||||||
#include "shell/common/gin_helper/error_thrower.h"
|
#include "shell/common/gin_helper/error_thrower.h"
|
||||||
#include "shell/common/options_switches.h"
|
#include "shell/common/options_switches.h"
|
||||||
#include "shell/common/thread_restrictions.h"
|
#include "shell/common/thread_restrictions.h"
|
||||||
|
#include "third_party/blink/public/mojom/media/capture_handle_config.mojom.h"
|
||||||
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
|
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
|
||||||
|
|
||||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||||
|
@ -91,6 +92,105 @@ namespace electron {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
// Copied from chrome/browser/media/webrtc/desktop_capture_devices_util.cc.
|
||||||
|
media::mojom::CaptureHandlePtr CreateCaptureHandle(
|
||||||
|
content::WebContents* capturer,
|
||||||
|
const url::Origin& capturer_origin,
|
||||||
|
const content::DesktopMediaID& captured_id) {
|
||||||
|
if (capturer_origin.opaque()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
content::RenderFrameHost* const captured_rfh =
|
||||||
|
content::RenderFrameHost::FromID(
|
||||||
|
captured_id.web_contents_id.render_process_id,
|
||||||
|
captured_id.web_contents_id.main_render_frame_id);
|
||||||
|
if (!captured_rfh || !captured_rfh->IsActive()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
content::WebContents* const captured =
|
||||||
|
content::WebContents::FromRenderFrameHost(captured_rfh);
|
||||||
|
if (!captured) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& captured_config = captured->GetCaptureHandleConfig();
|
||||||
|
if (!captured_config.all_origins_permitted &&
|
||||||
|
base::ranges::none_of(
|
||||||
|
captured_config.permitted_origins,
|
||||||
|
[capturer_origin](const url::Origin& permitted_origin) {
|
||||||
|
return capturer_origin.IsSameOriginWith(permitted_origin);
|
||||||
|
})) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Observing CaptureHandle when either the capturing or the captured party
|
||||||
|
// is incognito is disallowed, except for self-capture.
|
||||||
|
if (capturer->GetPrimaryMainFrame() != captured->GetPrimaryMainFrame()) {
|
||||||
|
if (capturer->GetBrowserContext()->IsOffTheRecord() ||
|
||||||
|
captured->GetBrowserContext()->IsOffTheRecord()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!captured_config.expose_origin &&
|
||||||
|
captured_config.capture_handle.empty()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = media::mojom::CaptureHandle::New();
|
||||||
|
if (captured_config.expose_origin) {
|
||||||
|
result->origin = captured->GetPrimaryMainFrame()->GetLastCommittedOrigin();
|
||||||
|
}
|
||||||
|
result->capture_handle = captured_config.capture_handle;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from chrome/browser/media/webrtc/desktop_capture_devices_util.cc.
|
||||||
|
media::mojom::DisplayMediaInformationPtr
|
||||||
|
DesktopMediaIDToDisplayMediaInformation(
|
||||||
|
content::WebContents* capturer,
|
||||||
|
const url::Origin& capturer_origin,
|
||||||
|
const content::DesktopMediaID& media_id) {
|
||||||
|
media::mojom::DisplayCaptureSurfaceType display_surface =
|
||||||
|
media::mojom::DisplayCaptureSurfaceType::MONITOR;
|
||||||
|
bool logical_surface = true;
|
||||||
|
media::mojom::CursorCaptureType cursor =
|
||||||
|
media::mojom::CursorCaptureType::NEVER;
|
||||||
|
#if defined(USE_AURA)
|
||||||
|
const bool uses_aura =
|
||||||
|
media_id.window_id != content::DesktopMediaID::kNullId ? true : false;
|
||||||
|
#else
|
||||||
|
const bool uses_aura = false;
|
||||||
|
#endif // defined(USE_AURA)
|
||||||
|
|
||||||
|
media::mojom::CaptureHandlePtr capture_handle;
|
||||||
|
switch (media_id.type) {
|
||||||
|
case content::DesktopMediaID::TYPE_SCREEN:
|
||||||
|
display_surface = media::mojom::DisplayCaptureSurfaceType::MONITOR;
|
||||||
|
cursor = uses_aura ? media::mojom::CursorCaptureType::MOTION
|
||||||
|
: media::mojom::CursorCaptureType::ALWAYS;
|
||||||
|
break;
|
||||||
|
case content::DesktopMediaID::TYPE_WINDOW:
|
||||||
|
display_surface = media::mojom::DisplayCaptureSurfaceType::WINDOW;
|
||||||
|
cursor = uses_aura ? media::mojom::CursorCaptureType::MOTION
|
||||||
|
: media::mojom::CursorCaptureType::ALWAYS;
|
||||||
|
break;
|
||||||
|
case content::DesktopMediaID::TYPE_WEB_CONTENTS:
|
||||||
|
display_surface = media::mojom::DisplayCaptureSurfaceType::BROWSER;
|
||||||
|
cursor = media::mojom::CursorCaptureType::MOTION;
|
||||||
|
capture_handle = CreateCaptureHandle(capturer, capturer_origin, media_id);
|
||||||
|
break;
|
||||||
|
case content::DesktopMediaID::TYPE_NONE:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return media::mojom::DisplayMediaInformation::New(
|
||||||
|
display_surface, logical_surface, cursor, std::move(capture_handle));
|
||||||
|
}
|
||||||
|
|
||||||
// Convert string to lower case and escape it.
|
// Convert string to lower case and escape it.
|
||||||
std::string MakePartitionName(const std::string& input) {
|
std::string MakePartitionName(const std::string& input) {
|
||||||
return base::EscapePath(base::ToLowerASCII(input));
|
return base::EscapePath(base::ToLowerASCII(input));
|
||||||
|
@ -478,16 +578,23 @@ void ElectronBrowserContext::DisplayMediaDeviceChosen(
|
||||||
content::RenderFrameHost* rfh;
|
content::RenderFrameHost* rfh;
|
||||||
if (result_dict.Get("video", &video_dict) && video_dict.Get("id", &id) &&
|
if (result_dict.Get("video", &video_dict) && video_dict.Get("id", &id) &&
|
||||||
video_dict.Get("name", &name)) {
|
video_dict.Get("name", &name)) {
|
||||||
devices.video_device =
|
blink::MediaStreamDevice video_device(request.video_type, id, name);
|
||||||
blink::MediaStreamDevice(request.video_type, id, name);
|
video_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
|
||||||
|
nullptr, url::Origin::Create(request.security_origin),
|
||||||
|
content::DesktopMediaID::Parse(request.requested_video_device_id));
|
||||||
|
devices.video_device = video_device;
|
||||||
} else if (result_dict.Get("video", &rfh)) {
|
} else if (result_dict.Get("video", &rfh)) {
|
||||||
devices.video_device = blink::MediaStreamDevice(
|
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
|
||||||
|
blink::MediaStreamDevice video_device(
|
||||||
request.video_type,
|
request.video_type,
|
||||||
content::WebContentsMediaCaptureId(rfh->GetProcess()->GetID(),
|
content::WebContentsMediaCaptureId(rfh->GetProcess()->GetID(),
|
||||||
rfh->GetRoutingID())
|
rfh->GetRoutingID())
|
||||||
.ToString(),
|
.ToString(),
|
||||||
base::UTF16ToUTF8(
|
base::UTF16ToUTF8(web_contents->GetTitle()));
|
||||||
content::WebContents::FromRenderFrameHost(rfh)->GetTitle()));
|
video_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
|
||||||
|
web_contents, url::Origin::Create(request.security_origin),
|
||||||
|
content::DesktopMediaID::Parse(request.requested_video_device_id));
|
||||||
|
devices.video_device = video_device;
|
||||||
} else {
|
} else {
|
||||||
gin_helper::ErrorThrower(args->isolate())
|
gin_helper::ErrorThrower(args->isolate())
|
||||||
.ThrowTypeError(
|
.ThrowTypeError(
|
||||||
|
@ -509,22 +616,34 @@ void ElectronBrowserContext::DisplayMediaDeviceChosen(
|
||||||
// future.
|
// future.
|
||||||
if (result_dict.Get("audio", &audio_dict) && audio_dict.Get("id", &id) &&
|
if (result_dict.Get("audio", &audio_dict) && audio_dict.Get("id", &id) &&
|
||||||
audio_dict.Get("name", &name)) {
|
audio_dict.Get("name", &name)) {
|
||||||
devices.audio_device =
|
blink::MediaStreamDevice audio_device(request.audio_type, id, name);
|
||||||
blink::MediaStreamDevice(request.audio_type, id, name);
|
audio_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
|
||||||
|
nullptr, url::Origin::Create(request.security_origin),
|
||||||
|
content::DesktopMediaID::Parse(request.requested_audio_device_id));
|
||||||
|
devices.audio_device = audio_device;
|
||||||
} else if (result_dict.Get("audio", &rfh)) {
|
} else if (result_dict.Get("audio", &rfh)) {
|
||||||
bool enable_local_echo = false;
|
bool enable_local_echo = false;
|
||||||
result_dict.Get("enableLocalEcho", &enable_local_echo);
|
result_dict.Get("enableLocalEcho", &enable_local_echo);
|
||||||
bool disable_local_echo = !enable_local_echo;
|
bool disable_local_echo = !enable_local_echo;
|
||||||
devices.audio_device =
|
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
|
||||||
blink::MediaStreamDevice(request.audio_type,
|
blink::MediaStreamDevice audio_device(
|
||||||
content::WebContentsMediaCaptureId(
|
request.audio_type,
|
||||||
rfh->GetProcess()->GetID(),
|
content::WebContentsMediaCaptureId(rfh->GetProcess()->GetID(),
|
||||||
rfh->GetRoutingID(), disable_local_echo)
|
rfh->GetRoutingID(),
|
||||||
|
disable_local_echo)
|
||||||
.ToString(),
|
.ToString(),
|
||||||
"Tab audio");
|
"Tab audio");
|
||||||
|
audio_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
|
||||||
|
web_contents, url::Origin::Create(request.security_origin),
|
||||||
|
content::DesktopMediaID::Parse(request.requested_audio_device_id));
|
||||||
|
devices.audio_device = audio_device;
|
||||||
} else if (result_dict.Get("audio", &id)) {
|
} else if (result_dict.Get("audio", &id)) {
|
||||||
devices.audio_device =
|
blink::MediaStreamDevice audio_device(request.audio_type, id,
|
||||||
blink::MediaStreamDevice(request.audio_type, id, "System audio");
|
"System audio");
|
||||||
|
audio_device.display_media_info = DesktopMediaIDToDisplayMediaInformation(
|
||||||
|
nullptr, url::Origin::Create(request.security_origin),
|
||||||
|
content::DesktopMediaID::Parse(request.requested_audio_device_id));
|
||||||
|
devices.audio_device = audio_device;
|
||||||
} else {
|
} else {
|
||||||
gin_helper::ErrorThrower(args->isolate())
|
gin_helper::ErrorThrower(args->isolate())
|
||||||
.ThrowTypeError(
|
.ThrowTypeError(
|
||||||
|
|
|
@ -85,6 +85,47 @@ ifdescribe(features.isDesktopCapturerEnabled())('setDisplayMediaRequestHandler',
|
||||||
expect(message).to.equal('Could not start video source');
|
expect(message).to.equal('Could not start video source');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('successfully returns a capture handle', async () => {
|
||||||
|
let w: BrowserWindow | null = null;
|
||||||
|
const ses = session.fromPartition('' + Math.random());
|
||||||
|
let requestHandlerCalled = false;
|
||||||
|
let mediaRequest: any = null;
|
||||||
|
ses.setDisplayMediaRequestHandler((request, callback) => {
|
||||||
|
requestHandlerCalled = true;
|
||||||
|
mediaRequest = request;
|
||||||
|
callback({ video: w?.webContents.mainFrame });
|
||||||
|
});
|
||||||
|
|
||||||
|
w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
||||||
|
await w.loadURL(serverUrl);
|
||||||
|
|
||||||
|
const { ok, handleID, captureHandle, message } = await w.webContents.executeJavaScript(`
|
||||||
|
const handleID = crypto.randomUUID();
|
||||||
|
navigator.mediaDevices.setCaptureHandleConfig({
|
||||||
|
handle: handleID,
|
||||||
|
exposeOrigin: true,
|
||||||
|
permittedOrigins: ["*"],
|
||||||
|
});
|
||||||
|
|
||||||
|
navigator.mediaDevices.getDisplayMedia({
|
||||||
|
video: true,
|
||||||
|
audio: false
|
||||||
|
}).then(stream => {
|
||||||
|
const [videoTrack] = stream.getVideoTracks();
|
||||||
|
const captureHandle = videoTrack.getCaptureHandle();
|
||||||
|
return { ok: true, handleID, captureHandle, message: null }
|
||||||
|
}, e => ({ ok: false, message: e.message }))
|
||||||
|
`, true);
|
||||||
|
|
||||||
|
expect(requestHandlerCalled).to.be.true();
|
||||||
|
expect(mediaRequest.videoRequested).to.be.true();
|
||||||
|
expect(mediaRequest.audioRequested).to.be.false();
|
||||||
|
expect(ok).to.be.true();
|
||||||
|
expect(captureHandle.handle).to.be.a('string');
|
||||||
|
expect(handleID).to.eq(captureHandle.handle);
|
||||||
|
expect(message).to.be.null();
|
||||||
|
});
|
||||||
|
|
||||||
it('does not crash when providing only audio for a video request', async () => {
|
it('does not crash when providing only audio for a video request', async () => {
|
||||||
const ses = session.fromPartition('' + Math.random());
|
const ses = session.fromPartition('' + Math.random());
|
||||||
let requestHandlerCalled = false;
|
let requestHandlerCalled = false;
|
||||||
|
|
Loading…
Reference in a new issue