feat: add WebContents.ipc (#34959)
This commit is contained in:
parent
bba22ae720
commit
6d859dcd7f
12 changed files with 341 additions and 12 deletions
|
@ -862,6 +862,8 @@ Returns:
|
|||
|
||||
Emitted when the renderer process sends an asynchronous message via `ipcRenderer.send()`.
|
||||
|
||||
See also [`webContents.ipc`](#contentsipc-readonly), which provides an [`IpcMain`](ipc-main.md)-like interface for responding to IPC messages specifically from this WebContents.
|
||||
|
||||
#### Event: 'ipc-message-sync'
|
||||
|
||||
Returns:
|
||||
|
@ -872,6 +874,8 @@ Returns:
|
|||
|
||||
Emitted when the renderer process sends a synchronous message via `ipcRenderer.sendSync()`.
|
||||
|
||||
See also [`webContents.ipc`](#contentsipc-readonly), which provides an [`IpcMain`](ipc-main.md)-like interface for responding to IPC messages specifically from this WebContents.
|
||||
|
||||
#### Event: 'preferred-size-changed'
|
||||
|
||||
Returns:
|
||||
|
@ -1985,6 +1989,35 @@ This corresponds to the [animationPolicy][] accessibility feature in Chromium.
|
|||
|
||||
### Instance Properties
|
||||
|
||||
#### `contents.ipc` _Readonly_
|
||||
|
||||
An [`IpcMain`](ipc-main.md) scoped to just IPC messages sent from this
|
||||
WebContents.
|
||||
|
||||
IPC messages sent with `ipcRenderer.send`, `ipcRenderer.sendSync` or
|
||||
`ipcRenderer.postMessage` will be delivered in the following order:
|
||||
|
||||
1. `contents.on('ipc-message')`
|
||||
2. `contents.mainFrame.on(channel)`
|
||||
3. `contents.ipc.on(channel)`
|
||||
4. `ipcMain.on(channel)`
|
||||
|
||||
Handlers registered with `invoke` will be checked in the following order. The
|
||||
first one that is defined will be called, the rest will be ignored.
|
||||
|
||||
1. `contents.mainFrame.handle(channel)`
|
||||
2. `contents.handle(channel)`
|
||||
3. `ipcMain.handle(channel)`
|
||||
|
||||
A handler or event listener registered on the WebContents will receive IPC
|
||||
messages sent from any frame, including child frames. In most cases, only the
|
||||
main frame can send IPC messages. However, if the `nodeIntegrationInSubFrames`
|
||||
option is enabled, it is possible for child frames to send IPC messages also.
|
||||
In that case, handlers should check the `senderFrame` property of the IPC event
|
||||
to ensure that the message is coming from the expected frame. Alternatively,
|
||||
register handlers on the appropriate frame directly using the
|
||||
[`WebFrameMain.ipc`](web-frame-main.md#frameipc-readonly) interface.
|
||||
|
||||
#### `contents.audioMuted`
|
||||
|
||||
A `boolean` property that determines whether this page is muted.
|
||||
|
|
|
@ -140,6 +140,31 @@ ipcRenderer.on('port', (e, msg) => {
|
|||
|
||||
### Instance Properties
|
||||
|
||||
#### `frame.ipc` _Readonly_
|
||||
|
||||
An [`IpcMain`](ipc-main.md) instance scoped to the frame.
|
||||
|
||||
IPC messages sent with `ipcRenderer.send`, `ipcRenderer.sendSync` or
|
||||
`ipcRenderer.postMessage` will be delivered in the following order:
|
||||
|
||||
1. `contents.on('ipc-message')`
|
||||
2. `contents.mainFrame.on(channel)`
|
||||
3. `contents.ipc.on(channel)`
|
||||
4. `ipcMain.on(channel)`
|
||||
|
||||
Handlers registered with `invoke` will be checked in the following order. The
|
||||
first one that is defined will be called, the rest will be ignored.
|
||||
|
||||
1. `contents.mainFrame.handle(channel)`
|
||||
2. `contents.handle(channel)`
|
||||
3. `ipcMain.handle(channel)`
|
||||
|
||||
In most cases, only the main frame of a WebContents can send or receive IPC
|
||||
messages. However, if the `nodeIntegrationInSubFrames` option is enabled, it is
|
||||
possible for child frames to send and receive IPC messages also. The
|
||||
[`WebContents.ipc`](web-contents.md#contentsipc-readonly) interface may be more
|
||||
convenient when `nodeIntegrationInSubFrames` is not enabled.
|
||||
|
||||
#### `frame.url` _Readonly_
|
||||
|
||||
A `string` representing the current URL of the frame.
|
||||
|
|
|
@ -2,7 +2,4 @@ import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
|||
|
||||
const ipcMain = new IpcMainImpl();
|
||||
|
||||
// Do not throw exception when channel name is "error".
|
||||
ipcMain.on('error', () => {});
|
||||
|
||||
export default ipcMain;
|
||||
|
|
|
@ -9,12 +9,15 @@ import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
|
|||
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
|
||||
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||
|
||||
// session is not used here, the purpose is to make sure session is initialized
|
||||
// before the webContents module.
|
||||
// eslint-disable-next-line
|
||||
session
|
||||
|
||||
const webFrameMainBinding = process._linkedBinding('electron_browser_web_frame_main');
|
||||
|
||||
let nextId = 0;
|
||||
const getNextId = function () {
|
||||
return ++nextId;
|
||||
|
@ -556,6 +559,12 @@ WebContents.prototype._init = function () {
|
|||
|
||||
this._windowOpenHandler = null;
|
||||
|
||||
const ipc = new IpcMainImpl();
|
||||
Object.defineProperty(this, 'ipc', {
|
||||
get () { return ipc; },
|
||||
enumerable: true
|
||||
});
|
||||
|
||||
// Dispatch IPC messages to the ipc module.
|
||||
this.on('-ipc-message' as any, function (this: Electron.WebContents, event: Electron.IpcMainEvent, internal: boolean, channel: string, args: any[]) {
|
||||
addSenderFrameToEvent(event);
|
||||
|
@ -564,6 +573,9 @@ WebContents.prototype._init = function () {
|
|||
} else {
|
||||
addReplyToEvent(event);
|
||||
this.emit('ipc-message', event, channel, ...args);
|
||||
const maybeWebFrame = webFrameMainBinding.fromIdOrNull(event.processId, event.frameId);
|
||||
maybeWebFrame && maybeWebFrame.ipc.emit(channel, event, ...args);
|
||||
ipc.emit(channel, event, ...args);
|
||||
ipcMain.emit(channel, event, ...args);
|
||||
}
|
||||
});
|
||||
|
@ -575,8 +587,10 @@ WebContents.prototype._init = function () {
|
|||
console.error(`Error occurred in handler for '${channel}':`, error);
|
||||
event.sendReply({ error: error.toString() });
|
||||
};
|
||||
const target = internal ? ipcMainInternal : ipcMain;
|
||||
if ((target as any)._invokeHandlers.has(channel)) {
|
||||
const maybeWebFrame = webFrameMainBinding.fromIdOrNull(event.processId, event.frameId);
|
||||
const targets: (ElectronInternal.IpcMainInternal| undefined)[] = internal ? [ipcMainInternal] : [maybeWebFrame && maybeWebFrame.ipc, ipc, ipcMain];
|
||||
const target = targets.find(target => target && (target as any)._invokeHandlers.has(channel));
|
||||
if (target) {
|
||||
(target as any)._invokeHandlers.get(channel)(event, ...args);
|
||||
} else {
|
||||
event._throw(`No handler registered for '${channel}'`);
|
||||
|
@ -590,10 +604,13 @@ WebContents.prototype._init = function () {
|
|||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else {
|
||||
addReplyToEvent(event);
|
||||
if (this.listenerCount('ipc-message-sync') === 0 && ipcMain.listenerCount(channel) === 0) {
|
||||
const maybeWebFrame = webFrameMainBinding.fromIdOrNull(event.processId, event.frameId);
|
||||
if (this.listenerCount('ipc-message-sync') === 0 && ipc.listenerCount(channel) === 0 && ipcMain.listenerCount(channel) === 0 && (!maybeWebFrame || maybeWebFrame.ipc.listenerCount(channel) === 0)) {
|
||||
console.warn(`WebContents #${this.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`);
|
||||
}
|
||||
this.emit('ipc-message-sync', event, channel, ...args);
|
||||
maybeWebFrame && maybeWebFrame.ipc.emit(channel, event, ...args);
|
||||
ipc.emit(channel, event, ...args);
|
||||
ipcMain.emit(channel, event, ...args);
|
||||
}
|
||||
});
|
||||
|
@ -601,6 +618,9 @@ WebContents.prototype._init = function () {
|
|||
this.on('-ipc-ports' as any, function (event: Electron.IpcMainEvent, internal: boolean, channel: string, message: any, ports: any[]) {
|
||||
addSenderFrameToEvent(event);
|
||||
event.ports = ports.map(p => new MessagePortMain(p));
|
||||
ipc.emit(channel, event, message);
|
||||
const maybeWebFrame = webFrameMainBinding.fromIdOrNull(event.processId, event.frameId);
|
||||
maybeWebFrame && maybeWebFrame.ipc.emit(channel, event, message);
|
||||
ipcMain.emit(channel, event, message);
|
||||
});
|
||||
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
||||
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||
|
||||
const { WebFrameMain, fromId } = process._linkedBinding('electron_browser_web_frame_main');
|
||||
|
||||
Object.defineProperty(WebFrameMain.prototype, 'ipc', {
|
||||
get () {
|
||||
const ipc = new IpcMainImpl();
|
||||
Object.defineProperty(this, 'ipc', { value: ipc });
|
||||
return ipc;
|
||||
}
|
||||
});
|
||||
|
||||
WebFrameMain.prototype.send = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
|
|
|
@ -4,6 +4,13 @@ import { IpcMainInvokeEvent } from 'electron/main';
|
|||
export class IpcMainImpl extends EventEmitter {
|
||||
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();
|
||||
|
||||
constructor () {
|
||||
super();
|
||||
|
||||
// Do not throw exception when channel name is "error".
|
||||
this.on('error', () => {});
|
||||
}
|
||||
|
||||
handle: Electron.IpcMain['handle'] = (method, fn) => {
|
||||
if (this._invokeHandlers.has(method)) {
|
||||
throw new Error(`Attempted to register a second handler for '${method}'`);
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||
|
||||
export const ipcMainInternal = new IpcMainImpl() as ElectronInternal.IpcMainInternal;
|
||||
|
||||
// Do not throw exception when channel name is "error".
|
||||
ipcMainInternal.on('error', () => {});
|
||||
|
|
|
@ -362,6 +362,18 @@ gin::Handle<WebFrameMain> WebFrameMain::From(v8::Isolate* isolate,
|
|||
return handle;
|
||||
}
|
||||
|
||||
// static
|
||||
gin::Handle<WebFrameMain> WebFrameMain::FromOrNull(
|
||||
v8::Isolate* isolate,
|
||||
content::RenderFrameHost* rfh) {
|
||||
if (rfh == nullptr)
|
||||
return gin::Handle<WebFrameMain>();
|
||||
auto* web_frame = FromRenderFrameHost(rfh);
|
||||
if (web_frame)
|
||||
return gin::CreateHandle(isolate, web_frame);
|
||||
return gin::Handle<WebFrameMain>();
|
||||
}
|
||||
|
||||
// static
|
||||
v8::Local<v8::ObjectTemplate> WebFrameMain::FillObjectTemplate(
|
||||
v8::Isolate* isolate,
|
||||
|
@ -409,6 +421,20 @@ v8::Local<v8::Value> FromID(gin_helper::ErrorThrower thrower,
|
|||
return WebFrameMain::From(thrower.isolate(), rfh).ToV8();
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> FromIDOrNull(gin_helper::ErrorThrower thrower,
|
||||
int render_process_id,
|
||||
int render_frame_id) {
|
||||
if (!electron::Browser::Get()->is_ready()) {
|
||||
thrower.ThrowError("WebFrameMain is available only after app ready");
|
||||
return v8::Null(thrower.isolate());
|
||||
}
|
||||
|
||||
auto* rfh =
|
||||
content::RenderFrameHost::FromID(render_process_id, render_frame_id);
|
||||
|
||||
return WebFrameMain::FromOrNull(thrower.isolate(), rfh).ToV8();
|
||||
}
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
|
@ -417,6 +443,7 @@ void Initialize(v8::Local<v8::Object> exports,
|
|||
gin_helper::Dictionary dict(isolate, exports);
|
||||
dict.Set("WebFrameMain", WebFrameMain::GetConstructor(context));
|
||||
dict.SetMethod("fromId", &FromID);
|
||||
dict.SetMethod("fromIdOrNull", &FromIDOrNull);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -44,6 +44,9 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain>,
|
|||
static gin::Handle<WebFrameMain> From(
|
||||
v8::Isolate* isolate,
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
static gin::Handle<WebFrameMain> FromOrNull(
|
||||
v8::Isolate* isolate,
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
static WebFrameMain* FromFrameTreeNodeId(int frame_tree_node_id);
|
||||
static WebFrameMain* FromRenderFrameHost(
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
|
|
|
@ -3,8 +3,13 @@ import { expect } from 'chai';
|
|||
import { BrowserWindow, ipcMain, IpcMainInvokeEvent, MessageChannelMain, WebContents } from 'electron/main';
|
||||
import { closeAllWindows } from './window-helpers';
|
||||
import { emittedOnce } from './events-helpers';
|
||||
import { defer } from './spec-helpers';
|
||||
import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
import { AddressInfo } from 'net';
|
||||
|
||||
const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||
|
||||
describe('ipc module', () => {
|
||||
describe('invoke', () => {
|
||||
|
@ -90,7 +95,7 @@ describe('ipc module', () => {
|
|||
});
|
||||
|
||||
it('throws an error when invoking a handler that was removed', async () => {
|
||||
ipcMain.handle('test', () => {});
|
||||
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/);
|
||||
|
@ -101,9 +106,9 @@ describe('ipc module', () => {
|
|||
});
|
||||
|
||||
it('forbids multiple handlers', async () => {
|
||||
ipcMain.handle('test', () => {});
|
||||
ipcMain.handle('test', () => { });
|
||||
try {
|
||||
expect(() => { ipcMain.handle('test', () => {}); }).to.throw(/second handler/);
|
||||
expect(() => { ipcMain.handle('test', () => { }); }).to.throw(/second handler/);
|
||||
} finally {
|
||||
ipcMain.removeHandler('test');
|
||||
}
|
||||
|
@ -563,4 +568,195 @@ describe('ipc module', () => {
|
|||
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 emittedOnce(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 emittedOnce(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('');
|
||||
});
|
||||
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
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 emittedOnce(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 emittedOnce(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 emittedOnce(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('');
|
||||
});
|
||||
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
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 emittedOnce(w.webContents.mainFrame.frames[0].ipc, 'test');
|
||||
expect(arg).to.equal(42);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
14
spec-main/fixtures/preload-expose-ipc.js
Normal file
14
spec-main/fixtures/preload-expose-ipc.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
// NOTE: Never do this in an actual app! Very insecure!
|
||||
contextBridge.exposeInMainWorld('ipc', {
|
||||
send (...args) {
|
||||
return ipcRenderer.send(...args);
|
||||
},
|
||||
sendSync (...args) {
|
||||
return ipcRenderer.sendSync(...args);
|
||||
},
|
||||
invoke (...args) {
|
||||
return ipcRenderer.invoke(...args);
|
||||
}
|
||||
});
|
1
typings/internal-ambient.d.ts
vendored
1
typings/internal-ambient.d.ts
vendored
|
@ -237,6 +237,7 @@ declare namespace NodeJS {
|
|||
_linkedBinding(name: 'electron_browser_web_frame_main'): {
|
||||
WebFrameMain: typeof Electron.WebFrameMain;
|
||||
fromId(processId: number, routingId: number): Electron.WebFrameMain;
|
||||
fromIdOrNull(processId: number, routingId: number): Electron.WebFrameMain;
|
||||
}
|
||||
_linkedBinding(name: 'electron_renderer_crash_reporter'): Electron.CrashReporter;
|
||||
_linkedBinding(name: 'electron_renderer_ipc'): { ipc: IpcRendererBinding };
|
||||
|
|
Loading…
Reference in a new issue