зеркало из https://github.com/electron/electron.git
feat: add webFrameMain.send() / webFrameMain.postMessage() (#26807)
This commit is contained in:
Родитель
28b6579538
Коммит
2be3d03630
|
@ -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;
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче