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

This commit is contained in:
Jeremy Rose 2023-02-23 15:53:53 -08:00 committed by GitHub
parent 46c8b9c728
commit a3e3efe4c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
47 changed files with 932 additions and 927 deletions

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,7 +7,7 @@ import * as ChildProcess from 'child_process';
import { session, net } from 'electron/main'; import { session, net } from 'electron/main';
import { Socket } from 'net'; import { Socket } from 'net';
import { ifit, listen } from './lib/spec-helpers'; 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 appPath = path.join(__dirname, 'fixtures', 'api', 'net-log');
const dumpFile = path.join(os.tmpdir(), 'net_log.json'); 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'); 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(dumpFile)).to.be.true('dump file exists');
expect(fs.existsSync(dumpFileDynamic)).to.be.true('dynamic 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'); expect(fs.existsSync(dumpFileDynamic)).to.be.true('dynamic dump file exists');
}); });
}); });

View file

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

View file

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

View file

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

View file

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

View file

@ -2,9 +2,9 @@ import * as cp from 'child_process';
import * as path from 'path'; import * as path from 'path';
import { safeStorage } from 'electron/main'; import { safeStorage } from 'electron/main';
import { expect } from 'chai'; import { expect } from 'chai';
import { emittedOnce } from './lib/events-helpers';
import { ifdescribe } from './lib/spec-helpers'; import { ifdescribe } from './lib/spec-helpers';
import * as fs from 'fs-extra'; 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 /* 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 * 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.stdout.on('data', data => { output += data; });
appProcess.stderr.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) { if (code !== 0 && output) {
console.log(output); console.log(output);
@ -98,7 +98,7 @@ ifdescribe(process.platform !== 'linux')('safeStorage module', () => {
encryptAppProcess.stderr.on('data', data => { stdout += data; }); encryptAppProcess.stderr.on('data', data => { stdout += data; });
try { try {
await emittedOnce(encryptAppProcess, 'exit'); await once(encryptAppProcess, 'exit');
const appPath = path.join(fixturesPath, 'api', 'safe-storage', 'decrypt-app'); const appPath = path.join(fixturesPath, 'api', 'safe-storage', 'decrypt-app');
const relaunchedAppProcess = cp.spawn(process.execPath, [appPath]); 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.stdout.on('data', data => { output += data; });
relaunchedAppProcess.stderr.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')) { if (!output.includes('plaintext')) {
console.log(code, output); console.log(code, output);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,53 +3,21 @@
* with events in async/await manner. * with events in async/await manner.
*/ */
/** import { on } from 'events';
* @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);
};
export const emittedNTimes = async (emitter: NodeJS.EventEmitter, eventName: string, times: number, trigger?: () => void) => { export const emittedNTimes = async (emitter: NodeJS.EventEmitter, eventName: string, times: number, trigger?: () => void) => {
const events: any[][] = []; const events: any[][] = [];
const p = new Promise<any[][]>(resolve => { const iter = on(emitter, eventName);
const handler = (...args: any[]) => { if (trigger) await Promise.resolve(trigger());
events.push(args); for await (const args of iter) {
if (events.length === times) { events.push(args);
emitter.removeListener(eventName, handler); if (events.length === times) { break; }
resolve(events);
}
};
emitter.on(eventName, handler);
});
if (trigger) {
await Promise.resolve(trigger());
} }
return p; return events;
}; };
export const emittedUntil = async (emitter: NodeJS.EventEmitter, eventName: string, untilFn: Function) => { export const emittedUntil = async (emitter: NodeJS.EventEmitter, eventName: string, untilFn: Function) => {
const p = new Promise<any[]>(resolve => { for await (const args of on(emitter, eventName)) {
const handler = (...args: any[]) => { if (untilFn(...args)) { return args; }
if (untilFn(...args)) { }
emitter.removeListener(eventName, handler);
resolve(args);
}
};
emitter.on(eventName, handler);
});
return p;
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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