diff --git a/docs/api/web-frame-main.md b/docs/api/web-frame-main.md index fa3c00a28ac3e..3c252c14beced 100644 --- a/docs/api/web-frame-main.md +++ b/docs/api/web-frame-main.md @@ -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_ diff --git a/lib/browser/api/web-frame-main.ts b/lib/browser/api/web-frame-main.ts index 43b9ac3d44c11..159571661cf22 100644 --- a/lib/browser/api/web-frame-main.ts +++ b/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 diff --git a/lib/browser/init.ts b/lib/browser/init.ts index 58414b751be61..1b07488c6f088 100644 --- a/lib/browser/init.ts +++ b/lib/browser/init.ts @@ -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'; diff --git a/shell/browser/api/electron_api_web_frame_main.cc b/shell/browser/api/electron_api_web_frame_main.cc index 6a25aab8c77f4..e4f35ee07494f 100644 --- a/shell/browser/api/electron_api_web_frame_main.cc +++ b/shell/browser/api/electron_api_web_frame_main.cc @@ -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" @@ -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 { @@ -114,6 +117,27 @@ bool WebFrameMain::Reload(v8::Isolate* isolate) { return render_frame_->Reload(); } +bool WebFrameMain::Send(const std::string& channel, v8::Local 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 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; @@ -191,6 +215,11 @@ std::vector WebFrameMain::FramesInSubtree( return frame_hosts; } +// static +gin::Handle WebFrameMain::New(v8::Isolate* isolate) { + return gin::Handle(); +} + // static gin::Handle WebFrameMain::From(v8::Isolate* isolate, content::RenderFrameHost* rfh) { @@ -218,11 +247,14 @@ void WebFrameMain::RenderFrameDeleted(content::RenderFrameHost* rfh) { web_frame->MarkRenderFrameDisposed(); } -gin::ObjectTemplateBuilder WebFrameMain::GetObjectTemplateBuilder( - v8::Isolate* isolate) { - return gin::Wrappable::GetObjectTemplateBuilder(isolate) +// static +v8::Local WebFrameMain::FillObjectTemplate( + v8::Isolate* isolate, + v8::Local 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) @@ -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() { @@ -266,6 +299,7 @@ void Initialize(v8::Local exports, void* priv) { v8::Isolate* isolate = context->GetIsolate(); gin_helper::Dictionary dict(isolate, exports); + dict.Set("WebFrameMain", WebFrameMain::GetConstructor(context)); dict.SetMethod("fromId", &FromID); } diff --git a/shell/browser/api/electron_api_web_frame_main.h b/shell/browser/api/electron_api_web_frame_main.h index 5c5801277ccaa..e824fc8c73813 100644 --- a/shell/browser/api/electron_api_web_frame_main.h +++ b/shell/browser/api/electron_api_web_frame_main.h @@ -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 { +class WebFrameMain : public gin::Wrappable, + public gin_helper::Constructible { public: + // Create a new WebFrameMain and return the V8 wrapper of it. + static gin::Handle New(v8::Isolate* isolate); + static gin::Handle FromID(v8::Isolate* isolate, int render_process_id, int render_frame_id); @@ -51,8 +56,9 @@ class WebFrameMain : public gin::Wrappable { // gin::Wrappable static gin::WrapperInfo kWrapperInfo; - gin::ObjectTemplateBuilder GetObjectTemplateBuilder( - v8::Isolate* isolate) override; + static v8::Local FillObjectTemplate( + v8::Isolate*, + v8::Local); const char* GetTypeName() override; protected: @@ -67,6 +73,7 @@ class WebFrameMain : public gin::Wrappable { v8::Local ExecuteJavaScript(gin::Arguments* args, const base::string16& code); bool Reload(v8::Isolate* isolate); + bool Send(const std::string& channel, v8::Local args); int FrameTreeNodeID(v8::Isolate* isolate) const; std::string Name(v8::Isolate* isolate) const; diff --git a/shell/common/api/electron_api_native_image.h b/shell/common/api/electron_api_native_image.h index 5e0ec3e0933c9..3e2cd5790eb01 100644 --- a/shell/common/api/electron_api_native_image.h +++ b/shell/common/api/electron_api_native_image.h @@ -75,8 +75,6 @@ class NativeImage : public gin::Wrappable { const gfx::Size& size); #endif - static v8::Local GetConstructor(v8::Isolate* isolate); - // gin::Wrappable static gin::WrapperInfo kWrapperInfo; gin::ObjectTemplateBuilder GetObjectTemplateBuilder( diff --git a/spec-main/api-web-frame-main-spec.ts b/spec-main/api-web-frame-main-spec.ts index 0507bf0f3c475..5d91cd494cbbf 100644 --- a/spec-main/api-web-frame-main-spec.ts +++ b/spec-main/api-web-frame-main-spec.ts @@ -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'; @@ -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; diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index 512fabdc343c3..c487cb1654e8f 100644 --- a/typings/internal-ambient.d.ts +++ b/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']; diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 86dad40ee6234..3e0d1592fd140 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -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;