From b51e62e560971d39f41b861462222feab9e14fb4 Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:25:33 -0400 Subject: [PATCH] test: add tab source ID tests for media handler (#51095) * test: add getMediaSourceId tab source coverage Co-authored-by: Charles Kerr * chore: move captureWithTabSourceId() to a shared helper Co-authored-by: Charles Kerr * test: improve "webContents module getMediaSourceId()" testing Co-authored-by: Charles Kerr --------- Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Charles Kerr --- spec/api-media-handler-spec.ts | 32 ++++++++++++++++++++++++++++ spec/api-web-contents-spec.ts | 31 ++++++++++++++++++++++++--- spec/lib/media-helpers.ts | 39 ++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 spec/lib/media-helpers.ts diff --git a/spec/api-media-handler-spec.ts b/spec/api-media-handler-spec.ts index 7f0c269fd1..38b14f290e 100644 --- a/spec/api-media-handler-spec.ts +++ b/spec/api-media-handler-spec.ts @@ -4,6 +4,7 @@ import { expect } from 'chai'; import * as http from 'node:http'; +import { captureWithTabSourceId } from './lib/media-helpers'; import { ifit, listen } from './lib/spec-helpers'; import { closeAllWindows } from './lib/window-helpers'; @@ -417,6 +418,37 @@ describe('setDisplayMediaRequestHandler', () => { expect(message).to.equal('Invalid state'); }); + it('works when mediaDevices.getUserMedia uses a tab source id from webContents.getMediaSourceId', async () => { + const sourceWindow = new BrowserWindow({ show: false }); + const requestingWindow = new BrowserWindow({ show: false }); + await Promise.all([sourceWindow.loadURL(serverUrl), requestingWindow.loadURL(serverUrl)]); + + const sourceId = sourceWindow.webContents.getMediaSourceId(requestingWindow.webContents); + const { ok, message, origin, videoTrackCount } = await captureWithTabSourceId(requestingWindow, sourceId); + + expect(ok).to.be.true(message); + expect(origin).to.equal(new URL(serverUrl).origin); + expect(videoTrackCount).to.equal(1); + }); + + it('rejects a tab source id when used from a different requesting webContents', async () => { + const sourceWindow = new BrowserWindow({ show: false }); + const registeredRequesterWindow = new BrowserWindow({ show: false }); + const otherRequesterWindow = new BrowserWindow({ show: false }); + await Promise.all([ + sourceWindow.loadURL(serverUrl), + registeredRequesterWindow.loadURL(serverUrl), + otherRequesterWindow.loadURL(serverUrl) + ]); + + const sourceId = sourceWindow.webContents.getMediaSourceId(registeredRequesterWindow.webContents); + const { ok, message, origin } = await captureWithTabSourceId(otherRequesterWindow, sourceId); + + expect(ok).to.be.false(); + expect(message).to.match(/Invalid state|Error starting tab capture/); + expect(origin).to.equal(new URL(serverUrl).origin); + }); + it('can remove a displayMediaRequestHandler', async () => { const ses = session.fromPartition('' + Math.random()); diff --git a/spec/api-web-contents-spec.ts b/spec/api-web-contents-spec.ts index a96436d77c..4a1cf4e256 100644 --- a/spec/api-web-contents-spec.ts +++ b/spec/api-web-contents-spec.ts @@ -12,6 +12,7 @@ import * as path from 'node:path'; import { setTimeout } from 'node:timers/promises'; import * as url from 'node:url'; +import { captureWithTabSourceId } from './lib/media-helpers'; import { ifdescribe, defer, waitUntil, listen, ifit } from './lib/spec-helpers'; import { cleanupWebContents, closeAllWindows } from './lib/window-helpers'; @@ -1797,9 +1798,33 @@ describe('webContents module', () => { describe('getMediaSourceId()', () => { afterEach(closeAllWindows); - it('returns a valid stream id', () => { - const w = new BrowserWindow({ show: false }); - expect(w.webContents.getMediaSourceId(w.webContents)).to.be.a('string').that.is.not.empty(); + let server: http.Server; + let serverUrl: string; + + before(async () => { + server = http.createServer((req, res) => { + res.setHeader('Content-Type', 'text/html'); + res.end(''); + }); + serverUrl = (await listen(server)).url; + }); + + after(() => { + server.close(); + }); + + it('returns a stream id that can be used by the registered requester', async () => { + const sourceWindow = new BrowserWindow({ show: false }); + const requesterWindow = new BrowserWindow({ show: false }); + await Promise.all([sourceWindow.loadURL(serverUrl), requesterWindow.loadURL(serverUrl)]); + + const streamId = sourceWindow.webContents.getMediaSourceId(requesterWindow.webContents); + const { ok, message, origin, videoTrackCount } = await captureWithTabSourceId(requesterWindow, streamId); + + expect(streamId).to.be.a('string').that.is.not.empty(); + expect(ok, message).to.equal(true); + expect(origin).to.equal(new url.URL(serverUrl).origin); + expect(videoTrackCount).to.equal(1); }); }); diff --git a/spec/lib/media-helpers.ts b/spec/lib/media-helpers.ts new file mode 100644 index 0000000000..6020817dac --- /dev/null +++ b/spec/lib/media-helpers.ts @@ -0,0 +1,39 @@ +import { BrowserWindow } from 'electron/main'; + +export interface TabSourceCaptureResult { + ok: boolean; + href: string; + message?: string; + origin: string; + videoTrackCount?: number; +} + +export const captureWithTabSourceId = async ( + requestingWindow: BrowserWindow, + streamId: string +): Promise => { + return requestingWindow.webContents.executeJavaScript(` + navigator.mediaDevices.getUserMedia({ + video: { + mandatory: { + chromeMediaSource: 'tab', + chromeMediaSourceId: ${JSON.stringify(streamId)}, + }, + }, + }).then((stream) => { + const result = { + ok: stream instanceof MediaStream, + href: location.href, + origin: location.origin, + videoTrackCount: stream.getVideoTracks().length, + }; + stream.getTracks().forEach((track) => track.stop()); + return result; + }, (error) => ({ + ok: false, + href: location.href, + message: error.message, + origin: location.origin, + })); + `); +};