123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836 |
- import { EventEmitter, once } from 'node:events';
- import { expect } from 'chai';
- import { BrowserWindow, ipcMain, IpcMainInvokeEvent, MessageChannelMain, WebContents } from 'electron/main';
- import { closeAllWindows } from './lib/window-helpers';
- import { defer, listen } from './lib/spec-helpers';
- import * as path from 'node:path';
- import * as http from 'node:http';
- const v8Util = process._linkedBinding('electron_common_v8_util');
- const fixturesPath = path.resolve(__dirname, 'fixtures');
- describe('ipc module', () => {
- describe('invoke', () => {
- let w: BrowserWindow;
- before(async () => {
- w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- await w.loadURL('about:blank');
- });
- after(async () => {
- w.destroy();
- });
- async function rendererInvoke (...args: any[]) {
- const { ipcRenderer } = require('electron');
- try {
- const result = await ipcRenderer.invoke('test', ...args);
- ipcRenderer.send('result', { result });
- } catch (e) {
- ipcRenderer.send('result', { error: (e as Error).message });
- }
- }
- it('receives a response from a synchronous handler', async () => {
- ipcMain.handleOnce('test', (e: IpcMainInvokeEvent, arg: number) => {
- expect(arg).to.equal(123);
- return 3;
- });
- const done = new Promise<void>(resolve => ipcMain.once('result', (e, arg) => {
- expect(arg).to.deep.equal({ result: 3 });
- resolve();
- }));
- await w.webContents.executeJavaScript(`(${rendererInvoke})(123)`);
- await done;
- });
- it('receives a response from an asynchronous handler', async () => {
- ipcMain.handleOnce('test', async (e: IpcMainInvokeEvent, arg: number) => {
- expect(arg).to.equal(123);
- await new Promise(setImmediate);
- return 3;
- });
- const done = new Promise<void>(resolve => ipcMain.once('result', (e, arg) => {
- expect(arg).to.deep.equal({ result: 3 });
- resolve();
- }));
- await w.webContents.executeJavaScript(`(${rendererInvoke})(123)`);
- await done;
- });
- it('receives an error from a synchronous handler', async () => {
- ipcMain.handleOnce('test', () => {
- throw new Error('some error');
- });
- const done = new Promise<void>(resolve => ipcMain.once('result', (e, arg) => {
- expect(arg.error).to.match(/some error/);
- resolve();
- }));
- await w.webContents.executeJavaScript(`(${rendererInvoke})()`);
- await done;
- });
- it('receives an error from an asynchronous handler', async () => {
- ipcMain.handleOnce('test', async () => {
- await new Promise(setImmediate);
- throw new Error('some error');
- });
- const done = new Promise<void>(resolve => ipcMain.once('result', (e, arg) => {
- expect(arg.error).to.match(/some error/);
- resolve();
- }));
- await w.webContents.executeJavaScript(`(${rendererInvoke})()`);
- await done;
- });
- it('throws an error if no handler is registered', async () => {
- const done = new Promise<void>(resolve => ipcMain.once('result', (e, arg) => {
- expect(arg.error).to.match(/No handler registered/);
- resolve();
- }));
- await w.webContents.executeJavaScript(`(${rendererInvoke})()`);
- await done;
- });
- it('throws an error when invoking a handler that was removed', async () => {
- ipcMain.handle('test', () => { });
- ipcMain.removeHandler('test');
- const done = new Promise<void>(resolve => ipcMain.once('result', (e, arg) => {
- expect(arg.error).to.match(/No handler registered/);
- resolve();
- }));
- await w.webContents.executeJavaScript(`(${rendererInvoke})()`);
- await done;
- });
- it('forbids multiple handlers', async () => {
- ipcMain.handle('test', () => { });
- try {
- expect(() => { ipcMain.handle('test', () => { }); }).to.throw(/second handler/);
- } finally {
- ipcMain.removeHandler('test');
- }
- });
- it('throws an error in the renderer if the reply callback is dropped', async () => {
- ipcMain.handleOnce('test', () => new Promise(() => {
- setTimeout(() => v8Util.requestGarbageCollectionForTesting());
- /* never resolve */
- }));
- w.webContents.executeJavaScript(`(${rendererInvoke})()`);
- const [, { error }] = await once(ipcMain, 'result');
- expect(error).to.match(/reply was never sent/);
- });
- });
- describe('ordering', () => {
- let w: BrowserWindow;
- before(async () => {
- w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- await w.loadURL('about:blank');
- });
- after(async () => {
- w.destroy();
- });
- it('between send and sendSync is consistent', async () => {
- const received: number[] = [];
- ipcMain.on('test-async', (e, i) => { received.push(i); });
- ipcMain.on('test-sync', (e, i) => { received.push(i); e.returnValue = null; });
- const done = new Promise<void>(resolve => ipcMain.once('done', () => { resolve(); }));
- function rendererStressTest () {
- const { ipcRenderer } = require('electron');
- for (let i = 0; i < 1000; i++) {
- switch ((Math.random() * 2) | 0) {
- case 0:
- ipcRenderer.send('test-async', i);
- break;
- case 1:
- ipcRenderer.sendSync('test-sync', i);
- break;
- }
- }
- ipcRenderer.send('done');
- }
- try {
- w.webContents.executeJavaScript(`(${rendererStressTest})()`);
- await done;
- } finally {
- ipcMain.removeAllListeners('test-async');
- ipcMain.removeAllListeners('test-sync');
- }
- expect(received).to.have.lengthOf(1000);
- expect(received).to.deep.equal([...received].sort((a, b) => a - b));
- });
- it('between send, sendSync, and invoke is consistent', async () => {
- const received: number[] = [];
- ipcMain.handle('test-invoke', (e, i) => { received.push(i); });
- ipcMain.on('test-async', (e, i) => { received.push(i); });
- ipcMain.on('test-sync', (e, i) => { received.push(i); e.returnValue = null; });
- const done = new Promise<void>(resolve => ipcMain.once('done', () => { resolve(); }));
- function rendererStressTest () {
- const { ipcRenderer } = require('electron');
- for (let i = 0; i < 1000; i++) {
- switch ((Math.random() * 3) | 0) {
- case 0:
- ipcRenderer.send('test-async', i);
- break;
- case 1:
- ipcRenderer.sendSync('test-sync', i);
- break;
- case 2:
- ipcRenderer.invoke('test-invoke', i);
- break;
- }
- }
- ipcRenderer.send('done');
- }
- try {
- w.webContents.executeJavaScript(`(${rendererStressTest})()`);
- await done;
- } finally {
- ipcMain.removeHandler('test-invoke');
- ipcMain.removeAllListeners('test-async');
- ipcMain.removeAllListeners('test-sync');
- }
- expect(received).to.have.lengthOf(1000);
- expect(received).to.deep.equal([...received].sort((a, b) => a - b));
- });
- });
- describe('MessagePort', () => {
- afterEach(closeAllWindows);
- it('can send a port to the main process', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- const p = once(ipcMain, 'port');
- await w.webContents.executeJavaScript(`(${function () {
- const channel = new MessageChannel();
- require('electron').ipcRenderer.postMessage('port', 'hi', [channel.port1]);
- }})()`);
- const [ev, msg] = await p;
- expect(msg).to.equal('hi');
- expect(ev.ports).to.have.length(1);
- expect(ev.senderFrame.parent).to.be.null();
- expect(ev.senderFrame.routingId).to.equal(w.webContents.mainFrame.routingId);
- const [port] = ev.ports;
- expect(port).to.be.an.instanceOf(EventEmitter);
- });
- it('can sent a message without a transfer', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- const p = once(ipcMain, 'port');
- await w.webContents.executeJavaScript(`(${function () {
- require('electron').ipcRenderer.postMessage('port', 'hi');
- }})()`);
- const [ev, msg] = await p;
- expect(msg).to.equal('hi');
- expect(ev.ports).to.deep.equal([]);
- expect(ev.senderFrame.routingId).to.equal(w.webContents.mainFrame.routingId);
- });
- it('can communicate between main and renderer', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- const p = once(ipcMain, 'port');
- await w.webContents.executeJavaScript(`(${function () {
- const channel = new MessageChannel();
- channel.port2.onmessage = (ev: any) => {
- channel.port2.postMessage(ev.data * 2);
- };
- require('electron').ipcRenderer.postMessage('port', '', [channel.port1]);
- }})()`);
- const [ev] = await p;
- expect(ev.ports).to.have.length(1);
- expect(ev.senderFrame.routingId).to.equal(w.webContents.mainFrame.routingId);
- const [port] = ev.ports;
- port.start();
- port.postMessage(42);
- const [ev2] = await once(port, 'message');
- expect(ev2.data).to.equal(84);
- });
- it('can receive a port from a renderer over a MessagePort connection', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- function fn () {
- const channel1 = new MessageChannel();
- const channel2 = new MessageChannel();
- channel1.port2.postMessage('', [channel2.port1]);
- channel2.port2.postMessage('matryoshka');
- require('electron').ipcRenderer.postMessage('port', '', [channel1.port1]);
- }
- w.webContents.executeJavaScript(`(${fn})()`);
- const [{ ports: [port1] }] = await once(ipcMain, 'port');
- port1.start();
- const [{ ports: [port2] }] = await once(port1, 'message');
- port2.start();
- const [{ data }] = await once(port2, 'message');
- expect(data).to.equal('matryoshka');
- });
- it('can forward a port from one renderer to another renderer', async () => {
- const w1 = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- const w2 = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w1.loadURL('about:blank');
- w2.loadURL('about:blank');
- w1.webContents.executeJavaScript(`(${function () {
- const channel = new MessageChannel();
- channel.port2.onmessage = (ev: any) => {
- require('electron').ipcRenderer.send('message received', ev.data);
- };
- require('electron').ipcRenderer.postMessage('port', '', [channel.port1]);
- }})()`);
- const [{ ports: [port] }] = await once(ipcMain, 'port');
- await w2.webContents.executeJavaScript(`(${function () {
- require('electron').ipcRenderer.on('port', ({ ports: [port] }: any) => {
- port.postMessage('a message');
- });
- }})()`);
- w2.webContents.postMessage('port', '', [port]);
- const [, data] = await once(ipcMain, 'message received');
- expect(data).to.equal('a message');
- });
- describe('close event', () => {
- describe('in renderer', () => {
- it('is emitted when the main process closes its end of the port', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- await w.webContents.executeJavaScript(`(${function () {
- const { ipcRenderer } = require('electron');
- ipcRenderer.on('port', e => {
- const [port] = e.ports;
- port.start();
- port.onclose = () => {
- ipcRenderer.send('closed');
- };
- });
- }})()`);
- const { port1, port2 } = new MessageChannelMain();
- w.webContents.postMessage('port', null, [port2]);
- port1.close();
- await once(ipcMain, 'closed');
- });
- it('is emitted when the other end of a port is garbage-collected', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- await w.webContents.executeJavaScript(`(${async function () {
- const { port2 } = new MessageChannel();
- await new Promise<void>(resolve => {
- port2.start();
- port2.onclose = resolve;
- // @ts-ignore --expose-gc is enabled.
- gc({ type: 'major', execution: 'async' });
- });
- }})()`);
- });
- it('is emitted when the other end of a port is sent to nowhere', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- ipcMain.once('do-a-gc', () => v8Util.requestGarbageCollectionForTesting());
- await w.webContents.executeJavaScript(`(${async function () {
- const { port1, port2 } = new MessageChannel();
- await new Promise<void>(resolve => {
- port2.start();
- port2.onclose = resolve;
- require('electron').ipcRenderer.postMessage('nobody-listening', null, [port1]);
- require('electron').ipcRenderer.send('do-a-gc');
- });
- }})()`);
- });
- });
- describe('when context destroyed', () => {
- it('does not crash', async () => {
- let count = 0;
- const server = http.createServer((req, res) => {
- switch (req.url) {
- case '/index.html':
- res.setHeader('content-type', 'text/html');
- res.statusCode = 200;
- count = count + 1;
- res.end(`
- <title>Hello${count}</title>
- <script>
- var sharedWorker = new SharedWorker('worker.js');
- sharedWorker.port.addEventListener('close', function(event) {
- console.log('close event', event.data);
- });
- </script>`);
- break;
- case '/worker.js':
- res.setHeader('content-type', 'application/javascript; charset=UTF-8');
- res.statusCode = 200;
- res.end(`
- self.addEventListener('connect', function(event) {
- var port = event.ports[0];
- port.addEventListener('message', function(event) {
- console.log('Message from main:', event.data);
- port.postMessage('Hello from SharedWorker!');
- });
- });`);
- break;
- default:
- throw new Error(`unsupported endpoint: ${req.url}`);
- }
- });
- const { port } = await listen(server);
- defer(() => {
- server.close();
- });
- const w = new BrowserWindow({ show: false });
- await w.loadURL(`http://localhost:${port}/index.html`);
- expect(w.webContents.getTitle()).to.equal('Hello1');
- // Before the fix, it would crash if reloaded, but now it doesn't
- await w.loadURL(`http://localhost:${port}/index.html`);
- expect(w.webContents.getTitle()).to.equal('Hello2');
- // const crashEvent = emittedOnce(w.webContents, 'render-process-gone');
- // await crashEvent;
- });
- });
- });
- describe('MessageChannelMain', () => {
- it('can be created', () => {
- const { port1, port2 } = new MessageChannelMain();
- expect(port1).not.to.be.null();
- expect(port2).not.to.be.null();
- });
- it('throws an error when an invalid parameter is sent to postMessage', () => {
- const { port1 } = new MessageChannelMain();
- expect(() => {
- const buffer = new ArrayBuffer(10) as any;
- port1.postMessage(null, [buffer]);
- }).to.throw(/Port at index 0 is not a valid port/);
- expect(() => {
- port1.postMessage(null, ['1' as any]);
- }).to.throw(/Port at index 0 is not a valid port/);
- expect(() => {
- port1.postMessage(null, [new Date() as any]);
- }).to.throw(/Port at index 0 is not a valid port/);
- });
- it('throws when postMessage transferables contains the source port', () => {
- const { port1 } = new MessageChannelMain();
- expect(() => {
- port1.postMessage(null, [port1]);
- }).to.throw(/Port at index 0 contains the source port./);
- });
- it('can send messages within the process', async () => {
- const { port1, port2 } = new MessageChannelMain();
- port2.postMessage('hello');
- port1.start();
- const [ev] = await once(port1, 'message');
- expect(ev.data).to.equal('hello');
- });
- it('can pass one end to a WebContents', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- await w.webContents.executeJavaScript(`(${function () {
- const { ipcRenderer } = require('electron');
- ipcRenderer.on('port', ev => {
- const [port] = ev.ports;
- port.onmessage = () => {
- ipcRenderer.send('done');
- };
- });
- }})()`);
- const { port1, port2 } = new MessageChannelMain();
- port1.postMessage('hello');
- w.webContents.postMessage('port', null, [port2]);
- await once(ipcMain, 'done');
- });
- it('can be passed over another channel', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- await w.webContents.executeJavaScript(`(${function () {
- const { ipcRenderer } = require('electron');
- ipcRenderer.on('port', e1 => {
- e1.ports[0].onmessage = e2 => {
- e2.ports[0].onmessage = e3 => {
- ipcRenderer.send('done', e3.data);
- };
- };
- });
- }})()`);
- const { port1, port2 } = new MessageChannelMain();
- const { port1: port3, port2: port4 } = new MessageChannelMain();
- port1.postMessage(null, [port4]);
- port3.postMessage('hello');
- w.webContents.postMessage('port', null, [port2]);
- const [, message] = await once(ipcMain, 'done');
- expect(message).to.equal('hello');
- });
- it('can send messages to a closed port', () => {
- const { port1, port2 } = new MessageChannelMain();
- port2.start();
- port2.on('message', () => { throw new Error('unexpected message received'); });
- port1.close();
- port1.postMessage('hello');
- });
- it('can send messages to a port whose remote end is closed', () => {
- const { port1, port2 } = new MessageChannelMain();
- port2.start();
- port2.on('message', () => { throw new Error('unexpected message received'); });
- port2.close();
- port1.postMessage('hello');
- });
- it('throws when passing null ports', () => {
- const { port1 } = new MessageChannelMain();
- expect(() => {
- port1.postMessage(null, [null] as any);
- }).to.throw(/Port at index 0 is not a valid port/);
- });
- it('throws when passing duplicate ports', () => {
- const { port1 } = new MessageChannelMain();
- const { port1: port3 } = new MessageChannelMain();
- expect(() => {
- port1.postMessage(null, [port3, port3]);
- }).to.throw(/duplicate/);
- });
- it('throws when passing ports that have already been neutered', () => {
- const { port1 } = new MessageChannelMain();
- const { port1: port3 } = new MessageChannelMain();
- port1.postMessage(null, [port3]);
- expect(() => {
- port1.postMessage(null, [port3]);
- }).to.throw(/already neutered/);
- });
- it('throws when passing itself', () => {
- const { port1 } = new MessageChannelMain();
- expect(() => {
- port1.postMessage(null, [port1]);
- }).to.throw(/contains the source port/);
- });
- describe('GC behavior', () => {
- it('is not collected while it could still receive messages', async () => {
- let trigger: Function;
- const promise = new Promise(resolve => { trigger = resolve; });
- const port1 = (() => {
- const { port1, port2 } = new MessageChannelMain();
- port2.on('message', (e) => { trigger(e.data); });
- port2.start();
- return port1;
- })();
- v8Util.requestGarbageCollectionForTesting();
- port1.postMessage('hello');
- expect(await promise).to.equal('hello');
- });
- });
- });
- const generateTests = (title: string, postMessage: (contents: WebContents) => WebContents['postMessage']) => {
- describe(title, () => {
- it('sends a message', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- await w.webContents.executeJavaScript(`(${function () {
- const { ipcRenderer } = require('electron');
- ipcRenderer.on('foo', (_e, msg) => {
- ipcRenderer.send('bar', msg);
- });
- }})()`);
- postMessage(w.webContents)('foo', { some: 'message' });
- const [, msg] = await once(ipcMain, 'bar');
- expect(msg).to.deep.equal({ some: 'message' });
- });
- describe('error handling', () => {
- it('throws on missing channel', async () => {
- const w = new BrowserWindow({ show: false });
- await w.loadURL('about:blank');
- expect(() => {
- (postMessage(w.webContents) as any)();
- }).to.throw(/Insufficient number of arguments/);
- });
- it('throws on invalid channel', async () => {
- const w = new BrowserWindow({ show: false });
- await w.loadURL('about:blank');
- expect(() => {
- postMessage(w.webContents)(null as any, '', []);
- }).to.throw(/Error processing argument at index 0/);
- });
- it('throws on missing message', async () => {
- const w = new BrowserWindow({ show: false });
- await w.loadURL('about:blank');
- expect(() => {
- (postMessage(w.webContents) as any)('channel');
- }).to.throw(/Insufficient number of arguments/);
- });
- it('throws on non-serializable message', async () => {
- const w = new BrowserWindow({ show: false });
- await w.loadURL('about:blank');
- expect(() => {
- postMessage(w.webContents)('channel', w);
- }).to.throw(/An object could not be cloned/);
- });
- it('throws on invalid transferable list', async () => {
- const w = new BrowserWindow({ show: false });
- await w.loadURL('about:blank');
- expect(() => {
- postMessage(w.webContents)('', '', null as any);
- }).to.throw(/Invalid value for transfer/);
- });
- it('throws on transferring non-transferable', async () => {
- const w = new BrowserWindow({ show: false });
- await w.loadURL('about:blank');
- expect(() => {
- (postMessage(w.webContents) as any)('channel', '', [123]);
- }).to.throw(/Invalid value for transfer/);
- });
- it('throws when passing null ports', async () => {
- const w = new BrowserWindow({ show: false });
- await w.loadURL('about:blank');
- expect(() => {
- postMessage(w.webContents)('foo', null, [null] as any);
- }).to.throw(/Invalid value for transfer/);
- });
- it('throws when passing duplicate ports', async () => {
- const w = new BrowserWindow({ show: false });
- await w.loadURL('about:blank');
- const { port1 } = new MessageChannelMain();
- expect(() => {
- postMessage(w.webContents)('foo', null, [port1, port1]);
- }).to.throw(/duplicate/);
- });
- it('throws when passing ports that have already been neutered', async () => {
- const w = new BrowserWindow({ show: false });
- await w.loadURL('about:blank');
- const { port1 } = new MessageChannelMain();
- postMessage(w.webContents)('foo', null, [port1]);
- expect(() => {
- postMessage(w.webContents)('foo', null, [port1]);
- }).to.throw(/already neutered/);
- });
- });
- });
- };
- generateTests('WebContents.postMessage', contents => contents.postMessage.bind(contents));
- generateTests('WebFrameMain.postMessage', contents => contents.mainFrame.postMessage.bind(contents.mainFrame));
- });
- describe('WebContents.ipc', () => {
- afterEach(closeAllWindows);
- it('receives ipc messages sent from the WebContents', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
- const [, num] = await once(w.webContents.ipc, 'test');
- expect(num).to.equal(42);
- });
- it('receives sync-ipc messages sent from the WebContents', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.ipc.on('test', (event, arg) => {
- event.returnValue = arg * 2;
- });
- const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.sendSync(\'test\', 42)');
- expect(result).to.equal(42 * 2);
- });
- it('receives postMessage messages sent from the WebContents, w/ MessagePorts', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.postMessage(\'test\', null, [(new MessageChannel).port1])');
- const [event] = await once(w.webContents.ipc, 'test');
- expect(event.ports.length).to.equal(1);
- });
- it('handles invoke messages sent from the WebContents', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.ipc.handle('test', (_event, arg) => arg * 2);
- const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
- expect(result).to.equal(42 * 2);
- });
- it('cascades to ipcMain', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- let gotFromIpcMain = false;
- const ipcMainReceived = new Promise<void>(resolve => ipcMain.on('test', () => { gotFromIpcMain = true; resolve(); }));
- const ipcReceived = new Promise<boolean>(resolve => w.webContents.ipc.on('test', () => { resolve(gotFromIpcMain); }));
- defer(() => ipcMain.removeAllListeners('test'));
- w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
- // assert that they are delivered in the correct order
- expect(await ipcReceived).to.be.false();
- await ipcMainReceived;
- });
- it('overrides ipcMain handlers', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.ipc.handle('test', (_event, arg) => arg * 2);
- ipcMain.handle('test', () => { throw new Error('should not be called'); });
- defer(() => ipcMain.removeHandler('test'));
- const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
- expect(result).to.equal(42 * 2);
- });
- it('falls back to ipcMain handlers', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- ipcMain.handle('test', (_event, arg) => { return arg * 2; });
- defer(() => ipcMain.removeHandler('test'));
- const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
- expect(result).to.equal(42 * 2);
- });
- it('receives ipcs from child frames', async () => {
- const server = http.createServer((req, res) => {
- res.setHeader('content-type', 'text/html');
- res.end('');
- });
- const { port } = await listen(server);
- defer(() => {
- server.close();
- });
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegrationInSubFrames: true, preload: path.resolve(fixturesPath, 'preload-expose-ipc.js') } });
- // Preloads don't run in about:blank windows, and file:// urls can't be loaded in iframes, so use a blank http page.
- await w.loadURL(`data:text/html,<iframe src="http://localhost:${port}"></iframe>`);
- w.webContents.mainFrame.frames[0].executeJavaScript('ipc.send(\'test\', 42)');
- const [, arg] = await once(w.webContents.ipc, 'test');
- expect(arg).to.equal(42);
- });
- });
- describe('WebFrameMain.ipc', () => {
- afterEach(closeAllWindows);
- it('responds to ipc messages in the main frame', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
- const [, arg] = await once(w.webContents.mainFrame.ipc, 'test');
- expect(arg).to.equal(42);
- });
- it('responds to sync ipc messages in the main frame', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.mainFrame.ipc.on('test', (event, arg) => {
- event.returnValue = arg * 2;
- });
- const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.sendSync(\'test\', 42)');
- expect(result).to.equal(42 * 2);
- });
- it('receives postMessage messages sent from the WebContents, w/ MessagePorts', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.postMessage(\'test\', null, [(new MessageChannel).port1])');
- const [event] = await once(w.webContents.mainFrame.ipc, 'test');
- expect(event.ports.length).to.equal(1);
- });
- it('handles invoke messages sent from the WebContents', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.mainFrame.ipc.handle('test', (_event, arg) => arg * 2);
- const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
- expect(result).to.equal(42 * 2);
- });
- it('cascades to WebContents and ipcMain', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- let gotFromIpcMain = false;
- let gotFromWebContents = false;
- const ipcMainReceived = new Promise<void>(resolve => ipcMain.on('test', () => { gotFromIpcMain = true; resolve(); }));
- const ipcWebContentsReceived = new Promise<boolean>(resolve => w.webContents.ipc.on('test', () => { gotFromWebContents = true; resolve(gotFromIpcMain); }));
- const ipcReceived = new Promise<boolean>(resolve => w.webContents.mainFrame.ipc.on('test', () => { resolve(gotFromWebContents); }));
- defer(() => ipcMain.removeAllListeners('test'));
- w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.send(\'test\', 42)');
- // assert that they are delivered in the correct order
- expect(await ipcReceived).to.be.false();
- expect(await ipcWebContentsReceived).to.be.false();
- await ipcMainReceived;
- });
- it('overrides ipcMain handlers', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.mainFrame.ipc.handle('test', (_event, arg) => arg * 2);
- ipcMain.handle('test', () => { throw new Error('should not be called'); });
- defer(() => ipcMain.removeHandler('test'));
- const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
- expect(result).to.equal(42 * 2);
- });
- it('overrides WebContents handlers', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.ipc.handle('test', () => { throw new Error('should not be called'); });
- w.webContents.mainFrame.ipc.handle('test', (_event, arg) => arg * 2);
- ipcMain.handle('test', () => { throw new Error('should not be called'); });
- defer(() => ipcMain.removeHandler('test'));
- const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
- expect(result).to.equal(42 * 2);
- });
- it('falls back to WebContents handlers', async () => {
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
- w.loadURL('about:blank');
- w.webContents.ipc.handle('test', (_event, arg) => { return arg * 2; });
- const result = await w.webContents.executeJavaScript('require(\'electron\').ipcRenderer.invoke(\'test\', 42)');
- expect(result).to.equal(42 * 2);
- });
- it('receives ipcs from child frames', async () => {
- const server = http.createServer((req, res) => {
- res.setHeader('content-type', 'text/html');
- res.end('');
- });
- const { port } = await listen(server);
- defer(() => {
- server.close();
- });
- const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegrationInSubFrames: true, preload: path.resolve(fixturesPath, 'preload-expose-ipc.js') } });
- // Preloads don't run in about:blank windows, and file:// urls can't be loaded in iframes, so use a blank http page.
- await w.loadURL(`data:text/html,<iframe src="http://localhost:${port}"></iframe>`);
- w.webContents.mainFrame.frames[0].executeJavaScript('ipc.send(\'test\', 42)');
- w.webContents.mainFrame.ipc.on('test', () => { throw new Error('should not be called'); });
- const [, arg] = await once(w.webContents.mainFrame.frames[0].ipc, 'test');
- expect(arg).to.equal(42);
- });
- });
- });
|