diff --git a/docs/api/structures/ipc-main-event.md b/docs/api/structures/ipc-main-event.md index f222de35b86dc..ae32dc46a6888 100644 --- a/docs/api/structures/ipc-main-event.md +++ b/docs/api/structures/ipc-main-event.md @@ -1,5 +1,6 @@ # IpcMainEvent Object extends `Event` +* `processId` Integer - The internal ID of the renderer process that sent this message * `frameId` Integer - The ID of the renderer frame that sent this message * `returnValue` any - Set this to the value to be returned in a synchronous message * `sender` WebContents - Returns the `webContents` that sent the message diff --git a/docs/api/structures/ipc-main-invoke-event.md b/docs/api/structures/ipc-main-invoke-event.md index 235b219c2d6ea..b765791e8258c 100644 --- a/docs/api/structures/ipc-main-invoke-event.md +++ b/docs/api/structures/ipc-main-invoke-event.md @@ -1,4 +1,5 @@ # IpcMainInvokeEvent Object extends `Event` +* `processId` Integer - The internal ID of the renderer process that sent this message * `frameId` Integer - The ID of the renderer frame that sent this message * `sender` WebContents - Returns the `webContents` that sent the message diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 2f23993e06590..15da8765d81b6 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -1570,7 +1570,7 @@ app.whenReady().then(() => { #### `contents.sendToFrame(frameId, channel, ...args)` -* `frameId` Integer +* `frameId` Integer | [number, number] * `channel` String * `...args` any[] diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 379fc5a4b4438..a31bdac3581ac 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -166,29 +166,29 @@ WebContents.prototype._sendInternalToAll = function (channel, ...args) { return this._send(internal, sendToAll, channel, args); }; -WebContents.prototype.sendToFrame = function (frameId, channel, ...args) { +WebContents.prototype.sendToFrame = function (frame, channel, ...args) { if (typeof channel !== 'string') { throw new Error('Missing required channel argument'); - } else if (typeof frameId !== 'number') { - throw new Error('Missing required frameId argument'); + } else if (!(typeof frame === 'number' || Array.isArray(frame))) { + throw new Error('Missing required frame argument (must be number or array)'); } const internal = false; const sendToAll = false; - return this._sendToFrame(internal, sendToAll, frameId, channel, args); + return this._sendToFrame(internal, sendToAll, frame, channel, args); }; -WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) { +WebContents.prototype._sendToFrameInternal = function (frame, channel, ...args) { if (typeof channel !== 'string') { throw new Error('Missing required channel argument'); - } else if (typeof frameId !== 'number') { - throw new Error('Missing required frameId argument'); + } else if (!(typeof frame === 'number' || Array.isArray(frame))) { + throw new Error('Missing required frame argument (must be number or array)'); } const internal = true; const sendToAll = false; - return this._sendToFrame(internal, sendToAll, frameId, channel, args); + return this._sendToFrame(internal, sendToAll, frame, channel, args); }; // Following methods are mapped to webFrame. @@ -437,8 +437,9 @@ WebContents.prototype.loadFile = function (filePath, options = {}) { }; const addReplyToEvent = (event) => { + const { processId, frameId } = event; event.reply = (...args) => { - event.sender.sendToFrame(event.frameId, ...args); + event.sender.sendToFrame([processId, frameId], ...args); }; }; diff --git a/lib/browser/remote/server.ts b/lib/browser/remote/server.ts index 8fac566f24ffd..e4b265e20a9d5 100644 --- a/lib/browser/remote/server.ts +++ b/lib/browser/remote/server.ts @@ -275,7 +275,7 @@ const fakeConstructor = (constructor: Function, name: string) => }); // Convert array of meta data from renderer into array of real values. -const unwrapArgs = function (sender: electron.WebContents, frameId: number, contextId: string, args: any[]) { +const unwrapArgs = function (sender: electron.WebContents, frameId: [number, number], contextId: string, args: any[]) { const metaToValue = function (meta: MetaTypeFromRenderer): any { switch (meta.type) { case 'nativeimage': @@ -329,7 +329,7 @@ const unwrapArgs = function (sender: electron.WebContents, frameId: number, cont v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location); Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }); - v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender); + v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId[0], frameId[1], contextId, meta.id, sender); rendererFunctions.set(objectId, callIntoRenderer); return callIntoRenderer; } @@ -478,7 +478,7 @@ handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, co }); handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) { - args = unwrapArgs(event.sender, event.frameId, contextId, args); + args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); const constructor = objectsRegistry.get(id); if (constructor == null) { @@ -489,7 +489,7 @@ handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, }); handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) { - args = unwrapArgs(event.sender, event.frameId, contextId, args); + args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); const func = objectsRegistry.get(id); if (func == null) { @@ -506,7 +506,7 @@ handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId }); handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) { - args = unwrapArgs(event.sender, event.frameId, contextId, args); + args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); const object = objectsRegistry.get(id); if (object == null) { @@ -517,7 +517,7 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, cont }); handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) { - args = unwrapArgs(event.sender, event.frameId, contextId, args); + args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); const object = objectsRegistry.get(id); if (object == null) { @@ -534,7 +534,7 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, }); handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) { - args = unwrapArgs(event.sender, event.frameId, contextId, args); + args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); const obj = objectsRegistry.get(id); if (obj == null) { diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index d3e9c2dc285b1..a736f7ffbd405 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -2320,7 +2320,7 @@ bool WebContents::SendIPCMessageWithSender(bool internal, bool WebContents::SendIPCMessageToFrame(bool internal, bool send_to_all, - int32_t frame_id, + v8::Local frame, const std::string& channel, v8::Local args) { blink::CloneableMessage message; @@ -2329,17 +2329,30 @@ bool WebContents::SendIPCMessageToFrame(bool internal, gin::StringToV8(isolate(), "Failed to serialize arguments"))); return false; } - auto frames = web_contents()->GetAllFrames(); - auto iter = std::find_if(frames.begin(), frames.end(), [frame_id](auto* f) { - return f->GetRoutingID() == frame_id; - }); - if (iter == frames.end()) - return false; - if (!(*iter)->IsRenderFrameLive()) + 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 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 electron_renderer; - (*iter)->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer); + rfh->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer); electron_renderer->Message(internal, send_to_all, channel, std::move(message), 0 /* sender_id */); return true; diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h index fd5093a175011..5e7233680fc38 100644 --- a/shell/browser/api/electron_api_web_contents.h +++ b/shell/browser/api/electron_api_web_contents.h @@ -275,7 +275,7 @@ class WebContents : public gin_helper::TrackableObject, bool SendIPCMessageToFrame(bool internal, bool send_to_all, - int32_t frame_id, + v8::Local frame, const std::string& channel, v8::Local args); diff --git a/shell/common/api/remote/remote_callback_freer.cc b/shell/common/api/remote/remote_callback_freer.cc index 40dd4a706d6cb..e61c38ae6cabd 100644 --- a/shell/common/api/remote/remote_callback_freer.cc +++ b/shell/common/api/remote/remote_callback_freer.cc @@ -17,22 +17,25 @@ namespace electron { // static void RemoteCallbackFreer::BindTo(v8::Isolate* isolate, v8::Local target, + int process_id, int frame_id, const std::string& context_id, int object_id, content::WebContents* web_contents) { - new RemoteCallbackFreer(isolate, target, frame_id, context_id, object_id, - web_contents); + new RemoteCallbackFreer(isolate, target, process_id, frame_id, context_id, + object_id, web_contents); } RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate, v8::Local target, + int process_id, int frame_id, const std::string& context_id, int object_id, content::WebContents* web_contents) : ObjectLifeMonitor(isolate, target), content::WebContentsObserver(web_contents), + process_id_(process_id), frame_id_(frame_id), context_id_(context_id), object_id_(object_id) {} @@ -40,16 +43,14 @@ RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate, RemoteCallbackFreer::~RemoteCallbackFreer() = default; void RemoteCallbackFreer::RunDestructor() { - auto frames = web_contents()->GetAllFrames(); - auto iter = std::find_if(frames.begin(), frames.end(), [this](auto* f) { - return f->GetRoutingID() == frame_id_; - }); - - if (iter != frames.end() && (*iter)->IsRenderFrameLive()) { - mojo::AssociatedRemote electron_renderer; - (*iter)->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer); - electron_renderer->DereferenceRemoteJSCallback(context_id_, object_id_); - } + auto* rfh = content::RenderFrameHost::FromID(process_id_, frame_id_); + if (!rfh || !rfh->IsRenderFrameLive() || + content::WebContents::FromRenderFrameHost(rfh) != web_contents()) + return; + + mojo::AssociatedRemote electron_renderer; + rfh->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer); + electron_renderer->DereferenceRemoteJSCallback(context_id_, object_id_); Observe(nullptr); } diff --git a/shell/common/api/remote/remote_callback_freer.h b/shell/common/api/remote/remote_callback_freer.h index 7e9d95a2e06d3..6c43a162808ef 100644 --- a/shell/common/api/remote/remote_callback_freer.h +++ b/shell/common/api/remote/remote_callback_freer.h @@ -17,6 +17,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor, public: static void BindTo(v8::Isolate* isolate, v8::Local target, + int process_id, int frame_id, const std::string& context_id, int object_id, @@ -25,6 +26,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor, protected: RemoteCallbackFreer(v8::Isolate* isolate, v8::Local target, + int process_id, int frame_id, const std::string& context_id, int object_id, @@ -37,6 +39,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor, void RenderViewDeleted(content::RenderViewHost*) override; private: + int process_id_; int frame_id_; std::string context_id_; int object_id_; diff --git a/shell/common/gin_helper/event_emitter.cc b/shell/common/gin_helper/event_emitter.cc index 0737b48802ea1..8a6d36a0cc22e 100644 --- a/shell/common/gin_helper/event_emitter.cc +++ b/shell/common/gin_helper/event_emitter.cc @@ -5,6 +5,7 @@ #include "shell/common/gin_helper/event_emitter.h" #include "content/public/browser/render_frame_host.h" +#include "content/public/browser/render_process_host.h" #include "shell/browser/api/event.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/object_template_builder.h" @@ -82,8 +83,10 @@ v8::Local CreateNativeEvent( Dictionary dict(isolate, event); dict.Set("sender", sender); // Should always set frameId even when callback is null. - if (frame) + if (frame) { dict.Set("frameId", frame->GetRoutingID()); + dict.Set("processId", frame->GetProcess()->GetID()); + } return event; } diff --git a/spec-main/api-ipc-main-spec.ts b/spec-main/api-ipc-main-spec.ts index 429565c555a3c..d9b0813f36b6d 100644 --- a/spec-main/api-ipc-main-spec.ts +++ b/spec-main/api-ipc-main-spec.ts @@ -46,6 +46,8 @@ describe('ipc main module', () => { }); describe('ipcMain.on', () => { + afterEach(() => { ipcMain.removeAllListeners('test-echo'); }); + it('is not used for internals', async () => { const appPath = path.join(fixtures, 'api', 'ipc-main-listeners'); const electronPath = process.execPath; @@ -59,5 +61,27 @@ describe('ipc main module', () => { output = JSON.parse(output); expect(output).to.deep.equal(['error']); }); + + it('can be replied to', async () => { + ipcMain.on('test-echo', (e, arg) => { + e.reply('test-echo', arg); + }); + + const w = new BrowserWindow({ + show: false, + webPreferences: { + nodeIntegration: true + } + }); + w.loadURL('about:blank'); + const v = await w.webContents.executeJavaScript(`new Promise((resolve, reject) => { + const { ipcRenderer } = require('electron') + ipcRenderer.send('test-echo', 'hello') + ipcRenderer.on('test-echo', (e, v) => { + resolve(v) + }) + })`); + expect(v).to.equal('hello'); + }); }); }); diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index 82e9b7241c5fd..3e37122dce783 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -40,7 +40,7 @@ declare namespace NodeJS { requestGarbageCollectionForTesting(): void; createIDWeakMap(): ElectronInternal.KeyWeakMap; createDoubleIDWeakMap(): ElectronInternal.KeyWeakMap<[string, number], V>; - setRemoteCallbackFreer(fn: Function, frameId: number, contextId: String, id: number, sender: any): void + setRemoteCallbackFreer(fn: Function, processId: number, frameId: number, contextId: String, id: number, sender: any): void weaklyTrackValue(value: any): void; clearWeaklyTrackedValues(): void; getWeaklyTrackedValues(): any[]; diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index c46cef4619b2a..a013f767a0349 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -24,6 +24,11 @@ declare namespace Electron { getOwnerBrowserWindow(): Electron.BrowserWindow; } + interface WebPreferences { + guestInstanceId?: number; + openerId?: number; + } + interface SerializedError { message: string; stack?: string,