feat: add webFrameMain.send() / webFrameMain.postMessage() (#26807)

This commit is contained in:
Milan Burda 2021-01-15 01:00:37 +01:00 коммит произвёл GitHub
Родитель 28b6579538
Коммит 2be3d03630
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 340 добавлений и 209 удалений

Просмотреть файл

@ -101,6 +101,47 @@ Works like `executeJavaScript` but evaluates `scripts` in an isolated context.
Returns `boolean` - Whether the reload was initiated successfully. Only results in `false` when the frame has no history.
#### `frame.send(channel, ...args)`
* `channel` String
* `...args` any[]
Send an asynchronous message to the renderer process via `channel`, along with
arguments. Arguments will be serialized with the [Structured Clone
Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
throw an exception.
The renderer process can handle the message by listening to `channel` with the
[`ipcRenderer`](ipc-renderer.md) module.
#### `frame.postMessage(channel, message, [transfer])`
* `channel` String
* `message` any
* `transfer` MessagePortMain[] (optional)
Send a message to the renderer process, optionally transferring ownership of
zero or more [`MessagePortMain`][] objects.
The transferred `MessagePortMain` objects will be available in the renderer
process by accessing the `ports` property of the emitted event. When they
arrive in the renderer, they will be native DOM `MessagePort` objects.
For example:
```js
// Main process
const { port1, port2 } = new MessageChannelMain()
webContents.mainFrame.postMessage('port', { message: 'hello' }, [port1])
// Renderer process
ipcRenderer.on('port', (e, msg) => {
const [port] = e.ports
// ...
})
```
### Instance Properties
#### `frame.url` _Readonly_

Просмотреть файл

@ -126,6 +126,10 @@ const binding = process._linkedBinding('electron_browser_web_contents');
const printing = process._linkedBinding('electron_browser_printing');
const { WebContents } = binding as { WebContents: { prototype: Electron.WebContents } };
WebContents.prototype.postMessage = function (...args) {
return this.mainFrame.postMessage(...args);
};
WebContents.prototype.send = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
@ -134,13 +138,6 @@ WebContents.prototype.send = function (channel, ...args) {
return this._send(false /* internal */, channel, args);
};
WebContents.prototype.postMessage = function (...args) {
if (Array.isArray(args[2])) {
args[2] = args[2].map(o => o instanceof MessagePortMain ? o._internalPort : o);
}
this._postMessage(...args);
};
WebContents.prototype._sendInternal = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
@ -148,23 +145,29 @@ WebContents.prototype._sendInternal = function (channel, ...args) {
return this._send(true /* internal */, channel, args);
};
WebContents.prototype.sendToFrame = function (frame, channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
} else if (!(typeof frame === 'number' || Array.isArray(frame))) {
throw new Error('Missing required frame argument (must be number or array)');
}
return this._sendToFrame(false /* internal */, frame, channel, args);
function getWebFrame (contents: Electron.WebContents, frame: number | [number, number]) {
if (typeof frame === 'number') {
return webFrameMain.fromId(contents.mainFrame.processId, frame);
} else if (Array.isArray(frame) && frame.length === 2 && frame.every(value => typeof value === 'number')) {
return webFrameMain.fromId(frame[0], frame[1]);
} else {
throw new Error('Missing required frame argument (must be number or [processId, frameId])');
}
}
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
const frame = getWebFrame(this, frameId);
if (!frame) return false;
frame.send(channel, ...args);
return true;
};
WebContents.prototype._sendToFrameInternal = function (frame, channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
} else if (!(typeof frame === 'number' || Array.isArray(frame))) {
throw new Error('Missing required frame argument (must be number or array)');
}
return this._sendToFrame(true /* internal */, frame, channel, args);
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
const frame = getWebFrame(this, frameId);
if (!frame) return false;
frame._sendInternal(channel, ...args);
return true;
};
// Following methods are mapped to webFrame.

Просмотреть файл

