feat: make desktopCapturer main-process-only (#30720)

* feat: make desktopCapturer main-process-only

* remove --enable-api-filtering-logging

* remove test

* merge lib/browser/api/desktop-capturer.ts with lib/browser/desktop-capturer.ts

* remove desktop-capturer-get-sources event

* fix specs

* getSources needs to be async

Co-authored-by: Milan Burda <milan.burda@gmail.com>
This commit is contained in:
Jeremy Rose 2021-10-03 20:16:00 -07:00 committed by GitHub
parent 6db8d7918d
commit 4fd7c2adcd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 149 additions and 312 deletions

View file

@ -500,16 +500,6 @@ gets emitted.
**Note:** Extra command line arguments might be added by Chromium, **Note:** Extra command line arguments might be added by Chromium,
such as `--original-process-start-time`. such as `--original-process-start-time`.
### Event: 'desktop-capturer-get-sources'
Returns:
* `event` Event
* `webContents` [WebContents](web-contents.md)
Emitted when `desktopCapturer.getSources()` is called in the renderer process of `webContents`.
Calling `event.preventDefault()` will make it return empty sources.
## Methods ## Methods
The `app` object has the following methods: The `app` object has the following methods:

View file

@ -61,12 +61,6 @@ throttling in one window, you can take the hack of
Forces the maximum disk space to be used by the disk cache, in bytes. Forces the maximum disk space to be used by the disk cache, in bytes.
### --enable-api-filtering-logging
Enables caller stack logging for the following APIs (filtering events):
* `desktopCapturer.getSources()` / `desktop-capturer-get-sources`
### --enable-logging[=file] ### --enable-logging[=file]
Prints Chromium's logging to stderr (or a log file). Prints Chromium's logging to stderr (or a log file).

View file

@ -3,25 +3,37 @@
> Access information about media sources that can be used to capture audio and > Access information about media sources that can be used to capture audio and
> video from the desktop using the [`navigator.mediaDevices.getUserMedia`] API. > video from the desktop using the [`navigator.mediaDevices.getUserMedia`] API.
Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) Process: [Main](../glossary.md#main-process)
The following example shows how to capture video from a desktop window whose The following example shows how to capture video from a desktop window whose
title is `Electron`: title is `Electron`:
```javascript ```javascript
// In the renderer process. // In the main process.
const { desktopCapturer } = require('electron') const { desktopCapturer } = require('electron')
desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources => { desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources => {
for (const source of sources) { for (const source of sources) {
if (source.name === 'Electron') { if (source.name === 'Electron') {
mainWindow.webContents.send('SET_SOURCE', source.id)
return
}
}
})
```
```javascript
// In the preload script.
const { ipcRenderer } = require('electron')
ipcRenderer.on('SET_SOURCE', async (event, sourceId) => {
try { try {
const stream = await navigator.mediaDevices.getUserMedia({ const stream = await navigator.mediaDevices.getUserMedia({
audio: false, audio: false,
video: { video: {
mandatory: { mandatory: {
chromeMediaSource: 'desktop', chromeMediaSource: 'desktop',
chromeMediaSourceId: source.id, chromeMediaSourceId: sourceId,
minWidth: 1280, minWidth: 1280,
maxWidth: 1280, maxWidth: 1280,
minHeight: 720, minHeight: 720,
@ -33,9 +45,6 @@ desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources =
} catch (e) { } catch (e) {
handleError(e) handleError(e)
} }
return
}
}
}) })
function handleStream (stream) { function handleStream (stream) {

View file

@ -856,15 +856,6 @@ Returns:
Emitted when the renderer process sends a synchronous message via `ipcRenderer.sendSync()`. Emitted when the renderer process sends a synchronous message via `ipcRenderer.sendSync()`.
#### Event: 'desktop-capturer-get-sources'
Returns:
* `event` Event
Emitted when `desktopCapturer.getSources()` is called in the renderer process.
Calling `event.preventDefault()` will make it return empty sources.
#### Event: 'preferred-size-changed' #### Event: 'preferred-size-changed'
Returns: Returns:

View file

@ -142,7 +142,6 @@ auto_filenames = {
"lib/common/web-view-methods.ts", "lib/common/web-view-methods.ts",
"lib/renderer/api/context-bridge.ts", "lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts", "lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/desktop-capturer.ts",
"lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/web-frame.ts", "lib/renderer/api/web-frame.ts",
"lib/renderer/inspector.ts", "lib/renderer/inspector.ts",
@ -224,7 +223,6 @@ auto_filenames = {
"lib/browser/api/web-contents.ts", "lib/browser/api/web-contents.ts",
"lib/browser/api/web-frame-main.ts", "lib/browser/api/web-frame-main.ts",
"lib/browser/default-menu.ts", "lib/browser/default-menu.ts",
"lib/browser/desktop-capturer.ts",
"lib/browser/devtools.ts", "lib/browser/devtools.ts",
"lib/browser/guest-view-manager.ts", "lib/browser/guest-view-manager.ts",
"lib/browser/guest-window-manager.ts", "lib/browser/guest-window-manager.ts",
@ -271,7 +269,6 @@ auto_filenames = {
"lib/common/webpack-provider.ts", "lib/common/webpack-provider.ts",
"lib/renderer/api/context-bridge.ts", "lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts", "lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/desktop-capturer.ts",
"lib/renderer/api/exports/electron.ts", "lib/renderer/api/exports/electron.ts",
"lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts", "lib/renderer/api/module-list.ts",
@ -309,7 +306,6 @@ auto_filenames = {
"lib/common/webpack-provider.ts", "lib/common/webpack-provider.ts",
"lib/renderer/api/context-bridge.ts", "lib/renderer/api/context-bridge.ts",
"lib/renderer/api/crash-reporter.ts", "lib/renderer/api/crash-reporter.ts",
"lib/renderer/api/desktop-capturer.ts",
"lib/renderer/api/exports/electron.ts", "lib/renderer/api/exports/electron.ts",
"lib/renderer/api/ipc-renderer.ts", "lib/renderer/api/ipc-renderer.ts",
"lib/renderer/api/module-list.ts", "lib/renderer/api/module-list.ts",

View file

@ -1,5 +1,72 @@
import { getSourcesImpl } from '@electron/internal/browser/desktop-capturer'; const { createDesktopCapturer } = process._linkedBinding('electron_browser_desktop_capturer');
export async function getSources (options: Electron.SourcesOptions) { const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
return getSourcesImpl(null, options);
let currentlyRunning: {
options: ElectronInternal.GetSourcesOptions;
getSources: Promise<ElectronInternal.GetSourcesResult[]>;
}[] = [];
// |options.types| can't be empty and must be an array
function isValid (options: Electron.SourcesOptions) {
const types = options ? options.types : undefined;
return Array.isArray(types);
}
export async function getSources (args: Electron.SourcesOptions) {
if (!isValid(args)) throw new Error('Invalid options');
const captureWindow = args.types.includes('window');
const captureScreen = args.types.includes('screen');
const { thumbnailSize = { width: 150, height: 150 } } = args;
const { fetchWindowIcons = false } = args;
const options = {
captureWindow,
captureScreen,
thumbnailSize,
fetchWindowIcons
};
for (const running of currentlyRunning) {
if (deepEqual(running.options, options)) {
// If a request is currently running for the same options
// return that promise
return running.getSources;
}
}
const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => {
let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer();
const stopRunning = () => {
if (capturer) {
delete capturer._onerror;
delete capturer._onfinished;
capturer = null;
}
// Remove from currentlyRunning once we resolve or reject
currentlyRunning = currentlyRunning.filter(running => running.options !== options);
};
capturer._onerror = (error: string) => {
stopRunning();
reject(error);
};
capturer._onfinished = (sources: Electron.DesktopCapturerSource[]) => {
stopRunning();
resolve(sources);
};
capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons);
});
currentlyRunning.push({
options,
getSources
});
return getSources;
} }

View file

@ -1,82 +0,0 @@
const { createDesktopCapturer } = process._linkedBinding('electron_browser_desktop_capturer');
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
let currentlyRunning: {
options: ElectronInternal.GetSourcesOptions;
getSources: Promise<ElectronInternal.GetSourcesResult[]>;
}[] = [];
// |options.types| can't be empty and must be an array
function isValid (options: Electron.SourcesOptions) {
const types = options ? options.types : undefined;
return Array.isArray(types);
}
export const getSourcesImpl = (sender: Electron.WebContents | null, args: Electron.SourcesOptions) => {
if (!isValid(args)) throw new Error('Invalid options');
const captureWindow = args.types.includes('window');
const captureScreen = args.types.includes('screen');
const { thumbnailSize = { width: 150, height: 150 } } = args;
const { fetchWindowIcons = false } = args;
const options = {
captureWindow,
captureScreen,
thumbnailSize,
fetchWindowIcons
};
for (const running of currentlyRunning) {
if (deepEqual(running.options, options)) {
// If a request is currently running for the same options
// return that promise
return running.getSources;
}
}
const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => {
let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer();
const stopRunning = () => {
if (capturer) {
delete capturer._onerror;
delete capturer._onfinished;
capturer = null;
}
// Remove from currentlyRunning once we resolve or reject
currentlyRunning = currentlyRunning.filter(running => running.options !== options);
if (sender) {
sender.removeListener('destroyed', stopRunning);
}
};
capturer._onerror = (error: string) => {
stopRunning();
reject(error);
};
capturer._onfinished = (sources: Electron.DesktopCapturerSource[]) => {
stopRunning();
resolve(sources);
};
capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons);
// If the WebContents is destroyed before receiving result, just remove the
// reference to emit and the capturer itself so that it never dispatches
// back to the renderer
if (sender) {
sender.once('destroyed', stopRunning);
}
});
currentlyRunning.push({
options,
getSources
});
return getSources;
};

View file

@ -1,30 +1,9 @@
import { app } from 'electron/main';
import type { WebContents } from 'electron/main';
import { clipboard } from 'electron/common'; import { clipboard } from 'electron/common';
import * as fs from 'fs'; import * as fs from 'fs';
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'; import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils'; import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages'; import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
import type * as desktopCapturerModule from '@electron/internal/browser/desktop-capturer';
const eventBinding = process._linkedBinding('electron_browser_event');
const emitCustomEvent = function (contents: WebContents, eventName: string, ...args: any[]) {
const event = eventBinding.createWithSender(contents);
app.emit(eventName, event, contents, ...args);
contents.emit(eventName, event, ...args);
return event;
};
const logStack = function (contents: WebContents, code: string, stack: string) {
if (stack) {
console.warn(`WebContents (${contents.id}): ${code}`, stack);
}
};
// Implements window.close() // Implements window.close()
ipcMainInternal.on(IPC_MESSAGES.BROWSER_WINDOW_CLOSE, function (event) { ipcMainInternal.on(IPC_MESSAGES.BROWSER_WINDOW_CLOSE, function (event) {
const window = event.sender.getOwnerBrowserWindow(); const window = event.sender.getOwnerBrowserWindow();
@ -58,22 +37,6 @@ ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_CLIPBOARD_SYNC, function (event, me
return (clipboard as any)[method](...args); return (clipboard as any)[method](...args);
}); });
if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
const desktopCapturer = require('@electron/internal/browser/desktop-capturer') as typeof desktopCapturerModule;
ipcMainInternal.handle(IPC_MESSAGES.DESKTOP_CAPTURER_GET_SOURCES, async function (event, options: Electron.SourcesOptions, stack: string) {
logStack(event.sender, 'desktopCapturer.getSources()', stack);
const customEvent = emitCustomEvent(event.sender, 'desktop-capturer-get-sources');
if (customEvent.defaultPrevented) {
console.error('Blocked desktopCapturer.getSources()');
return [];
}
return await desktopCapturer.getSourcesImpl(event.sender, options);
});
}
const getPreloadScript = async function (preloadPath: string) { const getPreloadScript = async function (preloadPath: string) {
let preloadSrc = null; let preloadSrc = null;
let preloadError = null; let preloadError = null;

View file

@ -28,6 +28,4 @@ export const enum IPC_MESSAGES {
INSPECTOR_CONFIRM = 'INSPECTOR_CONFIRM', INSPECTOR_CONFIRM = 'INSPECTOR_CONFIRM',
INSPECTOR_CONTEXT_MENU = 'INSPECTOR_CONTEXT_MENU', INSPECTOR_CONTEXT_MENU = 'INSPECTOR_CONTEXT_MENU',
INSPECTOR_SELECT_FILE = 'INSPECTOR_SELECT_FILE', INSPECTOR_SELECT_FILE = 'INSPECTOR_SELECT_FILE',
DESKTOP_CAPTURER_GET_SOURCES = 'DESKTOP_CAPTURER_GET_SOURCES',
} }

View file

@ -1,24 +0,0 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import deprecate from '@electron/internal/common/api/deprecate';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
const { hasSwitch } = process._linkedBinding('electron_common_command_line');
const enableStacks = hasSwitch('enable-api-filtering-logging');
function getCurrentStack () {
const target = {};
if (enableStacks) {
Error.captureStackTrace(target, getCurrentStack);
}
return (target as any).stack;
}
let warned = process.noDeprecation;
export async function getSources (options: Electron.SourcesOptions) {
if (!warned) {
deprecate.log('The use of \'desktopCapturer.getSources\' in the renderer process is deprecated and will be removed. See https://www.electronjs.org/docs/breaking-changes#removed-desktopcapturergetsources-in-the-renderer for more details.');
warned = true;
}
return await ipcRendererInternal.invoke(IPC_MESSAGES.DESKTOP_CAPTURER_GET_SOURCES, options, getCurrentStack());
}

View file

@ -5,10 +5,3 @@ export const rendererModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'ipcRenderer', loader: () => require('./ipc-renderer') }, { name: 'ipcRenderer', loader: () => require('./ipc-renderer') },
{ name: 'webFrame', loader: () => require('./web-frame') } { name: 'webFrame', loader: () => require('./web-frame') }
]; ];
if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
rendererModuleList.push({
name: 'desktopCapturer',
loader: () => require('@electron/internal/renderer/api/desktop-capturer')
});
}

View file

@ -26,10 +26,3 @@ export const moduleList: ElectronInternal.ModuleEntry[] = [
private: true private: true
} }
]; ];
if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
moduleList.push({
name: 'desktopCapturer',
loader: () => require('@electron/internal/renderer/api/desktop-capturer')
});
}

View file

@ -592,8 +592,7 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches(
switches::kStandardSchemes, switches::kEnableSandbox, switches::kStandardSchemes, switches::kEnableSandbox,
switches::kSecureSchemes, switches::kBypassCSPSchemes, switches::kSecureSchemes, switches::kBypassCSPSchemes,
switches::kCORSSchemes, switches::kFetchSchemes, switches::kCORSSchemes, switches::kFetchSchemes,
switches::kServiceWorkerSchemes, switches::kEnableApiFilteringLogging, switches::kServiceWorkerSchemes, switches::kStreamingSchemes};
switches::kStreamingSchemes};
command_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(), command_line->CopySwitchesFrom(*base::CommandLine::ForCurrentProcess(),
kCommonSwitchNames, kCommonSwitchNames,
base::size(kCommonSwitchNames)); base::size(kCommonSwitchNames));

