Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 1 commit into from Jan 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
41 changes: 41 additions & 0 deletions docs/api/web-frame-main.md
Expand Up @@ -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)`
miniak marked this conversation as resolved.
Show resolved Hide resolved

* `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
// ...
})
```
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❤️ this


### Instance Properties

#### `frame.url` _Readonly_
Expand Down
43 changes: 23 additions & 20 deletions lib/browser/api/web-contents.ts
Expand Up @@ -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');
Expand All @@ -134,37 +138,36 @@ 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');
}

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)');

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])');
miniak marked this conversation as resolved.
Show resolved Hide resolved
}
}

return this._sendToFrame(false /* internal */, frame, channel, args);
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.
Expand Down
27 changes: 26 additions & 1 deletion lib/browser/api/web-frame-main.ts
@@ -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
Expand Down
3 changes: 3 additions & 0 deletions lib/browser/init.ts
Expand Up @@ -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');
miniak marked this conversation as resolved.
Show resolved Hide resolved

// Set main startup script of the app.
const mainStartupScript = packageJson.main || 'index.js';

Expand Down
75 changes: 0 additions & 75 deletions shell/browser/api/electron_api_web_contents.cc
Expand Up @@ -1518,39 +1518,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,
Expand Down Expand Up @@ -2685,46 +2652,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 =
Expand Down Expand Up @@ -3628,8 +3555,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)
Expand Down
9 changes: 0 additions & 9 deletions shell/browser/api/electron_api_web_contents.h
Expand Up @@ -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);

Expand Down