@ -1,4 +1,29 @@
const { fromId } = process._linkedBinding('electron_browser_web_frame_main');
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
const { WebFrameMain, fromId } = process._linkedBinding('electron_browser_web_frame_main');
WebFrameMain.prototype.send = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
}
return this._send(false /* internal */, channel, args);
};
WebFrameMain.prototype._sendInternal = function (channel, ...args) {
if (typeof channel !== 'string') {
throw new Error('Missing required channel argument');
}
return this._send(true /* internal */, channel, args);
};
WebFrameMain.prototype.postMessage = function (...args) {
if (Array.isArray(args[2])) {
args[2] = args[2].map(o => o instanceof MessagePortMain ? o._internalPort : o);
}
this._postMessage(...args);
};
export default {
fromId

Просмотреть файл

@ -142,6 +142,9 @@ require('@electron/internal/browser/api/protocol');
// Load web-contents module to ensure it is populated on app ready
require('@electron/internal/browser/api/web-contents');
// Load web-frame-main module to ensure it is populated on app ready
require('@electron/internal/browser/api/web-frame-main');
// Set main startup script of the app.
const mainStartupScript = packageJson.main || 'index.js';

Просмотреть файл

@ -1548,39 +1548,6 @@ void WebContents::ReceivePostMessage(
channel, message_value, std::move(wrapped_ports));
}
void WebContents::PostMessage(const std::string& channel,
v8::Local<v8::Value> message_value,
base::Optional<v8::Local<v8::Value>> transfer) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
blink::TransferableMessage transferable_message;
if (!electron::SerializeV8Value(isolate, message_value,
&transferable_message)) {
// SerializeV8Value sets an exception.
return;
}
std::vector<gin::Handle<MessagePort>> wrapped_ports;
if (transfer) {
if (!gin::ConvertFromV8(isolate, *transfer, &wrapped_ports)) {
isolate->ThrowException(v8::Exception::Error(
gin::StringToV8(isolate, "Invalid value for transfer")));
return;
}
}
bool threw_exception = false;
transferable_message.ports =
MessagePort::DisentanglePorts(isolate, wrapped_ports, &threw_exception);
if (threw_exception)
return;
content::RenderFrameHost* frame_host = web_contents()->GetMainFrame();
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
electron_renderer->ReceivePostMessage(channel,
std::move(transferable_message));
}
void WebContents::MessageSync(
bool internal,
const std::string& channel,
@ -2720,46 +2687,6 @@ bool WebContents::SendIPCMessageWithSender(bool internal,
return true;
}
bool WebContents::SendIPCMessageToFrame(bool internal,
v8::Local<v8::Value> frame,
const std::string& channel,
v8::Local<v8::Value> args) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
blink::CloneableMessage message;
if (!gin::ConvertFromV8(isolate, args, &message)) {
isolate->ThrowException(v8::Exception::Error(
gin::StringToV8(isolate, "Failed to serialize arguments")));
return false;
}
int32_t frame_id;
int32_t process_id;
if (gin::ConvertFromV8(isolate, frame, &frame_id)) {
process_id = web_contents()->GetMainFrame()->GetProcess()->GetID();
} else {
std::vector<int32_t> id_pair;
if (gin::ConvertFromV8(isolate, frame, &id_pair) && id_pair.size() == 2) {
process_id = id_pair[0];
frame_id = id_pair[1];
} else {
isolate->ThrowException(v8::Exception::Error(gin::StringToV8(
isolate,
"frameId must be a number or a pair of [processId, frameId]")));
return false;
}
}
auto* rfh = content::RenderFrameHost::FromID(process_id, frame_id);
if (!rfh || !rfh->IsRenderFrameLive() ||
content::WebContents::FromRenderFrameHost(rfh) != web_contents())
return false;
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
rfh->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
electron_renderer->Message(internal, channel, std::move(message),
0 /* sender_id */);
return true;
}
void WebContents::SendInputEvent(v8::Isolate* isolate,
v8::Local<v8::Value> input_event) {
content::RenderWidgetHostView* view =
@ -3663,8 +3590,6 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
.SetMethod("focus", &WebContents::Focus)
.SetMethod("isFocused", &WebContents::IsFocused)
.SetMethod("_send", &WebContents::SendIPCMessage)
.SetMethod("_postMessage", &WebContents::PostMessage)
.SetMethod("_sendToFrame", &WebContents::SendIPCMessageToFrame)
.SetMethod("sendInputEvent", &WebContents::SendInputEvent)
.SetMethod("beginFrameSubscription", &WebContents::BeginFrameSubscription)
.SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription)

