Skip to content

Commit

Permalink
feat: add webFrameMain.send() replacing webContents.sendToFrame()
Browse files Browse the repository at this point in the history
  • Loading branch information
miniak committed Dec 3, 2020
1 parent 45eee46 commit 2f216a6
Show file tree
Hide file tree
Showing 9 changed files with 101 additions and 11 deletions.
14 changes: 14 additions & 0 deletions docs/api/web-frame-main.md
Expand Up @@ -89,6 +89,20 @@ this limitation.

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.

### Instance Properties

#### `frame.url` _Readonly_
Expand Down
10 changes: 9 additions & 1 deletion lib/browser/api/web-frame-main.ts
@@ -1,4 +1,12 @@
const { fromId } = process._linkedBinding('electron_browser_web_frame_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(channel, 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');

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

Expand Down
42 changes: 38 additions & 4 deletions shell/browser/api/electron_api_web_frame_main.cc
Expand Up @@ -13,9 +13,11 @@
#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/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"
Expand All @@ -24,6 +26,7 @@
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/gin_helper/promise.h"
#include "shell/common/node_includes.h"
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"

namespace electron {

Expand Down Expand Up @@ -114,6 +117,27 @@ bool WebFrameMain::Reload(v8::Isolate* isolate) {
return render_frame_->Reload();
}

bool WebFrameMain::Send(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;
}

if (!CheckRenderFrame())
return false;

mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
render_frame_->GetRemoteAssociatedInterfaces()->GetInterface(
&electron_renderer);
electron_renderer->Message(false /* internal */, channel, std::move(message),
0 /* sender_id */);

return true;
}

int WebFrameMain::FrameTreeNodeID(v8::Isolate* isolate) const {
if (!CheckRenderFrame())
return -1;
Expand Down Expand Up @@ -191,6 +215,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) {
Expand Down Expand Up @@ -218,11 +247,14 @@ 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("reload", &WebFrameMain::Reload)
.SetMethod("_send", &WebFrameMain::Send)
.SetProperty("frameTreeNodeId", &WebFrameMain::FrameTreeNodeID)
.SetProperty("name", &WebFrameMain::Name)
.SetProperty("osProcessId", &WebFrameMain::OSProcessID)
Expand All @@ -232,7 +264,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() {
Expand Down Expand Up @@ -266,6 +299,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);
}

Expand Down
13 changes: 10 additions & 3 deletions shell/browser/api/electron_api_web_frame_main.h
Expand Up @@ -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;

Expand All @@ -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);
Expand All @@ -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:
Expand All @@ -67,6 +73,7 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain> {
v8::Local<v8::Promise> ExecuteJavaScript(gin::Arguments* args,
const base::string16& code);
bool Reload(v8::Isolate* isolate);
bool Send(const std::string& channel, v8::Local<v8::Value> args);

int FrameTreeNodeID(v8::Isolate* isolate) const;
std::string Name(v8::Isolate* isolate) const;
Expand Down
2 changes: 0 additions & 2 deletions shell/common/api/electron_api_native_image.h
Expand Up @@ -75,8 +75,6 @@ class NativeImage : public gin::Wrappable<NativeImage> {
const gfx::Size& size);
#endif

static v8::Local<v8::FunctionTemplate> GetConstructor(v8::Isolate* isolate);

// gin::Wrappable
static gin::WrapperInfo kWrapperInfo;
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
Expand Down
20 changes: 19 additions & 1 deletion spec-main/api-web-frame-main-spec.ts
Expand Up @@ -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 } from './events-helpers';
import { AddressInfo } from 'net';
Expand Down Expand Up @@ -160,6 +160,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;
Expand Down
4 changes: 4 additions & 0 deletions typings/internal-ambient.d.ts
Expand Up @@ -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'];
Expand Down
4 changes: 4 additions & 0 deletions typings/internal-electron.d.ts
Expand Up @@ -93,6 +93,10 @@ declare namespace Electron {
allowGuestViewElementDefinition(window: Window, context: any): void;
}

interface WebFrameMain {
_send(channel: string, args: any): boolean;
}

interface WebPreferences {
guestInstanceId?: number;
openerId?: number;
Expand Down

0 comments on commit 2f216a6

Please sign in to comment.