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()`.
|
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'
|
#### Event: 'ipc-message-sync'
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -872,6 +874,8 @@ Returns:
|
||||||
|
|
||||||
Emitted when the renderer process sends a synchronous message via `ipcRenderer.sendSync()`.
|
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'
|
#### Event: 'preferred-size-changed'
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
|
@ -1985,6 +1989,35 @@ This corresponds to the [animationPolicy][] accessibility feature in Chromium.
|
||||||
|
|
||||||
### Instance Properties
|
### 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`
|
#### `contents.audioMuted`
|
||||||
|
|
||||||
A `boolean` property that determines whether this page is muted.
|
A `boolean` property that determines whether this page is muted.
|
||||||
|
|
|
@ -140,6 +140,31 @@ ipcRenderer.on('port', (e, msg) => {
|
||||||
|
|
||||||
### Instance Properties
|
### 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_
|
#### `frame.url` _Readonly_
|
||||||
|
|
||||||
A `string` representing the current URL of the frame.
|
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();
|
const ipcMain = new IpcMainImpl();
|
||||||
|
|
||||||
// Do not throw exception when channel name is "error".
|
|
||||||
ipcMain.on('error', () => {});
|
|
||||||
|
|
||||||
export default ipcMain;
|
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 * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
|
||||||
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
||||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
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
|
// session is not used here, the purpose is to make sure session is initialized
|
||||||
// before the webContents module.
|
// before the webContents module.
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
session
|
session
|
||||||
|
|
||||||
|
const webFrameMainBinding = process._linkedBinding('electron_browser_web_frame_main');
|
||||||
|
|
||||||
let nextId = 0;
|
let nextId = 0;
|
||||||
const getNextId = function () {
|
const getNextId = function () {
|
||||||
return ++nextId;
|
return ++nextId;
|
||||||
|
@ -556,6 +559,12 @@ WebContents.prototype._init = function () {
|
||||||
|
|
||||||
this._windowOpenHandler = null;
|
this._windowOpenHandler = null;
|
||||||
|
|
||||||
|
const ipc = new IpcMainImpl();
|
||||||
|
Object.defineProperty(this, 'ipc', {
|
||||||
|
get () { return ipc; },
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
|
||||||
// Dispatch IPC messages to the ipc module.
|
// 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[]) {
|
this.on('-ipc-message' as any, function (this: Electron.WebContents, event: Electron.IpcMainEvent, internal: boolean, channel: string, args: any[]) {
|
||||||
addSenderFrameToEvent(event);
|
addSenderFrameToEvent(event);
|
||||||
|
@ -564,6 +573,9 @@ WebContents.prototype._init = function () {
|
||||||
} else {
|
} else {
|
||||||
addReplyToEvent(event);
|
addReplyToEvent(event);
|
||||||
this.emit('ipc-message', event, channel, ...args);
|
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);
|
ipcMain.emit(channel, event, ...args);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -575,8 +587,10 @@ WebContents.prototype._init = function () {
|
||||||
console.error(`Error occurred in handler for '${channel}':`, error);
|
console.error(`Error occurred in handler for '${channel}':`, error);
|
||||||
event.sendReply({ error: error.toString() });
|
event.sendReply({ error: error.toString() });
|
||||||
};
|
};
|
||||||
const target = internal ? ipcMainInternal : ipcMain;
|
const maybeWebFrame = webFrameMainBinding.fromIdOrNull(event.processId, event.frameId);
|
||||||
if ((target as any)._invokeHandlers.has(channel)) {
|
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);
|
(target as any)._invokeHandlers.get(channel)(event, ...args);
|
||||||
} else {
|
} else {
|
||||||
event._throw(`No handler registered for '${channel}'`);
|
event._throw(`No handler registered for '${channel}'`);
|
||||||
|
@ -590,10 +604,13 @@ WebContents.prototype._init = function () {
|
||||||
ipcMainInternal.emit(channel, event, ...args);
|
ipcMainInternal.emit(channel, event, ...args);
|
||||||
} else {
|
} else {
|
||||||
addReplyToEvent(event);
|
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.`);
|
console.warn(`WebContents #${this.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`);
|
||||||
}
|
}
|
||||||
this.emit('ipc-message-sync', event, channel, ...args);
|
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);
|
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[]) {
|
this.on('-ipc-ports' as any, function (event: Electron.IpcMainEvent, internal: boolean, channel: string, message: any, ports: any[]) {
|
||||||
addSenderFrameToEvent(event);
|
addSenderFrameToEvent(event);
|
||||||
event.ports = ports.map(p => new MessagePortMain(p));
|
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);
|
ipcMain.emit(channel, event, message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,16 @@
|
||||||
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
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');
|
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) {
|
WebFrameMain.prototype.send = function (channel, ...args) {
|
||||||
if (typeof channel !== 'string') {
|
if (typeof channel !== 'string') {
|
||||||
throw new Error('Missing required channel argument');
|
throw new Error('Missing required channel argument');
|
||||||
|
|
|
@ -4,6 +4,13 @@ import { IpcMainInvokeEvent } from 'electron/main';
|
||||||
export class IpcMainImpl extends EventEmitter {
|
export class IpcMainImpl extends EventEmitter {
|
||||||
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();
|
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) => {
|
handle: Electron.IpcMain['handle'] = (method, fn) => {
|
||||||
if (this._invokeHandlers.has(method)) {
|
if (this._invokeHandlers.has(method)) {
|
||||||
throw new Error(`Attempted to register a second handler for '${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';
|
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||||
|
|
||||||
export const ipcMainInternal = new IpcMainImpl() as ElectronInternal.IpcMainInternal;
|
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;
|
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
|
// static
|
||||||
v8::Local<v8::ObjectTemplate> WebFrameMain::FillObjectTemplate(
|
v8::Local<v8::ObjectTemplate> WebFrameMain::FillObjectTemplate(
|
||||||
v8::Isolate* isolate,
|
v8::Isolate* isolate,
|
||||||
|
@ -409,6 +421,20 @@ v8::Local<v8::Value> FromID(gin_helper::ErrorThrower thrower,
|
||||||
return WebFrameMain::From(thrower.isolate(), rfh).ToV8();
|
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,
|
void Initialize(v8::Local<v8::Object> exports,
|
||||||
v8::Local<v8::Value> unused,
|
v8::Local<v8::Value> unused,
|
||||||
v8::Local<v8::Context> context,
|
v8::Local<v8::Context> context,
|
||||||
|
@ -417,6 +443,7 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||||
gin_helper::Dictionary dict(isolate, exports);
|
gin_helper::Dictionary dict(isolate, exports);
|
||||||
dict.Set("WebFrameMain", WebFrameMain::GetConstructor(context));
|
dict.Set("WebFrameMain", WebFrameMain::GetConstructor(context));
|
||||||
dict.SetMethod("fromId", &FromID);
|
dict.SetMethod("fromId", &FromID);
|
||||||
|
dict.SetMethod("fromIdOrNull", &FromIDOrNull);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -44,6 +44,9 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain>,
|
||||||
static gin::Handle<WebFrameMain> From(
|
static gin::Handle<WebFrameMain> From(
|
||||||
v8::Isolate* isolate,
|
v8::Isolate* isolate,
|
||||||
content::RenderFrameHost* render_frame_host);
|
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* FromFrameTreeNodeId(int frame_tree_node_id);
|
||||||
static WebFrameMain* FromRenderFrameHost(
|
static WebFrameMain* FromRenderFrameHost(
|
||||||
content::RenderFrameHost* render_frame_host);
|
content::RenderFrameHost* render_frame_host);
|
||||||
|
|
|
@ -3,8 +3,13 @@ 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 './window-helpers';
|
import { closeAllWindows } from './window-helpers';
|
||||||
import { emittedOnce } from './events-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 v8Util = process._linkedBinding('electron_common_v8_util');
|
||||||
|
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||||
|
|
||||||
describe('ipc module', () => {
|
describe('ipc module', () => {
|
||||||
describe('invoke', () => {
|
describe('invoke', () => {
|
||||||
|
@ -90,7 +95,7 @@ describe('ipc module', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('throws an error when invoking a handler that was removed', async () => {
|
it('throws an error when invoking a handler that was removed', async () => {
|
||||||
ipcMain.handle('test', () => {});
|
ipcMain.handle('test', () => { });
|
||||||
ipcMain.removeHandler('test');
|
ipcMain.removeHandler('test');
|
||||||
const done = new Promise<void>(resolve => ipcMain.once('result', (e, arg) => {
|
const done = new Promise<void>(resolve => ipcMain.once('result', (e, arg) => {
|
||||||
expect(arg.error).to.match(/No handler registered/);
|
expect(arg.error).to.match(/No handler registered/);
|
||||||
|
@ -101,9 +106,9 @@ describe('ipc module', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('forbids multiple handlers', async () => {
|
it('forbids multiple handlers', async () => {
|
||||||
ipcMain.handle('test', () => {});
|
ipcMain.handle('test', () => { });
|
||||||
try {
|
try {
|
||||||
expect(() => { ipcMain.handle('test', () => {}); }).to.throw(/second handler/);
|
expect(() => { ipcMain.handle('test', () => { }); }).to.throw(/second handler/);
|
||||||
} finally {
|
} finally {
|
||||||
ipcMain.removeHandler('test');
|
ipcMain.removeHandler('test');
|
||||||
}
|
}
|
||||||
|
@ -563,4 +568,195 @@ describe('ipc module', () => {
|
||||||
generateTests('WebContents.postMessage', contents => contents.postMessage.bind(contents));
|
generateTests('WebContents.postMessage', contents => contents.postMessage.bind(contents));
|
||||||
generateTests('WebFrameMain.postMessage', contents => contents.mainFrame.postMessage.bind(contents.mainFrame));
|
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'): {
|
_linkedBinding(name: 'electron_browser_web_frame_main'): {
|
||||||
WebFrameMain: typeof Electron.WebFrameMain;
|
WebFrameMain: typeof Electron.WebFrameMain;
|
||||||
fromId(processId: number, routingId: number): 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_crash_reporter'): Electron.CrashReporter;
|
||||||
_linkedBinding(name: 'electron_renderer_ipc'): { ipc: IpcRendererBinding };
|
_linkedBinding(name: 'electron_renderer_ipc'): { ipc: IpcRendererBinding };
|
||||||
|
|
Loading…
Reference in a new issue