Просмотреть файл

@ -260,15 +260,6 @@ class WebContents : public gin::Wrappable<WebContents>,
blink::CloneableMessage args,
int32_t sender_id = 0);
bool SendIPCMessageToFrame(bool internal,
v8::Local<v8::Value> frame,
const std::string& channel,
v8::Local<v8::Value> args);
void PostMessage(const std::string& channel,
v8::Local<v8::Value> message,
base::Optional<v8::Local<v8::Value>> transfer);
// Send WebInputEvent to the page.
void SendInputEvent(v8::Isolate* isolate, v8::Local<v8::Value> input_event);

Просмотреть файл

@ -13,9 +13,12 @@
#include "base/logging.h"
#include "content/browser/renderer_host/frame_tree_node.h" // nogncheck
#include "content/public/browser/render_frame_host.h"
#include "electron/shell/common/api/api.mojom.h"
#include "gin/object_template_builder.h"
#include "shell/browser/api/message_port.h"
#include "shell/browser/browser.h"
#include "shell/browser/javascript_environment.h"
#include "shell/common/gin_converters/blink_converter.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_converters/gurl_converter.h"
#include "shell/common/gin_converters/value_converter.h"
@ -24,6 +27,8 @@
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h"
#include "shell/common/v8_value_serializer.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
namespace electron {
@ -157,6 +162,63 @@ bool WebFrameMain::Reload(v8::Isolate* isolate) {
return render_frame_->Reload();
}
void WebFrameMain::Send(v8::Isolate* isolate,
bool internal,
const std::string& channel,
v8::Local<v8::Value> args) {
blink::CloneableMessage message;
if (!gin::ConvertFromV8(isolate, args, &message)) {
isolate->ThrowException(v8::Exception::Error(
gin::StringToV8(isolate, "Failed to serialize arguments")));
return;
}
if (!CheckRenderFrame())
return;
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
render_frame_->GetRemoteAssociatedInterfaces()->GetInterface(
&electron_renderer);
electron_renderer->Message(internal, channel, std::move(message),
0 /* sender_id */);
}
void WebFrameMain::PostMessage(v8::Isolate* isolate,
const std::string& channel,
v8::Local<v8::Value> message_value,
base::Optional<v8::Local<v8::Value>> transfer) {
blink::TransferableMessage transferable_message;
if (!electron::SerializeV8Value(isolate, message_value,
&transferable_message)) {
// SerializeV8Value sets an exception.
return;
}
std::vector<gin::Handle<MessagePort>> wrapped_ports;
if (transfer) {
if (!gin::ConvertFromV8(isolate, *transfer, &wrapped_ports)) {
isolate->ThrowException(v8::Exception::Error(
gin::StringToV8(isolate, "Invalid value for transfer")));
return;
}
}
bool threw_exception = false;
transferable_message.ports =
MessagePort::DisentanglePorts(isolate, wrapped_ports, &threw_exception);
if (threw_exception)
return;
if (!CheckRenderFrame())
return;
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
render_frame_->GetRemoteAssociatedInterfaces()->GetInterface(
&electron_renderer);
electron_renderer->ReceivePostMessage(channel,
std::move(transferable_message));
}
int WebFrameMain::FrameTreeNodeID(v8::Isolate* isolate) const {
if (!CheckRenderFrame())
return -1;
@ -234,6 +296,11 @@ std::vector<content::RenderFrameHost*> WebFrameMain::FramesInSubtree(
return frame_hosts;
}
// static
gin::Handle<WebFrameMain> WebFrameMain::New(v8::Isolate* isolate) {
return gin::Handle<WebFrameMain>();
}
// static
gin::Handle<WebFrameMain> WebFrameMain::From(v8::Isolate* isolate,
content::RenderFrameHost* rfh) {
@ -261,13 +328,17 @@ void WebFrameMain::RenderFrameDeleted(content::RenderFrameHost* rfh) {
web_frame->MarkRenderFrameDisposed();
}
gin::ObjectTemplateBuilder WebFrameMain::GetObjectTemplateBuilder(
v8::Isolate* isolate) {
return gin::Wrappable<WebFrameMain>::GetObjectTemplateBuilder(isolate)
// static
v8::Local<v8::ObjectTemplate> WebFrameMain::FillObjectTemplate(
v8::Isolate* isolate,
v8::Local<v8::ObjectTemplate> templ) {
return gin_helper::ObjectTemplateBuilder(isolate, templ)
.SetMethod("executeJavaScript", &WebFrameMain::ExecuteJavaScript)
.SetMethod("executeJavaScriptInIsolatedWorld",
&WebFrameMain::ExecuteJavaScriptInIsolatedWorld)
.SetMethod("reload", &WebFrameMain::Reload)
.SetMethod("_send", &WebFrameMain::Send)
.SetMethod("_postMessage", &WebFrameMain::PostMessage)
.SetProperty("frameTreeNodeId", &WebFrameMain::FrameTreeNodeID)
.SetProperty("name", &WebFrameMain::Name)
.SetProperty("osProcessId", &WebFrameMain::OSProcessID)
@ -277,7 +348,8 @@ gin::ObjectTemplateBuilder WebFrameMain::GetObjectTemplateBuilder(
.SetProperty("top", &WebFrameMain::Top)
.SetProperty("parent", &WebFrameMain::Parent)
.SetProperty("frames", &WebFrameMain::Frames)
.SetProperty("framesInSubtree", &WebFrameMain::FramesInSubtree);
.SetProperty("framesInSubtree", &WebFrameMain::FramesInSubtree)
.Build();
}
const char* WebFrameMain::GetTypeName() {
@ -311,6 +383,7 @@ void Initialize(v8::Local<v8::Object> exports,
void* priv) {
v8::Isolate* isolate = context->GetIsolate();
gin_helper::Dictionary dict(isolate, exports);
dict.Set("WebFrameMain", WebFrameMain::GetConstructor(context));
dict.SetMethod("fromId", &FromID);
}

Просмотреть файл

@ -12,6 +12,7 @@
#include "base/process/process.h"
#include "gin/handle.h"
#include "gin/wrappable.h"
#include "shell/common/gin_helper/constructible.h"
class GURL;
@ -32,8 +33,12 @@ namespace electron {
namespace api {
// Bindings for accessing frames from the main process.
class WebFrameMain : public gin::Wrappable<WebFrameMain> {
class WebFrameMain : public gin::Wrappable<WebFrameMain>,
public gin_helper::Constructible<WebFrameMain> {
public:
// Create a new WebFrameMain and return the V8 wrapper of it.
static gin::Handle<WebFrameMain> New(v8::Isolate* isolate);
static gin::Handle<WebFrameMain> FromID(v8::Isolate* isolate,
int render_process_id,
int render_frame_id);
@ -51,8 +56,9 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain> {
// gin::Wrappable
static gin::WrapperInfo kWrapperInfo;
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
v8::Isolate* isolate) override;
static v8::Local<v8::ObjectTemplate> FillObjectTemplate(
v8::Isolate*,
v8::Local<v8::ObjectTemplate>);
const char* GetTypeName() override;
protected:
@ -71,6 +77,14 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain> {
int world_id,
const base::string16& code);
bool Reload(v8::Isolate* isolate);
void Send(v8::Isolate* isolate,
bool internal,
const std::string& channel,
v8::Local<v8::Value> args);
void PostMessage(v8::Isolate* isolate,
const std::string& channel,
v8::Local<v8::Value> message_value,
base::Optional<v8::Local<v8::Value>> transfer);
int FrameTreeNodeID(v8::Isolate* isolate) const;
std::string Name(v8::Isolate* isolate) const;

Просмотреть файл

@ -75,8 +75,6 @@ class NativeImage : public gin::Wrappable<NativeImage> {
const gfx::Size& size);
#endif
static v8::Local<v8::FunctionTemplate> GetConstructor(v8::Isolate* isolate);
static bool TryConvertNativeImage(v8::Isolate* isolate,
v8::Local<v8::Value> image,
NativeImage** native_image);

Просмотреть файл

@ -1,6 +1,6 @@
import { EventEmitter } from 'events';
import { expect } from 'chai';
import { BrowserWindow, ipcMain, IpcMainInvokeEvent, MessageChannelMain } from 'electron/main';
import { BrowserWindow, ipcMain, IpcMainInvokeEvent, MessageChannelMain, WebContents } from 'electron/main';
import { closeAllWindows } from './window-helpers';
import { emittedOnce } from './events-helpers';
@ -449,97 +449,102 @@ describe('ipc module', () => {
});
});
describe('WebContents.postMessage', () => {
it('sends a message', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
w.loadURL('about:blank');
await w.webContents.executeJavaScript(`(${function () {
const { ipcRenderer } = require('electron');
ipcRenderer.on('foo', (_e, msg) => {
ipcRenderer.send('bar', msg);
const generateTests = (title: string, postMessage: (contents: WebContents) => typeof WebContents.prototype.postMessage) => {
describe(title, () => {
it('sends a message', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
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 emittedOnce(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/);
});
}})()`);
w.webContents.postMessage('foo', { some: 'message' });
const [, msg] = await emittedOnce(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(() => {
(w.webContents.postMessage 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 invalid channel', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
expect(() => {
w.webContents.postMessage(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 missing message', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
expect(() => {
(w.webContents.postMessage 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 non-serializable message', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
expect(() => {
w.webContents.postMessage('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 invalid transferable list', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
expect(() => {
w.webContents.postMessage('', '', 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 on transferring non-transferable', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
expect(() => {
(w.webContents.postMessage 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 null ports', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
expect(() => {
w.webContents.postMessage('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 duplicate ports', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
const { port1 } = new MessageChannelMain();
expect(() => {
w.webContents.postMessage('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();
w.webContents.postMessage('foo', null, [port1]);
expect(() => {
w.webContents.postMessage('foo', null, [port1]);
}).to.throw(/already neutered/);
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));
});
});

Просмотреть файл

@ -60,9 +60,18 @@ describe('renderer nodeIntegrationInSubFrames', () => {
const [event1] = await detailsPromise;
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
event1[0].reply('preload-ping');
const details = await pongPromise;
expect(details[1]).to.equal(event1[0].frameId);
expect(details[1]).to.equal(event1[0].senderFrame.routingId);
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event1[0].frameId);
});
it('should correctly reply to the main frame with using event.senderFrame.send', async () => {
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
const [event1] = await detailsPromise;
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
event1[0].senderFrame.send('preload-ping');
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event1[0].frameId);
});
it('should correctly reply to the sub-frames with using event.reply', async () => {
@ -71,9 +80,18 @@ describe('renderer nodeIntegrationInSubFrames', () => {
const [, event2] = await detailsPromise;
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
event2[0].reply('preload-ping');
const details = await pongPromise;
expect(details[1]).to.equal(event2[0].frameId);
expect(details[1]).to.equal(event2[0].senderFrame.routingId);
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event2[0].frameId);
});
it('should correctly reply to the sub-frames with using event.senderFrame.send', async () => {
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
const [, event2] = await detailsPromise;
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
event2[0].senderFrame.send('preload-ping');
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event2[0].frameId);
});
it('should correctly reply to the nested sub-frames with using event.reply', async () => {
@ -82,9 +100,18 @@ describe('renderer nodeIntegrationInSubFrames', () => {
const [, , event3] = await detailsPromise;
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
event3[0].reply('preload-ping');
const details = await pongPromise;
expect(details[1]).to.equal(event3[0].frameId);
expect(details[1]).to.equal(event3[0].senderFrame.routingId);
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event3[0].frameId);
});
it('should correctly reply to the nested sub-frames with using event.senderFrame.send', async () => {
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 3);
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-with-frame-container${fixtureSuffix}.html`));
const [, , event3] = await detailsPromise;
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
event3[0].senderFrame.send('preload-ping');
const [, frameId] = await pongPromise;
expect(frameId).to.equal(event3[0].frameId);
});
it('should not expose globals in main world', async () => {

Просмотреть файл

@ -2,7 +2,7 @@ import { expect } from 'chai';
import * as http from 'http';
import * as path from 'path';
import * as url from 'url';
import { BrowserWindow, WebFrameMain, webFrameMain } from 'electron/main';
import { BrowserWindow, WebFrameMain, webFrameMain, ipcMain } from 'electron/main';
import { closeAllWindows } from './window-helpers';
import { emittedOnce, emittedNTimes } from './events-helpers';
import { AddressInfo } from 'net';
@ -173,6 +173,24 @@ describe('webFrameMain module', () => {
});
});
describe('WebFrame.send', () => {
it('works', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
preload: path.join(subframesPath, 'preload.js'),
nodeIntegrationInSubFrames: true
}
});
await w.loadURL('about:blank');
const webFrame = w.webContents.mainFrame;
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
webFrame.send('preload-ping');
const [, routingId] = await pongPromise;
expect(routingId).to.equal(webFrame.routingId);
});
});
describe('disposed WebFrames', () => {
let w: BrowserWindow;
let webFrame: WebFrameMain;

4
typings/internal-ambient.d.ts поставляемый
Просмотреть файл

@ -215,6 +215,10 @@ declare namespace NodeJS {
_linkedBinding(name: 'electron_browser_view'): { View: Electron.View };
_linkedBinding(name: 'electron_browser_web_contents_view'): { WebContentsView: typeof Electron.WebContentsView };
_linkedBinding(name: 'electron_browser_web_view_manager'): WebViewManagerBinding;
_linkedBinding(name: 'electron_browser_web_frame_main'): {
WebFrameMain: typeof Electron.WebFrameMain;
fromId(processId: number, routingId: number): Electron.WebFrameMain;
}
_linkedBinding(name: 'electron_renderer_crash_reporter'): Electron.CrashReporter;
_linkedBinding(name: 'electron_renderer_ipc'): { ipc: IpcRendererBinding };
log: NodeJS.WriteStream['write'];

8
typings/internal-electron.d.ts поставляемый
Просмотреть файл

@ -68,9 +68,7 @@ declare namespace Electron {
_callWindowOpenHandler(event: any, url: string, frameName: string, rawFeatures: string): Electron.BrowserWindowConstructorOptions | null;
_setNextChildWebPreferences(prefs: Partial<Electron.BrowserWindowConstructorOptions['webPreferences']> & Pick<Electron.BrowserWindowConstructorOptions, 'backgroundColor'>): void;
_send(internal: boolean, channel: string, args: any): boolean;
_sendToFrame(internal: boolean, frameId: number | [number, number], channel: string, args: any): boolean;
_sendToFrameInternal(frameId: number | [number, number], channel: string, ...args: any[]): boolean;
_postMessage(channel: string, message: any, transfer?: any[]): void;
_sendInternal(channel: string, ...args: any[]): void;
_printToPDF(options: any): Promise<Buffer>;
_print(options: any, callback?: (success: boolean, failureReason: string) => void): void;
@ -93,6 +91,12 @@ declare namespace Electron {
allowGuestViewElementDefinition(window: Window, context: any): void;
}
interface WebFrameMain {
_send(internal: boolean, channel: string, args: any): void;
_sendInternal(channel: string, ...args: any[]): void;
_postMessage(channel: string, message: any, transfer?: any[]): void;
}
interface WebPreferences {
guestInstanceId?: number;
openerId?: number;