View file

@ -241,8 +241,6 @@ const char kAppUserModelId[] = "app-user-model-id";
// The application path // The application path
const char kAppPath[] = "app-path"; const char kAppPath[] = "app-path";
const char kEnableApiFilteringLogging[] = "enable-api-filtering-logging";
// The command line switch versions of the options. // The command line switch versions of the options.
const char kScrollBounce[] = "scroll-bounce"; const char kScrollBounce[] = "scroll-bounce";

View file

@ -118,7 +118,6 @@ extern const char kCORSSchemes[];
extern const char kStreamingSchemes[]; extern const char kStreamingSchemes[];
extern const char kAppUserModelId[]; extern const char kAppUserModelId[];
extern const char kAppPath[]; extern const char kAppPath[];
extern const char kEnableApiFilteringLogging[];
extern const char kScrollBounce[]; extern const char kScrollBounce[];
extern const char kNodeIntegrationInWorker[]; extern const char kNodeIntegrationInWorker[];

View file

@ -12,8 +12,6 @@ import { closeWindow, closeAllWindows } from './window-helpers';
import { ifdescribe, ifit } from './spec-helpers'; import { ifdescribe, ifit } from './spec-helpers';
import split = require('split') import split = require('split')
const features = process._linkedBinding('electron_common_features');
const fixturesPath = path.resolve(__dirname, '../spec/fixtures'); const fixturesPath = path.resolve(__dirname, '../spec/fixtures');
describe('electron module', () => { describe('electron module', () => {
@ -462,25 +460,6 @@ describe('app module', () => {
expect(webContents).to.equal(w.webContents); expect(webContents).to.equal(w.webContents);
expect(details.reason).to.be.oneOf(['crashed', 'abnormal-exit']); expect(details.reason).to.be.oneOf(['crashed', 'abnormal-exit']);
}); });
ifdescribe(features.isDesktopCapturerEnabled())('desktopCapturer module filtering', () => {
it('should emit desktop-capturer-get-sources event when desktopCapturer.getSources() is invoked', async () => {
w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false
}
});
await w.loadURL('about:blank');
const promise = emittedOnce(app, 'desktop-capturer-get-sources');
w.webContents.executeJavaScript('require(\'electron\').desktopCapturer.getSources({ types: [\'screen\'] })');
const [, webContents] = await promise;
expect(webContents).to.equal(w.webContents);
});
});
}); });
describe('app.badgeCount', () => { describe('app.badgeCount', () => {

View file

@ -1,6 +1,5 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { screen, BrowserWindow, SourcesOptions } from 'electron/main'; import { screen, desktopCapturer, BrowserWindow } from 'electron/main';
import { desktopCapturer } from 'electron/common';
import { emittedOnce } from './events-helpers'; import { emittedOnce } from './events-helpers';
import { ifdescribe, ifit } from './spec-helpers'; import { ifdescribe, ifit } from './spec-helpers';
import { closeAllWindows } from './window-helpers'; import { closeAllWindows } from './window-helpers';
@ -23,39 +22,31 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
after(closeAllWindows); after(closeAllWindows);
const getSources: typeof desktopCapturer.getSources = (options: SourcesOptions) => {
return w.webContents.executeJavaScript(`
require('electron').desktopCapturer.getSources(${JSON.stringify(options)}).then(m => JSON.parse(JSON.stringify(m)))
`);
};
const generateSpecs = (description: string, getSources: typeof desktopCapturer.getSources) => {
describe(description, () => {
// TODO(nornagon): figure out why this test is failing on Linux and re-enable it. // TODO(nornagon): figure out why this test is failing on Linux and re-enable it.
ifit(process.platform !== 'linux')('should return a non-empty array of sources', async () => { ifit(process.platform !== 'linux')('should return a non-empty array of sources', async () => {
const sources = await getSources({ types: ['window', 'screen'] }); const sources = await desktopCapturer.getSources({ types: ['window', 'screen'] });
expect(sources).to.be.an('array').that.is.not.empty(); expect(sources).to.be.an('array').that.is.not.empty();
}); });
it('throws an error for invalid options', async () => { it('throws an error for invalid options', async () => {
const promise = getSources(['window', 'screen'] as any); const promise = desktopCapturer.getSources(['window', 'screen'] as any);
await expect(promise).to.be.eventually.rejectedWith(Error, 'Invalid options'); await expect(promise).to.be.eventually.rejectedWith(Error, 'Invalid options');
}); });
// TODO(nornagon): figure out why this test is failing on Linux and re-enable it. // TODO(nornagon): figure out why this test is failing on Linux and re-enable it.
ifit(process.platform !== 'linux')('does not throw an error when called more than once (regression)', async () => { ifit(process.platform !== 'linux')('does not throw an error when called more than once (regression)', async () => {
const sources1 = await getSources({ types: ['window', 'screen'] }); const sources1 = await desktopCapturer.getSources({ types: ['window', 'screen'] });
expect(sources1).to.be.an('array').that.is.not.empty(); expect(sources1).to.be.an('array').that.is.not.empty();
const sources2 = await getSources({ types: ['window', 'screen'] }); const sources2 = await desktopCapturer.getSources({ types: ['window', 'screen'] });
expect(sources2).to.be.an('array').that.is.not.empty(); expect(sources2).to.be.an('array').that.is.not.empty();
}); });
ifit(process.platform !== 'linux')('responds to subsequent calls of different options', async () => { ifit(process.platform !== 'linux')('responds to subsequent calls of different options', async () => {
const promise1 = getSources({ types: ['window'] }); const promise1 = desktopCapturer.getSources({ types: ['window'] });
await expect(promise1).to.eventually.be.fulfilled(); await expect(promise1).to.eventually.be.fulfilled();
const promise2 = getSources({ types: ['screen'] }); const promise2 = desktopCapturer.getSources({ types: ['screen'] });
await expect(promise2).to.eventually.be.fulfilled(); await expect(promise2).to.eventually.be.fulfilled();
}); });
@ -64,7 +55,7 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
const w = new BrowserWindow({ width: 200, height: 200 }); const w = new BrowserWindow({ width: 200, height: 200 });
await w.loadURL('about:blank'); await w.loadURL('about:blank');
const sources = await getSources({ types: ['window'] }); const sources = await desktopCapturer.getSources({ types: ['window'] });
w.destroy(); w.destroy();
expect(sources).to.be.an('array').that.is.not.empty(); expect(sources).to.be.an('array').that.is.not.empty();
for (const { display_id: displayId } of sources) { for (const { display_id: displayId } of sources) {
@ -74,26 +65,13 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
ifit(process.platform !== 'linux')('returns display_ids matching the Screen API on Windows and Mac', async () => { ifit(process.platform !== 'linux')('returns display_ids matching the Screen API on Windows and Mac', async () => {
const displays = screen.getAllDisplays(); const displays = screen.getAllDisplays();
const sources = await getSources({ types: ['screen'] }); const sources = await desktopCapturer.getSources({ types: ['screen'] });
expect(sources).to.be.an('array').of.length(displays.length); expect(sources).to.be.an('array').of.length(displays.length);
for (let i = 0; i < sources.length; i++) { for (let i = 0; i < sources.length; i++) {
expect(sources[i].display_id).to.equal(displays[i].id.toString()); expect(sources[i].display_id).to.equal(displays[i].id.toString());
} }
}); });
});
};
generateSpecs('in renderer process', getSources);
generateSpecs('in main process', desktopCapturer.getSources);
ifit(process.platform !== 'linux')('returns an empty source list if blocked by the main process', async () => {
w.webContents.once('desktop-capturer-get-sources', (event) => {
event.preventDefault();
});
const sources = await getSources({ types: ['screen'] });
expect(sources).to.be.empty();
});
it('disabling thumbnail should return empty images', async () => { it('disabling thumbnail should return empty images', async () => {
const w2 = new BrowserWindow({ show: false, width: 200, height: 200, webPreferences: { contextIsolation: false } }); const w2 = new BrowserWindow({ show: false, width: 200, height: 200, webPreferences: { contextIsolation: false } });
@ -101,14 +79,10 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
w2.show(); w2.show();
await wShown; await wShown;
const isEmpties: boolean[] = await w.webContents.executeJavaScript(` const isEmpties: boolean[] = (await desktopCapturer.getSources({
require('electron').desktopCapturer.getSources({
types: ['window', 'screen'], types: ['window', 'screen'],
thumbnailSize: { width: 0, height: 0 } thumbnailSize: { width: 0, height: 0 }
}).then((sources) => { })).map(s => s.thumbnail.constructor.name === 'NativeImage' && s.thumbnail.isEmpty());
return sources.map(s => s.thumbnail.constructor.name === 'NativeImage' && s.thumbnail.isEmpty())
})
`);
w2.destroy(); w2.destroy();
expect(isEmpties).to.be.an('array').that.is.not.empty(); expect(isEmpties).to.be.an('array').that.is.not.empty();
@ -125,7 +99,7 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
await wFocused; await wFocused;
const mediaSourceId = w.getMediaSourceId(); const mediaSourceId = w.getMediaSourceId();
const sources = await getSources({ const sources = await desktopCapturer.getSources({
types: ['window'], types: ['window'],
thumbnailSize: { width: 0, height: 0 } thumbnailSize: { width: 0, height: 0 }
}); });
@ -161,7 +135,7 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
const ids = mediaSourceId.split(':'); const ids = mediaSourceId.split(':');
expect(ids[1]).to.not.equal(ids[2]); expect(ids[1]).to.not.equal(ids[2]);
const sources = await getSources({ const sources = await desktopCapturer.getSources({
types: ['window'], types: ['window'],
thumbnailSize: { width: 0, height: 0 } thumbnailSize: { width: 0, height: 0 }
}); });
@ -206,7 +180,7 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
// DesktopCapturer.getSources() returns sources sorted from foreground to // DesktopCapturer.getSources() returns sources sorted from foreground to
// background, i.e. top to bottom. // background, i.e. top to bottom.
let sources = await getSources({ let sources = await desktopCapturer.getSources({
types: ['window'], types: ['window'],
thumbnailSize: { width: 0, height: 0 } thumbnailSize: { width: 0, height: 0 }
}); });
@ -252,7 +226,7 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
} }
}); });
sources = await getSources({ sources = await desktopCapturer.getSources({
types: ['window'], types: ['window'],
thumbnailSize: { width: 0, height: 0 } thumbnailSize: { width: 0, height: 0 }
}); });