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

fix: MediaDevices missing DisplayMediaInformation #38390

Merged
merged 1 commit into from
May 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 135 additions & 16 deletions shell/browser/electron_browser_context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
#include "shell/common/gin_helper/error_thrower.h"
#include "shell/common/options_switches.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"

#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
Expand Down Expand Up @@ -91,6 +92,105 @@ namespace electron {

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.
std::string MakePartitionName(const std::string& input) {
return base::EscapePath(base::ToLowerASCII(input));
Expand Down Expand Up @@ -478,16 +578,23 @@ void ElectronBrowserContext::DisplayMediaDeviceChosen(
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);
blink::MediaStreamDevice video_device(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)) {
devices.video_device = blink::MediaStreamDevice(
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
blink::MediaStreamDevice video_device(
request.video_type,
content::WebContentsMediaCaptureId(rfh->GetProcess()->GetID(),
rfh->GetRoutingID())
.ToString(),
base::UTF16ToUTF8(
content::WebContents::FromRenderFrameHost(rfh)->GetTitle()));
base::UTF16ToUTF8(web_contents->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 {
gin_helper::ErrorThrower(args->isolate())
.ThrowTypeError(
Expand All @@ -509,22 +616,34 @@ void ElectronBrowserContext::DisplayMediaDeviceChosen(
// 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);
blink::MediaStreamDevice audio_device(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)) {
bool enable_local_echo = false;
result_dict.Get("enableLocalEcho", &enable_local_echo);
bool disable_local_echo = !enable_local_echo;
devices.audio_device =
blink::MediaStreamDevice(request.audio_type,
content::WebContentsMediaCaptureId(
rfh->GetProcess()->GetID(),
rfh->GetRoutingID(), disable_local_echo)
.ToString(),
"Tab audio");
auto* web_contents = content::WebContents::FromRenderFrameHost(rfh);
blink::MediaStreamDevice audio_device(
request.audio_type,
content::WebContentsMediaCaptureId(rfh->GetProcess()->GetID(),
rfh->GetRoutingID(),
disable_local_echo)
.ToString(),
"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)) {
devices.audio_device =
blink::MediaStreamDevice(request.audio_type, id, "System audio");
blink::MediaStreamDevice audio_device(request.audio_type, id,
"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 {
gin_helper::ErrorThrower(args->isolate())
.ThrowTypeError(
Expand Down
41 changes: 41 additions & 0 deletions spec/api-media-handler-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,47 @@ ifdescribe(features.isDesktopCapturerEnabled())('setDisplayMediaRequestHandler',
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 () => {
const ses = session.fromPartition('' + Math.random());
let requestHandlerCalled = false;
Expand Down