feat: expose the desktopCapturer module in the main process (#23548)

This commit is contained in:
Milan Burda 2020-05-21 02:25:49 +02:00 коммит произвёл GitHub
Родитель 4b23a85475
Коммит df53816eea
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 95 добавлений и 68 удалений

Просмотреть файл

@ -46,6 +46,7 @@ const ignoredModules = []
if (defines['ENABLE_DESKTOP_CAPTURER'] === 'false') {
ignoredModules.push(
'@electron/internal/browser/desktop-capturer',
'@electron/internal/browser/api/desktop-capturer',
'@electron/internal/renderer/api/desktop-capturer'
)
}

Просмотреть файл

@ -3,7 +3,7 @@
> Access information about media sources that can be used to capture audio and
> video from the desktop using the [`navigator.mediaDevices.getUserMedia`] API.
Process: [Renderer](../glossary.md#renderer-process)
Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process)
The following example shows how to capture video from a desktop window whose
title is `Electron`:

Просмотреть файл

@ -193,6 +193,7 @@ auto_filenames = {
"lib/browser/api/browser-window.js",
"lib/browser/api/content-tracing.js",
"lib/browser/api/crash-reporter.ts",
"lib/browser/api/desktop-capturer.ts",
"lib/browser/api/dialog.js",
"lib/browser/api/exports/electron.ts",
"lib/browser/api/global-shortcut.js",

Просмотреть файл

@ -0,0 +1,5 @@
import { getSourcesImpl } from '@electron/internal/browser/desktop-capturer';
export async function getSources (options: Electron.SourcesOptions) {
return getSourcesImpl(null, options);
}

Просмотреть файл

@ -33,6 +33,12 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'WebContentsView', loader: () => require('./web-contents-view') }
];
if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
browserModuleList.push(
{ name: 'desktopCapturer', loader: () => require('./desktop-capturer') }
);
}
if (BUILDFLAG(ENABLE_VIEWS_API)) {
browserModuleList.push(
{ name: 'ImageView', loader: () => require('./views/image-view') }

Просмотреть файл

@ -36,6 +36,10 @@ export const browserModuleNames = [
'WebContentsView'
];
if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
browserModuleNames.push('desktopCapturer');
}
if (BUILDFLAG(ENABLE_VIEWS_API)) {
browserModuleNames.push(
'ImageView'

Просмотреть файл

@ -7,7 +7,28 @@ let currentlyRunning: {
getSources: Promise<ElectronInternal.GetSourcesResult[]>;
}[] = [];
export const getSources = (event: Electron.IpcMainEvent, options: ElectronInternal.GetSourcesOptions) => {
// |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 = (event: Electron.IpcMainEvent | 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
@ -39,12 +60,14 @@ export const getSources = (event: Electron.IpcMainEvent, options: ElectronIntern
resolve(sources);
};
capturer.startHandling(options.captureWindow, options.captureScreen, options.thumbnailSize, options.fetchWindowIcons);
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
event.sender.once('destroyed', () => stopRunning());
if (event) {
event.sender.once('destroyed', () => stopRunning());
}
});
currentlyRunning.push({

Просмотреть файл

@ -71,7 +71,7 @@ if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
return [];
}
return typeUtils.serialize(await desktopCapturer.getSources(event, options));
return typeUtils.serialize(await desktopCapturer.getSourcesImpl(event, options));
});
}

Просмотреть файл

