![electron-roller[bot]](/assets/img/avatar_default.png)
* chore: bump chromium in DEPS to 134.0.6998.1 * chore: bump chromium in DEPS to 134.0.6998.5 * chore: bump chromium in DEPS to 134.0.6998.3 * chore: bump chromium to 134.0.6988.0 (main) (#45334) * chore: bump chromium in DEPS to 134.0.6976.0 * chore: update mas_avoid_private_macos_api_usage.patch.patch https://chromium-review.googlesource.com/c/chromium/src/+/6171046 process_info_mac.cc -> process_info_mac.mm * chore: update build_do_not_depend_on_packed_resource_integrity.patch https://chromium-review.googlesource.com/c/chromium/src/+/6196857 * chore: update feat_add_support_for_missing_dialog_features_to_shell_dialogs.patch https://chromium-review.googlesource.com/c/chromium/src/+/6182296 https://chromium-review.googlesource.com/c/chromium/src/+/6183404 https://chromium-review.googlesource.com/c/chromium/src/+/6187853 A lot changed in the upstream implementation. There's a good chance I got this wrong as threading has changed and moved some variables into globals. * chore: remove build_remove_vr_directx_helpers_dependency.patch https://chromium-review.googlesource.com/c/chromium/src/+/6186102 This landed upstream * chore: e patches all * chore: update net::CookieInclusionStatus::ExclusionReason enum https://chromium-review.googlesource.com/c/chromium/src/+/6183252 https://chromium-review.googlesource.com/c/chromium/src/+/6185544 * chore: update content::WebAuthenticationDelegate import https://chromium-review.googlesource.com/c/chromium/src/+/6189769 * Revert "chore: disable focus handling test due to win32/ia32 regression" This reverts commit 1a57ba5d59848d0c841ddda59c9299a4f957452a. * chore: bump chromium in DEPS to 134.0.6978.0 * chore: bump chromium in DEPS to 134.0.6980.0 * chore: bump chromium in DEPS to 134.0.6982.0 * chore: bump chromium in DEPS to 134.0.6984.0 * 6196281: Allow direct embedder IsPdfInternalPluginAllowedOrigin() interaction https://chromium-review.googlesource.com/c/chromium/src/+/6196281 * 6196283: Delete PdfInternalPluginDelegate https://chromium-review.googlesource.com/c/chromium/src/+/6196283 * chore: update patches * chore: bump chromium in DEPS to 134.0.6986.0 * chore: update patches * 6205762: Support option to use window.showSaveFilePicker() in PDF attachment code https://chromium-review.googlesource.com/c/chromium/src/+/6205762 See also: * https://issues.chromium.org/issues/373852607 * 5939153: [PDF] Add PdfUseShowSaveFilePicker feature flag | https://chromium-review.googlesource.com/c/chromium/src/+/5939153 * 6205761: Delete spurious Ink-specific code in pdf_viewer.ts | https://chromium-review.googlesource.com/c/chromium/src/+/6205761 * 6209609: Remove WebVector: Automatic changes https://chromium-review.googlesource.com/c/chromium/src/+/6209609 * 6205488: UI: make QT5 optional https://chromium-review.googlesource.com/c/chromium/src/+/6205488 * 6178281: Rename pak files from branding strings https://chromium-review.googlesource.com/c/chromium/src/+/6178281 * fixup! 6209609: Remove WebVector: Automatic changes https://chromium-review.googlesource.com/c/chromium/src/+/6209609 * 6193249: Switch from safe_browsing::EventResult to enterprise_connectors:EventResult https://chromium-review.googlesource.com/c/chromium/src/+/6193249 * 6197457: Remove Pause/ResumeReadingBodyFromNet IPCs https://chromium-review.googlesource.com/c/chromium/src/+/6197457 * 6191230: Record total time spent on a picture in picture window https://chromium-review.googlesource.com/c/chromium/src/+/6191230 * chore: bump chromium in DEPS to 134.0.6988.0 * chore: update patches * 6215440: Remove base/ranges/. https://chromium-review.googlesource.com/c/chromium/src/+/6215440 * Disable unsafe buffers error Not sure what changed, but we're now seeing unsafe buffer errors in Chromium code, at least when using reclient. Will update this comment if we find out the cause. * 6187853: SelectFileDialogLinuxPortal: Use dbus_xdg::Request and DbusType https://chromium-review.googlesource.com/c/chromium/src/+/6187853 * fix `setDisplayMediaRequestHandler` test Given how this test is written, I would expect this assertion to be false. It seems the oppositue was true before, but that was also acknowledged to be suprising. Seems that the underlying implementation is now fixed and works as expected. * fixup! 6187853: SelectFileDialogLinuxPortal: Use dbus_xdg::Request and DbusType https://chromium-review.googlesource.com/c/chromium/src/+/6187853 * chore: udpate patches * Multiple PRS: https://chromium-review.googlesource.com/c/chromium/src/+/6185544 | https://chromium-review.googlesource.com/c/chromium/src/+/6183252 * fix: cast enum class to numeric type * fix: add 1 to MAX_EXCLUSION_REASON because enum values are zero-based, and we want the total count of reasons. * Reapply "chore: disable focus handling test due to win32/ia32 regression" This reverts commit 760b1a519b5919b483c66bc3096eeefb4d7011f4. * refactor: use ExclusionReasonBitset::kValueCount for size --------- Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com> Co-authored-by: Samuel Maddock <smaddock@slack-corp.com> Co-authored-by: clavin <clavin@electronjs.org> Co-authored-by: alice <alice@makenotion.com> Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org> (cherry picked from commit 213165a467b84b3fb979a869d9bf10ad21e2d78e) --------- Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
420 lines
16 KiB
TypeScript
420 lines
16 KiB
TypeScript
import { BrowserWindow, session, desktopCapturer } from 'electron/main';
|
|
|
|
import { expect } from 'chai';
|
|
|
|
import * as http from 'node:http';
|
|
|
|
import { ifit, listen } from './lib/spec-helpers';
|
|
import { closeAllWindows } from './lib/window-helpers';
|
|
|
|
describe('setDisplayMediaRequestHandler', () => {
|
|
afterEach(closeAllWindows);
|
|
// These tests are done on an http server because navigator.userAgentData
|
|
// requires a secure context.
|
|
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();
|
|
});
|
|
|
|
ifit(process.platform !== 'darwin')('works when calling getDisplayMedia', async function () {
|
|
if ((await desktopCapturer.getSources({ types: ['screen'] })).length === 0) {
|
|
return this.skip();
|
|
}
|
|
const ses = session.fromPartition('' + Math.random());
|
|
let requestHandlerCalled = false;
|
|
let mediaRequest: any = null;
|
|
ses.setDisplayMediaRequestHandler((request, callback) => {
|
|
requestHandlerCalled = true;
|
|
mediaRequest = request;
|
|
desktopCapturer.getSources({ types: ['screen'] }).then((sources) => {
|
|
// Grant access to the first screen found.
|
|
const { id, name } = sources[0];
|
|
callback({
|
|
video: { id, name }
|
|
// TODO: 'loopback' and 'loopbackWithMute' are currently only supported on Windows.
|
|
// audio: { id: 'loopback', name: 'System Audio' }
|
|
});
|
|
});
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
video: true,
|
|
audio: false,
|
|
}).then(x => ({ok: x instanceof MediaStream}), 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(message);
|
|
});
|
|
|
|
it('does not crash when using a bogus ID', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
let requestHandlerCalled = false;
|
|
ses.setDisplayMediaRequestHandler((request, callback) => {
|
|
requestHandlerCalled = true;
|
|
callback({
|
|
video: { id: 'bogus', name: 'whatever' }
|
|
});
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
video: true,
|
|
audio: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(requestHandlerCalled).to.be.true();
|
|
expect(ok).to.be.false();
|
|
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;
|
|
let callbackError: any;
|
|
ses.setDisplayMediaRequestHandler((request, callback) => {
|
|
requestHandlerCalled = true;
|
|
try {
|
|
callback({
|
|
audio: 'loopback'
|
|
});
|
|
} catch (e) {
|
|
callbackError = e;
|
|
}
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
video: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(requestHandlerCalled).to.be.true();
|
|
expect(ok).to.be.false();
|
|
expect(callbackError?.message).to.equal('Video was requested, but no video stream was provided');
|
|
});
|
|
|
|
it('does not crash when providing only an audio stream for an audio+video request', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
let requestHandlerCalled = false;
|
|
let callbackError: any;
|
|
ses.setDisplayMediaRequestHandler((request, callback) => {
|
|
requestHandlerCalled = true;
|
|
try {
|
|
callback({
|
|
audio: 'loopback'
|
|
});
|
|
} catch (e) {
|
|
callbackError = e;
|
|
}
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
video: true,
|
|
audio: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(requestHandlerCalled).to.be.true();
|
|
expect(ok).to.be.false();
|
|
expect(callbackError?.message).to.equal('Video was requested, but no video stream was provided');
|
|
});
|
|
|
|
it('does not crash when providing a non-loopback audio stream', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
let requestHandlerCalled = false;
|
|
ses.setDisplayMediaRequestHandler((request, callback) => {
|
|
requestHandlerCalled = true;
|
|
callback({
|
|
video: w.webContents.mainFrame,
|
|
audio: 'default' as any
|
|
});
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
video: true,
|
|
audio: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(requestHandlerCalled).to.be.true();
|
|
expect(ok).to.be.true();
|
|
});
|
|
|
|
it('does not crash when providing no streams', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
let requestHandlerCalled = false;
|
|
let callbackError: any;
|
|
ses.setDisplayMediaRequestHandler((request, callback) => {
|
|
requestHandlerCalled = true;
|
|
try {
|
|
callback({});
|
|
} catch (e) {
|
|
callbackError = e;
|
|
}
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
video: true,
|
|
audio: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(requestHandlerCalled).to.be.true();
|
|
expect(ok).to.be.false();
|
|
expect(callbackError.message).to.equal('Video was requested, but no video stream was provided');
|
|
});
|
|
|
|
it('does not crash when using a bogus web-contents-media-stream:// ID', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
let requestHandlerCalled = false;
|
|
ses.setDisplayMediaRequestHandler((request, callback) => {
|
|
requestHandlerCalled = true;
|
|
callback({
|
|
video: { id: 'web-contents-media-stream://9999:9999', name: 'whatever' }
|
|
});
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
video: true,
|
|
audio: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(requestHandlerCalled).to.be.true();
|
|
expect(ok).to.be.false();
|
|
expect(message).to.equal('Could not start video source');
|
|
});
|
|
|
|
it('is not called when calling getUserMedia', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
ses.setDisplayMediaRequestHandler(() => {
|
|
throw new Error('bad');
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getUserMedia({
|
|
video: true,
|
|
audio: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`);
|
|
expect(ok).to.be.true(message);
|
|
});
|
|
|
|
it('works when calling getDisplayMedia with preferCurrentTab', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
let requestHandlerCalled = false;
|
|
ses.setDisplayMediaRequestHandler((request, callback) => {
|
|
requestHandlerCalled = true;
|
|
callback({ video: w.webContents.mainFrame });
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
preferCurrentTab: true,
|
|
video: true,
|
|
audio: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(requestHandlerCalled).to.be.true();
|
|
expect(ok).to.be.true(message);
|
|
});
|
|
|
|
it('returns a MediaStream with BrowserCaptureMediaStreamTrack when the current tab is selected', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
let requestHandlerCalled = false;
|
|
ses.setDisplayMediaRequestHandler((request, callback) => {
|
|
requestHandlerCalled = true;
|
|
callback({ video: w.webContents.mainFrame });
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
preferCurrentTab: true,
|
|
video: true,
|
|
audio: false,
|
|
}).then(stream => {
|
|
const [videoTrack] = stream.getVideoTracks();
|
|
return { ok: videoTrack instanceof BrowserCaptureMediaStreamTrack, message: null };
|
|
}, e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(requestHandlerCalled).to.be.true();
|
|
expect(ok).to.be.true(message);
|
|
});
|
|
|
|
ifit(process.platform !== 'darwin')('can supply a screen response to preferCurrentTab', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
let requestHandlerCalled = false;
|
|
ses.setDisplayMediaRequestHandler(async (request, callback) => {
|
|
requestHandlerCalled = true;
|
|
const sources = await desktopCapturer.getSources({ types: ['screen'] });
|
|
callback({ video: sources[0] });
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
preferCurrentTab: true,
|
|
video: true,
|
|
audio: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(requestHandlerCalled).to.be.true();
|
|
expect(ok).to.be.true(message);
|
|
});
|
|
|
|
it('can supply a frame response', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
let requestHandlerCalled = false;
|
|
ses.setDisplayMediaRequestHandler(async (request, callback) => {
|
|
requestHandlerCalled = true;
|
|
callback({ video: w.webContents.mainFrame });
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
video: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(requestHandlerCalled).to.be.true();
|
|
expect(ok).to.be.true(message);
|
|
});
|
|
|
|
it('is not called when calling legacy getUserMedia', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
ses.setDisplayMediaRequestHandler(() => {
|
|
throw new Error('bad');
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
new Promise((resolve, reject) => navigator.getUserMedia({
|
|
video: true,
|
|
audio: true,
|
|
}, x => resolve({ok: x instanceof MediaStream}), e => reject({ok: false, message: e.message})))
|
|
`);
|
|
expect(ok).to.be.true(message);
|
|
});
|
|
|
|
it('is not called when calling legacy getUserMedia with desktop capture constraint', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
ses.setDisplayMediaRequestHandler(() => {
|
|
throw new Error('bad');
|
|
});
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
new Promise((resolve, reject) => navigator.getUserMedia({
|
|
video: {
|
|
mandatory: {
|
|
chromeMediaSource: 'desktop'
|
|
}
|
|
},
|
|
}, x => resolve({ok: x instanceof MediaStream}), e => reject({ok: false, message: e.message})))
|
|
`);
|
|
expect(ok).to.be.true(message);
|
|
});
|
|
|
|
it('works when calling getUserMedia without a media request handler', async () => {
|
|
const w = new BrowserWindow({ show: false });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getUserMedia({
|
|
video: true,
|
|
audio: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`);
|
|
expect(ok).to.be.true(message);
|
|
});
|
|
|
|
it('works when calling legacy getUserMedia without a media request handler', async () => {
|
|
const w = new BrowserWindow({ show: false });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
new Promise((resolve, reject) => navigator.getUserMedia({
|
|
video: true,
|
|
audio: true,
|
|
}, x => resolve({ok: x instanceof MediaStream}), e => reject({ok: false, message: e.message})))
|
|
`);
|
|
expect(ok).to.be.true(message);
|
|
});
|
|
|
|
it('can remove a displayMediaRequestHandler', async () => {
|
|
const ses = session.fromPartition('' + Math.random());
|
|
|
|
ses.setDisplayMediaRequestHandler(() => {
|
|
throw new Error('bad');
|
|
});
|
|
ses.setDisplayMediaRequestHandler(null);
|
|
const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
|
|
await w.loadURL(serverUrl);
|
|
const { ok, message } = await w.webContents.executeJavaScript(`
|
|
navigator.mediaDevices.getDisplayMedia({
|
|
video: true,
|
|
}).then(x => ({ok: x instanceof MediaStream}), e => ({ok: false, message: e.message}))
|
|
`, true);
|
|
expect(ok).to.be.false();
|
|
expect(message).to.equal('Not supported');
|
|
});
|
|
});
|