Browse Source

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 <[email protected]>
Jeremy Rose 3 years ago
parent
commit
4fd7c2adcd

+ 0 - 10
docs/api/app.md

@@ -500,16 +500,6 @@ gets emitted.
 **Note:** Extra command line arguments might be added by Chromium,
 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
 
 The `app` object has the following methods:

+ 0 - 6
docs/api/command-line-switches.md

@@ -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.
 
-### --enable-api-filtering-logging
-
-Enables caller stack logging for the following APIs (filtering events):
-
-* `desktopCapturer.getSources()` / `desktop-capturer-get-sources`
-
 ### --enable-logging[=file]
 
 Prints Chromium's logging to stderr (or a log file).

+ 29 - 20
docs/api/desktop-capturer.md

@@ -3,40 +3,49 @@
 > Access information about media sources that can be used to capture audio and
 > 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
 title is `Electron`:
 
 ```javascript
-// In the renderer process.
+// In the main process.
 const { desktopCapturer } = require('electron')
 
 desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources => {
   for (const source of sources) {
     if (source.name === 'Electron') {
-      try {
-        const stream = await navigator.mediaDevices.getUserMedia({
-          audio: false,
-          video: {
-            mandatory: {
-              chromeMediaSource: 'desktop',
-              chromeMediaSourceId: source.id,
-              minWidth: 1280,
-              maxWidth: 1280,
-              minHeight: 720,
-              maxHeight: 720
-            }
-          }
-        })
-        handleStream(stream)
-      } catch (e) {
-        handleError(e)
-      }
+      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 {
+    const stream = await navigator.mediaDevices.getUserMedia({
+      audio: false,
+      video: {
+        mandatory: {
+          chromeMediaSource: 'desktop',
+          chromeMediaSourceId: sourceId,
+          minWidth: 1280,
+          maxWidth: 1280,
+          minHeight: 720,
+          maxHeight: 720
+        }
+      }
+    })
+    handleStream(stream)
+  } catch (e) {
+    handleError(e)
+  }
+})
 
 function handleStream (stream) {
   const video = document.querySelector('video')

+ 0 - 9
docs/api/web-contents.md

@@ -856,15 +856,6 @@ Returns:
 
 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'
 
 Returns:

+ 0 - 4
filenames.auto.gni

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

+ 70 - 3
lib/browser/api/desktop-capturer.ts

@@ -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) {
-  return getSourcesImpl(null, options);
+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 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;
 }

+ 0 - 82
lib/browser/desktop-capturer.ts

@@ -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;
-};

+ 0 - 37
lib/browser/rpc-server.ts

@@ -1,30 +1,9 @@
-import { app } from 'electron/main';
-import type { WebContents } from 'electron/main';
 import { clipboard } from 'electron/common';
 import * as fs from 'fs';
 import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
 import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
 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()
 ipcMainInternal.on(IPC_MESSAGES.BROWSER_WINDOW_CLOSE, function (event) {
   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);
 });
 
-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) {
   let preloadSrc = null;
   let preloadError = null;

+ 0 - 2
lib/common/ipc-messages.ts

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

+ 0 - 24
lib/renderer/api/desktop-capturer.ts

@@ -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());
-}

+ 0 - 7
lib/renderer/api/module-list.ts

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

+ 0 - 7
lib/sandboxed_renderer/api/module-list.ts

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

+ 1 - 2
shell/browser/electron_browser_client.cc

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

+ 0 - 2
shell/common/options_switches.cc

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

+ 0 - 1
shell/common/options_switches.h

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

+ 0 - 21
spec-main/api-app-spec.ts

@@ -12,8 +12,6 @@ import { closeWindow, closeAllWindows } from './window-helpers';
 import { ifdescribe, ifit } from './spec-helpers';
 import split = require('split')
 
-const features = process._linkedBinding('electron_common_features');
-
 const fixturesPath = path.resolve(__dirname, '../spec/fixtures');
 
 describe('electron module', () => {
@@ -462,25 +460,6 @@ describe('app module', () => {
       expect(webContents).to.equal(w.webContents);
       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', () => {

+ 49 - 75
spec-main/api-desktop-capturer-spec.ts

@@ -1,6 +1,5 @@
 import { expect } from 'chai';
-import { screen, BrowserWindow, SourcesOptions } from 'electron/main';
-import { desktopCapturer } from 'electron/common';
+import { screen, desktopCapturer, BrowserWindow } from 'electron/main';
 import { emittedOnce } from './events-helpers';
 import { ifdescribe, ifit } from './spec-helpers';
 import { closeAllWindows } from './window-helpers';
@@ -23,76 +22,55 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
 
   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.
-      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');
-      });
-
-      // 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')('should return a non-empty array of sources', async () => {
+    const sources = await desktopCapturer.getSources({ types: ['window', 'screen'] });
+    expect(sources).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();
-      });
+  it('throws an error for invalid options', async () => {
+    const promise = desktopCapturer.getSources(['window', 'screen'] as any);
+    await expect(promise).to.be.eventually.rejectedWith(Error, 'Invalid options');
+  });
 
-      ifit(process.platform !== 'linux')('responds to subsequent calls of different options', async () => {
-        const promise1 = getSources({ types: ['window'] });
-        await expect(promise1).to.eventually.be.fulfilled();
+  // 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 desktopCapturer.getSources({ types: ['window', 'screen'] });
+    expect(sources1).to.be.an('array').that.is.not.empty();
 
-        const promise2 = getSources({ types: ['screen'] });
-        await expect(promise2).to.eventually.be.fulfilled();
-      });
+    const sources2 = await desktopCapturer.getSources({ types: ['window', 'screen'] });
+    expect(sources2).to.be.an('array').that.is.not.empty();
+  });
 
-      // 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');
+  ifit(process.platform !== 'linux')('responds to subsequent calls of different options', async () => {
+    const promise1 = desktopCapturer.getSources({ types: ['window'] });
+    await expect(promise1).to.eventually.be.fulfilled();
 
-        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 promise2 = desktopCapturer.getSources({ types: ['screen'] });
+    await expect(promise2).to.eventually.be.fulfilled();
+  });
 
-      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);
+  // 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');
 
-        for (let i = 0; i < sources.length; i++) {
-          expect(sources[i].display_id).to.equal(displays[i].id.toString());
-        }
-      });
-    });
-  };
+    const sources = await desktopCapturer.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();
+    }
+  });
 
-  generateSpecs('in renderer process', getSources);
-  generateSpecs('in main process', desktopCapturer.getSources);
+  ifit(process.platform !== 'linux')('returns display_ids matching the Screen API on Windows and Mac', async () => {
+    const displays = screen.getAllDisplays();
+    const sources = await desktopCapturer.getSources({ types: ['screen'] });
+    expect(sources).to.be.an('array').of.length(displays.length);
 
-  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();
+    for (let i = 0; i < sources.length; i++) {
+      expect(sources[i].display_id).to.equal(displays[i].id.toString());
+    }
   });
 
   it('disabling thumbnail should return empty images', async () => {
@@ -101,14 +79,10 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
     w2.show();
     await wShown;
 
-    const isEmpties: boolean[] = await w.webContents.executeJavaScript(`
-      require('electron').desktopCapturer.getSources({
-        types: ['window', 'screen'],
-        thumbnailSize: { width: 0, height: 0 }
-      }).then((sources) => {
-        return sources.map(s => s.thumbnail.constructor.name === 'NativeImage' && s.thumbnail.isEmpty())
-      })
-    `);
+    const isEmpties: boolean[] = (await desktopCapturer.getSources({
+      types: ['window', 'screen'],
+      thumbnailSize: { width: 0, height: 0 }
+    })).map(s => s.thumbnail.constructor.name === 'NativeImage' && s.thumbnail.isEmpty());
 
     w2.destroy();
     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;
 
     const mediaSourceId = w.getMediaSourceId();
-    const sources = await getSources({
+    const sources = await desktopCapturer.getSources({
       types: ['window'],
       thumbnailSize: { width: 0, height: 0 }
     });
@@ -161,7 +135,7 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
     const ids = mediaSourceId.split(':');
     expect(ids[1]).to.not.equal(ids[2]);
 
-    const sources = await getSources({
+    const sources = await desktopCapturer.getSources({
       types: ['window'],
       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
       // background, i.e. top to bottom.
-      let sources = await getSources({
+      let sources = await desktopCapturer.getSources({
         types: ['window'],
         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'],
         thumbnailSize: { width: 0, height: 0 }
       });