@ -3,12 +3,6 @@ import { deserialize } from '@electron/internal/common/type-utils';
const { hasSwitch } = process.electronBinding('command_line');
// |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);
}
const enableStacks = hasSwitch('enable-api-filtering-logging');
function getCurrentStack () {
@ -20,20 +14,5 @@ function getCurrentStack () {
}
export async function getSources (options: Electron.SourcesOptions) {
if (!isValid(options)) throw new Error('Invalid options');
const captureWindow = options.types.includes('window');
const captureScreen = options.types.includes('screen');
const { thumbnailSize = { width: 150, height: 150 } } = options;
const { fetchWindowIcons = false } = options;
const sources = await ipcRendererInternal.invoke<ElectronInternal.GetSourcesResult[]>('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', {
captureWindow,
captureScreen,
thumbnailSize,
fetchWindowIcons
} as ElectronInternal.GetSourcesOptions, getCurrentStack());
return deserialize(sources);
return deserialize(await ipcRendererInternal.invoke('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', options, getCurrentStack()));
}

Просмотреть файл

@ -1,6 +1,6 @@
import { expect } from 'chai';
import { screen, BrowserWindow, SourcesOptions } from 'electron/main';
import { desktopCapturer } from 'electron/renderer';
import { desktopCapturer } from 'electron/common';
import { emittedOnce } from './events-helpers';
import { ifdescribe, ifit } from './spec-helpers';
import { closeAllWindows } from './window-helpers';
@ -21,55 +21,63 @@ ifdescribe(features.isDesktopCapturerEnabled() && !process.arch.includes('arm')
`);
};
// 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 () => {
const sources = await getSources({ types: ['window', 'screen'] });
expect(sources).to.be.an('array').that.is.not.empty();
});
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.
ifit(process.platform !== 'linux')('should return a non-empty array of sources', async () => {
const sources = await getSources({ types: ['window', 'screen'] });
expect(sources).to.be.an('array').that.is.not.empty();
});
it('throws an error for invalid options', async () => {
const promise = getSources(['window', 'screen'] as any);
await expect(promise).to.be.eventually.rejectedWith(Error, 'Invalid options');
});
it('throws an error for invalid options', async () => {
const promise = getSources(['window', 'screen'] as any);
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.
ifit(process.platform !== 'linux')('does not throw an error when called more than once (regression)', async () => {
const sources1 = await getSources({ types: ['window', 'screen'] });
expect(sources1).to.be.an('array').that.is.not.empty();
// 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 () => {
const sources1 = await getSources({ types: ['window', 'screen'] });
expect(sources1).to.be.an('array').that.is.not.empty();
const sources2 = await getSources({ types: ['window', 'screen'] });
expect(sources2).to.be.an('array').that.is.not.empty();
});
const sources2 = await getSources({ types: ['window', 'screen'] });
expect(sources2).to.be.an('array').that.is.not.empty();
});
ifit(process.platform !== 'linux')('responds to subsequent calls of different options', async () => {
const promise1 = getSources({ types: ['window'] });
await expect(promise1).to.eventually.be.fulfilled();
ifit(process.platform !== 'linux')('responds to subsequent calls of different options', async () => {
const promise1 = getSources({ types: ['window'] });
await expect(promise1).to.eventually.be.fulfilled();
const promise2 = getSources({ types: ['screen'] });
await expect(promise2).to.eventually.be.fulfilled();
});
const promise2 = getSources({ types: ['screen'] });
await expect(promise2).to.eventually.be.fulfilled();
});
// Linux doesn't return any window sources.
ifit(process.platform !== 'linux')('returns an empty display_id for window sources on Windows and Mac', async () => {
const w = new BrowserWindow({ width: 200, height: 200 });
// Linux doesn't return any window sources.
ifit(process.platform !== 'linux')('returns an empty display_id for window sources on Windows and Mac', async () => {
const w = new BrowserWindow({ width: 200, height: 200 });
await w.loadURL('about:blank');
const sources = await getSources({ types: ['window'] });
w.destroy();
expect(sources).to.be.an('array').that.is.not.empty();
for (const { display_id: displayId } of sources) {
expect(displayId).to.be.a('string').and.be.empty();
}
});
const sources = await getSources({ types: ['window'] });
w.destroy();
expect(sources).to.be.an('array').that.is.not.empty();
for (const { display_id: displayId } of sources) {
expect(displayId).to.be.a('string').and.be.empty();
}
});
ifit(process.platform !== 'linux')('returns display_ids matching the Screen API on Windows and Mac', async () => {
const displays = screen.getAllDisplays();
const sources = await getSources({ types: ['screen'] });
expect(sources).to.be.an('array').of.length(displays.length);
ifit(process.platform !== 'linux')('returns display_ids matching the Screen API on Windows and Mac', async () => {
const displays = screen.getAllDisplays();
const sources = await getSources({ types: ['screen'] });
expect(sources).to.be.an('array').of.length(displays.length);
for (let i = 0; i < sources.length; i++) {
expect(sources[i].display_id).to.equal(displays[i].id.toString());
}
});
for (let i = 0; i < sources.length; i++) {
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) => {