Browse Source

test: use node helpers for events.once and setTimeout promise (#37374)

Jeremy Rose 2 years ago
parent
commit
a3e3efe4c4
47 changed files with 761 additions and 756 deletions
  1. 29 29
      spec/api-app-spec.ts
  2. 4 4
      spec/api-auto-updater-spec.ts
  3. 7 7
      spec/api-browser-view-spec.ts
  4. 142 134
      spec/api-browser-window-spec.ts
  5. 4 3
      spec/api-content-tracing-spec.ts
  6. 10 7
      spec/api-context-bridge-spec.ts
  7. 6 5
      spec/api-crash-reporter-spec.ts
  8. 6 5
      spec/api-debugger-spec.ts
  9. 11 10
      spec/api-desktop-capturer-spec.ts
  10. 4 3
      spec/api-dialog-spec.ts
  11. 2 2
      spec/api-ipc-main-spec.ts
  12. 6 6
      spec/api-ipc-renderer-spec.ts
  13. 22 23
      spec/api-ipc-spec.ts
  14. 9 8
      spec/api-menu-spec.ts
  15. 11 10
      spec/api-native-theme-spec.ts
  16. 4 4
      spec/api-net-log-spec.ts
  17. 25 24
      spec/api-net-spec.ts
  18. 4 4
      spec/api-notification-spec.ts
  19. 3 2
      spec/api-power-monitor-spec.ts
  20. 17 17
      spec/api-protocol-spec.ts
  21. 4 4
      spec/api-safe-storage-spec.ts
  22. 10 8
      spec/api-service-workers-spec.ts
  23. 13 12
      spec/api-session-spec.ts
  24. 2 2
      spec/api-shell-spec.ts
  25. 10 9
      spec/api-subframe-spec.ts
  26. 42 42
      spec/api-utility-process-spec.ts
  27. 75 74
      spec/api-web-contents-spec.ts
  28. 14 12
      spec/api-web-frame-main-spec.ts
  29. 3 3
      spec/api-web-frame-spec.ts
  30. 2 2
      spec/api-web-request-spec.ts
  31. 5 5
      spec/asar-spec.ts
  32. 3 3
      spec/autofill-spec.ts
  33. 84 82
      spec/chromium-spec.ts
  34. 19 18
      spec/extensions-spec.ts
  35. 2 0
      spec/fixtures/apps/remote-control/main.js
  36. 2 2
      spec/get-files.ts
  37. 3 3
      spec/guest-window-manager-spec.ts
  38. 10 42
      spec/lib/events-helpers.ts
  39. 1 2
      spec/lib/spec-helpers.ts
  40. 2 2
      spec/lib/window-helpers.ts
  41. 6 6
      spec/logging-spec.ts
  42. 3 3
      spec/modules-spec.ts
  43. 16 16
      spec/node-spec.ts
  44. 3 2
      spec/security-warnings-spec.ts
  45. 8 7
      spec/spellchecker-spec.ts
  46. 23 20
      spec/visibility-state-spec.ts
  47. 70 68
      spec/webview-spec.ts

+ 29 - 29
spec/api-app-spec.ts

@@ -7,9 +7,9 @@ import * as fs from 'fs-extra';
 import * as path from 'path';
 import { promisify } from 'util';
 import { app, BrowserWindow, Menu, session, net as electronNet } from 'electron/main';
-import { emittedOnce } from './lib/events-helpers';
 import { closeWindow, closeAllWindows } from './lib/window-helpers';
 import { ifdescribe, ifit, listen, waitUntil } from './lib/spec-helpers';
+import { once } from 'events';
 import split = require('split')
 
 const fixturesPath = path.resolve(__dirname, 'fixtures');
@@ -169,7 +169,7 @@ describe('app module', () => {
       if (appProcess && appProcess.stdout) {
         appProcess.stdout.on('data', data => { output += data; });
       }
-      const [code] = await emittedOnce(appProcess, 'exit');
+      const [code] = await once(appProcess, 'exit');
 
       if (process.platform !== 'win32') {
         expect(output).to.include('Exit event with code: 123');
@@ -182,7 +182,7 @@ describe('app module', () => {
       const electronPath = process.execPath;
 
       appProcess = cp.spawn(electronPath, [appPath]);
-      const [code, signal] = await emittedOnce(appProcess, 'exit');
+      const [code, signal] = await once(appProcess, 'exit');
 
       expect(signal).to.equal(null, 'exit signal should be null, if you see this please tag @MarshallOfSound');
       expect(code).to.equal(123, 'exit code should be 123, if you see this please tag @MarshallOfSound');
@@ -203,7 +203,7 @@ describe('app module', () => {
       if (appProcess && appProcess.stdout) {
         appProcess.stdout.on('data', () => appProcess!.kill());
       }
-      const [code, signal] = await emittedOnce(appProcess, 'exit');
+      const [code, signal] = await once(appProcess, 'exit');
 
       const message = `code:\n${code}\nsignal:\n${signal}`;
       expect(code).to.equal(0, message);
@@ -229,37 +229,37 @@ describe('app module', () => {
       this.timeout(120000);
       const appPath = path.join(fixturesPath, 'api', 'singleton-data');
       const first = cp.spawn(process.execPath, [appPath]);
-      await emittedOnce(first.stdout, 'data');
+      await once(first.stdout, 'data');
       // Start second app when received output.
       const second = cp.spawn(process.execPath, [appPath]);
-      const [code2] = await emittedOnce(second, 'exit');
+      const [code2] = await once(second, 'exit');
       expect(code2).to.equal(1);
-      const [code1] = await emittedOnce(first, 'exit');
+      const [code1] = await once(first, 'exit');
       expect(code1).to.equal(0);
     });
 
     it('returns true when setting non-existent user data folder', async function () {
       const appPath = path.join(fixturesPath, 'api', 'singleton-userdata');
       const instance = cp.spawn(process.execPath, [appPath]);
-      const [code] = await emittedOnce(instance, 'exit');
+      const [code] = await once(instance, 'exit');
       expect(code).to.equal(0);
     });
 
     async function testArgumentPassing (testArgs: SingleInstanceLockTestArgs) {
       const appPath = path.join(fixturesPath, 'api', 'singleton-data');
       const first = cp.spawn(process.execPath, [appPath, ...testArgs.args]);
-      const firstExited = emittedOnce(first, 'exit');
+      const firstExited = once(first, 'exit');
 
       // Wait for the first app to boot.
       const firstStdoutLines = first.stdout.pipe(split());
-      while ((await emittedOnce(firstStdoutLines, 'data')).toString() !== 'started') {
+      while ((await once(firstStdoutLines, 'data')).toString() !== 'started') {
         // wait.
       }
-      const additionalDataPromise = emittedOnce(firstStdoutLines, 'data');
+      const additionalDataPromise = once(firstStdoutLines, 'data');
 
       const secondInstanceArgs = [process.execPath, appPath, ...testArgs.args, '--some-switch', 'some-arg'];
       const second = cp.spawn(secondInstanceArgs[0], secondInstanceArgs.slice(1));
-      const secondExited = emittedOnce(second, 'exit');
+      const secondExited = once(second, 'exit');
 
       const [code2] = await secondExited;
       expect(code2).to.equal(1);
@@ -427,7 +427,7 @@ describe('app module', () => {
     it('is emitted when visiting a server with a self-signed cert', async () => {
       const w = new BrowserWindow({ show: false });
       w.loadURL(secureUrl);
-      await emittedOnce(app, 'certificate-error');
+      await once(app, 'certificate-error');
     });
 
     describe('when denied', () => {
@@ -444,7 +444,7 @@ describe('app module', () => {
       it('causes did-fail-load', async () => {
         const w = new BrowserWindow({ show: false });
         w.loadURL(secureUrl);
-        await emittedOnce(w.webContents, 'did-fail-load');
+        await once(w.webContents, 'did-fail-load');
       });
     });
   });
@@ -506,7 +506,7 @@ describe('app module', () => {
     afterEach(() => closeWindow(w).then(() => { w = null as any; }));
 
     it('should emit browser-window-focus event when window is focused', async () => {
-      const emitted = emittedOnce(app, 'browser-window-focus');
+      const emitted = once(app, 'browser-window-focus');
       w = new BrowserWindow({ show: false });
       w.emit('focus');
       const [, window] = await emitted;
@@ -514,7 +514,7 @@ describe('app module', () => {
     });
 
     it('should emit browser-window-blur event when window is blurred', async () => {
-      const emitted = emittedOnce(app, 'browser-window-blur');
+      const emitted = once(app, 'browser-window-blur');
       w = new BrowserWindow({ show: false });
       w.emit('blur');
       const [, window] = await emitted;
@@ -522,14 +522,14 @@ describe('app module', () => {
     });
 
     it('should emit browser-window-created event when window is created', async () => {
-      const emitted = emittedOnce(app, 'browser-window-created');
+      const emitted = once(app, 'browser-window-created');
       w = new BrowserWindow({ show: false });
       const [, window] = await emitted;
       expect(window.id).to.equal(w.id);
     });
 
     it('should emit web-contents-created event when a webContents is created', async () => {
-      const emitted = emittedOnce(app, 'web-contents-created');
+      const emitted = once(app, 'web-contents-created');
       w = new BrowserWindow({ show: false });
       const [, webContents] = await emitted;
       expect(webContents.id).to.equal(w.webContents.id);
@@ -546,7 +546,7 @@ describe('app module', () => {
       });
       await w.loadURL('about:blank');
 
-      const emitted = emittedOnce(app, 'renderer-process-crashed');
+      const emitted = once(app, 'renderer-process-crashed');
       w.webContents.executeJavaScript('process.crash()');
 
       const [, webContents] = await emitted;
@@ -564,7 +564,7 @@ describe('app module', () => {
       });
       await w.loadURL('about:blank');
 
-      const emitted = emittedOnce(app, 'render-process-gone');
+      const emitted = once(app, 'render-process-gone');
       w.webContents.executeJavaScript('process.crash()');
 
       const [, webContents, details] = await emitted;
@@ -890,7 +890,7 @@ describe('app module', () => {
     ifit(process.platform === 'win32')('detects disabled by TaskManager', async function () {
       app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: true, args: ['arg1'] });
       const appProcess = cp.spawn('reg', [...regAddArgs, '030000000000000000000000']);
-      await emittedOnce(appProcess, 'exit');
+      await once(appProcess, 'exit');
       expect(app.getLoginItemSettings()).to.deep.equal({
         openAtLogin: false,
         openAsHidden: false,
@@ -927,12 +927,12 @@ describe('app module', () => {
 
       app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: false, args: ['arg1'] });
       let appProcess = cp.spawn('reg', [...regAddArgs, '020000000000000000000000']);
-      await emittedOnce(appProcess, 'exit');
+      await once(appProcess, 'exit');
       expect(app.getLoginItemSettings()).to.deep.equal(expectation);
 
       app.setLoginItemSettings({ openAtLogin: true, name: 'additionalEntry', enabled: false, args: ['arg1'] });
       appProcess = cp.spawn('reg', [...regAddArgs, '000000000000000000000000']);
-      await emittedOnce(appProcess, 'exit');
+      await once(appProcess, 'exit');
       expect(app.getLoginItemSettings()).to.deep.equal(expectation);
     });
   });
@@ -1290,7 +1290,7 @@ describe('app module', () => {
       const appPath = path.join(fixturesPath, 'api', 'quit-app');
       // App should exit with non 123 code.
       const first = cp.spawn(process.execPath, [appPath, 'electron-test:?', 'abc']);
-      const [code] = await emittedOnce(first, 'exit');
+      const [code] = await once(first, 'exit');
       expect(code).to.not.equal(123);
     });
 
@@ -1298,7 +1298,7 @@ describe('app module', () => {
       const appPath = path.join(fixturesPath, 'api', 'quit-app');
       // App should exit with code 123.
       const first = cp.spawn(process.execPath, [appPath, 'e:\\abc', 'abc']);
-      const [code] = await emittedOnce(first, 'exit');
+      const [code] = await once(first, 'exit');
       expect(code).to.equal(123);
     });
 
@@ -1306,7 +1306,7 @@ describe('app module', () => {
       const appPath = path.join(fixturesPath, 'api', 'quit-app');
       // App should exit with code 123.
       const first = cp.spawn(process.execPath, [appPath, '--', 'http://electronjs.org', 'electron-test://testdata']);
-      const [code] = await emittedOnce(first, 'exit');
+      const [code] = await once(first, 'exit');
       expect(code).to.equal(123);
     });
   });
@@ -1432,7 +1432,7 @@ describe('app module', () => {
       appProcess.stderr.on('data', (data) => {
         errorData += data;
       });
-      const [exitCode] = await emittedOnce(appProcess, 'exit');
+      const [exitCode] = await once(appProcess, 'exit');
       if (exitCode === 0) {
         try {
           const [, json] = /HERE COMES THE JSON: (.+) AND THERE IT WAS/.exec(gpuInfoData)!;
@@ -1949,7 +1949,7 @@ describe('default behavior', () => {
     it('should emit a login event on app when a WebContents hits a 401', async () => {
       const w = new BrowserWindow({ show: false });
       w.loadURL(serverUrl);
-      const [, webContents] = await emittedOnce(app, 'login');
+      const [, webContents] = await once(app, 'login');
       expect(webContents).to.equal(w.webContents);
     });
   });
@@ -1976,7 +1976,7 @@ async function runTestApp (name: string, ...args: any[]) {
   let output = '';
   appProcess.stdout.on('data', (data) => { output += data; });
 
-  await emittedOnce(appProcess.stdout, 'end');
+  await once(appProcess.stdout, 'end');
 
   return JSON.parse(output);
 }

+ 4 - 4
spec/api-auto-updater-spec.ts

@@ -1,12 +1,12 @@
 import { autoUpdater } from 'electron/main';
 import { expect } from 'chai';
 import { ifit, ifdescribe } from './lib/spec-helpers';
-import { emittedOnce } from './lib/events-helpers';
+import { once } from 'events';
 
 ifdescribe(!process.mas)('autoUpdater module', function () {
   describe('checkForUpdates', function () {
     ifit(process.platform === 'win32')('emits an error on Windows if the feed URL is not set', async function () {
-      const errorEvent = emittedOnce(autoUpdater, 'error');
+      const errorEvent = once(autoUpdater, 'error');
       autoUpdater.setFeedURL({ url: '' });
       autoUpdater.checkForUpdates();
       const [error] = await errorEvent;
@@ -56,7 +56,7 @@ ifdescribe(!process.mas)('autoUpdater module', function () {
 
     ifdescribe(process.platform === 'darwin' && process.arch !== 'arm64')('on Mac', function () {
       it('emits an error when the application is unsigned', async () => {
-        const errorEvent = emittedOnce(autoUpdater, 'error');
+        const errorEvent = once(autoUpdater, 'error');
         autoUpdater.setFeedURL({ url: '' });
         const [error] = await errorEvent;
         expect(error.message).equal('Could not get code signature for running application');
@@ -80,7 +80,7 @@ ifdescribe(!process.mas)('autoUpdater module', function () {
 
   describe('quitAndInstall', () => {
     ifit(process.platform === 'win32')('emits an error on Windows when no update is available', async function () {
-      const errorEvent = emittedOnce(autoUpdater, 'error');
+      const errorEvent = once(autoUpdater, 'error');
       autoUpdater.quitAndInstall();
       const [error] = await errorEvent;
       expect(error.message).to.equal('No update available, can\'t quit and install');

+ 7 - 7
spec/api-browser-view-spec.ts

@@ -1,10 +1,10 @@
 import { expect } from 'chai';
 import * as path from 'path';
-import { emittedOnce } from './lib/events-helpers';
 import { BrowserView, BrowserWindow, screen, webContents } from 'electron/main';
 import { closeWindow } from './lib/window-helpers';
 import { defer, ifit, startRemoteControlApp } from './lib/spec-helpers';
 import { areColorsSimilar, captureScreen, getPixelColor } from './lib/screen-helpers';
+import { once } from 'events';
 
 describe('BrowserView module', () => {
   const fixtures = path.resolve(__dirname, 'fixtures');
@@ -25,13 +25,13 @@ describe('BrowserView module', () => {
   });
 
   afterEach(async () => {
-    const p = emittedOnce(w.webContents, 'destroyed');
+    const p = once(w.webContents, 'destroyed');
     await closeWindow(w);
     w = null as any;
     await p;
 
     if (view && view.webContents) {
-      const p = emittedOnce(view.webContents, 'destroyed');
+      const p = once(view.webContents, 'destroyed');
       view.webContents.destroy();
       view = null as any;
       await p;
@@ -231,7 +231,7 @@ describe('BrowserView module', () => {
 
       w.addBrowserView(view);
       view.webContents.loadURL('about:blank');
-      await emittedOnce(view.webContents, 'did-finish-load');
+      await once(view.webContents, 'did-finish-load');
 
       const w2 = new BrowserWindow({ show: false });
       w2.addBrowserView(view);
@@ -239,7 +239,7 @@ describe('BrowserView module', () => {
       w.close();
 
       view.webContents.loadURL(`file://${fixtures}/pages/blank.html`);
-      await emittedOnce(view.webContents, 'did-finish-load');
+      await once(view.webContents, 'did-finish-load');
 
       // Clean up - the afterEach hook assumes the webContents on w is still alive.
       w = new BrowserWindow({ show: false });
@@ -326,7 +326,7 @@ describe('BrowserView module', () => {
           app.quit();
         });
       });
-      const [code] = await emittedOnce(rc.process, 'exit');
+      const [code] = await once(rc.process, 'exit');
       expect(code).to.equal(0);
     });
 
@@ -342,7 +342,7 @@ describe('BrowserView module', () => {
           app.quit();
         });
       });
-      const [code] = await emittedOnce(rc.process, 'exit');
+      const [code] = await once(rc.process, 'exit');
       expect(code).to.equal(0);
     });
   });

File diff suppressed because it is too large
+ 142 - 134
spec/api-browser-window-spec.ts


+ 4 - 3
spec/api-content-tracing-spec.ts

@@ -2,7 +2,8 @@ import { expect } from 'chai';
 import { app, contentTracing, TraceConfig, TraceCategoriesAndOptions } from 'electron/main';
 import * as fs from 'fs';
 import * as path from 'path';
-import { ifdescribe, delay } from './lib/spec-helpers';
+import { setTimeout } from 'timers/promises';
+import { ifdescribe } from './lib/spec-helpers';
 
 // FIXME: The tests are skipped on arm/arm64 and ia32.
 ifdescribe(!(['arm', 'arm64', 'ia32'].includes(process.arch)))('contentTracing', () => {
@@ -10,7 +11,7 @@ ifdescribe(!(['arm', 'arm64', 'ia32'].includes(process.arch)))('contentTracing',
     await app.whenReady();
 
     await contentTracing.startRecording(options);
-    await delay(recordTimeInMilliseconds);
+    await setTimeout(recordTimeInMilliseconds);
     const resultFilePath = await contentTracing.stopRecording(outputFilePath);
 
     return resultFilePath;
@@ -131,7 +132,7 @@ ifdescribe(!(['arm', 'arm64', 'ia32'].includes(process.arch)))('contentTracing',
         let n = 0;
         const f = () => {};
         while (+new Date() - start < 200 || n < 500) {
-          await delay(0);
+          await setTimeout(0);
           f();
           n++;
         }

+ 10 - 7
spec/api-context-bridge-spec.ts

@@ -8,8 +8,8 @@ import * as path from 'path';
 import * as cp from 'child_process';
 
 import { closeWindow } from './lib/window-helpers';
-import { emittedOnce } from './lib/events-helpers';
 import { listen } from './lib/spec-helpers';
+import { once } from 'events';
 
 const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'context-bridge');
 
@@ -45,7 +45,8 @@ describe('contextBridge', () => {
         preload: path.resolve(fixturesPath, 'can-bind-preload.js')
       }
     });
-    const [, bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html')));
+    w.loadFile(path.resolve(fixturesPath, 'empty.html'));
+    const [, bound] = await once(ipcMain, 'context-bridge-bound');
     expect(bound).to.equal(false);
   });
 
@@ -57,7 +58,8 @@ describe('contextBridge', () => {
         preload: path.resolve(fixturesPath, 'can-bind-preload.js')
       }
     });
-    const [, bound] = await emittedOnce(ipcMain, 'context-bridge-bound', () => w.loadFile(path.resolve(fixturesPath, 'empty.html')));
+    w.loadFile(path.resolve(fixturesPath, 'empty.html'));
+    const [, bound] = await once(ipcMain, 'context-bridge-bound');
     expect(bound).to.equal(true);
   });
 
@@ -105,7 +107,8 @@ describe('contextBridge', () => {
       const getGCInfo = async (): Promise<{
         trackedValues: number;
       }> => {
-        const [, info] = await emittedOnce(ipcMain, 'gc-info', () => w.webContents.send('get-gc-info'));
+        w.webContents.send('get-gc-info');
+        const [, info] = await once(ipcMain, 'gc-info');
         return info;
       };
 
@@ -686,7 +689,7 @@ describe('contextBridge', () => {
             });
             require('electron').ipcRenderer.send('window-ready-for-tasking');
           });
-          const loadPromise = emittedOnce(ipcMain, 'window-ready-for-tasking');
+          const loadPromise = once(ipcMain, 'window-ready-for-tasking');
           expect((await getGCInfo()).trackedValues).to.equal(0);
           await callWithBindings((root: any) => {
             root.example.track(root.example.getFunction());
@@ -1263,7 +1266,7 @@ describe('ContextBridgeMutability', () => {
 
     let output = '';
     appProcess.stdout.on('data', data => { output += data; });
-    await emittedOnce(appProcess, 'exit');
+    await once(appProcess, 'exit');
 
     expect(output).to.include('some-modified-text');
     expect(output).to.include('obj-modified-prop');
@@ -1276,7 +1279,7 @@ describe('ContextBridgeMutability', () => {
 
     let output = '';
     appProcess.stdout.on('data', data => { output += data; });
-    await emittedOnce(appProcess, 'exit');
+    await once(appProcess, 'exit');
 
     expect(output).to.include('some-text');
     expect(output).to.include('obj-prop');

+ 6 - 5
spec/api-crash-reporter-spec.ts

@@ -3,12 +3,13 @@ import * as childProcess from 'child_process';
 import * as http from 'http';
 import * as Busboy from 'busboy';
 import * as path from 'path';
-import { ifdescribe, ifit, defer, startRemoteControlApp, delay, repeatedly, listen } from './lib/spec-helpers';
+import { ifdescribe, ifit, defer, startRemoteControlApp, repeatedly, listen } from './lib/spec-helpers';
 import { app } from 'electron/main';
 import { crashReporter } from 'electron/common';
 import { EventEmitter } from 'events';
 import * as fs from 'fs';
 import * as uuid from 'uuid';
+import { setTimeout } from 'timers/promises';
 
 const isWindowsOnArm = process.platform === 'win32' && process.arch === 'arm64';
 const isLinuxOnArm = process.platform === 'linux' && process.arch.includes('arm');
@@ -298,7 +299,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
           ignoreSystemCrashHandler: true,
           extra: { longParam: 'a'.repeat(100000) }
         });
-        setTimeout(() => process.crash());
+        setTimeout().then(() => process.crash());
       }, port);
       const crash = await waitForCrash();
       expect(stitchLongCrashParam(crash, 'longParam')).to.have.lengthOf(160 * 127, 'crash should have truncated longParam');
@@ -320,7 +321,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
           }
         });
         require('electron').crashReporter.addExtraParameter('c'.repeat(kKeyLengthMax + 10), 'value');
-        setTimeout(() => process.crash());
+        setTimeout().then(() => process.crash());
       }, port, kKeyLengthMax);
       const crash = await waitForCrash();
       expect(crash).not.to.have.property('a'.repeat(kKeyLengthMax + 10));
@@ -375,7 +376,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
     waitForCrash().then(() => expect.fail('expected not to receive a dump'));
     await runCrashApp('renderer', port, ['--no-upload']);
     // wait a sec in case the crash reporter is about to upload a crash
-    await delay(1000);
+    await setTimeout(1000);
     expect(getCrashes()).to.have.length(0);
   });
 
@@ -502,7 +503,7 @@ ifdescribe(!isLinuxOnArm && !process.mas && !process.env.DISABLE_CRASH_REPORTER_
     function crash (processType: string, remotely: Function) {
       if (processType === 'main') {
         return remotely(() => {
-          setTimeout(() => { process.crash(); });
+          setTimeout().then(() => { process.crash(); });
         });
       } else if (processType === 'renderer') {
         return remotely(() => {

+ 6 - 5
spec/api-debugger-spec.ts

@@ -3,8 +3,9 @@ import * as http from 'http';
 import * as path from 'path';
 import { BrowserWindow } from 'electron/main';
 import { closeAllWindows } from './lib/window-helpers';
-import { emittedOnce, emittedUntil } from './lib/events-helpers';
+import { emittedUntil } from './lib/events-helpers';
 import { listen } from './lib/spec-helpers';
+import { once } from 'events';
 
 describe('debugger module', () => {
   const fixtures = path.resolve(__dirname, 'fixtures');
@@ -45,7 +46,7 @@ describe('debugger module', () => {
 
   describe('debugger.detach', () => {
     it('fires detach event', async () => {
-      const detach = emittedOnce(w.webContents.debugger, 'detach');
+      const detach = once(w.webContents.debugger, 'detach');
       w.webContents.debugger.attach();
       w.webContents.debugger.detach();
       const [, reason] = await detach;
@@ -55,7 +56,7 @@ describe('debugger module', () => {
 
     it('doesn\'t disconnect an active devtools session', async () => {
       w.webContents.loadURL('about:blank');
-      const detach = emittedOnce(w.webContents.debugger, 'detach');
+      const detach = once(w.webContents.debugger, 'detach');
       w.webContents.debugger.attach();
       w.webContents.openDevTools();
       w.webContents.once('devtools-opened', () => {
@@ -94,7 +95,7 @@ describe('debugger module', () => {
       w.webContents.loadURL('about:blank');
       w.webContents.debugger.attach();
 
-      const opened = emittedOnce(w.webContents, 'devtools-opened');
+      const opened = once(w.webContents, 'devtools-opened');
       w.webContents.openDevTools();
       await opened;
 
@@ -183,7 +184,7 @@ describe('debugger module', () => {
     it('uses empty sessionId by default', async () => {
       w.webContents.loadURL('about:blank');
       w.webContents.debugger.attach();
-      const onMessage = emittedOnce(w.webContents.debugger, 'message');
+      const onMessage = once(w.webContents.debugger, 'message');
       await w.webContents.debugger.sendCommand('Target.setDiscoverTargets', { discover: true });
       const [, method, params, sessionId] = await onMessage;
       expect(method).to.equal('Target.targetCreated');

+ 11 - 10
spec/api-desktop-capturer-spec.ts

@@ -1,7 +1,8 @@
 import { expect } from 'chai';
 import { screen, desktopCapturer, BrowserWindow } from 'electron/main';
-import { delay, ifdescribe, ifit } from './lib/spec-helpers';
-import { emittedOnce } from './lib/events-helpers';
+import { once } from 'events';
+import { setTimeout } from 'timers/promises';
+import { ifdescribe, ifit } from './lib/spec-helpers';
 
 import { closeAllWindows } from './lib/window-helpers';
 
@@ -74,7 +75,7 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
 
   it('disabling thumbnail should return empty images', async () => {
     const w2 = new BrowserWindow({ show: false, width: 200, height: 200, webPreferences: { contextIsolation: false } });
-    const wShown = emittedOnce(w2, 'show');
+    const wShown = once(w2, 'show');
     w2.show();
     await wShown;
 
@@ -90,8 +91,8 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
 
   it('getMediaSourceId should match DesktopCapturerSource.id', async () => {
     const w = new BrowserWindow({ show: false, width: 100, height: 100, webPreferences: { contextIsolation: false } });
-    const wShown = emittedOnce(w, 'show');
-    const wFocused = emittedOnce(w, 'focus');
+    const wShown = once(w, 'show');
+    const wFocused = once(w, 'focus');
     w.show();
     w.focus();
     await wShown;
@@ -121,8 +122,8 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
 
   it('getSources should not incorrectly duplicate window_id', async () => {
     const w = new BrowserWindow({ show: false, width: 100, height: 100, webPreferences: { contextIsolation: false } });
-    const wShown = emittedOnce(w, 'show');
-    const wFocused = emittedOnce(w, 'focus');
+    const wShown = once(w, 'show');
+    const wFocused = once(w, 'focus');
     w.show();
     w.focus();
     await wShown;
@@ -176,8 +177,8 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
 
       // Show and focus all the windows.
       for (const w of wList) {
-        const wShown = emittedOnce(w, 'show');
-        const wFocused = emittedOnce(w, 'focus');
+        const wShown = once(w, 'show');
+        const wFocused = once(w, 'focus');
 
         w.show();
         w.focus();
@@ -227,7 +228,7 @@ ifdescribe(!process.arch.includes('arm') && process.platform !== 'win32')('deskt
           w.focus();
           w.moveAbove(next.getMediaSourceId());
           // Ensure the window has time to move.
-          await delay(2000);
+          await setTimeout(2000);
         }
       }
 

+ 4 - 3
spec/api-dialog-spec.ts

@@ -1,7 +1,8 @@
 import { expect } from 'chai';
 import { dialog, BrowserWindow } from 'electron/main';
 import { closeAllWindows } from './lib/window-helpers';
-import { ifit, delay } from './lib/spec-helpers';
+import { ifit } from './lib/spec-helpers';
+import { setTimeout } from 'timers/promises';
 
 describe('dialog module', () => {
   describe('showOpenDialog', () => {
@@ -139,7 +140,7 @@ describe('dialog module', () => {
       const signal = controller.signal;
       const w = new BrowserWindow();
       const p = dialog.showMessageBox(w, { signal, message: 'i am message' });
-      await delay(500);
+      await setTimeout(500);
       controller.abort();
       const result = await p;
       expect(result.response).to.equal(0);
@@ -170,7 +171,7 @@ describe('dialog module', () => {
         buttons: ['OK', 'Cancel'],
         cancelId: 1
       });
-      await delay(500);
+      await setTimeout(500);
       controller.abort();
       const result = await p;
       expect(result.response).to.equal(1);

+ 2 - 2
spec/api-ipc-main-spec.ts

@@ -2,9 +2,9 @@ import { expect } from 'chai';
 import * as path from 'path';
 import * as cp from 'child_process';
 import { closeAllWindows } from './lib/window-helpers';
-import { emittedOnce } from './lib/events-helpers';
 import { defer } from './lib/spec-helpers';
 import { ipcMain, BrowserWindow } from 'electron/main';
+import { once } from 'events';
 
 describe('ipc main module', () => {
   const fixtures = path.join(__dirname, 'fixtures');
@@ -57,7 +57,7 @@ describe('ipc main module', () => {
       let output = '';
       appProcess.stdout.on('data', (data) => { output += data; });
 
-      await emittedOnce(appProcess.stdout, 'end');
+      await once(appProcess.stdout, 'end');
 
       output = JSON.parse(output);
       expect(output).to.deep.equal(['error']);

+ 6 - 6
spec/api-ipc-renderer-spec.ts

@@ -1,8 +1,8 @@
 import { expect } from 'chai';
 import * as path from 'path';
 import { ipcMain, BrowserWindow, WebContents, WebPreferences, webContents } from 'electron/main';
-import { emittedOnce } from './lib/events-helpers';
 import { closeWindow } from './lib/window-helpers';
+import { once } from 'events';
 
 describe('ipcRenderer module', () => {
   const fixtures = path.join(__dirname, 'fixtures');
@@ -27,7 +27,7 @@ describe('ipcRenderer module', () => {
         const { ipcRenderer } = require('electron')
         ipcRenderer.send('message', ${JSON.stringify(obj)})
       }`);
-      const [, received] = await emittedOnce(ipcMain, 'message');
+      const [, received] = await once(ipcMain, 'message');
       expect(received).to.deep.equal(obj);
     });
 
@@ -37,7 +37,7 @@ describe('ipcRenderer module', () => {
         const { ipcRenderer } = require('electron')
         ipcRenderer.send('message', new Date(${JSON.stringify(isoDate)}))
       }`);
-      const [, received] = await emittedOnce(ipcMain, 'message');
+      const [, received] = await once(ipcMain, 'message');
       expect(received.toISOString()).to.equal(isoDate);
     });
 
@@ -47,7 +47,7 @@ describe('ipcRenderer module', () => {
         const { ipcRenderer } = require('electron')
         ipcRenderer.send('message', Buffer.from(${JSON.stringify(data)}))
       }`);
-      const [, received] = await emittedOnce(ipcMain, 'message');
+      const [, received] = await once(ipcMain, 'message');
       expect(received).to.be.an.instanceOf(Uint8Array);
       expect(Buffer.from(data).equals(received)).to.be.true();
     });
@@ -88,7 +88,7 @@ describe('ipcRenderer module', () => {
       const bar = { name: 'bar', child: child };
       const array = [foo, bar];
 
-      const [, arrayValue, fooValue, barValue, childValue] = await emittedOnce(ipcMain, 'message');
+      const [, arrayValue, fooValue, barValue, childValue] = await once(ipcMain, 'message');
       expect(arrayValue).to.deep.equal(array);
       expect(fooValue).to.deep.equal(foo);
       expect(barValue).to.deep.equal(bar);
@@ -106,7 +106,7 @@ describe('ipcRenderer module', () => {
         ipcRenderer.send('message', array, child)
       }`);
 
-      const [, arrayValue, childValue] = await emittedOnce(ipcMain, 'message');
+      const [, arrayValue, childValue] = await once(ipcMain, 'message');
       expect(arrayValue[0]).to.equal(5);
       expect(arrayValue[1]).to.equal(arrayValue);
 

+ 22 - 23
spec/api-ipc-spec.ts

@@ -1,8 +1,7 @@
-import { EventEmitter } from 'events';
+import { EventEmitter, once } from 'events';
 import { expect } from 'chai';
 import { BrowserWindow, ipcMain, IpcMainInvokeEvent, MessageChannelMain, WebContents } from 'electron/main';
 import { closeAllWindows } from './lib/window-helpers';
-import { emittedOnce } from './lib/events-helpers';
 import { defer, listen } from './lib/spec-helpers';
 import * as path from 'path';
 import * as http from 'http';
@@ -120,7 +119,7 @@ describe('ipc module', () => {
         /* never resolve */
       }));
       w.webContents.executeJavaScript(`(${rendererInvoke})()`);
-      const [, { error }] = await emittedOnce(ipcMain, 'result');
+      const [, { error }] = await once(ipcMain, 'result');
       expect(error).to.match(/reply was never sent/);
     });
   });
@@ -208,7 +207,7 @@ describe('ipc module', () => {
     it('can send a port to the main process', async () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
       w.loadURL('about:blank');
-      const p = emittedOnce(ipcMain, 'port');
+      const p = once(ipcMain, 'port');
       await w.webContents.executeJavaScript(`(${function () {
         const channel = new MessageChannel();
         require('electron').ipcRenderer.postMessage('port', 'hi', [channel.port1]);
@@ -225,7 +224,7 @@ describe('ipc module', () => {
     it('can sent a message without a transfer', async () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
       w.loadURL('about:blank');
-      const p = emittedOnce(ipcMain, 'port');
+      const p = once(ipcMain, 'port');
       await w.webContents.executeJavaScript(`(${function () {
         require('electron').ipcRenderer.postMessage('port', 'hi');
       }})()`);
@@ -238,7 +237,7 @@ describe('ipc module', () => {
     it('can communicate between main and renderer', async () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
       w.loadURL('about:blank');
-      const p = emittedOnce(ipcMain, 'port');
+      const p = once(ipcMain, 'port');
       await w.webContents.executeJavaScript(`(${function () {
         const channel = new MessageChannel();
         (channel.port2 as any).onmessage = (ev: any) => {
@@ -252,7 +251,7 @@ describe('ipc module', () => {
       const [port] = ev.ports;
       port.start();
       port.postMessage(42);
-      const [ev2] = await emittedOnce(port, 'message');
+      const [ev2] = await once(port, 'message');
       expect(ev2.data).to.equal(84);
     });
 
@@ -267,11 +266,11 @@ describe('ipc module', () => {
         require('electron').ipcRenderer.postMessage('port', '', [channel1.port1]);
       }
       w.webContents.executeJavaScript(`(${fn})()`);
-      const [{ ports: [port1] }] = await emittedOnce(ipcMain, 'port');
+      const [{ ports: [port1] }] = await once(ipcMain, 'port');
       port1.start();
-      const [{ ports: [port2] }] = await emittedOnce(port1, 'message');
+      const [{ ports: [port2] }] = await once(port1, 'message');
       port2.start();
-      const [{ data }] = await emittedOnce(port2, 'message');
+      const [{ data }] = await once(port2, 'message');
       expect(data).to.equal('matryoshka');
     });
 
@@ -287,14 +286,14 @@ describe('ipc module', () => {
         };
         require('electron').ipcRenderer.postMessage('port', '', [channel.port1]);
       }})()`);
-      const [{ ports: [port] }] = await emittedOnce(ipcMain, 'port');
+      const [{ ports: [port] }] = await once(ipcMain, 'port');
       await w2.webContents.executeJavaScript(`(${function () {
         require('electron').ipcRenderer.on('port', ({ ports: [port] }: any) => {
           port.postMessage('a message');
         });
       }})()`);
       w2.webContents.postMessage('port', '', [port]);
-      const [, data] = await emittedOnce(ipcMain, 'message received');
+      const [, data] = await once(ipcMain, 'message received');
       expect(data).to.equal('a message');
     });
 
@@ -316,7 +315,7 @@ describe('ipc module', () => {
           const { port1, port2 } = new MessageChannelMain();
           w.webContents.postMessage('port', null, [port2]);
           port1.close();
-          await emittedOnce(ipcMain, 'closed');
+          await once(ipcMain, 'closed');
         });
 
         it('is emitted when the other end of a port is garbage-collected', async () => {
@@ -360,7 +359,7 @@ describe('ipc module', () => {
         const { port1, port2 } = new MessageChannelMain();
         port2.postMessage('hello');
         port1.start();
-        const [ev] = await emittedOnce(port1, 'message');
+        const [ev] = await once(port1, 'message');
         expect(ev.data).to.equal('hello');
       });
 
@@ -379,7 +378,7 @@ describe('ipc module', () => {
         const { port1, port2 } = new MessageChannelMain();
         port1.postMessage('hello');
         w.webContents.postMessage('port', null, [port2]);
-        await emittedOnce(ipcMain, 'done');
+        await once(ipcMain, 'done');
       });
 
       it('can be passed over another channel', async () => {
@@ -400,7 +399,7 @@ describe('ipc module', () => {
         port1.postMessage(null, [port4]);
         port3.postMessage('hello');
         w.webContents.postMessage('port', null, [port2]);
-        const [, message] = await emittedOnce(ipcMain, 'done');
+        const [, message] = await once(ipcMain, 'done');
         expect(message).to.equal('hello');
       });
 
@@ -481,7 +480,7 @@ describe('ipc module', () => {
             });
           }})()`);
           postMessage(w.webContents)('foo', { some: 'message' });
-          const [, msg] = await emittedOnce(ipcMain, 'bar');
+          const [, msg] = await once(ipcMain, 'bar');
           expect(msg).to.deep.equal({ some: 'message' });
         });
 
@@ -575,7 +574,7 @@ describe('ipc module', () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
       w.loadURL('about:blank');
       w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
-      const [, num] = await emittedOnce(w.webContents.ipc, 'test');
+      const [, num] = await once(w.webContents.ipc, 'test');
       expect(num).to.equal(42);
     });
 
@@ -593,7 +592,7 @@ describe('ipc module', () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
       w.loadURL('about:blank');
       w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.postMessage(\'test\', null, [(new MessageChannel).port1])');
-      const [event] = await emittedOnce(w.webContents.ipc, 'test');
+      const [event] = await once(w.webContents.ipc, 'test');
       expect(event.ports.length).to.equal(1);
     });
 
@@ -651,7 +650,7 @@ describe('ipc module', () => {
       // Preloads don't run in about:blank windows, and file:// urls can't be loaded in iframes, so use a blank http page.
       await w.loadURL(`data:text/html,<iframe src="http://localhost:${port}"></iframe>`);
       w.webContents.mainFrame.frames[0].executeJavaScript('ipc.send(\'test\', 42)');
-      const [, arg] = await emittedOnce(w.webContents.ipc, 'test');
+      const [, arg] = await once(w.webContents.ipc, 'test');
       expect(arg).to.equal(42);
     });
   });
@@ -662,7 +661,7 @@ describe('ipc module', () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
       w.loadURL('about:blank');
       w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
-      const [, arg] = await emittedOnce(w.webContents.mainFrame.ipc, 'test');
+      const [, arg] = await once(w.webContents.mainFrame.ipc, 'test');
       expect(arg).to.equal(42);
     });
 
@@ -680,7 +679,7 @@ describe('ipc module', () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
       w.loadURL('about:blank');
       w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.postMessage(\'test\', null, [(new MessageChannel).port1])');
-      const [event] = await emittedOnce(w.webContents.mainFrame.ipc, 'test');
+      const [event] = await once(w.webContents.mainFrame.ipc, 'test');
       expect(event.ports.length).to.equal(1);
     });
 
@@ -752,7 +751,7 @@ describe('ipc module', () => {
       await w.loadURL(`data:text/html,<iframe src="http://localhost:${port}"></iframe>`);
       w.webContents.mainFrame.frames[0].executeJavaScript('ipc.send(\'test\', 42)');
       w.webContents.mainFrame.ipc.on('test', () => { throw new Error('should not be called'); });
-      const [, arg] = await emittedOnce(w.webContents.mainFrame.frames[0].ipc, 'test');
+      const [, arg] = await once(w.webContents.mainFrame.frames[0].ipc, 'test');
       expect(arg).to.equal(42);
     });
   });

+ 9 - 8
spec/api-menu-spec.ts

@@ -3,9 +3,10 @@ import * as path from 'path';
 import { expect } from 'chai';
 import { BrowserWindow, Menu, MenuItem } from 'electron/main';
 import { sortMenuItems } from '../lib/browser/api/menu-utils';
-import { emittedOnce } from './lib/events-helpers';
-import { ifit, delay } from './lib/spec-helpers';
+import { ifit } from './lib/spec-helpers';
 import { closeWindow } from './lib/window-helpers';
+import { once } from 'events';
+import { setTimeout } from 'timers/promises';
 
 const fixturesPath = path.resolve(__dirname, 'fixtures');
 
@@ -817,7 +818,7 @@ describe('Menu module', function () {
       menu.on('menu-will-close', () => { done(); });
       menu.popup({ window: w });
       // https://github.com/electron/electron/issues/19411
-      setTimeout(() => {
+      setTimeout().then(() => {
         menu.closePopup();
       });
     });
@@ -849,7 +850,7 @@ describe('Menu module', function () {
       expect(x).to.equal(100);
       expect(y).to.equal(101);
       // https://github.com/electron/electron/issues/19411
-      setTimeout(() => {
+      setTimeout().then(() => {
         menu.closePopup();
       });
     });
@@ -857,7 +858,7 @@ describe('Menu module', function () {
     it('works with a given BrowserWindow, no options, and a callback', (done) => {
       menu.popup({ window: w, callback: () => done() });
       // https://github.com/electron/electron/issues/19411
-      setTimeout(() => {
+      setTimeout().then(() => {
         menu.closePopup();
       });
     });
@@ -870,14 +871,14 @@ describe('Menu module', function () {
       // eslint-disable-next-line no-undef
       const wr = new WeakRef(menu);
 
-      await delay();
+      await setTimeout();
 
       // Do garbage collection, since |menu| is not referenced in this closure
       // it would be gone after next call.
       const v8Util = process._linkedBinding('electron_common_v8_util');
       v8Util.requestGarbageCollectionForTesting();
 
-      await delay();
+      await setTimeout();
 
       // Try to receive menu from weak reference.
       if (wr.deref()) {
@@ -929,7 +930,7 @@ describe('Menu module', function () {
       appProcess.stdout.on('data', data => { output += data; });
       appProcess.stderr.on('data', data => { output += data; });
 
-      const [code] = await emittedOnce(appProcess, 'exit');
+      const [code] = await once(appProcess, 'exit');
       if (!output.includes('Window has no menu')) {
         console.log(code, output);
       }

+ 11 - 10
spec/api-native-theme-spec.ts

@@ -1,11 +1,12 @@
 import { expect } from 'chai';
 import { nativeTheme, systemPreferences, BrowserWindow, ipcMain } from 'electron/main';
+import { once } from 'events';
 import * as os from 'os';
 import * as path from 'path';
 import * as semver from 'semver';
+import { setTimeout } from 'timers/promises';
 
-import { delay, ifdescribe } from './lib/spec-helpers';
-import { emittedOnce } from './lib/events-helpers';
+import { ifdescribe } from './lib/spec-helpers';
 import { closeAllWindows } from './lib/window-helpers';
 
 describe('nativeTheme module', () => {
@@ -19,7 +20,7 @@ describe('nativeTheme module', () => {
     afterEach(async () => {
       nativeTheme.themeSource = 'system';
       // Wait for any pending events to emit
-      await delay(20);
+      await setTimeout(20);
 
       closeAllWindows();
     });
@@ -37,10 +38,10 @@ describe('nativeTheme module', () => {
 
     it('should emit the "updated" event when it is set and the resulting "shouldUseDarkColors" value changes', async () => {
       nativeTheme.themeSource = 'light';
-      let updatedEmitted = emittedOnce(nativeTheme, 'updated');
+      let updatedEmitted = once(nativeTheme, 'updated');
       nativeTheme.themeSource = 'dark';
       await updatedEmitted;
-      updatedEmitted = emittedOnce(nativeTheme, 'updated');
+      updatedEmitted = once(nativeTheme, 'updated');
       nativeTheme.themeSource = 'light';
       await updatedEmitted;
     });
@@ -48,14 +49,14 @@ describe('nativeTheme module', () => {
     it('should not emit the "updated" event when it is set and the resulting "shouldUseDarkColors" value is the same', async () => {
       nativeTheme.themeSource = 'dark';
       // Wait a few ticks to allow an async events to flush
-      await delay(20);
+      await setTimeout(20);
       let called = false;
       nativeTheme.once('updated', () => {
         called = true;
       });
       nativeTheme.themeSource = 'dark';
       // Wait a few ticks to allow an async events to flush
-      await delay(20);
+      await setTimeout(20);
       expect(called).to.equal(false);
     });
 
@@ -83,15 +84,15 @@ describe('nativeTheme module', () => {
           .addEventListener('change', () => require('electron').ipcRenderer.send('theme-change'))
       `);
       const originalSystemIsDark = await getPrefersColorSchemeIsDark(w);
-      let changePromise: Promise<any[]> = emittedOnce(ipcMain, 'theme-change');
+      let changePromise: Promise<any[]> = once(ipcMain, 'theme-change');
       nativeTheme.themeSource = 'dark';
       if (!originalSystemIsDark) await changePromise;
       expect(await getPrefersColorSchemeIsDark(w)).to.equal(true);
-      changePromise = emittedOnce(ipcMain, 'theme-change');
+      changePromise = once(ipcMain, 'theme-change');
       nativeTheme.themeSource = 'light';
       await changePromise;
       expect(await getPrefersColorSchemeIsDark(w)).to.equal(false);
-      changePromise = emittedOnce(ipcMain, 'theme-change');
+      changePromise = once(ipcMain, 'theme-change');
       nativeTheme.themeSource = 'system';
       if (originalSystemIsDark) await changePromise;
       expect(await getPrefersColorSchemeIsDark(w)).to.equal(originalSystemIsDark);

+ 4 - 4
spec/api-net-log-spec.ts

@@ -7,7 +7,7 @@ import * as ChildProcess from 'child_process';
 import { session, net } from 'electron/main';
 import { Socket } from 'net';
 import { ifit, listen } from './lib/spec-helpers';
-import { emittedOnce } from './lib/events-helpers';
+import { once } from 'events';
 
 const appPath = path.join(__dirname, 'fixtures', 'api', 'net-log');
 const dumpFile = path.join(os.tmpdir(), 'net_log.json');
@@ -127,7 +127,7 @@ describe('netLog module', () => {
         }
       });
 
-    await emittedOnce(appProcess, 'exit');
+    await once(appProcess, 'exit');
     expect(fs.existsSync(dumpFile)).to.be.true('dump file exists');
   });
 
@@ -142,7 +142,7 @@ describe('netLog module', () => {
         }
       });
 
-    await emittedOnce(appProcess, 'exit');
+    await once(appProcess, 'exit');
     expect(fs.existsSync(dumpFile)).to.be.true('dump file exists');
     expect(fs.existsSync(dumpFileDynamic)).to.be.true('dynamic dump file exists');
   });
@@ -156,7 +156,7 @@ describe('netLog module', () => {
         }
       });
 
-    await emittedOnce(appProcess, 'exit');
+    await once(appProcess, 'exit');
     expect(fs.existsSync(dumpFileDynamic)).to.be.true('dynamic dump file exists');
   });
 });

+ 25 - 24
spec/api-net-spec.ts

@@ -4,8 +4,9 @@ import { net, session, ClientRequest, BrowserWindow, ClientRequestConstructorOpt
 import * as http from 'http';
 import * as url from 'url';
 import { Socket } from 'net';
-import { emittedOnce } from './lib/events-helpers';
-import { defer, delay, listen } from './lib/spec-helpers';
+import { defer, listen } from './lib/spec-helpers';
+import { once } from 'events';
+import { setTimeout } from 'timers/promises';
 
 // See https://github.com/nodejs/node/issues/40702.
 dns.setDefaultResultOrder('ipv4first');
@@ -412,9 +413,9 @@ describe('net module', () => {
 
       const urlRequest = net.request(serverUrl);
       // request close event
-      const closePromise = emittedOnce(urlRequest, 'close');
+      const closePromise = once(urlRequest, 'close');
       // request finish event
-      const finishPromise = emittedOnce(urlRequest, 'close');
+      const finishPromise = once(urlRequest, 'close');
       // request "response" event
       const response = await getResponse(urlRequest);
       response.on('error', (error: Error) => {
@@ -1056,7 +1057,7 @@ describe('net module', () => {
       urlRequest.on('response', () => {
         expect.fail('unexpected response event');
       });
-      const aborted = emittedOnce(urlRequest, 'abort');
+      const aborted = once(urlRequest, 'abort');
       urlRequest.abort();
       urlRequest.write('');
       urlRequest.end();
@@ -1086,10 +1087,10 @@ describe('net module', () => {
         requestAbortEventEmitted = true;
       });
 
-      await emittedOnce(urlRequest, 'close', () => {
-        urlRequest!.chunkedEncoding = true;
-        urlRequest!.write(randomString(kOneKiloByte));
-      });
+      const p = once(urlRequest, 'close');
+      urlRequest.chunkedEncoding = true;
+      urlRequest.write(randomString(kOneKiloByte));
+      await p;
       expect(requestReceivedByServer).to.equal(true);
       expect(requestAbortEventEmitted).to.equal(true);
     });
@@ -1119,7 +1120,7 @@ describe('net module', () => {
         expect.fail('Unexpected error event');
       });
       urlRequest.end(randomString(kOneKiloByte));
-      await emittedOnce(urlRequest, 'abort');
+      await once(urlRequest, 'abort');
       expect(requestFinishEventEmitted).to.equal(true);
       expect(requestReceivedByServer).to.equal(true);
     });
@@ -1160,7 +1161,7 @@ describe('net module', () => {
         expect.fail('Unexpected error event');
       });
       urlRequest.end(randomString(kOneKiloByte));
-      await emittedOnce(urlRequest, 'abort');
+      await once(urlRequest, 'abort');
       expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event');
       expect(requestReceivedByServer).to.be.true('request should be received by the server');
       expect(requestResponseEventEmitted).to.be.true('"response" event should be emitted');
@@ -1192,7 +1193,7 @@ describe('net module', () => {
         abortsEmitted++;
       });
       urlRequest.end(randomString(kOneKiloByte));
-      await emittedOnce(urlRequest, 'abort');
+      await once(urlRequest, 'abort');
       expect(requestFinishEventEmitted).to.be.true('request should emit "finish" event');
       expect(requestReceivedByServer).to.be.true('request should be received by server');
       expect(abortsEmitted).to.equal(1, 'request should emit exactly 1 "abort" event');
@@ -1214,7 +1215,7 @@ describe('net module', () => {
       });
       const eventHandlers = Promise.all([
         bodyCheckPromise,
-        emittedOnce(urlRequest, 'close')
+        once(urlRequest, 'close')
       ]);
 
       urlRequest.end();
@@ -1445,7 +1446,7 @@ describe('net module', () => {
       urlRequest.end();
       urlRequest.on('redirect', () => { urlRequest.abort(); });
       urlRequest.on('error', () => {});
-      await emittedOnce(urlRequest, 'abort');
+      await once(urlRequest, 'abort');
     });
 
     it('should not follow redirect when mode is error', async () => {
@@ -1459,7 +1460,7 @@ describe('net module', () => {
         redirect: 'error'
       });
       urlRequest.end();
-      await emittedOnce(urlRequest, 'error');
+      await once(urlRequest, 'error');
     });
 
     it('should follow redirect when handler calls callback', async () => {
@@ -1559,7 +1560,7 @@ describe('net module', () => {
       const nodeRequest = http.request(nodeServerUrl);
       const nodeResponse = await getResponse(nodeRequest as any) as any as http.ServerResponse;
       const netRequest = net.request(netServerUrl);
-      const responsePromise = emittedOnce(netRequest, 'response');
+      const responsePromise = once(netRequest, 'response');
       // TODO(@MarshallOfSound) - FIXME with #22730
       nodeResponse.pipe(netRequest as any);
       const [netResponse] = await responsePromise;
@@ -1576,7 +1577,7 @@ describe('net module', () => {
       const netRequest = net.request({ url: serverUrl, method: 'POST' });
       expect(netRequest.getUploadProgress()).to.have.property('active', false);
       netRequest.end(Buffer.from('hello'));
-      const [position, total] = await emittedOnce(netRequest, 'upload-progress');
+      const [position, total] = await once(netRequest, 'upload-progress');
       expect(netRequest.getUploadProgress()).to.deep.equal({ active: true, started: true, current: position, total });
     });
 
@@ -1586,7 +1587,7 @@ describe('net module', () => {
       });
       const urlRequest = net.request(serverUrl);
       urlRequest.end();
-      const [error] = await emittedOnce(urlRequest, 'error');
+      const [error] = await once(urlRequest, 'error');
       expect(error.message).to.equal('net::ERR_EMPTY_RESPONSE');
     });
 
@@ -1597,7 +1598,7 @@ describe('net module', () => {
       });
       const urlRequest = net.request(serverUrl);
       urlRequest.end(randomBuffer(kOneMegaByte));
-      const [error] = await emittedOnce(urlRequest, 'error');
+      const [error] = await once(urlRequest, 'error');
       expect(error.message).to.be.oneOf(['net::ERR_FAILED', 'net::ERR_CONNECTION_RESET', 'net::ERR_CONNECTION_ABORTED']);
     });
 
@@ -1609,14 +1610,14 @@ describe('net module', () => {
       const urlRequest = net.request(serverUrl);
       urlRequest.end();
 
-      await emittedOnce(urlRequest, 'close');
+      await once(urlRequest, 'close');
       await new Promise((resolve, reject) => {
         ['finish', 'abort', 'close', 'error'].forEach(evName => {
           urlRequest.on(evName as any, () => {
             reject(new Error(`Unexpected ${evName} event`));
           });
         });
-        setTimeout(resolve, 50);
+        setTimeout(50).then(resolve);
       });
     });
 
@@ -1902,7 +1903,7 @@ describe('net module', () => {
         port: serverUrl.port
       };
       const nodeRequest = http.request(nodeOptions);
-      const nodeResponsePromise = emittedOnce(nodeRequest, 'response');
+      const nodeResponsePromise = once(nodeRequest, 'response');
       // TODO(@MarshallOfSound) - FIXME with #22730
       (netResponse as any).pipe(nodeRequest);
       const [nodeResponse] = await nodeResponsePromise;
@@ -1929,7 +1930,7 @@ describe('net module', () => {
       const urlRequest = net.request(serverUrl);
       urlRequest.on('response', () => {});
       urlRequest.end();
-      await delay(2000);
+      await setTimeout(2000);
       // TODO(nornagon): I think this ought to max out at 20, but in practice
       // it seems to exceed that sometimes. This is at 25 to avoid test flakes,
       // but we should investigate if there's actually something broken here and
@@ -2159,7 +2160,7 @@ describe('net module', () => {
       it('should reject body promise when stream fails', async () => {
         const serverUrl = await respondOnce.toSingleURL((request, response) => {
           response.write('first chunk');
-          setTimeout(() => response.destroy());
+          setTimeout().then(() => response.destroy());
         });
         const r = await net.fetch(serverUrl);
         expect(r.status).to.equal(200);

+ 4 - 4
spec/api-notification-spec.ts

@@ -1,6 +1,6 @@
 import { expect } from 'chai';
 import { Notification } from 'electron/main';
-import { emittedOnce } from './lib/events-helpers';
+import { once } from 'events';
 import { ifit } from './lib/spec-helpers';
 
 describe('Notification module', () => {
@@ -123,12 +123,12 @@ describe('Notification module', () => {
       silent: true
     });
     {
-      const e = emittedOnce(n, 'show');
+      const e = once(n, 'show');
       n.show();
       await e;
     }
     {
-      const e = emittedOnce(n, 'close');
+      const e = once(n, 'close');
       n.close();
       await e;
     }
@@ -139,7 +139,7 @@ describe('Notification module', () => {
       toastXml: 'not xml'
     });
     {
-      const e = emittedOnce(n, 'failed');
+      const e = once(n, 'failed');
       n.show();
       await e;
     }

+ 3 - 2
spec/api-power-monitor-spec.ts

@@ -8,8 +8,9 @@
 // python-dbusmock.
 import { expect } from 'chai';
 import * as dbus from 'dbus-native';
-import { ifdescribe, delay } from './lib/spec-helpers';
+import { ifdescribe } from './lib/spec-helpers';
 import { promisify } from 'util';
+import { setTimeout } from 'timers/promises';
 
 describe('powerMonitor', () => {
   let logindMock: any, dbusMockPowerMonitor: any, getCalls: any, emitSignal: any, reset: any;
@@ -59,7 +60,7 @@ describe('powerMonitor', () => {
         while (retriesRemaining-- > 0) {
           calls = await getCalls();
           if (calls.length > 0) break;
-          await delay(1000);
+          await setTimeout(1000);
         }
         expect(calls).to.be.an('array').that.has.lengthOf(1);
         expect(calls[0].slice(1)).to.deep.equal([

+ 17 - 17
spec/api-protocol-spec.ts

@@ -7,11 +7,11 @@ import * as http from 'http';
 import * as fs from 'fs';
 import * as qs from 'querystring';
 import * as stream from 'stream';
-import { EventEmitter } from 'events';
+import { EventEmitter, once } from 'events';
 import { closeAllWindows, closeWindow } from './lib/window-helpers';
-import { emittedOnce } from './lib/events-helpers';
 import { WebmGenerator } from './lib/video-helpers';
-import { delay, listen } from './lib/spec-helpers';
+import { listen } from './lib/spec-helpers';
+import { setTimeout } from 'timers/promises';
 
 const fixturesPath = path.resolve(__dirname, 'fixtures');
 
@@ -37,7 +37,7 @@ function getStream (chunkSize = text.length, data: Buffer | string = text) {
   const body = new stream.PassThrough();
 
   async function sendChunks () {
-    await delay(0); // the stream protocol API breaks if you send data immediately.
+    await setTimeout(0); // the stream protocol API breaks if you send data immediately.
     let buf = Buffer.from(data as any); // nodejs typings are wrong, Buffer.from can take a Buffer
     for (;;) {
       body.push(buf.slice(0, chunkSize));
@@ -46,7 +46,7 @@ function getStream (chunkSize = text.length, data: Buffer | string = text) {
         break;
       }
       // emulate some network delay
-      await delay(10);
+      await setTimeout(10);
     }
     body.push(null);
   }
@@ -499,7 +499,7 @@ describe('protocol module', () => {
             data: createStream()
           });
         });
-        const hasEndedPromise = emittedOnce(events, 'end');
+        const hasEndedPromise = once(events, 'end');
         ajax(protocolName + '://fake-host').catch(() => {});
         await hasEndedPromise;
       });
@@ -520,8 +520,8 @@ describe('protocol module', () => {
           events.emit('respond');
         });
 
-        const hasRespondedPromise = emittedOnce(events, 'respond');
-        const hasClosedPromise = emittedOnce(events, 'close');
+        const hasRespondedPromise = once(events, 'respond');
+        const hasClosedPromise = once(events, 'close');
         ajax(protocolName + '://fake-host').catch(() => {});
         await hasRespondedPromise;
         await contents.loadFile(path.join(__dirname, 'fixtures', 'pages', 'fetch.html'));
@@ -713,7 +713,7 @@ describe('protocol module', () => {
     it('can execute redirects', async () => {
       interceptStreamProtocol('http', (request, callback) => {
         if (request.url.indexOf('http://fake-host') === 0) {
-          setTimeout(() => {
+          setTimeout(300).then(() => {
             callback({
               data: '',
               statusCode: 302,
@@ -721,7 +721,7 @@ describe('protocol module', () => {
                 Location: 'http://fake-redirect'
               }
             });
-          }, 300);
+          });
         } else {
           expect(request.url.indexOf('http://fake-redirect')).to.equal(0);
           callback(getStream(1, 'redirect'));
@@ -734,14 +734,14 @@ describe('protocol module', () => {
     it('should discard post data after redirection', async () => {
       interceptStreamProtocol('http', (request, callback) => {
         if (request.url.indexOf('http://fake-host') === 0) {
-          setTimeout(() => {
+          setTimeout(300).then(() => {
             callback({
               statusCode: 302,
               headers: {
                 Location: 'http://fake-redirect'
               }
             });
-          }, 300);
+          });
         } else {
           expect(request.url.indexOf('http://fake-redirect')).to.equal(0);
           callback(getStream(3, request.method));
@@ -770,7 +770,7 @@ describe('protocol module', () => {
       let stderr = '';
       appProcess.stdout.on('data', data => { process.stdout.write(data); stdout += data; });
       appProcess.stderr.on('data', data => { process.stderr.write(data); stderr += data; });
-      const [code] = await emittedOnce(appProcess, 'exit');
+      const [code] = await once(appProcess, 'exit');
       if (code !== 0) {
         console.log('Exit code : ', code);
         console.log('stdout : ', stdout);
@@ -979,13 +979,13 @@ describe('protocol module', () => {
       newContents.on('console-message', (e, level, message) => consoleMessages.push(message));
       try {
         newContents.loadURL(standardScheme + '://fake-host');
-        const [, response] = await emittedOnce(ipcMain, 'response');
+        const [, response] = await once(ipcMain, 'response');
         expect(response).to.deep.equal(expected);
         expect(consoleMessages.join('\n')).to.match(expectedConsole);
       } finally {
         // This is called in a timeout to avoid a crash that happens when
         // calling destroy() in a microtask.
-        setTimeout(() => {
+        setTimeout().then(() => {
           newContents.destroy();
         });
       }
@@ -1082,12 +1082,12 @@ describe('protocol module', () => {
 
       try {
         newContents.loadURL(testingScheme + '://fake-host');
-        const [, response] = await emittedOnce(ipcMain, 'result');
+        const [, response] = await once(ipcMain, 'result');
         expect(response).to.deep.equal(expected);
       } finally {
         // This is called in a timeout to avoid a crash that happens when
         // calling destroy() in a microtask.
-        setTimeout(() => {
+        setTimeout().then(() => {
           newContents.destroy();
         });
       }

+ 4 - 4
spec/api-safe-storage-spec.ts

@@ -2,9 +2,9 @@ import * as cp from 'child_process';
 import * as path from 'path';
 import { safeStorage } from 'electron/main';
 import { expect } from 'chai';
-import { emittedOnce } from './lib/events-helpers';
 import { ifdescribe } from './lib/spec-helpers';
 import * as fs from 'fs-extra';
+import { once } from 'events';
 
 /* isEncryptionAvailable returns false in Linux when running CI due to a mocked dbus. This stops
 * Chrome from reaching the system's keyring or libsecret. When running the tests with config.store
@@ -24,7 +24,7 @@ describe('safeStorage module', () => {
     appProcess.stdout.on('data', data => { output += data; });
     appProcess.stderr.on('data', data => { output += data; });
 
-    const code = (await emittedOnce(appProcess, 'exit'))[0] ?? 1;
+    const code = (await once(appProcess, 'exit'))[0] ?? 1;
 
     if (code !== 0 && output) {
       console.log(output);
@@ -98,7 +98,7 @@ ifdescribe(process.platform !== 'linux')('safeStorage module', () => {
       encryptAppProcess.stderr.on('data', data => { stdout += data; });
 
       try {
-        await emittedOnce(encryptAppProcess, 'exit');
+        await once(encryptAppProcess, 'exit');
 
         const appPath = path.join(fixturesPath, 'api', 'safe-storage', 'decrypt-app');
         const relaunchedAppProcess = cp.spawn(process.execPath, [appPath]);
@@ -107,7 +107,7 @@ ifdescribe(process.platform !== 'linux')('safeStorage module', () => {
         relaunchedAppProcess.stdout.on('data', data => { output += data; });
         relaunchedAppProcess.stderr.on('data', data => { output += data; });
 
-        const [code] = await emittedOnce(relaunchedAppProcess, 'exit');
+        const [code] = await once(relaunchedAppProcess, 'exit');
 
         if (!output.includes('plaintext')) {
           console.log(code, output);

+ 10 - 8
spec/api-service-workers-spec.ts

@@ -4,8 +4,8 @@ import * as path from 'path';
 import { session, webContents, WebContents } from 'electron/main';
 import { expect } from 'chai';
 import { v4 } from 'uuid';
-import { emittedOnce, emittedNTimes } from './lib/events-helpers';
 import { listen } from './lib/spec-helpers';
+import { on, once } from 'events';
 
 const partition = 'service-workers-spec';
 
@@ -50,7 +50,8 @@ describe('session.serviceWorkers', () => {
     });
 
     it('should report one as running once you load a page with a service worker', async () => {
-      await emittedOnce(ses.serviceWorkers, 'console-message', () => w.loadURL(`${baseUrl}/index.html`));
+      w.loadURL(`${baseUrl}/index.html`);
+      await once(ses.serviceWorkers, 'console-message');
       const workers = ses.serviceWorkers.getAllRunning();
       const ids = Object.keys(workers) as any[] as number[];
       expect(ids).to.have.lengthOf(1, 'should have one worker running');
@@ -59,7 +60,8 @@ describe('session.serviceWorkers', () => {
 
   describe('getFromVersionID()', () => {
     it('should report the correct script url and scope', async () => {
-      const eventInfo = await emittedOnce(ses.serviceWorkers, 'console-message', () => w.loadURL(`${baseUrl}/index.html`));
+      w.loadURL(`${baseUrl}/index.html`);
+      const eventInfo = await once(ses.serviceWorkers, 'console-message');
       const details: Electron.MessageDetails = eventInfo[1];
       const worker = ses.serviceWorkers.getFromVersionID(details.versionId);
       expect(worker).to.not.equal(null);
@@ -71,11 +73,11 @@ describe('session.serviceWorkers', () => {
   describe('console-message event', () => {
     it('should correctly keep the source, message and level', async () => {
       const messages: Record<string, Electron.MessageDetails> = {};
-      const events = await emittedNTimes(ses.serviceWorkers, 'console-message', 4, () => w.loadURL(`${baseUrl}/logs.html`));
-      for (const event of events) {
-        messages[event[1].message] = event[1];
-
-        expect(event[1]).to.have.property('source', 'console-api');
+      w.loadURL(`${baseUrl}/logs.html`);
+      for await (const [, details] of on(ses.serviceWorkers, 'console-message')) {
+        messages[details.message] = details;
+        expect(details).to.have.property('source', 'console-api');
+        if (Object.keys(messages).length >= 4) break;
       }
 
       expect(messages).to.have.property('log log');

+ 13 - 12
spec/api-session-spec.ts

@@ -8,8 +8,9 @@ import { app, session, BrowserWindow, net, ipcMain, Session, webFrameMain, WebFr
 import * as send from 'send';
 import * as auth from 'basic-auth';
 import { closeAllWindows } from './lib/window-helpers';
-import { emittedOnce } from './lib/events-helpers';
-import { defer, delay, listen } from './lib/spec-helpers';
+import { defer, listen } from './lib/spec-helpers';
+import { once } from 'events';
+import { setTimeout } from 'timers/promises';
 
 /* The whole session API doesn't use standard callbacks */
 /* eslint-disable standard/no-callback-literal */
@@ -184,11 +185,11 @@ describe('session module', () => {
       const name = 'foo';
       const value = 'bar';
 
-      const a = emittedOnce(cookies, 'changed');
+      const a = once(cookies, 'changed');
       await cookies.set({ url, name, value, expirationDate: (+new Date()) / 1000 + 120 });
       const [, setEventCookie, setEventCause, setEventRemoved] = await a;
 
-      const b = emittedOnce(cookies, 'changed');
+      const b = once(cookies, 'changed');
       await cookies.remove(url, name);
       const [, removeEventCookie, removeEventCause, removeEventRemoved] = await b;
 
@@ -331,7 +332,7 @@ describe('session module', () => {
       customSession = session.fromPartition(partitionName);
       await customSession.protocol.registerStringProtocol(protocolName, handler);
       w.loadURL(`${protocolName}://fake-host`);
-      await emittedOnce(ipcMain, 'hello');
+      await once(ipcMain, 'hello');
     });
   });
 
@@ -345,7 +346,7 @@ describe('session module', () => {
       if (!created) {
         // Work around for https://github.com/electron/electron/issues/26166 to
         // reduce flake
-        await delay(100);
+        await setTimeout(100);
         created = true;
       }
     });
@@ -654,7 +655,7 @@ describe('session module', () => {
 
       const w = new BrowserWindow({ show: false, webPreferences: { session: ses } });
       await expect(w.loadURL(serverUrl), 'first load').to.eventually.be.rejectedWith(/ERR_FAILED/);
-      await emittedOnce(w.webContents, 'did-stop-loading');
+      await once(w.webContents, 'did-stop-loading');
       await expect(w.loadURL(serverUrl + '/test'), 'second load').to.eventually.be.rejectedWith(/ERR_FAILED/);
       expect(w.webContents.getTitle()).to.equal(serverUrl + '/test');
       expect(numVerificationRequests).to.equal(1);
@@ -667,7 +668,7 @@ describe('session module', () => {
 
       const req = net.request({ url: serverUrl, session: ses1, credentials: 'include' });
       req.end();
-      setTimeout(() => {
+      setTimeout().then(() => {
         ses2.setCertificateVerifyProc((opts, callback) => callback(0));
       });
       await expect(new Promise<void>((resolve, reject) => {
@@ -963,7 +964,7 @@ describe('session module', () => {
         length: 5242880
       };
       const w = new BrowserWindow({ show: false });
-      const p = emittedOnce(w.webContents.session, 'will-download');
+      const p = once(w.webContents.session, 'will-download');
       w.webContents.session.createInterruptedDownload(options);
       const [, item] = await p;
       expect(item.getState()).to.equal('interrupted');
@@ -1070,7 +1071,7 @@ describe('session module', () => {
         cb(`<html><script>(${remote})()</script></html>`);
       });
 
-      const result = emittedOnce(require('electron').ipcMain, 'message');
+      const result = once(require('electron').ipcMain, 'message');
 
       function remote () {
         (navigator as any).requestMIDIAccess({ sysex: true }).then(() => {}, (err: any) => {
@@ -1197,7 +1198,7 @@ describe('session module', () => {
         document.body.appendChild(iframe);
         null;
       `);
-      const [,, frameProcessId, frameRoutingId] = await emittedOnce(w.webContents, 'did-frame-finish-load');
+      const [,, frameProcessId, frameRoutingId] = await once(w.webContents, 'did-frame-finish-load');
       const state = await readClipboardPermission(webFrameMain.fromId(frameProcessId, frameRoutingId));
       expect(state).to.equal('granted');
       expect(handlerDetails!.requestingUrl).to.equal(loadUrl);
@@ -1260,7 +1261,7 @@ describe('session module', () => {
 
   describe('session-created event', () => {
     it('is emitted when a session is created', async () => {
-      const sessionCreated = emittedOnce(app, 'session-created');
+      const sessionCreated = once(app, 'session-created');
       const session1 = session.fromPartition('' + Math.random());
       const [session2] = await sessionCreated;
       expect(session1).to.equal(session2);

+ 2 - 2
spec/api-shell-spec.ts

@@ -1,13 +1,13 @@
 import { BrowserWindow, app } from 'electron/main';
 import { shell } from 'electron/common';
 import { closeAllWindows } from './lib/window-helpers';
-import { emittedOnce } from './lib/events-helpers';
 import { ifdescribe, ifit, listen } from './lib/spec-helpers';
 import * as http from 'http';
 import * as fs from 'fs-extra';
 import * as os from 'os';
 import * as path from 'path';
 import { expect } from 'chai';
+import { once } from 'events';
 
 describe('shell module', () => {
   describe('shell.openExternal()', () => {
@@ -45,7 +45,7 @@ describe('shell module', () => {
         // https://github.com/electron/electron/pull/19969#issuecomment-526278890),
         // so use a blur event as a crude proxy.
         const w = new BrowserWindow({ show: true });
-        requestReceived = emittedOnce(w, 'blur');
+        requestReceived = once(w, 'blur');
       } else {
         const server = http.createServer((req, res) => {
           res.end();

+ 10 - 9
spec/api-subframe-spec.ts

@@ -1,10 +1,11 @@
 import { expect } from 'chai';
 import * as path from 'path';
 import * as http from 'http';
-import { emittedNTimes, emittedOnce } from './lib/events-helpers';
+import { emittedNTimes } from './lib/events-helpers';
 import { closeWindow } from './lib/window-helpers';
 import { app, BrowserWindow, ipcMain } from 'electron/main';
 import { ifdescribe, listen } from './lib/spec-helpers';
+import { once } from 'events';
 
 describe('renderer nodeIntegrationInSubFrames', () => {
   const generateTests = (description: string, webPreferences: any) => {
@@ -57,7 +58,7 @@ describe('renderer nodeIntegrationInSubFrames', () => {
         const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
         w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
         const [event1] = await detailsPromise;
-        const pongPromise = emittedOnce(ipcMain, 'preload-pong');
+        const pongPromise = once(ipcMain, 'preload-pong');
         event1[0].reply('preload-ping');
         const [, frameId] = await pongPromise;
         expect(frameId).to.equal(event1[0].frameId);
@@ -67,7 +68,7 @@ describe('renderer nodeIntegrationInSubFrames', () => {
         const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
         w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
         const [event1] = await detailsPromise;
-        const pongPromise = emittedOnce(ipcMain, 'preload-pong');
+        const pongPromise = once(ipcMain, 'preload-pong');
         event1[0].senderFrame.send('preload-ping');
         const [, frameId] = await pongPromise;
         expect(frameId).to.equal(event1[0].frameId);
@@ -77,7 +78,7 @@ describe('renderer nodeIntegrationInSubFrames', () => {
         const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
         w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
         const [, event2] = await detailsPromise;
-        const pongPromise = emittedOnce(ipcMain, 'preload-pong');
+        const pongPromise = once(ipcMain, 'preload-pong');
         event2[0].reply('preload-ping');
         const [, frameId] = await pongPromise;
         expect(frameId).to.equal(event2[0].frameId);
@@ -87,7 +88,7 @@ describe('renderer nodeIntegrationInSubFrames', () => {
         const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
         w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
         const [, event2] = await detailsPromise;
-        const pongPromise = emittedOnce(ipcMain, 'preload-pong');
+        const pongPromise = once(ipcMain, 'preload-pong');
         event2[0].senderFrame.send('preload-ping');
         const [, frameId] = await pongPromise;
         expect(frameId).to.equal(event2[0].frameId);
@@ -97,7 +98,7 @@ describe('renderer nodeIntegrationInSubFrames', () => {
         const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 3);
         w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-with-frame-container${fixtureSuffix}.html`));
         const [, , event3] = await detailsPromise;
-        const pongPromise = emittedOnce(ipcMain, 'preload-pong');
+        const pongPromise = once(ipcMain, 'preload-pong');
         event3[0].reply('preload-ping');
         const [, frameId] = await pongPromise;
         expect(frameId).to.equal(event3[0].frameId);
@@ -107,7 +108,7 @@ describe('renderer nodeIntegrationInSubFrames', () => {
         const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 3);
         w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-with-frame-container${fixtureSuffix}.html`));
         const [, , event3] = await detailsPromise;
-        const pongPromise = emittedOnce(ipcMain, 'preload-pong');
+        const pongPromise = once(ipcMain, 'preload-pong');
         event3[0].senderFrame.send('preload-ping');
         const [, frameId] = await pongPromise;
         expect(frameId).to.equal(event3[0].frameId);
@@ -201,8 +202,8 @@ describe('renderer nodeIntegrationInSubFrames', () => {
     });
 
     it('should not load preload scripts', async () => {
-      const promisePass = emittedOnce(ipcMain, 'webview-loaded');
-      const promiseFail = emittedOnce(ipcMain, 'preload-in-frame').then(() => {
+      const promisePass = once(ipcMain, 'webview-loaded');
+      const promiseFail = once(ipcMain, 'preload-in-frame').then(() => {
         throw new Error('preload loaded in internal frame');
       });
       await w.loadURL('about:blank');

+ 42 - 42
spec/api-utility-process-spec.ts

@@ -2,9 +2,9 @@ import { expect } from 'chai';
 import * as childProcess from 'child_process';
 import * as path from 'path';
 import { BrowserWindow, MessageChannelMain, utilityProcess } from 'electron/main';
-import { emittedOnce } from './lib/events-helpers';
 import { ifit } from './lib/spec-helpers';
 import { closeWindow } from './lib/window-helpers';
+import { once } from 'events';
 
 const fixturesPath = path.resolve(__dirname, 'fixtures', 'api', 'utility-process');
 const isWindowsOnArm = process.platform === 'win32' && process.arch === 'arm64';
@@ -55,12 +55,12 @@ describe('utilityProcess module', () => {
   describe('lifecycle events', () => {
     it('emits \'spawn\' when child process successfully launches', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'empty.js'));
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
     });
 
     it('emits \'exit\' when child process exits gracefully', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'empty.js'));
-      const [code] = await emittedOnce(child, 'exit');
+      const [code] = await once(child, 'exit');
       expect(code).to.equal(0);
     });
 
@@ -68,28 +68,28 @@ describe('utilityProcess module', () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'crash.js'));
       // Do not check for exit code in this case,
       // SIGSEGV code can be 139 or 11 across our different CI pipeline.
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
     });
 
     it('emits \'exit\' corresponding to the child process', async () => {
       const child1 = utilityProcess.fork(path.join(fixturesPath, 'endless.js'));
-      await emittedOnce(child1, 'spawn');
+      await once(child1, 'spawn');
       const child2 = utilityProcess.fork(path.join(fixturesPath, 'crash.js'));
-      await emittedOnce(child2, 'exit');
+      await once(child2, 'exit');
       expect(child1.kill()).to.be.true();
-      await emittedOnce(child1, 'exit');
+      await once(child1, 'exit');
     });
 
     it('emits \'exit\' when there is uncaught exception', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'exception.js'));
-      const [code] = await emittedOnce(child, 'exit');
+      const [code] = await once(child, 'exit');
       expect(code).to.equal(1);
     });
 
     it('emits \'exit\' when process.exit is called', async () => {
       const exitCode = 2;
       const child = utilityProcess.fork(path.join(fixturesPath, 'custom-exit.js'), [`--exitCode=${exitCode}`]);
-      const [code] = await emittedOnce(child, 'exit');
+      const [code] = await once(child, 'exit');
       expect(code).to.equal(exitCode);
     });
   });
@@ -99,16 +99,16 @@ describe('utilityProcess module', () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'endless.js'), [], {
         serviceName: 'endless'
       });
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       expect(child.kill()).to.be.true();
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
     });
   });
 
   describe('pid property', () => {
     it('is valid when child process launches successfully', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'empty.js'));
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       expect(child.pid).to.not.be.null();
     });
 
@@ -121,33 +121,33 @@ describe('utilityProcess module', () => {
   describe('stdout property', () => {
     it('is null when child process launches with default stdio', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'));
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       expect(child.stdout).to.be.null();
       expect(child.stderr).to.be.null();
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
     });
 
     it('is null when child process launches with ignore stdio configuration', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'), [], {
         stdio: 'ignore'
       });
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       expect(child.stdout).to.be.null();
       expect(child.stderr).to.be.null();
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
     });
 
     it('is valid when child process launches with pipe stdio configuration', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'), [], {
         stdio: 'pipe'
       });
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       expect(child.stdout).to.not.be.null();
       let log = '';
       child.stdout!.on('data', (chunk) => {
         log += chunk.toString('utf8');
       });
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
       expect(log).to.equal('hello\n');
     });
   });
@@ -155,32 +155,32 @@ describe('utilityProcess module', () => {
   describe('stderr property', () => {
     it('is null when child process launches with default stdio', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'));
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       expect(child.stdout).to.be.null();
       expect(child.stderr).to.be.null();
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
     });
 
     it('is null when child process launches with ignore stdio configuration', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'), [], {
         stdio: 'ignore'
       });
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       expect(child.stderr).to.be.null();
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
     });
 
     ifit(!isWindowsOnArm)('is valid when child process launches with pipe stdio configuration', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'log.js'), [], {
         stdio: ['ignore', 'pipe', 'pipe']
       });
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       expect(child.stderr).to.not.be.null();
       let log = '';
       child.stderr!.on('data', (chunk) => {
         log += chunk.toString('utf8');
       });
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
       expect(log).to.equal('world');
     });
   });
@@ -189,25 +189,25 @@ describe('utilityProcess module', () => {
     it('establishes a default ipc channel with the child process', async () => {
       const result = 'I will be echoed.';
       const child = utilityProcess.fork(path.join(fixturesPath, 'post-message.js'));
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       child.postMessage(result);
-      const [data] = await emittedOnce(child, 'message');
+      const [data] = await once(child, 'message');
       expect(data).to.equal(result);
-      const exit = emittedOnce(child, 'exit');
+      const exit = once(child, 'exit');
       expect(child.kill()).to.be.true();
       await exit;
     });
 
     it('supports queuing messages on the receiving end', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'post-message-queue.js'));
-      const p = emittedOnce(child, 'spawn');
+      const p = once(child, 'spawn');
       child.postMessage('This message');
       child.postMessage(' is');
       child.postMessage(' queued');
       await p;
-      const [data] = await emittedOnce(child, 'message');
+      const [data] = await once(child, 'message');
       expect(data).to.equal('This message is queued');
-      const exit = emittedOnce(child, 'exit');
+      const exit = once(child, 'exit');
       expect(child.kill()).to.be.true();
       await exit;
     });
@@ -270,7 +270,7 @@ describe('utilityProcess module', () => {
       const appProcess = childProcess.spawn(process.execPath, [path.join(fixturesPath, 'inherit-stdout'), `--payload=${result}`]);
       let output = '';
       appProcess.stdout.on('data', (data: Buffer) => { output += data; });
-      await emittedOnce(appProcess, 'exit');
+      await once(appProcess, 'exit');
       expect(output).to.equal(result);
     });
 
@@ -279,7 +279,7 @@ describe('utilityProcess module', () => {
       const appProcess = childProcess.spawn(process.execPath, [path.join(fixturesPath, 'inherit-stderr'), `--payload=${result}`]);
       let output = '';
       appProcess.stderr.on('data', (data: Buffer) => { output += data; });
-      await emittedOnce(appProcess, 'exit');
+      await once(appProcess, 'exit');
       expect(output).to.include(result);
     });
 
@@ -297,12 +297,12 @@ describe('utilityProcess module', () => {
       w.webContents.postMessage('port', result, [rendererPort]);
       // Send renderer and main channel port to utility process.
       const child = utilityProcess.fork(path.join(fixturesPath, 'receive-message.js'));
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       child.postMessage('', [childPort1]);
-      const [data] = await emittedOnce(child, 'message');
+      const [data] = await once(child, 'message');
       expect(data).to.equal(result);
       // Cleanup.
-      const exit = emittedOnce(child, 'exit');
+      const exit = once(child, 'exit');
       expect(child.kill()).to.be.true();
       await exit;
       await closeWindow(w);
@@ -310,10 +310,10 @@ describe('utilityProcess module', () => {
 
     ifit(process.platform === 'linux')('allows executing a setuid binary with child_process', async () => {
       const child = utilityProcess.fork(path.join(fixturesPath, 'suid.js'));
-      await emittedOnce(child, 'spawn');
-      const [data] = await emittedOnce(child, 'message');
+      await once(child, 'spawn');
+      const [data] = await once(child, 'message');
       expect(data).to.not.be.empty();
-      const exit = emittedOnce(child, 'exit');
+      const exit = once(child, 'exit');
       expect(child.kill()).to.be.true();
       await exit;
     });
@@ -327,7 +327,7 @@ describe('utilityProcess module', () => {
       });
       let output = '';
       appProcess.stdout.on('data', (data: Buffer) => { output += data; });
-      await emittedOnce(appProcess.stdout, 'end');
+      await once(appProcess.stdout, 'end');
       const result = process.platform === 'win32' ? '\r\nparent' : 'parent';
       expect(output).to.equal(result);
     });
@@ -341,7 +341,7 @@ describe('utilityProcess module', () => {
       });
       let output = '';
       appProcess.stdout.on('data', (data: Buffer) => { output += data; });
-      await emittedOnce(appProcess.stdout, 'end');
+      await once(appProcess.stdout, 'end');
       const result = process.platform === 'win32' ? '\r\nchild' : 'child';
       expect(output).to.equal(result);
     });
@@ -351,13 +351,13 @@ describe('utilityProcess module', () => {
         cwd: fixturesPath,
         stdio: ['ignore', 'pipe', 'ignore']
       });
-      await emittedOnce(child, 'spawn');
+      await once(child, 'spawn');
       expect(child.stdout).to.not.be.null();
       let log = '';
       child.stdout!.on('data', (chunk) => {
         log += chunk.toString('utf8');
       });
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
       expect(log).to.equal('hello\n');
     });
   });

+ 75 - 74
spec/api-web-contents-spec.ts

@@ -4,9 +4,10 @@ import * as path from 'path';
 import * as fs from 'fs';
 import * as http from 'http';
 import { BrowserWindow, ipcMain, webContents, session, app, BrowserView } from 'electron/main';
-import { emittedOnce } from './lib/events-helpers';
 import { closeAllWindows } from './lib/window-helpers';
-import { ifdescribe, delay, defer, waitUntil, listen } from './lib/spec-helpers';
+import { ifdescribe, defer, waitUntil, listen } from './lib/spec-helpers';
+import { once } from 'events';
+import { setTimeout } from 'timers/promises';
 
 const pdfjs = require('pdfjs-dist');
 const fixturesPath = path.resolve(__dirname, 'fixtures');
@@ -23,11 +24,11 @@ describe('webContents module', () => {
       });
       w.loadFile(path.join(fixturesPath, 'pages', 'webview-zoom-factor.html'));
 
-      await emittedOnce(w.webContents, 'did-attach-webview');
+      await once(w.webContents, 'did-attach-webview');
 
       w.webContents.openDevTools();
 
-      await emittedOnce(w.webContents, 'devtools-opened');
+      await once(w.webContents, 'devtools-opened');
 
       const all = webContents.getAllWebContents().sort((a, b) => {
         return a.id - b.id;
@@ -94,7 +95,7 @@ describe('webContents module', () => {
         expect.fail('should not have fired');
       });
       await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'beforeunload-undefined.html'));
-      const wait = emittedOnce(w, 'closed');
+      const wait = once(w, 'closed');
       w.close();
       await wait;
     });
@@ -110,7 +111,7 @@ describe('webContents module', () => {
       });
 
       await view.webContents.loadFile(path.join(__dirname, 'fixtures', 'api', 'beforeunload-undefined.html'));
-      const wait = emittedOnce(w, 'closed');
+      const wait = once(w, 'closed');
       w.close();
       await wait;
     });
@@ -119,7 +120,7 @@ describe('webContents module', () => {
       const w = new BrowserWindow({ show: false });
       await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'beforeunload-false.html'));
       w.close();
-      await emittedOnce(w.webContents, 'will-prevent-unload');
+      await once(w.webContents, 'will-prevent-unload');
     });
 
     it('emits if beforeunload returns false in a BrowserView', async () => {
@@ -130,14 +131,14 @@ describe('webContents module', () => {
 
       await view.webContents.loadFile(path.join(__dirname, 'fixtures', 'api', 'beforeunload-false.html'));
       w.close();
-      await emittedOnce(view.webContents, 'will-prevent-unload');
+      await once(view.webContents, 'will-prevent-unload');
     });
 
     it('supports calling preventDefault on will-prevent-unload events in a BrowserWindow', async () => {
       const w = new BrowserWindow({ show: false });
       w.webContents.once('will-prevent-unload', event => event.preventDefault());
       await w.loadFile(path.join(__dirname, 'fixtures', 'api', 'beforeunload-false.html'));
-      const wait = emittedOnce(w, 'closed');
+      const wait = once(w, 'closed');
       w.close();
       await wait;
     });
@@ -171,9 +172,9 @@ describe('webContents module', () => {
         }
       });
       w.loadFile(path.join(fixturesPath, 'pages', 'send-after-node.html'));
-      setTimeout(() => {
+      setTimeout(50).then(() => {
         w.webContents.send('test');
-      }, 50);
+      });
     });
   });
 
@@ -362,7 +363,7 @@ describe('webContents module', () => {
 
     it('resolves when navigating within the page', async () => {
       await w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'));
-      await delay();
+      await setTimeout();
       await expect(w.loadURL(w.getURL() + '#foo')).to.eventually.be.fulfilled();
     });
 
@@ -494,11 +495,11 @@ describe('webContents module', () => {
       await w.loadFile(path.join(__dirname, 'fixtures', 'blank.html'));
       expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.id);
 
-      const devToolsOpened = emittedOnce(w.webContents, 'devtools-opened');
+      const devToolsOpened = once(w.webContents, 'devtools-opened');
       w.webContents.openDevTools();
       await devToolsOpened;
       expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.devToolsWebContents!.id);
-      const devToolsClosed = emittedOnce(w.webContents, 'devtools-closed');
+      const devToolsClosed = once(w.webContents, 'devtools-closed');
       w.webContents.closeDevTools();
       await devToolsClosed;
       expect(webContents.getFocusedWebContents().id).to.equal(w.webContents.id);
@@ -511,14 +512,14 @@ describe('webContents module', () => {
       w.webContents.inspectElement(100, 100);
 
       // For some reason we have to wait for two focused events...?
-      await emittedOnce(w.webContents, 'devtools-focused');
+      await once(w.webContents, 'devtools-focused');
 
       expect(() => { webContents.getFocusedWebContents(); }).to.not.throw();
 
       // Work around https://github.com/electron/electron/issues/19985
-      await delay();
+      await setTimeout();
 
-      const devToolsClosed = emittedOnce(w.webContents, 'devtools-closed');
+      const devToolsClosed = once(w.webContents, 'devtools-closed');
       w.webContents.closeDevTools();
       await devToolsClosed;
       expect(() => { webContents.getFocusedWebContents(); }).to.not.throw();
@@ -530,7 +531,7 @@ describe('webContents module', () => {
     it('sets arbitrary webContents as devtools', async () => {
       const w = new BrowserWindow({ show: false });
       const devtools = new BrowserWindow({ show: false });
-      const promise = emittedOnce(devtools.webContents, 'dom-ready');
+      const promise = once(devtools.webContents, 'dom-ready');
       w.webContents.setDevToolsWebContents(devtools.webContents);
       w.webContents.openDevTools();
       await promise;
@@ -564,11 +565,11 @@ describe('webContents module', () => {
         oscillator.connect(context.destination)
         oscillator.start()
       `);
-      let p = emittedOnce(w.webContents, '-audio-state-changed');
+      let p = once(w.webContents, '-audio-state-changed');
       w.webContents.executeJavaScript('context.resume()');
       await p;
       expect(w.webContents.isCurrentlyAudible()).to.be.true();
-      p = emittedOnce(w.webContents, '-audio-state-changed');
+      p = once(w.webContents, '-audio-state-changed');
       w.webContents.executeJavaScript('oscillator.stop()');
       await p;
       expect(w.webContents.isCurrentlyAudible()).to.be.false();
@@ -579,15 +580,15 @@ describe('webContents module', () => {
     afterEach(closeAllWindows);
     it('can show window with activation', async () => {
       const w = new BrowserWindow({ show: false });
-      const focused = emittedOnce(w, 'focus');
+      const focused = once(w, 'focus');
       w.show();
       await focused;
       expect(w.isFocused()).to.be.true();
-      const blurred = emittedOnce(w, 'blur');
+      const blurred = once(w, 'blur');
       w.webContents.openDevTools({ mode: 'detach', activate: true });
       await Promise.all([
-        emittedOnce(w.webContents, 'devtools-opened'),
-        emittedOnce(w.webContents, 'devtools-focused')
+        once(w.webContents, 'devtools-opened'),
+        once(w.webContents, 'devtools-focused')
       ]);
       await blurred;
       expect(w.isFocused()).to.be.false();
@@ -595,7 +596,7 @@ describe('webContents module', () => {
 
     it('can show window without activation', async () => {
       const w = new BrowserWindow({ show: false });
-      const devtoolsOpened = emittedOnce(w.webContents, 'devtools-opened');
+      const devtoolsOpened = once(w.webContents, 'devtools-opened');
       w.webContents.openDevTools({ mode: 'detach', activate: false });
       await devtoolsOpened;
       expect(w.webContents.isDevToolsOpened()).to.be.true();
@@ -629,7 +630,7 @@ describe('webContents module', () => {
         if (opts.meta) modifiers.push('meta');
         if (opts.isAutoRepeat) modifiers.push('isAutoRepeat');
 
-        const p = emittedOnce(w.webContents, 'before-input-event');
+        const p = once(w.webContents, 'before-input-event');
         w.webContents.sendInputEvent({
           type: opts.type,
           keyCode: opts.keyCode,
@@ -712,7 +713,7 @@ describe('webContents module', () => {
           modifiers: ['control', 'meta']
         });
 
-        const [, zoomDirection] = await emittedOnce(w.webContents, 'zoom-changed');
+        const [, zoomDirection] = await once(w.webContents, 'zoom-changed');
         expect(zoomDirection).to.equal('in');
       };
 
@@ -735,7 +736,7 @@ describe('webContents module', () => {
           modifiers: ['control', 'meta']
         });
 
-        const [, zoomDirection] = await emittedOnce(w.webContents, 'zoom-changed');
+        const [, zoomDirection] = await once(w.webContents, 'zoom-changed');
         expect(zoomDirection).to.equal('out');
       };
 
@@ -752,7 +753,7 @@ describe('webContents module', () => {
     afterEach(closeAllWindows);
 
     it('can send keydown events', async () => {
-      const keydown = emittedOnce(ipcMain, 'keydown');
+      const keydown = once(ipcMain, 'keydown');
       w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' });
       const [, key, code, keyCode, shiftKey, ctrlKey, altKey] = await keydown;
       expect(key).to.equal('a');
@@ -764,7 +765,7 @@ describe('webContents module', () => {
     });
 
     it('can send keydown events with modifiers', async () => {
-      const keydown = emittedOnce(ipcMain, 'keydown');
+      const keydown = once(ipcMain, 'keydown');
       w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Z', modifiers: ['shift', 'ctrl'] });
       const [, key, code, keyCode, shiftKey, ctrlKey, altKey] = await keydown;
       expect(key).to.equal('Z');
@@ -776,7 +777,7 @@ describe('webContents module', () => {
     });
 
     it('can send keydown events with special keys', async () => {
-      const keydown = emittedOnce(ipcMain, 'keydown');
+      const keydown = once(ipcMain, 'keydown');
       w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Tab', modifiers: ['alt'] });
       const [, key, code, keyCode, shiftKey, ctrlKey, altKey] = await keydown;
       expect(key).to.equal('Tab');
@@ -788,7 +789,7 @@ describe('webContents module', () => {
     });
 
     it('can send char events', async () => {
-      const keypress = emittedOnce(ipcMain, 'keypress');
+      const keypress = once(ipcMain, 'keypress');
       w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'A' });
       w.webContents.sendInputEvent({ type: 'char', keyCode: 'A' });
       const [, key, code, keyCode, shiftKey, ctrlKey, altKey] = await keypress;
@@ -801,7 +802,7 @@ describe('webContents module', () => {
     });
 
     it('can send char events with modifiers', async () => {
-      const keypress = emittedOnce(ipcMain, 'keypress');
+      const keypress = once(ipcMain, 'keypress');
       w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Z' });
       w.webContents.sendInputEvent({ type: 'char', keyCode: 'Z', modifiers: ['shift', 'ctrl'] });
       const [, key, code, keyCode, shiftKey, ctrlKey, altKey] = await keypress;
@@ -839,7 +840,7 @@ describe('webContents module', () => {
     it('supports inspecting an element in the devtools', async () => {
       const w = new BrowserWindow({ show: false });
       w.loadURL('about:blank');
-      const event = emittedOnce(w.webContents, 'devtools-opened');
+      const event = once(w.webContents, 'devtools-opened');
       w.webContents.inspectElement(10, 10);
       await event;
     });
@@ -882,7 +883,7 @@ describe('webContents module', () => {
     });
 
     const moveFocusToDevTools = async (win: BrowserWindow) => {
-      const devToolsOpened = emittedOnce(win.webContents, 'devtools-opened');
+      const devToolsOpened = once(win.webContents, 'devtools-opened');
       win.webContents.openDevTools({ mode: 'right' });
       await devToolsOpened;
       win.webContents.devToolsWebContents!.focus();
@@ -895,7 +896,7 @@ describe('webContents module', () => {
         const w = new BrowserWindow({ show: false });
         await w.loadURL('about:blank');
         await moveFocusToDevTools(w);
-        const focusPromise = emittedOnce(w.webContents, 'focus');
+        const focusPromise = once(w.webContents, 'focus');
         w.webContents.focus();
         await expect(focusPromise).to.eventually.be.fulfilled();
       });
@@ -907,7 +908,7 @@ describe('webContents module', () => {
         const w = new BrowserWindow({ show: true });
         await w.loadURL('about:blank');
         w.webContents.focus();
-        const blurPromise = emittedOnce(w.webContents, 'blur');
+        const blurPromise = once(w.webContents, 'blur');
         await moveFocusToDevTools(w);
         await expect(blurPromise).to.eventually.be.fulfilled();
       });
@@ -1175,9 +1176,9 @@ describe('webContents module', () => {
     it('can persist when it contains iframe', (done) => {
       const w = new BrowserWindow({ show: false });
       const server = http.createServer((req, res) => {
-        setTimeout(() => {
+        setTimeout(200).then(() => {
           res.end();
-        }, 200);
+        });
       });
       server.listen(0, '127.0.0.1', () => {
         const url = 'http://127.0.0.1:' + (server.address() as AddressInfo).port;
@@ -1208,7 +1209,7 @@ describe('webContents module', () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
       const w2 = new BrowserWindow({ show: false });
 
-      const temporaryZoomSet = emittedOnce(ipcMain, 'temporary-zoom-set');
+      const temporaryZoomSet = once(ipcMain, 'temporary-zoom-set');
       w.loadFile(path.join(fixturesPath, 'pages', 'webframe-zoom.html'));
       await temporaryZoomSet;
 
@@ -1232,7 +1233,7 @@ describe('webContents module', () => {
 
       before(async () => {
         server = http.createServer((req, res) => {
-          setTimeout(() => res.end('hey'), 0);
+          setTimeout().then(() => res.end('hey'));
         });
         serverUrl = (await listen(server)).url;
         crossSiteUrl = serverUrl.replace('127.0.0.1', 'localhost');
@@ -1249,12 +1250,12 @@ describe('webContents module', () => {
           webFrame.setZoomLevel(0.6)
           ipcRenderer.send('zoom-level-set', webFrame.getZoomLevel())
         `;
-        const zoomLevelPromise = emittedOnce(ipcMain, 'zoom-level-set');
+        const zoomLevelPromise = once(ipcMain, 'zoom-level-set');
         await w.loadURL(serverUrl);
         await w.webContents.executeJavaScript(source);
         let [, zoomLevel] = await zoomLevelPromise;
         expect(zoomLevel).to.equal(0.6);
-        const loadPromise = emittedOnce(w.webContents, 'did-finish-load');
+        const loadPromise = once(w.webContents, 'did-finish-load');
         await w.loadURL(crossSiteUrl);
         await loadPromise;
         zoomLevel = w.webContents.zoomLevel;
@@ -1285,7 +1286,7 @@ describe('webContents module', () => {
     it('can get opener with window.open()', async () => {
       const w = new BrowserWindow({ show: false, webPreferences: { sandbox: true } });
       await w.loadURL('about:blank');
-      const childPromise = emittedOnce(w.webContents, 'did-create-window');
+      const childPromise = once(w.webContents, 'did-create-window');
       w.webContents.executeJavaScript('window.open("about:blank")', true);
       const [childWindow] = await childPromise;
       expect(childWindow.webContents.opener).to.equal(w.webContents.mainFrame);
@@ -1293,7 +1294,7 @@ describe('webContents module', () => {
     it('has no opener when using "noopener"', async () => {
       const w = new BrowserWindow({ show: false, webPreferences: { sandbox: true } });
       await w.loadURL('about:blank');
-      const childPromise = emittedOnce(w.webContents, 'did-create-window');
+      const childPromise = once(w.webContents, 'did-create-window');
       w.webContents.executeJavaScript('window.open("about:blank", undefined, "noopener")', true);
       const [childWindow] = await childPromise;
       expect(childWindow.webContents.opener).to.be.null();
@@ -1301,7 +1302,7 @@ describe('webContents module', () => {
     it('can get opener with a[target=_blank][rel=opener]', async () => {
       const w = new BrowserWindow({ show: false, webPreferences: { sandbox: true } });
       await w.loadURL('about:blank');
-      const childPromise = emittedOnce(w.webContents, 'did-create-window');
+      const childPromise = once(w.webContents, 'did-create-window');
       w.webContents.executeJavaScript(`(function() {
         const a = document.createElement('a');
         a.target = '_blank';
@@ -1315,7 +1316,7 @@ describe('webContents module', () => {
     it('has no opener with a[target=_blank][rel=noopener]', async () => {
       const w = new BrowserWindow({ show: false, webPreferences: { sandbox: true } });
       await w.loadURL('about:blank');
-      const childPromise = emittedOnce(w.webContents, 'did-create-window');
+      const childPromise = once(w.webContents, 'did-create-window');
       w.webContents.executeJavaScript(`(function() {
         const a = document.createElement('a');
         a.target = '_blank';
@@ -1350,7 +1351,7 @@ describe('webContents module', () => {
             res.end();
           }
         };
-        setTimeout(respond, 0);
+        setTimeout().then(respond);
       });
       serverUrl = (await listen(server)).url;
       crossSiteUrl = serverUrl.replace('127.0.0.1', 'localhost');
@@ -1373,7 +1374,7 @@ describe('webContents module', () => {
         w.webContents.removeListener('current-render-view-deleted' as any, renderViewDeletedHandler);
         w.close();
       });
-      const destroyed = emittedOnce(w.webContents, 'destroyed');
+      const destroyed = once(w.webContents, 'destroyed');
       w.loadURL(`${serverUrl}/redirect-cross-site`);
       await destroyed;
       expect(currentRenderViewDeletedEmitted).to.be.false('current-render-view-deleted was emitted');
@@ -1383,7 +1384,7 @@ describe('webContents module', () => {
       const parentWindow = new BrowserWindow({ show: false });
       let currentRenderViewDeletedEmitted = false;
       let childWindow: BrowserWindow | null = null;
-      const destroyed = emittedOnce(parentWindow.webContents, 'destroyed');
+      const destroyed = once(parentWindow.webContents, 'destroyed');
       const renderViewDeletedHandler = () => {
         currentRenderViewDeletedEmitted = true;
       };
@@ -1411,7 +1412,7 @@ describe('webContents module', () => {
       w.webContents.on('did-finish-load', () => {
         w.close();
       });
-      const destroyed = emittedOnce(w.webContents, 'destroyed');
+      const destroyed = once(w.webContents, 'destroyed');
       w.loadURL(`${serverUrl}/redirect-cross-site`);
       await destroyed;
       expect(currentRenderViewDeletedEmitted).to.be.true('current-render-view-deleted wasn\'t emitted');
@@ -1426,7 +1427,7 @@ describe('webContents module', () => {
       w.webContents.on('did-finish-load', () => {
         w.close();
       });
-      const destroyed = emittedOnce(w.webContents, 'destroyed');
+      const destroyed = once(w.webContents, 'destroyed');
       w.loadURL(`${serverUrl}/redirect-cross-site`);
       await destroyed;
       const expectedRenderViewDeletedEventCount = 1;
@@ -1477,7 +1478,7 @@ describe('webContents module', () => {
 
       it('forcefullyCrashRenderer() crashes the process with reason=killed||crashed', async () => {
         expect(w.webContents.isCrashed()).to.equal(false);
-        const crashEvent = emittedOnce(w.webContents, 'render-process-gone');
+        const crashEvent = once(w.webContents, 'render-process-gone');
         w.webContents.forcefullyCrashRenderer();
         const [, details] = await crashEvent;
         expect(details.reason === 'killed' || details.reason === 'crashed').to.equal(true, 'reason should be killed || crashed');
@@ -1543,7 +1544,7 @@ describe('webContents module', () => {
         const originalEmit = contents.emit.bind(contents);
         contents.emit = (...args) => { return originalEmit(...args); };
         contents.once(e.name as any, () => contents.destroy());
-        const destroyed = emittedOnce(contents, 'destroyed');
+        const destroyed = once(contents, 'destroyed');
         contents.loadURL(serverUrl + e.url);
         await destroyed;
       });
@@ -1596,7 +1597,7 @@ describe('webContents module', () => {
         require('electron').ipcRenderer.send('message', 'Hello World!')
       `);
 
-      const [, channel, message] = await emittedOnce(w.webContents, 'ipc-message');
+      const [, channel, message] = await once(w.webContents, 'ipc-message');
       expect(channel).to.equal('message');
       expect(message).to.equal('Hello World!');
     });
@@ -1708,7 +1709,7 @@ describe('webContents module', () => {
             }
           });
 
-          const promise = emittedOnce(w.webContents, 'preload-error');
+          const promise = once(w.webContents, 'preload-error');
           w.loadURL('about:blank');
 
           const [, preloadPath, error] = await promise;
@@ -1727,7 +1728,7 @@ describe('webContents module', () => {
             }
           });
 
-          const promise = emittedOnce(w.webContents, 'preload-error');
+          const promise = once(w.webContents, 'preload-error');
           w.loadURL('about:blank');
 
           const [, preloadPath, error] = await promise;
@@ -1746,7 +1747,7 @@ describe('webContents module', () => {
             }
           });
 
-          const promise = emittedOnce(w.webContents, 'preload-error');
+          const promise = once(w.webContents, 'preload-error');
           w.loadURL('about:blank');
 
           const [, preloadPath, error] = await promise;
@@ -2061,7 +2062,7 @@ describe('webContents module', () => {
     it('can get multiple shared workers', async () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
 
-      const ready = emittedOnce(ipcMain, 'ready');
+      const ready = once(ipcMain, 'ready');
       w.loadFile(path.join(fixturesPath, 'api', 'shared-worker', 'shared-worker.html'));
       await ready;
 
@@ -2075,17 +2076,17 @@ describe('webContents module', () => {
     it('can inspect a specific shared worker', async () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
 
-      const ready = emittedOnce(ipcMain, 'ready');
+      const ready = once(ipcMain, 'ready');
       w.loadFile(path.join(fixturesPath, 'api', 'shared-worker', 'shared-worker.html'));
       await ready;
 
       const sharedWorkers = w.webContents.getAllSharedWorkers();
 
-      const devtoolsOpened = emittedOnce(w.webContents, 'devtools-opened');
+      const devtoolsOpened = once(w.webContents, 'devtools-opened');
       w.webContents.inspectSharedWorkerById(sharedWorkers[0].id);
       await devtoolsOpened;
 
-      const devtoolsClosed = emittedOnce(w.webContents, 'devtools-closed');
+      const devtoolsClosed = once(w.webContents, 'devtools-closed');
       w.webContents.closeDevTools();
       await devtoolsClosed;
     });
@@ -2202,9 +2203,9 @@ describe('webContents module', () => {
       const bw = new BrowserWindow({ show: false });
       await bw.loadURL('about:blank');
       bw.webContents.executeJavaScript('child = window.open("", "", "show=no"); null');
-      const [, child] = await emittedOnce(app, 'web-contents-created');
+      const [, child] = await once(app, 'web-contents-created');
       bw.webContents.executeJavaScript('child.document.title = "new title"');
-      const [, title] = await emittedOnce(child, 'page-title-updated');
+      const [, title] = await once(child, 'page-title-updated');
       expect(title).to.equal('new title');
     });
   });
@@ -2212,7 +2213,7 @@ describe('webContents module', () => {
   describe('crashed event', () => {
     it('does not crash main process when destroying WebContents in it', async () => {
       const contents = (webContents as typeof ElectronInternal.WebContents).create({ nodeIntegration: true });
-      const crashEvent = emittedOnce(contents, 'render-process-gone');
+      const crashEvent = once(contents, 'render-process-gone');
       await contents.loadURL('about:blank');
       contents.forcefullyCrashRenderer();
       await crashEvent;
@@ -2226,7 +2227,7 @@ describe('webContents module', () => {
       const w = new BrowserWindow({ show: false });
       await w.loadFile(path.join(fixturesPath, 'pages', 'base-page.html'));
 
-      const promise = emittedOnce(w.webContents, 'context-menu');
+      const promise = once(w.webContents, 'context-menu');
 
       // Simulate right-click to create context-menu event.
       const opts = { x: 0, y: 0, button: 'right' as any };
@@ -2247,7 +2248,7 @@ describe('webContents module', () => {
 
     it('closes when close() is called', async () => {
       const w = (webContents as typeof ElectronInternal.WebContents).create();
-      const destroyed = emittedOnce(w, 'destroyed');
+      const destroyed = once(w, 'destroyed');
       w.close();
       await destroyed;
       expect(w.isDestroyed()).to.be.true();
@@ -2256,7 +2257,7 @@ describe('webContents module', () => {
     it('closes when close() is called after loading a page', async () => {
       const w = (webContents as typeof ElectronInternal.WebContents).create();
       await w.loadURL('about:blank');
-      const destroyed = emittedOnce(w, 'destroyed');
+      const destroyed = once(w, 'destroyed');
       w.close();
       await destroyed;
       expect(w.isDestroyed()).to.be.true();
@@ -2280,7 +2281,7 @@ describe('webContents module', () => {
     it('causes its parent browserwindow to be closed', async () => {
       const w = new BrowserWindow({ show: false });
       await w.loadURL('about:blank');
-      const closed = emittedOnce(w, 'closed');
+      const closed = once(w, 'closed');
       w.webContents.close();
       await closed;
       expect(w.isDestroyed()).to.be.true();
@@ -2291,7 +2292,7 @@ describe('webContents module', () => {
       await w.loadURL('about:blank');
       await w.executeJavaScript('window.onbeforeunload = () => "hello"; null');
       w.on('will-prevent-unload', () => { throw new Error('unexpected will-prevent-unload'); });
-      const destroyed = emittedOnce(w, 'destroyed');
+      const destroyed = once(w, 'destroyed');
       w.close();
       await destroyed;
       expect(w.isDestroyed()).to.be.true();
@@ -2301,7 +2302,7 @@ describe('webContents module', () => {
       const w = (webContents as typeof ElectronInternal.WebContents).create();
       await w.loadURL('about:blank');
       await w.executeJavaScript('window.onbeforeunload = () => "hello"; null');
-      const willPreventUnload = emittedOnce(w, 'will-prevent-unload');
+      const willPreventUnload = once(w, 'will-prevent-unload');
       w.close({ waitForBeforeUnload: true });
       await willPreventUnload;
       expect(w.isDestroyed()).to.be.false();
@@ -2312,7 +2313,7 @@ describe('webContents module', () => {
       await w.loadURL('about:blank');
       await w.executeJavaScript('window.onbeforeunload = () => "hello"; null');
       w.once('will-prevent-unload', e => e.preventDefault());
-      const destroyed = emittedOnce(w, 'destroyed');
+      const destroyed = once(w, 'destroyed');
       w.close({ waitForBeforeUnload: true });
       await destroyed;
       expect(w.isDestroyed()).to.be.true();
@@ -2325,7 +2326,7 @@ describe('webContents module', () => {
       const w = new BrowserWindow({ show: false });
       w.loadURL('about:blank');
       w.webContents.executeJavaScript('window.moveTo(100, 100)', true);
-      const [, rect] = await emittedOnce(w.webContents, 'content-bounds-updated');
+      const [, rect] = await once(w.webContents, 'content-bounds-updated');
       const { width, height } = w.getBounds();
       expect(rect).to.deep.equal({
         x: 100,
@@ -2342,7 +2343,7 @@ describe('webContents module', () => {
       const w = new BrowserWindow({ show: false });
       w.loadURL('about:blank');
       w.webContents.executeJavaScript('window.resizeTo(100, 100)', true);
-      const [, rect] = await emittedOnce(w.webContents, 'content-bounds-updated');
+      const [, rect] = await once(w.webContents, 'content-bounds-updated');
       const { x, y } = w.getBounds();
       expect(rect).to.deep.equal({
         x,

+ 14 - 12
spec/api-web-frame-main-spec.ts

@@ -4,8 +4,10 @@ import * as path from 'path';
 import * as url from 'url';
 import { BrowserWindow, WebFrameMain, webFrameMain, ipcMain, app, WebContents } from 'electron/main';
 import { closeAllWindows } from './lib/window-helpers';
-import { emittedOnce, emittedNTimes } from './lib/events-helpers';
-import { defer, delay, ifit, listen, waitUntil } from './lib/spec-helpers';
+import { emittedNTimes } from './lib/events-helpers';
+import { defer, ifit, listen, waitUntil } from './lib/spec-helpers';
+import { once } from 'events';
+import { setTimeout } from 'timers/promises';
 
 describe('webFrameMain module', () => {
   const fixtures = path.resolve(__dirname, 'fixtures');
@@ -141,7 +143,7 @@ describe('webFrameMain module', () => {
     it('should show parent origin when child page is about:blank', async () => {
       const w = new BrowserWindow({ show: false });
       await w.loadFile(path.join(fixtures, 'pages', 'blank.html'));
-      const webContentsCreated: Promise<[unknown, WebContents]> = emittedOnce(app, 'web-contents-created') as any;
+      const webContentsCreated: Promise<[unknown, WebContents]> = once(app, 'web-contents-created') as any;
       expect(w.webContents.mainFrame.origin).to.equal('file://');
       await w.webContents.executeJavaScript('window.open("", null, "show=false"), null');
       const [, childWebContents] = await webContentsCreated;
@@ -161,7 +163,7 @@ describe('webFrameMain module', () => {
       expect(mainFrame.origin).to.equal(serverA.url.replace(/\/$/, ''));
       const [childFrame] = mainFrame.frames;
       expect(childFrame.origin).to.equal(serverB.url.replace(/\/$/, ''));
-      const webContentsCreated: Promise<[unknown, WebContents]> = emittedOnce(app, 'web-contents-created') as any;
+      const webContentsCreated: Promise<[unknown, WebContents]> = once(app, 'web-contents-created') as any;
       await childFrame.executeJavaScript('window.open("", null, "show=false"), null');
       const [, childWebContents] = await webContentsCreated;
       expect(childWebContents.mainFrame.origin).to.equal(childFrame.origin);
@@ -257,7 +259,7 @@ describe('webFrameMain module', () => {
 
       await webFrame.executeJavaScript('window.TEMP = 1', false);
       expect(webFrame.reload()).to.be.true();
-      await emittedOnce(w.webContents, 'dom-ready');
+      await once(w.webContents, 'dom-ready');
       expect(await webFrame.executeJavaScript('window.TEMP', false)).to.be.null();
     });
   });
@@ -273,7 +275,7 @@ describe('webFrameMain module', () => {
       });
       await w.loadURL('about:blank');
       const webFrame = w.webContents.mainFrame;
-      const pongPromise = emittedOnce(ipcMain, 'preload-pong');
+      const pongPromise = once(ipcMain, 'preload-pong');
       webFrame.send('preload-ping');
       const [, routingId] = await pongPromise;
       expect(routingId).to.equal(webFrame.routingId);
@@ -293,7 +295,7 @@ describe('webFrameMain module', () => {
       const { mainFrame } = w.webContents;
       w.destroy();
       // Wait for WebContents, and thus RenderFrameHost, to be destroyed.
-      await delay();
+      await setTimeout();
       expect(() => mainFrame.url).to.throw();
     });
 
@@ -314,7 +316,7 @@ describe('webFrameMain module', () => {
       // Keep reference to mainFrame alive throughout crash and recovery.
       const { mainFrame } = w.webContents;
       await w.webContents.loadURL(server.url);
-      const crashEvent = emittedOnce(w.webContents, 'render-process-gone');
+      const crashEvent = once(w.webContents, 'render-process-gone');
       w.webContents.forcefullyCrashRenderer();
       await crashEvent;
       await w.webContents.loadURL(server.url);
@@ -330,11 +332,11 @@ describe('webFrameMain module', () => {
       // Keep reference to mainFrame alive throughout crash and recovery.
       const { mainFrame } = w.webContents;
       await w.webContents.loadURL(server.url);
-      const crashEvent = emittedOnce(w.webContents, 'render-process-gone');
+      const crashEvent = once(w.webContents, 'render-process-gone');
       w.webContents.forcefullyCrashRenderer();
       await crashEvent;
       // A short wait seems to be required to reproduce the crash.
-      await delay(100);
+      await setTimeout(100);
       await w.webContents.loadURL(crossOriginUrl);
       // Log just to keep mainFrame in scope.
       console.log('mainFrame.url', mainFrame.url);
@@ -366,7 +368,7 @@ describe('webFrameMain module', () => {
   describe('"frame-created" event', () => {
     it('emits when the main frame is created', async () => {
       const w = new BrowserWindow({ show: false });
-      const promise = emittedOnce(w.webContents, 'frame-created');
+      const promise = once(w.webContents, 'frame-created');
       w.webContents.loadFile(path.join(subframesPath, 'frame.html'));
       const [, details] = await promise;
       expect(details.frame).to.equal(w.webContents.mainFrame);
@@ -406,7 +408,7 @@ describe('webFrameMain module', () => {
   describe('"dom-ready" event', () => {
     it('emits for top-level frame', async () => {
       const w = new BrowserWindow({ show: false });
-      const promise = emittedOnce(w.webContents.mainFrame, 'dom-ready');
+      const promise = once(w.webContents.mainFrame, 'dom-ready');
       w.webContents.loadURL('about:blank');
       await promise;
     });

+ 3 - 3
spec/api-web-frame-spec.ts

@@ -1,8 +1,8 @@
 import { expect } from 'chai';
 import * as path from 'path';
 import { BrowserWindow, ipcMain, WebContents } from 'electron/main';
-import { emittedOnce } from './lib/events-helpers';
 import { defer } from './lib/spec-helpers';
+import { once } from 'events';
 
 describe('webFrame module', () => {
   const fixtures = path.resolve(__dirname, 'fixtures');
@@ -17,7 +17,7 @@ describe('webFrame module', () => {
       }
     });
     defer(() => w.close());
-    const isSafe = emittedOnce(ipcMain, 'executejs-safe');
+    const isSafe = once(ipcMain, 'executejs-safe');
     w.loadURL('about:blank');
     const [, wasSafe] = await isSafe;
     expect(wasSafe).to.equal(true);
@@ -33,7 +33,7 @@ describe('webFrame module', () => {
       }
     });
     defer(() => w.close());
-    const execError = emittedOnce(ipcMain, 'executejs-safe');
+    const execError = once(ipcMain, 'executejs-safe');
     w.loadURL('about:blank');
     const [, error] = await execError;
     expect(error).to.not.equal(null, 'Error should not be null');

+ 2 - 2
spec/api-web-request-spec.ts

@@ -6,8 +6,8 @@ import * as url from 'url';
 import * as WebSocket from 'ws';
 import { ipcMain, protocol, session, WebContents, webContents } from 'electron/main';
 import { Socket } from 'net';
-import { emittedOnce } from './lib/events-helpers';
 import { listen } from './lib/spec-helpers';
+import { once } from 'events';
 
 const fixturesPath = path.resolve(__dirname, 'fixtures');
 
@@ -545,7 +545,7 @@ describe('webRequest module', () => {
       });
 
       contents.loadFile(path.join(fixturesPath, 'api', 'webrequest.html'), { query: { port: `${port}` } });
-      await emittedOnce(ipcMain, 'websocket-success');
+      await once(ipcMain, 'websocket-success');
 
       expect(receivedHeaders['/websocket'].Upgrade[0]).to.equal('websocket');
       expect(receivedHeaders['/'].foo1[0]).to.equal('bar1');

+ 5 - 5
spec/asar-spec.ts

@@ -4,9 +4,9 @@ import * as url from 'url';
 import { Worker } from 'worker_threads';
 import { BrowserWindow, ipcMain } from 'electron/main';
 import { closeAllWindows } from './lib/window-helpers';
-import { emittedOnce } from './lib/events-helpers';
 import { getRemoteContext, ifdescribe, itremote, useRemoteContext } from './lib/spec-helpers';
 import * as importedFs from 'fs';
+import { once } from 'events';
 
 const features = process._linkedBinding('electron_common_features');
 
@@ -32,7 +32,7 @@ describe('asar package', () => {
         }
       });
       const p = path.resolve(asarDir, 'web.asar', 'index.html');
-      const dirnameEvent = emittedOnce(ipcMain, 'dirname');
+      const dirnameEvent = once(ipcMain, 'dirname');
       w.loadFile(p);
       const [, dirname] = await dirnameEvent;
       expect(dirname).to.equal(path.dirname(p));
@@ -53,7 +53,7 @@ describe('asar package', () => {
         }
       });
       const p = path.resolve(asarDir, 'script.asar', 'index.html');
-      const ping = emittedOnce(ipcMain, 'ping');
+      const ping = once(ipcMain, 'ping');
       w.loadFile(p);
       const [, message] = await ping;
       expect(message).to.equal('pong');
@@ -77,7 +77,7 @@ describe('asar package', () => {
       });
       const p = path.resolve(asarDir, 'video.asar', 'index.html');
       w.loadFile(p);
-      const [, message, error] = await emittedOnce(ipcMain, 'asar-video');
+      const [, message, error] = await once(ipcMain, 'asar-video');
       if (message === 'ended') {
         expect(error).to.be.null();
       } else if (message === 'error') {
@@ -1514,7 +1514,7 @@ describe('asar package', function () {
     /*
     ifit(features.isRunAsNodeEnabled())('is available in forked scripts', async function () {
       const child = ChildProcess.fork(path.join(fixtures, 'module', 'original-fs.js'));
-      const message = emittedOnce(child, 'message');
+      const message = once(child, 'message');
       child.send('message');
       const [msg] = await message;
       expect(msg).to.equal('object');

+ 3 - 3
spec/autofill-spec.ts

@@ -1,8 +1,8 @@
 import { BrowserWindow } from 'electron';
 import * as path from 'path';
-import { delay } from './lib/spec-helpers';
 import { expect } from 'chai';
 import { closeAllWindows } from './lib/window-helpers';
+import { setTimeout } from 'timers/promises';
 
 const fixturesPath = path.resolve(__dirname, 'fixtures');
 
@@ -17,7 +17,7 @@ describe('autofill', () => {
     const inputText = 'clap';
     for (const keyCode of inputText) {
       w.webContents.sendInputEvent({ type: 'char', keyCode });
-      await delay(100);
+      await setTimeout(100);
     }
 
     w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Down' });
@@ -36,7 +36,7 @@ describe('autofill', () => {
       w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Tab' });
       w.webContents.sendInputEvent({ type: 'keyDown', keyCode });
       w.webContents.sendInputEvent({ type: 'char', keyCode });
-      await delay(100);
+      await setTimeout(100);
     }
 
     w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Tab' });

+ 84 - 82
spec/chromium-spec.ts

@@ -1,6 +1,5 @@
 import { expect } from 'chai';
 import { BrowserWindow, WebContents, webFrameMain, session, ipcMain, app, protocol, webContents } from 'electron/main';
-import { emittedOnce } from './lib/events-helpers';
 import { closeAllWindows } from './lib/window-helpers';
 import * as https from 'https';
 import * as http from 'http';
@@ -8,11 +7,12 @@ import * as path from 'path';
 import * as fs from 'fs';
 import * as url from 'url';
 import * as ChildProcess from 'child_process';
-import { EventEmitter } from 'events';
+import { EventEmitter, once } from 'events';
 import { promisify } from 'util';
-import { ifit, ifdescribe, defer, delay, itremote, listen } from './lib/spec-helpers';
+import { ifit, ifdescribe, defer, itremote, listen } from './lib/spec-helpers';
 import { PipeTransport } from './pipe-transport';
 import * as ws from 'ws';
+import { setTimeout } from 'timers/promises';
 
 const features = process._linkedBinding('electron_common_features');
 
@@ -64,7 +64,7 @@ describe('reporting api', () => {
       show: false
     });
     try {
-      const reportGenerated = emittedOnce(reports, 'report');
+      const reportGenerated = once(reports, 'report');
       await bw.loadURL(url);
       const [report] = await reportGenerated;
       expect(report).to.be.an('array');
@@ -86,7 +86,7 @@ describe('window.postMessage', () => {
   it('sets the source and origin correctly', async () => {
     const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
     w.loadURL(`file://${fixturesPath}/pages/window-open-postMessage-driver.html`);
-    const [, message] = await emittedOnce(ipcMain, 'complete');
+    const [, message] = await once(ipcMain, 'complete');
     expect(message.data).to.equal('testing');
     expect(message.origin).to.equal('file://');
     expect(message.sourceEqualsOpener).to.equal(true);
@@ -108,11 +108,11 @@ describe('focus handling', () => {
       }
     });
 
-    const webviewReady = emittedOnce(w.webContents, 'did-attach-webview');
+    const webviewReady = once(w.webContents, 'did-attach-webview');
     await w.loadFile(path.join(fixturesPath, 'pages', 'tab-focus-loop-elements.html'));
     const [, wvContents] = await webviewReady;
     webviewContents = wvContents;
-    await emittedOnce(webviewContents, 'did-finish-load');
+    await once(webviewContents, 'did-finish-load');
     w.focus();
   });
 
@@ -123,7 +123,7 @@ describe('focus handling', () => {
   });
 
   const expectFocusChange = async () => {
-    const [, focusedElementId] = await emittedOnce(ipcMain, 'focus-changed');
+    const [, focusedElementId] = await once(ipcMain, 'focus-changed');
     return focusedElementId;
   };
 
@@ -224,7 +224,7 @@ describe('web security', () => {
 
   it('engages CORB when web security is not disabled', async () => {
     const w = new BrowserWindow({ show: false, webPreferences: { webSecurity: true, nodeIntegration: true, contextIsolation: false } });
-    const p = emittedOnce(ipcMain, 'success');
+    const p = once(ipcMain, 'success');
     await w.loadURL(`data:text/html,<script>
         const s = document.createElement('script')
         s.src = "${serverUrl}"
@@ -238,7 +238,7 @@ describe('web security', () => {
 
   it('bypasses CORB when web security is disabled', async () => {
     const w = new BrowserWindow({ show: false, webPreferences: { webSecurity: false, nodeIntegration: true, contextIsolation: false } });
-    const p = emittedOnce(ipcMain, 'success');
+    const p = once(ipcMain, 'success');
     await w.loadURL(`data:text/html,
       <script>
         window.onerror = (e) => { require('electron').ipcRenderer.send('success', e) }
@@ -249,7 +249,7 @@ describe('web security', () => {
 
   it('engages CORS when web security is not disabled', async () => {
     const w = new BrowserWindow({ show: false, webPreferences: { webSecurity: true, nodeIntegration: true, contextIsolation: false } });
-    const p = emittedOnce(ipcMain, 'response');
+    const p = once(ipcMain, 'response');
     await w.loadURL(`data:text/html,<script>
         (async function() {
           try {
@@ -266,7 +266,7 @@ describe('web security', () => {
 
   it('bypasses CORS when web security is disabled', async () => {
     const w = new BrowserWindow({ show: false, webPreferences: { webSecurity: false, nodeIntegration: true, contextIsolation: false } });
-    const p = emittedOnce(ipcMain, 'response');
+    const p = once(ipcMain, 'response');
     await w.loadURL(`data:text/html,<script>
         (async function() {
           try {
@@ -371,7 +371,7 @@ describe('web security', () => {
             console.log('success')
           }
         </script>`);
-      const [,, message] = await emittedOnce(w.webContents, 'console-message');
+      const [,, message] = await once(w.webContents, 'console-message');
       expect(message).to.equal('success');
     });
   });
@@ -410,7 +410,7 @@ describe('command line switches', () => {
       let stderr = '';
       appProcess.stderr.on('data', (data) => { stderr += data; });
 
-      const [code, signal] = await emittedOnce(appProcess, 'exit');
+      const [code, signal] = await once(appProcess, 'exit');
       if (code !== 0) {
         throw new Error(`Process exited with code "${code}" signal "${signal}" output "${output}" stderr "${stderr}"`);
       }
@@ -471,7 +471,7 @@ describe('command line switches', () => {
       const stdio = appProcess.stdio as unknown as [NodeJS.ReadableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.WritableStream, NodeJS.ReadableStream];
       const pipe = new PipeTransport(stdio[3], stdio[4]);
       pipe.send({ id: 1, method: 'Browser.close', params: {} });
-      await emittedOnce(appProcess, 'exit');
+      await once(appProcess, 'exit');
     });
   });
 
@@ -533,7 +533,7 @@ describe('chromium features', () => {
 
       let output = '';
       fpsProcess.stdout.on('data', data => { output += data; });
-      await emittedOnce(fpsProcess, 'exit');
+      await once(fpsProcess, 'exit');
 
       expect(output).to.include(fps.join(','));
     });
@@ -545,7 +545,7 @@ describe('chromium features', () => {
 
       let output = '';
       fpsProcess.stdout.on('data', data => { output += data; });
-      await emittedOnce(fpsProcess, 'exit');
+      await once(fpsProcess, 'exit');
 
       expect(output).to.include(fps.join(','));
     });
@@ -711,7 +711,7 @@ describe('chromium features', () => {
           contextIsolation: false
         }
       });
-      const message = emittedOnce(w.webContents, 'ipc-message');
+      const message = once(w.webContents, 'ipc-message');
       w.webContents.session.setPermissionRequestHandler((wc, permission, callback) => {
         if (permission === 'geolocation') {
           callback(false);
@@ -751,7 +751,7 @@ describe('chromium features', () => {
 
       appProcess = ChildProcess.spawn(process.execPath, [appPath]);
 
-      const [code] = await emittedOnce(appProcess, 'exit');
+      const [code] = await once(appProcess, 'exit');
       expect(code).to.equal(0);
     });
 
@@ -781,7 +781,7 @@ describe('chromium features', () => {
     it('Worker has node integration with nodeIntegrationInWorker', async () => {
       const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nodeIntegrationInWorker: true, contextIsolation: false } });
       w.loadURL(`file://${fixturesPath}/pages/worker.html`);
-      const [, data] = await emittedOnce(ipcMain, 'worker-result');
+      const [, data] = await once(ipcMain, 'worker-result');
       expect(data).to.equal('object function object function');
     });
 
@@ -863,7 +863,7 @@ describe('chromium features', () => {
 
           await w.loadFile(path.join(fixturesPath, 'pages', 'form-with-data.html'));
 
-          const loadPromise = emittedOnce(w.webContents, 'did-finish-load');
+          const loadPromise = once(w.webContents, 'did-finish-load');
 
           w.webContents.executeJavaScript(`
             const form = document.querySelector('form')
@@ -887,7 +887,7 @@ describe('chromium features', () => {
 
           await w.loadFile(path.join(fixturesPath, 'pages', 'form-with-data.html'));
 
-          const windowCreatedPromise = emittedOnce(app, 'browser-window-created');
+          const windowCreatedPromise = once(app, 'browser-window-created');
 
           w.webContents.executeJavaScript(`
             const form = document.querySelector('form')
@@ -919,7 +919,7 @@ describe('chromium features', () => {
 
         defer(() => { w.close(); });
 
-        const promise = emittedOnce(app, 'browser-window-created');
+        const promise = once(app, 'browser-window-created');
         w.loadFile(path.join(fixturesPath, 'pages', 'window-open.html'));
         const [, newWindow] = await promise;
         expect(newWindow.isVisible()).to.equal(true);
@@ -955,7 +955,7 @@ describe('chromium features', () => {
       w.webContents.executeJavaScript(`
         { b = window.open('devtools://devtools/bundled/inspector.html', '', 'nodeIntegration=no,show=no'); null }
       `);
-      const [, contents] = await emittedOnce(app, 'web-contents-created');
+      const [, contents] = await once(app, 'web-contents-created');
       const typeofProcessGlobal = await contents.executeJavaScript('typeof process');
       expect(typeofProcessGlobal).to.equal('undefined');
     });
@@ -966,7 +966,7 @@ describe('chromium features', () => {
       w.webContents.executeJavaScript(`
         { b = window.open('about:blank', '', 'nodeIntegration=no,show=no'); null }
       `);
-      const [, contents] = await emittedOnce(app, 'web-contents-created');
+      const [, contents] = await once(app, 'web-contents-created');
       const typeofProcessGlobal = await contents.executeJavaScript('typeof process');
       expect(typeofProcessGlobal).to.equal('undefined');
     });
@@ -983,12 +983,12 @@ describe('chromium features', () => {
       w.webContents.executeJavaScript(`
         { b = window.open(${JSON.stringify(windowUrl)}, '', 'javascript=no,show=no'); null }
       `);
-      const [, contents] = await emittedOnce(app, 'web-contents-created');
-      await emittedOnce(contents, 'did-finish-load');
+      const [, contents] = await once(app, 'web-contents-created');
+      await once(contents, 'did-finish-load');
       // Click link on page
       contents.sendInputEvent({ type: 'mouseDown', clickCount: 1, x: 1, y: 1 });
       contents.sendInputEvent({ type: 'mouseUp', clickCount: 1, x: 1, y: 1 });
-      const [, window] = await emittedOnce(app, 'browser-window-created');
+      const [, window] = await once(app, 'browser-window-created');
       const preferences = window.webContents.getLastWebPreferences();
       expect(preferences.javascript).to.be.false();
     });
@@ -1003,8 +1003,8 @@ describe('chromium features', () => {
       const w = new BrowserWindow({ show: false });
       w.webContents.loadFile(path.resolve(__dirname, 'fixtures', 'blank.html'));
       w.webContents.executeJavaScript(`{ b = window.open(${JSON.stringify(targetURL)}); null }`);
-      const [, window] = await emittedOnce(app, 'browser-window-created');
-      await emittedOnce(window.webContents, 'did-finish-load');
+      const [, window] = await once(app, 'browser-window-created');
+      await once(window.webContents, 'did-finish-load');
       expect(await w.webContents.executeJavaScript('b.location.href')).to.equal(targetURL);
     });
 
@@ -1012,30 +1012,30 @@ describe('chromium features', () => {
       const w = new BrowserWindow({ show: false });
       w.webContents.loadFile(path.resolve(__dirname, 'fixtures', 'blank.html'));
       w.webContents.executeJavaScript('{ b = window.open("about:blank"); null }');
-      const [, { webContents }] = await emittedOnce(app, 'browser-window-created');
-      await emittedOnce(webContents, 'did-finish-load');
+      const [, { webContents }] = await once(app, 'browser-window-created');
+      await once(webContents, 'did-finish-load');
       // When it loads, redirect
       w.webContents.executeJavaScript(`{ b.location = ${JSON.stringify(`file://${fixturesPath}/pages/base-page.html`)}; null }`);
-      await emittedOnce(webContents, 'did-finish-load');
+      await once(webContents, 'did-finish-load');
     });
 
     it('defines a window.location.href setter', async () => {
       const w = new BrowserWindow({ show: false });
       w.webContents.loadFile(path.resolve(__dirname, 'fixtures', 'blank.html'));
       w.webContents.executeJavaScript('{ b = window.open("about:blank"); null }');
-      const [, { webContents }] = await emittedOnce(app, 'browser-window-created');
-      await emittedOnce(webContents, 'did-finish-load');
+      const [, { webContents }] = await once(app, 'browser-window-created');
+      await once(webContents, 'did-finish-load');
       // When it loads, redirect
       w.webContents.executeJavaScript(`{ b.location.href = ${JSON.stringify(`file://${fixturesPath}/pages/base-page.html`)}; null }`);
-      await emittedOnce(webContents, 'did-finish-load');
+      await once(webContents, 'did-finish-load');
     });
 
     it('open a blank page when no URL is specified', async () => {
       const w = new BrowserWindow({ show: false });
       w.loadURL('about:blank');
       w.webContents.executeJavaScript('{ b = window.open(); null }');
-      const [, { webContents }] = await emittedOnce(app, 'browser-window-created');
-      await emittedOnce(webContents, 'did-finish-load');
+      const [, { webContents }] = await once(app, 'browser-window-created');
+      await once(webContents, 'did-finish-load');
       expect(await w.webContents.executeJavaScript('b.location.href')).to.equal('about:blank');
     });
 
@@ -1043,8 +1043,8 @@ describe('chromium features', () => {
       const w = new BrowserWindow({ show: false });
       w.loadURL('about:blank');
       w.webContents.executeJavaScript('{ b = window.open(\'\'); null }');
-      const [, { webContents }] = await emittedOnce(app, 'browser-window-created');
-      await emittedOnce(webContents, 'did-finish-load');
+      const [, { webContents }] = await once(app, 'browser-window-created');
+      await once(webContents, 'did-finish-load');
       expect(await w.webContents.executeJavaScript('b.location.href')).to.equal('about:blank');
     });
 
@@ -1145,7 +1145,7 @@ describe('chromium features', () => {
         }
       });
       w.loadFile(path.join(fixturesPath, 'pages', 'window-opener.html'));
-      const [, channel, opener] = await emittedOnce(w.webContents, 'ipc-message');
+      const [, channel, opener] = await once(w.webContents, 'ipc-message');
       expect(channel).to.equal('opener');
       expect(opener).to.equal(null);
     });
@@ -1267,8 +1267,9 @@ describe('chromium features', () => {
         }
       });
       w.loadFile(path.join(fixturesPath, 'pages', 'media-id-reset.html'));
-      const [, firstDeviceIds] = await emittedOnce(ipcMain, 'deviceIds');
-      const [, secondDeviceIds] = await emittedOnce(ipcMain, 'deviceIds', () => w.webContents.reload());
+      const [, firstDeviceIds] = await once(ipcMain, 'deviceIds');
+      w.webContents.reload();
+      const [, secondDeviceIds] = await once(ipcMain, 'deviceIds');
       expect(firstDeviceIds).to.deep.equal(secondDeviceIds);
     });
 
@@ -1283,9 +1284,10 @@ describe('chromium features', () => {
         }
       });
       w.loadFile(path.join(fixturesPath, 'pages', 'media-id-reset.html'));
-      const [, firstDeviceIds] = await emittedOnce(ipcMain, 'deviceIds');
+      const [, firstDeviceIds] = await once(ipcMain, 'deviceIds');
       await ses.clearStorageData({ storages: ['cookies'] });
-      const [, secondDeviceIds] = await emittedOnce(ipcMain, 'deviceIds', () => w.webContents.reload());
+      w.webContents.reload();
+      const [, secondDeviceIds] = await once(ipcMain, 'deviceIds');
       expect(firstDeviceIds).to.not.deep.equal(secondDeviceIds);
     });
 
@@ -1477,35 +1479,35 @@ describe('chromium features', () => {
       });
 
       it('cannot access localStorage', async () => {
-        const response = emittedOnce(ipcMain, 'local-storage-response');
+        const response = once(ipcMain, 'local-storage-response');
         contents.loadURL(protocolName + '://host/localStorage');
         const [, error] = await response;
         expect(error).to.equal('Failed to read the \'localStorage\' property from \'Window\': Access is denied for this document.');
       });
 
       it('cannot access sessionStorage', async () => {
-        const response = emittedOnce(ipcMain, 'session-storage-response');
+        const response = once(ipcMain, 'session-storage-response');
         contents.loadURL(`${protocolName}://host/sessionStorage`);
         const [, error] = await response;
         expect(error).to.equal('Failed to read the \'sessionStorage\' property from \'Window\': Access is denied for this document.');
       });
 
       it('cannot access WebSQL database', async () => {
-        const response = emittedOnce(ipcMain, 'web-sql-response');
+        const response = once(ipcMain, 'web-sql-response');
         contents.loadURL(`${protocolName}://host/WebSQL`);
         const [, error] = await response;
         expect(error).to.equal('Failed to execute \'openDatabase\' on \'Window\': Access to the WebDatabase API is denied in this context.');
       });
 
       it('cannot access indexedDB', async () => {
-        const response = emittedOnce(ipcMain, 'indexed-db-response');
+        const response = once(ipcMain, 'indexed-db-response');
         contents.loadURL(`${protocolName}://host/indexedDB`);
         const [, error] = await response;
         expect(error).to.equal('Failed to execute \'open\' on \'IDBFactory\': access to the Indexed Database API is denied in this context.');
       });
 
       it('cannot access cookie', async () => {
-        const response = emittedOnce(ipcMain, 'cookie-response');
+        const response = once(ipcMain, 'cookie-response');
         contents.loadURL(`${protocolName}://host/cookie`);
         const [, error] = await response;
         expect(error).to.equal('Failed to set the \'cookie\' property on \'Document\': Access is denied for this document.');
@@ -1529,7 +1531,7 @@ describe('chromium features', () => {
               res.end();
             }
           };
-          setTimeout(respond, 0);
+          setTimeout().then(respond);
         });
         serverUrl = (await listen(server)).url;
         serverCrossSiteUrl = serverUrl.replace('127.0.0.1', 'localhost');
@@ -1599,7 +1601,7 @@ describe('chromium features', () => {
           contextIsolation: false
         });
         contents.loadURL(origin);
-        const [, error] = await emittedOnce(ipcMain, 'web-sql-response');
+        const [, error] = await once(ipcMain, 'web-sql-response');
         expect(error).to.be.null();
       });
 
@@ -1611,7 +1613,7 @@ describe('chromium features', () => {
           contextIsolation: false
         });
         contents.loadURL(origin);
-        const [, error] = await emittedOnce(ipcMain, 'web-sql-response');
+        const [, error] = await once(ipcMain, 'web-sql-response');
         expect(error).to.equal(securityError);
       });
 
@@ -1623,7 +1625,7 @@ describe('chromium features', () => {
           contextIsolation: false
         });
         contents.loadURL(origin);
-        const [, error] = await emittedOnce(ipcMain, 'web-sql-response');
+        const [, error] = await once(ipcMain, 'web-sql-response');
         expect(error).to.equal(securityError);
         const dbName = 'random';
         const result = await contents.executeJavaScript(`
@@ -1654,9 +1656,9 @@ describe('chromium features', () => {
           }
         });
         w.webContents.loadURL(origin);
-        const [, error] = await emittedOnce(ipcMain, 'web-sql-response');
+        const [, error] = await once(ipcMain, 'web-sql-response');
         expect(error).to.be.null();
-        const webviewResult = emittedOnce(ipcMain, 'web-sql-response');
+        const webviewResult = once(ipcMain, 'web-sql-response');
         await w.webContents.executeJavaScript(`
           new Promise((resolve, reject) => {
             const webview = new WebView();
@@ -1684,7 +1686,7 @@ describe('chromium features', () => {
           }
         });
         w.webContents.loadURL('data:text/html,<html></html>');
-        const webviewResult = emittedOnce(ipcMain, 'web-sql-response');
+        const webviewResult = once(ipcMain, 'web-sql-response');
         await w.webContents.executeJavaScript(`
           new Promise((resolve, reject) => {
             const webview = new WebView();
@@ -1711,9 +1713,9 @@ describe('chromium features', () => {
           }
         });
         w.webContents.loadURL(origin);
-        const [, error] = await emittedOnce(ipcMain, 'web-sql-response');
+        const [, error] = await once(ipcMain, 'web-sql-response');
         expect(error).to.be.null();
-        const webviewResult = emittedOnce(ipcMain, 'web-sql-response');
+        const webviewResult = once(ipcMain, 'web-sql-response');
         await w.webContents.executeJavaScript(`
           new Promise((resolve, reject) => {
             const webview = new WebView();
@@ -1753,7 +1755,7 @@ describe('chromium features', () => {
           // failed to detect a real problem (perhaps related to DOM storage data caching)
           // wherein calling `getItem` immediately after `setItem` would appear to work
           // but then later (e.g. next tick) it would not.
-          await delay(1);
+          await setTimeout(1);
           try {
             const storedLength = await w.webContents.executeJavaScript(`${storageName}.getItem(${JSON.stringify(testKeyName)}).length`);
             expect(storedLength).to.equal(length);
@@ -1803,22 +1805,22 @@ describe('chromium features', () => {
       const w = new BrowserWindow({ show: false });
 
       w.loadURL(pdfSource);
-      await emittedOnce(w.webContents, 'did-finish-load');
+      await once(w.webContents, 'did-finish-load');
     });
 
     it('opens when loading a pdf resource as top level navigation', async () => {
       const w = new BrowserWindow({ show: false });
       w.loadURL(pdfSource);
-      const [, contents] = await emittedOnce(app, 'web-contents-created');
-      await emittedOnce(contents, 'did-navigate');
+      const [, contents] = await once(app, 'web-contents-created');
+      await once(contents, 'did-navigate');
       expect(contents.getURL()).to.equal('chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html');
     });
 
     it('opens when loading a pdf resource in a iframe', async () => {
       const w = new BrowserWindow({ show: false });
       w.loadFile(path.join(__dirname, 'fixtures', 'pages', 'pdf-in-iframe.html'));
-      const [, contents] = await emittedOnce(app, 'web-contents-created');
-      await emittedOnce(contents, 'did-navigate');
+      const [, contents] = await once(app, 'web-contents-created');
+      await once(contents, 'did-navigate');
       expect(contents.getURL()).to.equal('chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html');
     });
   });
@@ -1831,7 +1833,7 @@ describe('chromium features', () => {
         // History should have current page by now.
         expect((w.webContents as any).length()).to.equal(1);
 
-        const waitCommit = emittedOnce(w.webContents, 'navigation-entry-committed');
+        const waitCommit = once(w.webContents, 'navigation-entry-committed');
         w.webContents.executeJavaScript('window.history.pushState({}, "")');
         await waitCommit;
         // Initial page + pushed state.
@@ -1844,15 +1846,15 @@ describe('chromium features', () => {
         const w = new BrowserWindow({ show: false });
         w.loadURL('data:text/html,<iframe sandbox="allow-scripts"></iframe>');
         await Promise.all([
-          emittedOnce(w.webContents, 'navigation-entry-committed'),
-          emittedOnce(w.webContents, 'did-frame-navigate'),
-          emittedOnce(w.webContents, 'did-navigate')
+          once(w.webContents, 'navigation-entry-committed'),
+          once(w.webContents, 'did-frame-navigate'),
+          once(w.webContents, 'did-navigate')
         ]);
 
         w.webContents.executeJavaScript('window.history.pushState(1, "")');
         await Promise.all([
-          emittedOnce(w.webContents, 'navigation-entry-committed'),
-          emittedOnce(w.webContents, 'did-navigate-in-page')
+          once(w.webContents, 'navigation-entry-committed'),
+          once(w.webContents, 'did-navigate-in-page')
         ]);
 
         (w.webContents as any).once('navigation-entry-committed', () => {
@@ -2287,7 +2289,7 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
   });
 
   ifit(process.platform !== 'darwin')('can fullscreen from out-of-process iframes (non-macOS)', async () => {
-    const fullscreenChange = emittedOnce(ipcMain, 'fullscreenChange');
+    const fullscreenChange = once(ipcMain, 'fullscreenChange');
     const html =
       `<iframe style="width: 0" frameborder=0 src="${crossSiteUrl}" allowfullscreen></iframe>`;
     w.loadURL(`data:text/html,${html}`);
@@ -2302,7 +2304,7 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
       "document.querySelector('iframe').contentWindow.postMessage('exitFullscreen', '*')"
     );
 
-    await delay(500);
+    await setTimeout(500);
 
     const width = await w.webContents.executeJavaScript(
       "document.querySelector('iframe').offsetWidth"
@@ -2311,8 +2313,8 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
   });
 
   ifit(process.platform === 'darwin')('can fullscreen from out-of-process iframes (macOS)', async () => {
-    await emittedOnce(w, 'enter-full-screen');
-    const fullscreenChange = emittedOnce(ipcMain, 'fullscreenChange');
+    await once(w, 'enter-full-screen');
+    const fullscreenChange = once(ipcMain, 'fullscreenChange');
     const html =
       `<iframe style="width: 0" frameborder=0 src="${crossSiteUrl}" allowfullscreen></iframe>`;
     w.loadURL(`data:text/html,${html}`);
@@ -2326,7 +2328,7 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
     await w.webContents.executeJavaScript(
       "document.querySelector('iframe').contentWindow.postMessage('exitFullscreen', '*')"
     );
-    await emittedOnce(w.webContents, 'leave-html-full-screen');
+    await once(w.webContents, 'leave-html-full-screen');
 
     const width = await w.webContents.executeJavaScript(
       "document.querySelector('iframe').offsetWidth"
@@ -2334,14 +2336,14 @@ describe('iframe using HTML fullscreen API while window is OS-fullscreened', ()
     expect(width).to.equal(0);
 
     w.setFullScreen(false);
-    await emittedOnce(w, 'leave-full-screen');
+    await once(w, 'leave-full-screen');
   });
 
   // TODO(jkleinsc) fix this flaky test on WOA
   ifit(process.platform !== 'win32' || process.arch !== 'arm64')('can fullscreen from in-process iframes', async () => {
-    if (process.platform === 'darwin') await emittedOnce(w, 'enter-full-screen');
+    if (process.platform === 'darwin') await once(w, 'enter-full-screen');
 
-    const fullscreenChange = emittedOnce(ipcMain, 'fullscreenChange');
+    const fullscreenChange = once(ipcMain, 'fullscreenChange');
     w.loadFile(path.join(fixturesPath, 'pages', 'fullscreen-ipif.html'));
     await fullscreenChange;
 
@@ -2644,7 +2646,7 @@ ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.se
   async function waitForBadgeCount (value: number) {
     let badgeCount = app.getBadgeCount();
     while (badgeCount !== value) {
-      await delay(10);
+      await setTimeout(10);
       badgeCount = app.getBadgeCount();
     }
     return badgeCount;
@@ -2839,7 +2841,7 @@ describe('navigator.hid', () => {
       const grantedDevices = await w.webContents.executeJavaScript('navigator.hid.getDevices()');
       expect(grantedDevices).to.not.be.empty();
       w.loadURL(serverUrl);
-      const [,,,,, frameProcessId, frameRoutingId] = await emittedOnce(w.webContents, 'did-frame-navigate');
+      const [,,,,, frameProcessId, frameRoutingId] = await once(w.webContents, 'did-frame-navigate');
       const frame = webFrameMain.fromId(frameProcessId, frameRoutingId);
       expect(!!frame).to.be.true();
       if (frame) {
@@ -3039,7 +3041,7 @@ describe('navigator.usb', () => {
       const grantedDevices = await w.webContents.executeJavaScript('navigator.usb.getDevices()');
       expect(grantedDevices).to.not.be.empty();
       w.loadURL(serverUrl);
-      const [,,,,, frameProcessId, frameRoutingId] = await emittedOnce(w.webContents, 'did-frame-navigate');
+      const [,,,,, frameProcessId, frameRoutingId] = await once(w.webContents, 'did-frame-navigate');
       const frame = webFrameMain.fromId(frameProcessId, frameRoutingId);
       expect(!!frame).to.be.true();
       if (frame) {

+ 19 - 18
spec/extensions-spec.ts

@@ -5,8 +5,9 @@ import * as http from 'http';
 import * as path from 'path';
 import * as fs from 'fs';
 import * as WebSocket from 'ws';
-import { emittedOnce, emittedNTimes, emittedUntil } from './lib/events-helpers';
+import { emittedNTimes, emittedUntil } from './lib/events-helpers';
 import { ifit, listen } from './lib/spec-helpers';
+import { once } from 'events';
 
 const uuid = require('uuid');
 
@@ -53,7 +54,7 @@ describe('chrome extensions', () => {
     const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } });
     await w.loadURL('about:blank');
 
-    const promise = emittedOnce(app, 'web-contents-created');
+    const promise = once(app, 'web-contents-created');
     await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'));
     const args: any = await promise;
     const wc: Electron.WebContents = args[1];
@@ -73,7 +74,7 @@ describe('chrome extensions', () => {
     const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, sandbox: true } });
     await w.loadURL('about:blank');
 
-    const promise = emittedOnce(app, 'web-contents-created');
+    const promise = once(app, 'web-contents-created');
     await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'));
     const args: any = await promise;
     const wc: Electron.WebContents = args[1];
@@ -151,7 +152,7 @@ describe('chrome extensions', () => {
   it('emits extension lifecycle events', async () => {
     const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
 
-    const loadedPromise = emittedOnce(customSession, 'extension-loaded');
+    const loadedPromise = once(customSession, 'extension-loaded');
     const extension = await customSession.loadExtension(path.join(fixtures, 'extensions', 'red-bg'));
     const [, loadedExtension] = await loadedPromise;
     const [, readyExtension] = await emittedUntil(customSession, 'extension-ready', (event: Event, extension: Extension) => {
@@ -161,7 +162,7 @@ describe('chrome extensions', () => {
     expect(loadedExtension).to.deep.equal(extension);
     expect(readyExtension).to.deep.equal(extension);
 
-    const unloadedPromise = emittedOnce(customSession, 'extension-unloaded');
+    const unloadedPromise = once(customSession, 'extension-unloaded');
     await customSession.removeExtension(extension.id);
     const [, unloadedExtension] = await unloadedPromise;
     expect(unloadedExtension).to.deep.equal(extension);
@@ -199,7 +200,7 @@ describe('chrome extensions', () => {
     let w: BrowserWindow;
     let extension: Extension;
     const exec = async (name: string) => {
-      const p = emittedOnce(ipcMain, 'success');
+      const p = once(ipcMain, 'success');
       await w.webContents.executeJavaScript(`exec('${name}')`);
       const [, result] = await p;
       return result;
@@ -224,7 +225,7 @@ describe('chrome extensions', () => {
   describe('chrome.runtime', () => {
     let w: BrowserWindow;
     const exec = async (name: string) => {
-      const p = emittedOnce(ipcMain, 'success');
+      const p = once(ipcMain, 'success');
       await w.webContents.executeJavaScript(`exec('${name}')`);
       const [, result] = await p;
       return result;
@@ -262,7 +263,7 @@ describe('chrome extensions', () => {
       await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-storage'));
       const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true, contextIsolation: false } });
       try {
-        const p = emittedOnce(ipcMain, 'storage-success');
+        const p = once(ipcMain, 'storage-success');
         await w.loadURL(url);
         const [, v] = await p;
         expect(v).to.equal('value');
@@ -352,7 +353,7 @@ describe('chrome extensions', () => {
       const message = { method: 'executeScript', args: ['1 + 2'] };
       w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
 
-      const [,, responseString] = await emittedOnce(w.webContents, 'console-message');
+      const [,, responseString] = await once(w.webContents, 'console-message');
       const response = JSON.parse(responseString);
 
       expect(response).to.equal(3);
@@ -366,7 +367,7 @@ describe('chrome extensions', () => {
       const message = { method: 'connectTab', args: [portName] };
       w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
 
-      const [,, responseString] = await emittedOnce(w.webContents, 'console-message');
+      const [,, responseString] = await once(w.webContents, 'console-message');
       const response = responseString.split(',');
       expect(response[0]).to.equal(portName);
       expect(response[1]).to.equal('howdy');
@@ -379,7 +380,7 @@ describe('chrome extensions', () => {
       const message = { method: 'sendMessage', args: ['Hello World!'] };
       w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
 
-      const [,, responseString] = await emittedOnce(w.webContents, 'console-message');
+      const [,, responseString] = await once(w.webContents, 'console-message');
       const response = JSON.parse(responseString);
 
       expect(response.message).to.equal('Hello World!');
@@ -393,12 +394,12 @@ describe('chrome extensions', () => {
       const w2 = new BrowserWindow({ show: false, webPreferences: { session: customSession } });
       await w2.loadURL('about:blank');
 
-      const w2Navigated = emittedOnce(w2.webContents, 'did-navigate');
+      const w2Navigated = once(w2.webContents, 'did-navigate');
 
       const message = { method: 'update', args: [w2.webContents.id, { url }] };
       w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
 
-      const [,, responseString] = await emittedOnce(w.webContents, 'console-message');
+      const [,, responseString] = await once(w.webContents, 'console-message');
       const response = JSON.parse(responseString);
 
       await w2Navigated;
@@ -416,7 +417,7 @@ describe('chrome extensions', () => {
       const w = new BrowserWindow({ show: false, webPreferences: { session: customSession, nodeIntegration: true, contextIsolation: false } });
       try {
         w.loadURL(url);
-        const [, resp] = await emittedOnce(ipcMain, 'bg-page-message-response');
+        const [, resp] = await once(ipcMain, 'bg-page-message-response');
         expect(resp.message).to.deep.equal({ some: 'message' });
         expect(resp.sender.id).to.be.a('string');
         expect(resp.sender.origin).to.equal(url);
@@ -455,18 +456,18 @@ describe('chrome extensions', () => {
 
     it('has session in background page', async () => {
       const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
-      const promise = emittedOnce(app, 'web-contents-created');
+      const promise = once(app, 'web-contents-created');
       const { id } = await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'));
       const [, bgPageContents] = await promise;
       expect(bgPageContents.getType()).to.equal('backgroundPage');
-      await emittedOnce(bgPageContents, 'did-finish-load');
+      await once(bgPageContents, 'did-finish-load');
       expect(bgPageContents.getURL()).to.equal(`chrome-extension://${id}/_generated_background_page.html`);
       expect(bgPageContents.session).to.not.equal(undefined);
     });
 
     it('can open devtools of background page', async () => {
       const customSession = session.fromPartition(`persist:${require('uuid').v4()}`);
-      const promise = emittedOnce(app, 'web-contents-created');
+      const promise = once(app, 'web-contents-created');
       await customSession.loadExtension(path.join(fixtures, 'extensions', 'persistent-background-page'));
       const [, bgPageContents] = await promise;
       expect(bgPageContents.getType()).to.equal('backgroundPage');
@@ -508,7 +509,7 @@ describe('chrome extensions', () => {
     ifit(process.platform !== 'win32' || process.arch !== 'arm64')('loads a devtools extension', async () => {
       const customSession = session.fromPartition(`persist:${uuid.v4()}`);
       customSession.loadExtension(path.join(fixtures, 'extensions', 'devtools-extension'));
-      const winningMessage = emittedOnce(ipcMain, 'winning');
+      const winningMessage = once(ipcMain, 'winning');
       const w = new BrowserWindow({ show: true, webPreferences: { session: customSession, nodeIntegration: true, contextIsolation: false } });
       await w.loadURL(url);
       w.webContents.openDevTools();

+ 2 - 0
spec/fixtures/apps/remote-control/main.js

@@ -1,6 +1,8 @@
 const { app } = require('electron');
 const http = require('http');
 const v8 = require('v8');
+// eslint-disable-next-line camelcase
+const promises_1 = require('timers/promises');
 
 if (app.commandLine.hasSwitch('boot-eval')) {
   // eslint-disable-next-line no-eval

+ 2 - 2
spec/get-files.ts

@@ -1,5 +1,5 @@
+import { once } from 'events';
 import * as walkdir from 'walkdir';
-import { emittedOnce } from './lib/events-helpers';
 
 export async function getFiles (directoryPath: string, { filter = null }: {filter?: ((file: string) => boolean) | null} = {}) {
   const files: string[] = [];
@@ -9,6 +9,6 @@ export async function getFiles (directoryPath: string, { filter = null }: {filte
   walker.on('file', (file) => {
     if (!filter || filter(file)) { files.push(file); }
   });
-  await emittedOnce(walker, 'end');
+  await once(walker, 'end');
   return files;
 }

+ 3 - 3
spec/guest-window-manager-spec.ts

@@ -1,7 +1,7 @@
 import { BrowserWindow } from 'electron';
 import { expect, assert } from 'chai';
 import { closeAllWindows } from './lib/window-helpers';
-const { emittedOnce } = require('./lib/events-helpers');
+import { once } from 'events';
 
 describe('webContents.setWindowOpenHandler', () => {
   let browserWindow: BrowserWindow;
@@ -173,13 +173,13 @@ describe('webContents.setWindowOpenHandler', () => {
       browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
     });
 
-    await emittedOnce(browserWindow.webContents, 'did-create-window');
+    await once(browserWindow.webContents, 'did-create-window');
   });
 
   it('can change webPreferences of child windows', async () => {
     browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { defaultFontSize: 30 } } }));
 
-    const didCreateWindow = emittedOnce(browserWindow.webContents, 'did-create-window');
+    const didCreateWindow = once(browserWindow.webContents, 'did-create-window');
     browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
     const [childWindow] = await didCreateWindow;
 

+ 10 - 42
spec/lib/events-helpers.ts

@@ -3,53 +3,21 @@
  * with events in async/await manner.
  */
 
-/**
- * @param {!EventTarget} target
- * @param {string} eventName
- * @return {!Promise<!Event>}
- */
-export const waitForEvent = (target: EventTarget, eventName: string) => {
-  return new Promise(resolve => {
-    target.addEventListener(eventName, resolve, { once: true });
-  });
-};
-
-/**
- * @param {!EventEmitter} emitter
- * @param {string} eventName
- * @return {!Promise<!Array>} With Event as the first item.
- */
-export const emittedOnce = (emitter: NodeJS.EventEmitter, eventName: string, trigger?: () => void) => {
-  return emittedNTimes(emitter, eventName, 1, trigger).then(([result]) => result);
-};
+import { on } from 'events';
 
 export const emittedNTimes = async (emitter: NodeJS.EventEmitter, eventName: string, times: number, trigger?: () => void) => {
   const events: any[][] = [];
-  const p = new Promise<any[][]>(resolve => {
-    const handler = (...args: any[]) => {
-      events.push(args);
-      if (events.length === times) {
-        emitter.removeListener(eventName, handler);
-        resolve(events);
-      }
-    };
-    emitter.on(eventName, handler);
-  });
-  if (trigger) {
-    await Promise.resolve(trigger());
+  const iter = on(emitter, eventName);
+  if (trigger) await Promise.resolve(trigger());
+  for await (const args of iter) {
+    events.push(args);
+    if (events.length === times) { break; }
   }
-  return p;
+  return events;
 };
 
 export const emittedUntil = async (emitter: NodeJS.EventEmitter, eventName: string, untilFn: Function) => {
-  const p = new Promise<any[]>(resolve => {
-    const handler = (...args: any[]) => {
-      if (untilFn(...args)) {
-        emitter.removeListener(eventName, handler);
-        resolve(args);
-      }
-    };
-    emitter.on(eventName, handler);
-  });
-  return p;
+  for await (const args of on(emitter, eventName)) {
+    if (untilFn(...args)) { return args; }
+  }
 };

+ 1 - 2
spec/lib/spec-helpers.ts

@@ -21,8 +21,6 @@ const addOnly = <T>(fn: Function): T => {
 export const ifit = (condition: boolean) => (condition ? it : addOnly<TestFunction>(it.skip));
 export const ifdescribe = (condition: boolean) => (condition ? describe : addOnly<SuiteFunction>(describe.skip));
 
-export const delay = (time: number = 0) => new Promise(resolve => setTimeout(resolve, time));
-
 type CleanupFunction = (() => void) | (() => Promise<void>)
 const cleanupFunctions: CleanupFunction[] = [];
 export async function runCleanupFunctions () {
@@ -183,6 +181,7 @@ export async function itremote (name: string, fn: Function, args?: any[]) {
     const { ok, message } = await w.webContents.executeJavaScript(`(async () => {
       try {
         const chai_1 = require('chai')
+        const promises_1 = require('timers/promises')
         chai_1.use(require('chai-as-promised'))
         chai_1.use(require('dirty-chai'))
         await (${fn})(...${JSON.stringify(args ?? [])})

+ 2 - 2
spec/lib/window-helpers.ts

@@ -1,6 +1,6 @@
 import { expect } from 'chai';
 import { BrowserWindow } from 'electron/main';
-import { emittedOnce } from './events-helpers';
+import { once } from 'events';
 
 async function ensureWindowIsClosed (window: BrowserWindow | null) {
   if (window && !window.isDestroyed()) {
@@ -10,7 +10,7 @@ async function ensureWindowIsClosed (window: BrowserWindow | null) {
       // <webview> children which need to be destroyed first. In that case, we
       // await the 'closed' event which signals the complete shutdown of the
       // window.
-      const isClosed = emittedOnce(window, 'closed');
+      const isClosed = once(window, 'closed');
       window.destroy();
       await isClosed;
     } else {

+ 6 - 6
spec/logging-spec.ts

@@ -1,11 +1,11 @@
 import { app } from 'electron';
 import { expect } from 'chai';
-import { emittedOnce } from './lib/events-helpers';
 import { startRemoteControlApp, ifdescribe } from './lib/spec-helpers';
 
 import * as fs from 'fs/promises';
 import * as path from 'path';
 import * as uuid from 'uuid';
+import { once } from 'events';
 
 function isTestingBindingAvailable () {
   try {
@@ -87,7 +87,7 @@ ifdescribe(isTestingBindingAvailable())('logging', () => {
       setTimeout(() => { app.quit(); });
       return app.getPath('userData');
     });
-    await emittedOnce(rc.process, 'exit');
+    await once(rc.process, 'exit');
     const logFilePath = path.join(userDataDir, 'electron_debug.log');
     const stat = await fs.stat(logFilePath);
     expect(stat.isFile()).to.be.true();
@@ -103,7 +103,7 @@ ifdescribe(isTestingBindingAvailable())('logging', () => {
       setTimeout(() => { app.quit(); });
       return app.getPath('userData');
     });
-    await emittedOnce(rc.process, 'exit');
+    await once(rc.process, 'exit');
     const logFilePath = path.join(userDataDir, 'electron_debug.log');
     const stat = await fs.stat(logFilePath);
     expect(stat.isFile()).to.be.true();
@@ -118,7 +118,7 @@ ifdescribe(isTestingBindingAvailable())('logging', () => {
       process._linkedBinding('electron_common_testing').log(0, 'TEST_LOG');
       setTimeout(() => { require('electron').app.quit(); });
     });
-    await emittedOnce(rc.process, 'exit');
+    await once(rc.process, 'exit');
     const stat = await fs.stat(logFilePath);
     expect(stat.isFile()).to.be.true();
     const contents = await fs.readFile(logFilePath, 'utf8');
@@ -132,7 +132,7 @@ ifdescribe(isTestingBindingAvailable())('logging', () => {
       process._linkedBinding('electron_common_testing').log(0, 'TEST_LOG');
       setTimeout(() => { require('electron').app.quit(); });
     });
-    await emittedOnce(rc.process, 'exit');
+    await once(rc.process, 'exit');
     const stat = await fs.stat(logFilePath);
     expect(stat.isFile()).to.be.true();
     const contents = await fs.readFile(logFilePath, 'utf8');
@@ -146,7 +146,7 @@ ifdescribe(isTestingBindingAvailable())('logging', () => {
       process._linkedBinding('electron_common_testing').log(0, 'LATER_LOG');
       setTimeout(() => { require('electron').app.quit(); });
     });
-    await emittedOnce(rc.process, 'exit');
+    await once(rc.process, 'exit');
     const stat = await fs.stat(logFilePath);
     expect(stat.isFile()).to.be.true();
     const contents = await fs.readFile(logFilePath, 'utf8');

+ 3 - 3
spec/modules-spec.ts

@@ -4,8 +4,8 @@ import * as fs from 'fs';
 import { BrowserWindow } from 'electron/main';
 import { ifdescribe, ifit } from './lib/spec-helpers';
 import { closeAllWindows } from './lib/window-helpers';
-import { emittedOnce } from './lib/events-helpers';
 import * as childProcess from 'child_process';
+import { once } from 'events';
 
 const Module = require('module');
 
@@ -30,7 +30,7 @@ describe('modules support', () => {
 
       ifit(features.isRunAsNodeEnabled())('can be required in node binary', async function () {
         const child = childProcess.fork(path.join(fixtures, 'module', 'echo.js'));
-        const [msg] = await emittedOnce(child, 'message');
+        const [msg] = await once(child, 'message');
         expect(msg).to.equal('ok');
       });
 
@@ -62,7 +62,7 @@ describe('modules support', () => {
 
       ifit(features.isRunAsNodeEnabled())('can be required in node binary', async function () {
         const child = childProcess.fork(path.join(fixtures, 'module', 'uv-dlopen.js'));
-        const [exitCode] = await emittedOnce(child, 'exit');
+        const [exitCode] = await once(child, 'exit');
         expect(exitCode).to.equal(0);
       });
     });

+ 16 - 16
spec/node-spec.ts

@@ -3,10 +3,10 @@ import * as childProcess from 'child_process';
 import * as fs from 'fs';
 import * as path from 'path';
 import * as util from 'util';
-import { emittedOnce } from './lib/events-helpers';
 import { getRemoteContext, ifdescribe, ifit, itremote, useRemoteContext } from './lib/spec-helpers';
 import { webContents } from 'electron/main';
 import { EventEmitter } from 'stream';
+import { once } from 'events';
 
 const features = process._linkedBinding('electron_common_features');
 const mainFixturesPath = path.resolve(__dirname, 'fixtures');
@@ -18,7 +18,7 @@ describe('node feature', () => {
     describe('child_process.fork', () => {
       it('Works in browser process', async () => {
         const child = childProcess.fork(path.join(fixtures, 'module', 'ping.js'));
-        const message = emittedOnce(child, 'message');
+        const message = once(child, 'message');
         child.send('message');
         const [msg] = await message;
         expect(msg).to.equal('message');
@@ -104,7 +104,7 @@ describe('node feature', () => {
       it('has the electron version in process.versions', async () => {
         const source = 'process.send(process.versions)';
         const forked = require('child_process').fork('--eval', [source]);
-        const [message] = await emittedOnce(forked, 'message');
+        const [message] = await once(forked, 'message');
         expect(message)
           .to.have.own.property('electron')
           .that.is.a('string')
@@ -158,7 +158,7 @@ describe('node feature', () => {
       cwd: path.join(mainFixturesPath, 'apps', 'libuv-hang'),
       stdio: 'inherit'
     });
-    const [code] = await emittedOnce(appProcess, 'close');
+    const [code] = await once(appProcess, 'close');
     expect(code).to.equal(0);
   });
 
@@ -546,7 +546,7 @@ describe('node feature', () => {
 
       const env = { ...process.env, NODE_OPTIONS: '--v8-options' };
       child = childProcess.spawn(process.execPath, { env });
-      exitPromise = emittedOnce(child, 'exit');
+      exitPromise = once(child, 'exit');
 
       let output = '';
       let success = false;
@@ -609,7 +609,7 @@ describe('node feature', () => {
       };
       // App should exit with code 1.
       const child = childProcess.spawn(process.execPath, [appPath], { env });
-      const [code] = await emittedOnce(child, 'exit');
+      const [code] = await once(child, 'exit');
       expect(code).to.equal(1);
     });
 
@@ -622,7 +622,7 @@ describe('node feature', () => {
       };
       // App should exit with code 0.
       const child = childProcess.spawn(process.execPath, [appPath], { env });
-      const [code] = await emittedOnce(child, 'exit');
+      const [code] = await once(child, 'exit');
       expect(code).to.equal(0);
     });
   });
@@ -642,7 +642,7 @@ describe('node feature', () => {
       child = childProcess.spawn(process.execPath, ['--force-fips'], {
         env: { ELECTRON_RUN_AS_NODE: 'true' }
       });
-      exitPromise = emittedOnce(child, 'exit');
+      exitPromise = once(child, 'exit');
 
       let output = '';
       const cleanup = () => {
@@ -723,7 +723,7 @@ describe('node feature', () => {
       child = childProcess.spawn(process.execPath, ['--inspect=17364', path.join(fixtures, 'module', 'run-as-node.js')], {
         env: { ELECTRON_RUN_AS_NODE: 'true' }
       });
-      exitPromise = emittedOnce(child, 'exit');
+      exitPromise = once(child, 'exit');
 
       let output = '';
       const listener = (data: Buffer) => { output += data; };
@@ -734,7 +734,7 @@ describe('node feature', () => {
 
       child.stderr.on('data', listener);
       child.stdout.on('data', listener);
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
       cleanup();
       if (/^Debugger listening on ws:/m.test(output)) {
         expect(output.trim()).to.contain(':17364', 'should be listening on port 17364');
@@ -745,13 +745,13 @@ describe('node feature', () => {
 
     it('Does not start the v8 inspector when --inspect is after a -- argument', async () => {
       child = childProcess.spawn(process.execPath, [path.join(fixtures, 'module', 'noop.js'), '--', '--inspect']);
-      exitPromise = emittedOnce(child, 'exit');
+      exitPromise = once(child, 'exit');
 
       let output = '';
       const listener = (data: Buffer) => { output += data; };
       child.stderr.on('data', listener);
       child.stdout.on('data', listener);
-      await emittedOnce(child, 'exit');
+      await once(child, 'exit');
       if (output.trim().startsWith('Debugger listening on ws://')) {
         throw new Error('Inspector was started when it should not have been');
       }
@@ -762,7 +762,7 @@ describe('node feature', () => {
       child = childProcess.spawn(process.execPath, [path.join(fixtures, 'module', 'delay-exit'), '--inspect=0'], {
         stdio: ['ipc']
       }) as childProcess.ChildProcessWithoutNullStreams;
-      exitPromise = emittedOnce(child, 'exit');
+      exitPromise = once(child, 'exit');
 
       const cleanup = () => {
         child.stderr.removeListener('data', listener);
@@ -809,9 +809,9 @@ describe('node feature', () => {
         env: { ELECTRON_RUN_AS_NODE: 'true' },
         stdio: ['ipc']
       }) as childProcess.ChildProcessWithoutNullStreams;
-      exitPromise = emittedOnce(child, 'exit');
+      exitPromise = once(child, 'exit');
 
-      const [{ cmd, debuggerEnabled, success }] = await emittedOnce(child, 'message');
+      const [{ cmd, debuggerEnabled, success }] = await once(child, 'message');
       expect(cmd).to.equal('assert');
       expect(debuggerEnabled).to.be.true();
       expect(success).to.be.true();
@@ -828,7 +828,7 @@ describe('node feature', () => {
     const child = childProcess.spawn(process.execPath, [scriptPath], {
       env: { ELECTRON_RUN_AS_NODE: 'true' }
     });
-    const [code, signal] = await emittedOnce(child, 'exit');
+    const [code, signal] = await once(child, 'exit');
     expect(code).to.equal(0);
     expect(signal).to.equal(null);
     child.kill();

+ 3 - 2
spec/security-warnings-spec.ts

@@ -8,7 +8,8 @@ import { BrowserWindow, WebPreferences } from 'electron/main';
 
 import { closeWindow } from './lib/window-helpers';
 import { emittedUntil } from './lib/events-helpers';
-import { delay, listen } from './lib/spec-helpers';
+import { listen } from './lib/spec-helpers';
+import { setTimeout } from 'timers/promises';
 
 const messageContainsSecurityWarning = (event: Event, level: number, message: string) => {
   return message.indexOf('Electron Security Warning') > -1;
@@ -140,7 +141,7 @@ describe('security warnings', () => {
         w.webContents.on('console-message', () => {
           didNotWarn = false;
         });
-        await delay(500);
+        await setTimeout(500);
         expect(didNotWarn).to.equal(true);
       });
 

+ 8 - 7
spec/spellchecker-spec.ts

@@ -5,8 +5,9 @@ import * as path from 'path';
 import * as fs from 'fs';
 import * as http from 'http';
 import { closeWindow } from './lib/window-helpers';
-import { emittedOnce } from './lib/events-helpers';
-import { ifit, ifdescribe, delay, listen } from './lib/spec-helpers';
+import { ifit, ifdescribe, listen } from './lib/spec-helpers';
+import { once } from 'events';
+import { setTimeout } from 'timers/promises';
 
 const features = process._linkedBinding('electron_common_features');
 const v8Util = process._linkedBinding('electron_common_v8_util');
@@ -17,7 +18,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
   let w: BrowserWindow;
 
   async function rightClick () {
-    const contextMenuPromise = emittedOnce(w.webContents, 'context-menu');
+    const contextMenuPromise = once(w.webContents, 'context-menu');
     w.webContents.sendInputEvent({
       type: 'mouseDown',
       button: 'right',
@@ -35,7 +36,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
     const timeout = (process.env.IS_ASAN ? 180 : 10) * 1000;
     let contextMenuParams = await rightClick();
     while (!fn(contextMenuParams) && (Date.now() - now < timeout)) {
-      await delay(100);
+      await setTimeout(100);
       contextMenuParams = await rightClick();
     }
     return contextMenuParams;
@@ -107,7 +108,7 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
 
       ifit(shouldRun)('should detect incorrectly spelled words as incorrect after disabling all languages and re-enabling', async () => {
         w.webContents.session.setSpellCheckerLanguages([]);
-        await delay(500);
+        await setTimeout(500);
         w.webContents.session.setSpellCheckerLanguages(['en-US']);
         await w.webContents.executeJavaScript('document.body.querySelector("textarea").value = "typograpy"');
         await w.webContents.executeJavaScript('document.body.querySelector("textarea").focus()');
@@ -147,13 +148,13 @@ ifdescribe(features.isBuiltinSpellCheckerEnabled())('spellchecker', function ()
           // spellCheckerEnabled is sent to renderer asynchronously and there is
           // no event notifying when it is finished, so wait a little while to
           // ensure the setting has been changed in renderer.
-          await delay(500);
+          await setTimeout(500);
           expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(false);
 
           w.webContents.session.spellCheckerEnabled = true;
           v8Util.runUntilIdle();
           expect(w.webContents.session.spellCheckerEnabled).to.be.true();
-          await delay(500);
+          await setTimeout(500);
           expect(await callWebFrameFn('isWordMisspelled("typograpy")')).to.equal(true);
         });
       });

+ 23 - 20
spec/visibility-state-spec.ts

@@ -3,9 +3,10 @@ import * as cp from 'child_process';
 import { BrowserWindow, BrowserWindowConstructorOptions, ipcMain } from 'electron/main';
 import * as path from 'path';
 
-import { emittedOnce } from './lib/events-helpers';
 import { closeWindow } from './lib/window-helpers';
-import { ifdescribe, delay } from './lib/spec-helpers';
+import { ifdescribe } from './lib/spec-helpers';
+import { once } from 'events';
+import { setTimeout } from 'timers/promises';
 
 // visibilityState specs pass on linux with a real window manager but on CI
 // the environment does not let these specs pass
@@ -35,7 +36,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
 
   itWithOptions('should be visible when the window is initially shown by default', {}, async () => {
     load();
-    const [, state] = await emittedOnce(ipcMain, 'initial-visibility-state');
+    const [, state] = await once(ipcMain, 'initial-visibility-state');
     expect(state).to.equal('visible');
   });
 
@@ -43,7 +44,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
     show: true
   }, async () => {
     load();
-    const [, state] = await emittedOnce(ipcMain, 'initial-visibility-state');
+    const [, state] = await once(ipcMain, 'initial-visibility-state');
     expect(state).to.equal('visible');
   });
 
@@ -51,7 +52,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
     show: false
   }, async () => {
     load();
-    const [, state] = await emittedOnce(ipcMain, 'initial-visibility-state');
+    const [, state] = await once(ipcMain, 'initial-visibility-state');
     expect(state).to.equal('hidden');
   });
 
@@ -60,7 +61,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
   }, async () => {
     w.show();
     load();
-    const [, state] = await emittedOnce(ipcMain, 'initial-visibility-state');
+    const [, state] = await once(ipcMain, 'initial-visibility-state');
     expect(state).to.equal('visible');
   });
 
@@ -70,40 +71,42 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
     // TODO(MarshallOfSound): Figure out if we can work around this 1 tick issue for users
     if (process.platform === 'darwin') {
       // Wait for a tick, the window being "shown" takes 1 tick on macOS
-      await delay(10000);
+      await setTimeout(10000);
     }
     w.hide();
     load();
-    const [, state] = await emittedOnce(ipcMain, 'initial-visibility-state');
+    const [, state] = await once(ipcMain, 'initial-visibility-state');
     expect(state).to.equal('hidden');
   });
 
   itWithOptions('should be toggle between visible and hidden as the window is hidden and shown', {}, async () => {
     load();
-    const [, initialState] = await emittedOnce(ipcMain, 'initial-visibility-state');
+    const [, initialState] = await once(ipcMain, 'initial-visibility-state');
     expect(initialState).to.equal('visible');
     w.hide();
-    await emittedOnce(ipcMain, 'visibility-change-hidden');
+    await once(ipcMain, 'visibility-change-hidden');
     w.show();
-    await emittedOnce(ipcMain, 'visibility-change-visible');
+    await once(ipcMain, 'visibility-change-visible');
   });
 
   itWithOptions('should become hidden when a window is minimized', {}, async () => {
     load();
-    const [, initialState] = await emittedOnce(ipcMain, 'initial-visibility-state');
+    const [, initialState] = await once(ipcMain, 'initial-visibility-state');
     expect(initialState).to.equal('visible');
     w.minimize();
-    await emittedOnce(ipcMain, 'visibility-change-hidden', () => w.minimize());
+    const p = once(ipcMain, 'visibility-change-hidden');
+    w.minimize();
+    await p;
   });
 
   itWithOptions('should become visible when a window is restored', {}, async () => {
     load();
-    const [, initialState] = await emittedOnce(ipcMain, 'initial-visibility-state');
+    const [, initialState] = await once(ipcMain, 'initial-visibility-state');
     expect(initialState).to.equal('visible');
     w.minimize();
-    await emittedOnce(ipcMain, 'visibility-change-hidden');
+    await once(ipcMain, 'visibility-change-hidden');
     w.restore();
-    await emittedOnce(ipcMain, 'visibility-change-visible');
+    await once(ipcMain, 'visibility-change-visible');
   });
 
   describe('on platforms that support occlusion detection', () => {
@@ -141,7 +144,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
         height: 200
       });
       load();
-      const [, state] = await emittedOnce(ipcMain, 'initial-visibility-state');
+      const [, state] = await once(ipcMain, 'initial-visibility-state');
       expect(state).to.equal('visible');
     });
 
@@ -158,7 +161,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
         height: 200
       });
       load();
-      const [, state] = await emittedOnce(ipcMain, 'initial-visibility-state');
+      const [, state] = await once(ipcMain, 'initial-visibility-state');
       expect(state).to.equal('visible');
     });
 
@@ -170,7 +173,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
     }, async function () {
       this.timeout(240000);
       load();
-      const [, state] = await emittedOnce(ipcMain, 'initial-visibility-state');
+      const [, state] = await once(ipcMain, 'initial-visibility-state');
       expect(state).to.equal('visible');
       makeOtherWindow({
         x: 0,
@@ -178,7 +181,7 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
         width: 300,
         height: 300
       });
-      await emittedOnce(ipcMain, 'visibility-change-hidden');
+      await once(ipcMain, 'visibility-change-hidden');
     });
   });
 });

+ 70 - 68
spec/webview-spec.ts

@@ -2,11 +2,13 @@ import * as path from 'path';
 import * as url from 'url';
 import { BrowserWindow, session, ipcMain, app, WebContents } from 'electron/main';
 import { closeAllWindows } from './lib/window-helpers';
-import { emittedOnce, emittedUntil } from './lib/events-helpers';
-import { ifit, ifdescribe, delay, defer, itremote, useRemoteContext, listen } from './lib/spec-helpers';
+import { emittedUntil } from './lib/events-helpers';
+import { ifit, ifdescribe, defer, itremote, useRemoteContext, listen } from './lib/spec-helpers';
 import { expect } from 'chai';
 import * as http from 'http';
 import * as auth from 'basic-auth';
+import { once } from 'events';
+import { setTimeout } from 'timers/promises';
 
 declare let WebView: any;
 const features = process._linkedBinding('electron_common_features');
@@ -85,7 +87,7 @@ describe('<webview> tag', function () {
         }
       });
       w.loadFile(path.join(fixtures, 'pages', 'webview-no-script.html'));
-      await emittedOnce(ipcMain, 'pong');
+      await once(ipcMain, 'pong');
     });
 
     it('works with sandbox', async () => {
@@ -97,7 +99,7 @@ describe('<webview> tag', function () {
         }
       });
       w.loadFile(path.join(fixtures, 'pages', 'webview-isolated.html'));
-      await emittedOnce(ipcMain, 'pong');
+      await once(ipcMain, 'pong');
     });
 
     it('works with contextIsolation', async () => {
@@ -109,7 +111,7 @@ describe('<webview> tag', function () {
         }
       });
       w.loadFile(path.join(fixtures, 'pages', 'webview-isolated.html'));
-      await emittedOnce(ipcMain, 'pong');
+      await once(ipcMain, 'pong');
     });
 
     it('works with contextIsolation + sandbox', async () => {
@@ -122,7 +124,7 @@ describe('<webview> tag', function () {
         }
       });
       w.loadFile(path.join(fixtures, 'pages', 'webview-isolated.html'));
-      await emittedOnce(ipcMain, 'pong');
+      await once(ipcMain, 'pong');
     });
 
     it('works with Trusted Types', async () => {
@@ -133,7 +135,7 @@ describe('<webview> tag', function () {
         }
       });
       w.loadFile(path.join(fixtures, 'pages', 'webview-trusted-types.html'));
-      await emittedOnce(ipcMain, 'pong');
+      await once(ipcMain, 'pong');
     });
 
     it('is disabled by default', async () => {
@@ -145,7 +147,7 @@ describe('<webview> tag', function () {
         }
       });
 
-      const webview = emittedOnce(ipcMain, 'webview');
+      const webview = once(ipcMain, 'webview');
       w.loadFile(path.join(fixtures, 'pages', 'webview-no-script.html'));
       const [, type] = await webview;
 
@@ -163,11 +165,11 @@ describe('<webview> tag', function () {
 
     it('updates when the window is shown after the ready-to-show event', async () => {
       const w = new BrowserWindow({ show: false });
-      const readyToShowSignal = emittedOnce(w, 'ready-to-show');
-      const pongSignal1 = emittedOnce(ipcMain, 'pong');
+      const readyToShowSignal = once(w, 'ready-to-show');
+      const pongSignal1 = once(ipcMain, 'pong');
       w.loadFile(path.join(fixtures, 'pages', 'webview-visibilitychange.html'));
       await pongSignal1;
-      const pongSignal2 = emittedOnce(ipcMain, 'pong');
+      const pongSignal2 = once(ipcMain, 'pong');
       await readyToShowSignal;
       w.show();
 
@@ -179,13 +181,13 @@ describe('<webview> tag', function () {
     it('inherits the parent window visibility state and receives visibilitychange events', async () => {
       const w = new BrowserWindow({ show: false });
       w.loadFile(path.join(fixtures, 'pages', 'webview-visibilitychange.html'));
-      const [, visibilityState, hidden] = await emittedOnce(ipcMain, 'pong');
+      const [, visibilityState, hidden] = await once(ipcMain, 'pong');
       expect(visibilityState).to.equal('hidden');
       expect(hidden).to.be.true();
 
       // We have to start waiting for the event
       // before we ask the webContents to resize.
-      const getResponse = emittedOnce(ipcMain, 'pong');
+      const getResponse = once(ipcMain, 'pong');
       w.webContents.emit('-window-visibility-change', 'visible');
 
       return getResponse.then(([, visibilityState, hidden]) => {
@@ -206,8 +208,8 @@ describe('<webview> tag', function () {
           contextIsolation: false
         }
       });
-      const didAttachWebview = emittedOnce(w.webContents, 'did-attach-webview');
-      const webviewDomReady = emittedOnce(ipcMain, 'webview-dom-ready');
+      const didAttachWebview = once(w.webContents, 'did-attach-webview');
+      const webviewDomReady = once(ipcMain, 'webview-dom-ready');
       w.loadFile(path.join(fixtures, 'pages', 'webview-did-attach-event.html'));
 
       const [, webContents] = await didAttachWebview;
@@ -305,7 +307,7 @@ describe('<webview> tag', function () {
         });
       });
 
-      const [, { runtimeId, tabId }] = await emittedOnce(ipcMain, 'answer');
+      const [, { runtimeId, tabId }] = await once(ipcMain, 'answer');
       expect(runtimeId).to.match(/^[a-z]{32}$/);
       expect(tabId).to.equal(childWebContentsId);
       await w.webContents.executeJavaScript('webview.closeDevTools()');
@@ -340,7 +342,7 @@ describe('<webview> tag', function () {
           contextIsolation: false
         }
       });
-      const zoomEventPromise = emittedOnce(ipcMain, 'webview-parent-zoom-level');
+      const zoomEventPromise = once(ipcMain, 'webview-parent-zoom-level');
       w.loadFile(path.join(fixtures, 'pages', 'webview-zoom-factor.html'));
 
       const [, zoomFactor, zoomLevel] = await zoomEventPromise;
@@ -417,7 +419,7 @@ describe('<webview> tag', function () {
       });
       w.loadFile(path.join(fixtures, 'pages', 'webview-origin-zoom-level.html'));
 
-      const [, zoomLevel] = await emittedOnce(ipcMain, 'webview-origin-zoom-level');
+      const [, zoomLevel] = await once(ipcMain, 'webview-origin-zoom-level');
       expect(zoomLevel).to.equal(2.0);
     });
 
@@ -432,8 +434,8 @@ describe('<webview> tag', function () {
           contextIsolation: false
         }
       });
-      const attachPromise = emittedOnce(w.webContents, 'did-attach-webview');
-      const readyPromise = emittedOnce(ipcMain, 'dom-ready');
+      const attachPromise = once(w.webContents, 'did-attach-webview');
+      const readyPromise = once(ipcMain, 'dom-ready');
       w.loadFile(path.join(fixtures, 'pages', 'webview-zoom-inherited.html'));
       const [, webview] = await attachPromise;
       await readyPromise;
@@ -451,7 +453,7 @@ describe('<webview> tag', function () {
           contextIsolation: false
         }
       });
-      const attachPromise = emittedOnce(w.webContents, 'did-attach-webview');
+      const attachPromise = once(w.webContents, 'did-attach-webview');
       await w.loadFile(path.join(fixtures, 'pages', 'webview-zoom-inherited.html'));
       await attachPromise;
       await w.webContents.executeJavaScript('view.remove()');
@@ -470,9 +472,9 @@ describe('<webview> tag', function () {
         }
       });
 
-      const attachPromise = emittedOnce(w.webContents, 'did-attach-webview');
-      const loadPromise = emittedOnce(w.webContents, 'did-finish-load');
-      const readyPromise = emittedOnce(ipcMain, 'webview-ready');
+      const attachPromise = once(w.webContents, 'did-attach-webview');
+      const loadPromise = once(w.webContents, 'did-finish-load');
+      const readyPromise = once(ipcMain, 'webview-ready');
 
       w.loadFile(path.join(__dirname, 'fixtures', 'webview', 'fullscreen', 'main.html'));
 
@@ -485,7 +487,7 @@ describe('<webview> tag', function () {
     afterEach(async () => {
       // The leaving animation is un-observable but can interfere with future tests
       // Specifically this is async on macOS but can be on other platforms too
-      await delay(1000);
+      await setTimeout(1000);
 
       closeAllWindows();
     });
@@ -494,13 +496,13 @@ describe('<webview> tag', function () {
       const [w, webview] = await loadWebViewWindow();
       expect(await w.webContents.executeJavaScript('isIframeFullscreen()')).to.be.false();
 
-      const parentFullscreen = emittedOnce(ipcMain, 'fullscreenchange');
+      const parentFullscreen = once(ipcMain, 'fullscreenchange');
       await webview.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
       await parentFullscreen;
 
       expect(await w.webContents.executeJavaScript('isIframeFullscreen()')).to.be.true();
 
-      const close = emittedOnce(w, 'closed');
+      const close = once(w, 'closed');
       w.close();
       await close;
     });
@@ -509,9 +511,9 @@ describe('<webview> tag', function () {
       const [w, webview] = await loadWebViewWindow();
       expect(await w.webContents.executeJavaScript('isIframeFullscreen()')).to.be.false();
 
-      const parentFullscreen = emittedOnce(ipcMain, 'fullscreenchange');
-      const enterHTMLFS = emittedOnce(w.webContents, 'enter-html-full-screen');
-      const leaveHTMLFS = emittedOnce(w.webContents, 'leave-html-full-screen');
+      const parentFullscreen = once(ipcMain, 'fullscreenchange');
+      const enterHTMLFS = once(w.webContents, 'enter-html-full-screen');
+      const leaveHTMLFS = once(w.webContents, 'leave-html-full-screen');
 
       await webview.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
       expect(await w.webContents.executeJavaScript('isIframeFullscreen()')).to.be.true();
@@ -519,7 +521,7 @@ describe('<webview> tag', function () {
       await webview.executeJavaScript('document.exitFullscreen()');
       await Promise.all([enterHTMLFS, leaveHTMLFS, parentFullscreen]);
 
-      const close = emittedOnce(w, 'closed');
+      const close = once(w, 'closed');
       w.close();
       await close;
     });
@@ -527,17 +529,17 @@ describe('<webview> tag', function () {
     // FIXME(zcbenz): Fullscreen events do not work on Linux.
     ifit(process.platform !== 'linux')('exiting fullscreen should unfullscreen window', async () => {
       const [w, webview] = await loadWebViewWindow();
-      const enterFullScreen = emittedOnce(w, 'enter-full-screen');
+      const enterFullScreen = once(w, 'enter-full-screen');
       await webview.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
       await enterFullScreen;
 
-      const leaveFullScreen = emittedOnce(w, 'leave-full-screen');
+      const leaveFullScreen = once(w, 'leave-full-screen');
       await webview.executeJavaScript('document.exitFullscreen()', true);
       await leaveFullScreen;
-      await delay(0);
+      await setTimeout();
       expect(w.isFullScreen()).to.be.false();
 
-      const close = emittedOnce(w, 'closed');
+      const close = once(w, 'closed');
       w.close();
       await close;
     });
@@ -545,17 +547,17 @@ describe('<webview> tag', function () {
     // Sending ESC via sendInputEvent only works on Windows.
     ifit(process.platform === 'win32')('pressing ESC should unfullscreen window', async () => {
       const [w, webview] = await loadWebViewWindow();
-      const enterFullScreen = emittedOnce(w, 'enter-full-screen');
+      const enterFullScreen = once(w, 'enter-full-screen');
       await webview.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
       await enterFullScreen;
 
-      const leaveFullScreen = emittedOnce(w, 'leave-full-screen');
+      const leaveFullScreen = once(w, 'leave-full-screen');
       w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Escape' });
       await leaveFullScreen;
-      await delay(0);
+      await setTimeout();
       expect(w.isFullScreen()).to.be.false();
 
-      const close = emittedOnce(w, 'closed');
+      const close = once(w, 'closed');
       w.close();
       await close;
     });
@@ -570,24 +572,24 @@ describe('<webview> tag', function () {
         }
       });
 
-      const didAttachWebview = emittedOnce(w.webContents, 'did-attach-webview');
+      const didAttachWebview = once(w.webContents, 'did-attach-webview');
       w.loadFile(path.join(fixtures, 'pages', 'webview-did-attach-event.html'));
 
       const [, webContents] = await didAttachWebview;
 
-      const enterFSWindow = emittedOnce(w, 'enter-html-full-screen');
-      const enterFSWebview = emittedOnce(webContents, 'enter-html-full-screen');
+      const enterFSWindow = once(w, 'enter-html-full-screen');
+      const enterFSWebview = once(webContents, 'enter-html-full-screen');
       await webContents.executeJavaScript('document.getElementById("div").requestFullscreen()', true);
       await enterFSWindow;
       await enterFSWebview;
 
-      const leaveFSWindow = emittedOnce(w, 'leave-html-full-screen');
-      const leaveFSWebview = emittedOnce(webContents, 'leave-html-full-screen');
+      const leaveFSWindow = once(w, 'leave-html-full-screen');
+      const leaveFSWebview = once(webContents, 'leave-html-full-screen');
       webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Escape' });
       await leaveFSWebview;
       await leaveFSWindow;
 
-      const close = emittedOnce(w, 'closed');
+      const close = once(w, 'closed');
       w.close();
       await close;
     });
@@ -595,14 +597,14 @@ describe('<webview> tag', function () {
     it('should support user gesture', async () => {
       const [w, webview] = await loadWebViewWindow();
 
-      const waitForEnterHtmlFullScreen = emittedOnce(webview, 'enter-html-full-screen');
+      const waitForEnterHtmlFullScreen = once(webview, 'enter-html-full-screen');
 
       const jsScript = "document.querySelector('video').webkitRequestFullscreen()";
       webview.executeJavaScript(jsScript, true);
 
       await waitForEnterHtmlFullScreen;
 
-      const close = emittedOnce(w, 'closed');
+      const close = once(w, 'closed');
       w.close();
       await close;
     });
@@ -625,7 +627,7 @@ describe('<webview> tag', function () {
         src: `file://${path.join(fixtures, 'api', 'native-window-open-blank.html')}`
       });
 
-      const [, content] = await emittedOnce(ipcMain, 'answer');
+      const [, content] = await once(ipcMain, 'answer');
       expect(content).to.equal('Hello');
     });
 
@@ -638,7 +640,7 @@ describe('<webview> tag', function () {
         src: `file://${path.join(fixtures, 'api', 'native-window-open-file.html')}`
       });
 
-      const [, content] = await emittedOnce(ipcMain, 'answer');
+      const [, content] = await once(ipcMain, 'answer');
       expect(content).to.equal('Hello');
     });
 
@@ -650,7 +652,7 @@ describe('<webview> tag', function () {
         src: `file://${path.join(fixtures, 'api', 'native-window-open-no-allowpopups.html')}`
       });
 
-      const [, { windowOpenReturnedNull }] = await emittedOnce(ipcMain, 'answer');
+      const [, { windowOpenReturnedNull }] = await once(ipcMain, 'answer');
       expect(windowOpenReturnedNull).to.be.true();
     });
 
@@ -663,7 +665,7 @@ describe('<webview> tag', function () {
         src: `file://${path.join(fixtures, 'api', 'native-window-open-cross-origin.html')}`
       });
 
-      const [, content] = await emittedOnce(ipcMain, 'answer');
+      const [, content] = await once(ipcMain, 'answer');
       const expectedContent =
           'Blocked a frame with origin "file://" from accessing a cross-origin frame.';
 
@@ -678,7 +680,7 @@ describe('<webview> tag', function () {
         src: `file://${fixtures}/pages/window-open.html`
       });
 
-      await emittedOnce(app, 'browser-window-created');
+      await once(app, 'browser-window-created');
     });
 
     it('emits a web-contents-created event', async () => {
@@ -699,7 +701,7 @@ describe('<webview> tag', function () {
         allowpopups: 'on',
         src: `file://${path.join(fixtures, 'api', 'native-window-open-noopener.html')}`
       });
-      await emittedOnce(app, 'browser-window-created');
+      await once(app, 'browser-window-created');
     });
   });
 
@@ -719,7 +721,7 @@ describe('<webview> tag', function () {
         webpreferences: 'contextIsolation=yes'
       });
 
-      const [, data] = await emittedOnce(ipcMain, 'isolated-world');
+      const [, data] = await once(ipcMain, 'isolated-world');
       expect(data).to.deep.equal({
         preloadContext: {
           preloadProperty: 'number',
@@ -784,55 +786,55 @@ describe('<webview> tag', function () {
     // "PermissionDeniedError". It should be re-enabled if we find a way to mock
     // the presence of a microphone & camera.
     xit('emits when using navigator.getUserMedia api', async () => {
-      const errorFromRenderer = emittedOnce(ipcMain, 'message');
+      const errorFromRenderer = once(ipcMain, 'message');
       loadWebView(w.webContents, {
         src: `file://${fixtures}/pages/permissions/media.html`,
         partition,
         nodeintegration: 'on'
       });
-      const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
+      const [, webViewContents] = await once(app, 'web-contents-created');
       setUpRequestHandler(webViewContents.id, 'media');
       const [, errorName] = await errorFromRenderer;
       expect(errorName).to.equal('PermissionDeniedError');
     });
 
     it('emits when using navigator.geolocation api', async () => {
-      const errorFromRenderer = emittedOnce(ipcMain, 'message');
+      const errorFromRenderer = once(ipcMain, 'message');
       loadWebView(w.webContents, {
         src: `file://${fixtures}/pages/permissions/geolocation.html`,
         partition,
         nodeintegration: 'on',
         webpreferences: 'contextIsolation=no'
       });
-      const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
+      const [, webViewContents] = await once(app, 'web-contents-created');
       setUpRequestHandler(webViewContents.id, 'geolocation');
       const [, error] = await errorFromRenderer;
       expect(error).to.equal('User denied Geolocation');
     });
 
     it('emits when using navigator.requestMIDIAccess without sysex api', async () => {
-      const errorFromRenderer = emittedOnce(ipcMain, 'message');
+      const errorFromRenderer = once(ipcMain, 'message');
       loadWebView(w.webContents, {
         src: `file://${fixtures}/pages/permissions/midi.html`,
         partition,
         nodeintegration: 'on',
         webpreferences: 'contextIsolation=no'
       });
-      const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
+      const [, webViewContents] = await once(app, 'web-contents-created');
       setUpRequestHandler(webViewContents.id, 'midi');
       const [, error] = await errorFromRenderer;
       expect(error).to.equal('SecurityError');
     });
 
     it('emits when using navigator.requestMIDIAccess with sysex api', async () => {
-      const errorFromRenderer = emittedOnce(ipcMain, 'message');
+      const errorFromRenderer = once(ipcMain, 'message');
       loadWebView(w.webContents, {
         src: `file://${fixtures}/pages/permissions/midi-sysex.html`,
         partition,
         nodeintegration: 'on',
         webpreferences: 'contextIsolation=no'
       });
-      const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
+      const [, webViewContents] = await once(app, 'web-contents-created');
       setUpRequestHandler(webViewContents.id, 'midiSysex');
       const [, error] = await errorFromRenderer;
       expect(error).to.equal('SecurityError');
@@ -843,19 +845,19 @@ describe('<webview> tag', function () {
         src: 'magnet:test',
         partition
       });
-      const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
+      const [, webViewContents] = await once(app, 'web-contents-created');
       await setUpRequestHandler(webViewContents.id, 'openExternal');
     });
 
     it('emits when using Notification.requestPermission', async () => {
-      const errorFromRenderer = emittedOnce(ipcMain, 'message');
+      const errorFromRenderer = once(ipcMain, 'message');
       loadWebView(w.webContents, {
         src: `file://${fixtures}/pages/permissions/notification.html`,
         partition,
         nodeintegration: 'on',
         webpreferences: 'contextIsolation=no'
       });
-      const [, webViewContents] = await emittedOnce(app, 'web-contents-created');
+      const [, webViewContents] = await once(app, 'web-contents-created');
 
       await setUpRequestHandler(webViewContents.id, 'notifications');
 
@@ -1141,12 +1143,12 @@ describe('<webview> tag', function () {
         w.setAttribute('preload', `file://${fixtures}/module/preload-ipc.js`);
         w.setAttribute('src', `file://${fixtures}/pages/ipc-message.html`);
         document.body.appendChild(w);
-        const { frameId } = await new Promise(resolve => w.addEventListener('ipc-message', resolve, { once: true }));
+        const { frameId } = await new Promise<any>(resolve => w.addEventListener('ipc-message', resolve, { once: true }));
 
         const message = 'boom!';
 
         w.sendToFrame(frameId, 'ping', message);
-        const { channel, args } = await new Promise(resolve => w.addEventListener('ipc-message', resolve, { once: true }));
+        const { channel, args } = await new Promise<any>(resolve => w.addEventListener('ipc-message', resolve, { once: true }));
 
         expect(channel).to.equal('pong');
         expect(args).to.deep.equal([message]);
@@ -1642,7 +1644,7 @@ describe('<webview> tag', function () {
       itremote('does not emit when src is not changed', async () => {
         const webview = new WebView();
         document.body.appendChild(webview);
-        await new Promise(resolve => setTimeout(resolve));
+        await setTimeout();
         const expectedErrorMessage = 'The WebView must be attached to the DOM and the dom-ready event emitted before this method can be called.';
         expect(() => { webview.stop(); }).to.throw(expectedErrorMessage);
       });

Some files were not shown because too many files changed in this diff