From c0422d7cc97da03815963bf55384a8b68294999c Mon Sep 17 00:00:00 2001 From: Sam Maddock Date: Mon, 17 Feb 2025 16:36:28 -0500 Subject: [PATCH] refactor: dispatch IPC messages from Session (#45452) * refactor: dispatch IPC messages from Session * refactor: move MessageHost to Session --- lib/browser/api/session.ts | 2 +- lib/browser/api/web-contents.ts | 111 +-------------- lib/browser/guest-view-manager.ts | 2 +- lib/browser/ipc-dispatch.ts | 86 ++++++++++-- .../browser/api/electron_api_web_contents.cc | 91 ------------- shell/browser/api/electron_api_web_contents.h | 46 ------- shell/browser/api/ipc_dispatcher.h | 37 +++-- .../browser/electron_api_ipc_handler_impl.cc | 126 ++++++++++++++---- shell/browser/electron_api_ipc_handler_impl.h | 8 ++ .../electron_api_sw_ipc_handler_impl.cc | 74 +++++----- .../electron_api_sw_ipc_handler_impl.h | 8 +- 11 files changed, 250 insertions(+), 341 deletions(-) diff --git a/lib/browser/api/session.ts b/lib/browser/api/session.ts index 7aecefbd230f..2b2c6da886ab 100644 --- a/lib/browser/api/session.ts +++ b/lib/browser/api/session.ts @@ -23,7 +23,7 @@ systemPickerVideoSource.name = ''; Object.freeze(systemPickerVideoSource); Session.prototype._init = function () { - addIpcDispatchListeners(this, this.serviceWorkers); + addIpcDispatchListeners(this); }; Session.prototype.fetch = function (input: RequestInfo, init?: RequestInit) { diff --git a/lib/browser/api/web-contents.ts b/lib/browser/api/web-contents.ts index caa9d1873289..a81dd7d5ae7e 100644 --- a/lib/browser/api/web-contents.ts +++ b/lib/browser/api/web-contents.ts @@ -1,13 +1,11 @@ import { openGuestWindow, makeWebPreferences, parseContentTypeFormat } from '@electron/internal/browser/guest-window-manager'; import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl'; -import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'; import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils'; -import { MessagePortMain } from '@electron/internal/browser/message-port-main'; import { parseFeatures } from '@electron/internal/browser/parse-features-string'; import * as deprecate from '@electron/internal/common/deprecate'; import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages'; -import { app, ipcMain, session, webFrameMain, dialog } from 'electron/main'; +import { app, session, webFrameMain, dialog } from 'electron/main'; import type { BrowserWindowConstructorOptions, MessageBoxOptions, NavigationEntry } from 'electron/main'; import * as path from 'path'; @@ -18,8 +16,6 @@ import * as url from 'url'; // eslint-disable-next-line no-unused-expressions session; -const webFrameMainBinding = process._linkedBinding('electron_browser_web_frame_main'); - let nextId = 0; const getNextId = function () { return ++nextId; @@ -480,24 +476,6 @@ WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event, } }; -const addReplyToEvent = (event: Electron.IpcMainEvent) => { - const { processId, frameId } = event; - event.reply = (channel: string, ...args: any[]) => { - event.sender.sendToFrame([processId, frameId], channel, ...args); - }; -}; - -const addSenderToEvent = (event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent, sender: Electron.WebContents) => { - event.sender = sender; -}; - -const addReturnValueToEvent = (event: Electron.IpcMainEvent) => { - Object.defineProperty(event, 'returnValue', { - set: (value) => event._replyChannel.sendReply(value), - get: () => {} - }); -}; - const commandLine = process._linkedBinding('electron_common_command_line'); const environment = process._linkedBinding('electron_common_environment'); @@ -577,28 +555,6 @@ WebContents.prototype._init = function () { enumerable: true }); - /** - * Cached IPC emitters sorted by dispatch priority. - * Caching is used to avoid frequent array allocations. - * - * 0: WebFrameMain ipc - * 1: WebContents ipc - * 2: ipcMain - */ - const cachedIpcEmitters: (ElectronInternal.IpcMainInternal | undefined)[] = [undefined, ipc, ipcMain]; - - // Get list of relevant IPC emitters for dispatch. - const getIpcEmittersForEvent = (event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent): (ElectronInternal.IpcMainInternal | undefined)[] => { - // Lookup by FrameTreeNode ID to ensure IPCs received after a frame swap are - // always received. This occurs when a RenderFrame sends an IPC while it's - // unloading and its internal state is pending deletion. - const { frameTreeNodeId } = event; - const webFrameByFtn = frameTreeNodeId ? webFrameMainBinding._fromFtnIdIfExists(frameTreeNodeId) : undefined; - cachedIpcEmitters[0] = webFrameByFtn?.ipc; - - return cachedIpcEmitters; - }; - // Add navigationHistory property which handles session history, // maintaining a list of navigation entries for backward and forward navigation. Object.defineProperty(this, 'navigationHistory', { @@ -641,71 +597,6 @@ WebContents.prototype._init = function () { enumerable: true }); - // Dispatch IPC messages to the ipc module. - this.on('-ipc-message', function (this: Electron.WebContents, event, internal, channel, args) { - addSenderToEvent(event, this); - if (internal) { - ipcMainInternal.emit(channel, event, ...args); - } else { - addReplyToEvent(event); - this.emit('ipc-message', event, channel, ...args); - for (const ipcEmitter of getIpcEmittersForEvent(event)) { - ipcEmitter?.emit(channel, event, ...args); - } - } - }); - - this.on('-ipc-invoke', async function (this: Electron.WebContents, event, internal, channel, args) { - addSenderToEvent(event, this); - const replyWithResult = (result: any) => event._replyChannel.sendReply({ result }); - const replyWithError = (error: Error) => { - console.error(`Error occurred in handler for '${channel}':`, error); - event._replyChannel.sendReply({ error: error.toString() }); - }; - const targets: (ElectronInternal.IpcMainInternal | undefined)[] = internal ? [ipcMainInternal] : getIpcEmittersForEvent(event); - const target = targets.find(target => (target as any)?._invokeHandlers.has(channel)); - if (target) { - const handler = (target as any)._invokeHandlers.get(channel); - try { - replyWithResult(await Promise.resolve(handler(event, ...args))); - } catch (err) { - replyWithError(err as Error); - } - } else { - replyWithError(new Error(`No handler registered for '${channel}'`)); - } - }); - - this.on('-ipc-message-sync', function (this: Electron.WebContents, event, internal, channel, args) { - addSenderToEvent(event, this); - addReturnValueToEvent(event); - if (internal) { - ipcMainInternal.emit(channel, event, ...args); - } else { - addReplyToEvent(event); - const ipcEmitters = getIpcEmittersForEvent(event); - if ( - this.listenerCount('ipc-message-sync') === 0 && - ipcEmitters.every(emitter => !emitter || emitter.listenerCount(channel) === 0) - ) { - console.warn(`WebContents #${this.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`); - } - this.emit('ipc-message-sync', event, channel, ...args); - for (const ipcEmitter of ipcEmitters) { - ipcEmitter?.emit(channel, event, ...args); - } - } - }); - - this.on('-ipc-ports', function (this: Electron.WebContents, event: Electron.IpcMainEvent, internal: boolean, channel: string, message: any, ports: any[]) { - addSenderToEvent(event, this); - event.ports = ports.map(p => new MessagePortMain(p)); - const ipcEmitters = getIpcEmittersForEvent(event); - for (const ipcEmitter of ipcEmitters) { - ipcEmitter?.emit(channel, event, message); - } - }); - this.on('render-process-gone', (event, details) => { app.emit('render-process-gone', event, this, details); diff --git a/lib/browser/guest-view-manager.ts b/lib/browser/guest-view-manager.ts index edceacaf5fb2..2ef1ed201d08 100644 --- a/lib/browser/guest-view-manager.ts +++ b/lib/browser/guest-view-manager.ts @@ -155,7 +155,7 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n } // Dispatch guest's IPC messages to embedder. - guest.on('ipc-message-host' as any, function (event: Electron.IpcMainEvent, channel: string, args: any[]) { + guest.on('-ipc-message-host' as any, function (event: Electron.IpcMainEvent, channel: string, args: any[]) { sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, 'ipc-message', { frameId: [event.processId, event.frameId], channel, diff --git a/lib/browser/ipc-dispatch.ts b/lib/browser/ipc-dispatch.ts index d801d72d091d..6736d1c19faa 100644 --- a/lib/browser/ipc-dispatch.ts +++ b/lib/browser/ipc-dispatch.ts @@ -2,8 +2,17 @@ import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'; import { MessagePortMain } from '@electron/internal/browser/message-port-main'; import type { ServiceWorkerMain } from 'electron/main'; +import { ipcMain } from 'electron/main'; const v8Util = process._linkedBinding('electron_common_v8_util'); +const webFrameMainBinding = process._linkedBinding('electron_browser_web_frame_main'); + +const addReplyToEvent = (event: Electron.IpcMainEvent) => { + const { processId, frameId } = event; + event.reply = (channel: string, ...args: any[]) => { + event.sender.sendToFrame([processId, frameId], channel, ...args); + }; +}; const addReturnValueToEvent = (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent) => { Object.defineProperty(event, 'returnValue', { @@ -12,26 +21,52 @@ const addReturnValueToEvent = (event: Electron.IpcMainEvent | Electron.IpcMainSe }); }; +const getServiceWorkerFromEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent): ServiceWorkerMain | undefined => { + return event.session.serviceWorkers._getWorkerFromVersionIDIfExists(event.versionId); +}; +const addServiceWorkerPropertyToEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent) => { + Object.defineProperty(event, 'serviceWorker', { + get: () => event.session.serviceWorkers.getWorkerFromVersionID(event.versionId) + }); +}; + +/** + * Cached IPC emitters sorted by dispatch priority. + * Caching is used to avoid frequent array allocations. + */ +const cachedIpcEmitters: (ElectronInternal.IpcMainInternal | undefined)[] = [ + undefined, // WebFrameMain ipc + undefined, // WebContents ipc + ipcMain +]; + +// Get list of relevant IPC emitters for dispatch. +const getIpcEmittersForFrameEvent = (event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent): (ElectronInternal.IpcMainInternal | undefined)[] => { + // Lookup by FrameTreeNode ID to ensure IPCs received after a frame swap are + // always received. This occurs when a RenderFrame sends an IPC while it's + // unloading and its internal state is pending deletion. + const { frameTreeNodeId } = event; + const webFrameByFtn = frameTreeNodeId ? webFrameMainBinding._fromFtnIdIfExists(frameTreeNodeId) : undefined; + cachedIpcEmitters[0] = webFrameByFtn?.ipc; + cachedIpcEmitters[1] = event.sender.ipc; + return cachedIpcEmitters; +}; + /** * Listens for IPC dispatch events on `api`. - * - * NOTE: Currently this only supports dispatching IPCs for ServiceWorkerMain. */ -export function addIpcDispatchListeners (api: NodeJS.EventEmitter, serviceWorkers: Electron.ServiceWorkers) { - const getServiceWorkerFromEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent): ServiceWorkerMain | undefined => { - return serviceWorkers._getWorkerFromVersionIDIfExists(event.versionId); - }; - const addServiceWorkerPropertyToEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent) => { - Object.defineProperty(event, 'serviceWorker', { - get: () => serviceWorkers.getWorkerFromVersionID(event.versionId) - }); - }; - +export function addIpcDispatchListeners (api: NodeJS.EventEmitter) { api.on('-ipc-message' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) { const internal = v8Util.getHiddenValue(event, 'internal'); if (internal) { ipcMainInternal.emit(channel, event, ...args); + } else if (event.type === 'frame') { + addReplyToEvent(event); + event.sender.emit('ipc-message', event, channel, ...args); + for (const ipcEmitter of getIpcEmittersForFrameEvent(event)) { + ipcEmitter?.emit(channel, event, ...args); + } } else if (event.type === 'service-worker') { addServiceWorkerPropertyToEvent(event); getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args); @@ -51,6 +86,8 @@ export function addIpcDispatchListeners (api: NodeJS.EventEmitter, serviceWorker if (internal) { targets.push(ipcMainInternal); + } else if (event.type === 'frame') { + targets.push(...getIpcEmittersForFrameEvent(event)); } else if (event.type === 'service-worker') { addServiceWorkerPropertyToEvent(event); const workerIpc = getServiceWorkerFromEvent(event)?.ipc; @@ -75,15 +112,38 @@ export function addIpcDispatchListeners (api: NodeJS.EventEmitter, serviceWorker addReturnValueToEvent(event); if (internal) { ipcMainInternal.emit(channel, event, ...args); + } else if (event.type === 'frame') { + addReplyToEvent(event); + const webContents = event.sender; + const ipcEmitters = getIpcEmittersForFrameEvent(event); + if ( + webContents.listenerCount('ipc-message-sync') === 0 && + ipcEmitters.every(emitter => !emitter || emitter.listenerCount(channel) === 0) + ) { + console.warn(`WebContents #${webContents.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`); + } + webContents.emit('ipc-message-sync', event, channel, ...args); + for (const ipcEmitter of ipcEmitters) { + ipcEmitter?.emit(channel, event, ...args); + } } else if (event.type === 'service-worker') { addServiceWorkerPropertyToEvent(event); getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args); } } as any); + api.on('-ipc-message-host', function (event: Electron.IpcMainEvent, channel: string, args: any[]) { + event.sender.emit('-ipc-message-host', event, channel, args); + }); + api.on('-ipc-ports' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, message: any, ports: any[]) { event.ports = ports.map(p => new MessagePortMain(p)); - if (event.type === 'service-worker') { + if (event.type === 'frame') { + const ipcEmitters = getIpcEmittersForFrameEvent(event); + for (const ipcEmitter of ipcEmitters) { + ipcEmitter?.emit(channel, event, message); + } + } if (event.type === 'service-worker') { addServiceWorkerPropertyToEvent(event); getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, message); } diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index 15f07ab865cb..871dcbdf4466 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -2005,30 +2005,6 @@ bool WebContents::EmitNavigationEvent( return event->GetDefaultPrevented(); } -void WebContents::Message(bool internal, - const std::string& channel, - blink::CloneableMessage arguments, - content::RenderFrameHost* render_frame_host) { - TRACE_EVENT1("electron", "WebContents::Message", "channel", channel); - // webContents.emit('-ipc-message', new Event(), internal, channel, - // arguments); - EmitWithSender("-ipc-message", render_frame_host, - electron::mojom::ElectronApiIPC::InvokeCallback(), internal, - channel, std::move(arguments)); -} - -void WebContents::Invoke( - bool internal, - const std::string& channel, - blink::CloneableMessage arguments, - electron::mojom::ElectronApiIPC::InvokeCallback callback, - content::RenderFrameHost* render_frame_host) { - TRACE_EVENT1("electron", "WebContents::Invoke", "channel", channel); - // webContents.emit('-ipc-invoke', new Event(), internal, channel, arguments); - EmitWithSender("-ipc-invoke", render_frame_host, std::move(callback), - internal, channel, std::move(arguments)); -} - void WebContents::OnFirstNonEmptyLayout( content::RenderFrameHost* render_frame_host) { if (render_frame_host == web_contents()->GetPrimaryMainFrame()) { @@ -2036,73 +2012,6 @@ void WebContents::OnFirstNonEmptyLayout( } } -gin::Handle WebContents::MakeEventWithSender( - v8::Isolate* isolate, - content::RenderFrameHost* frame, - electron::mojom::ElectronApiIPC::InvokeCallback callback) { - v8::Local wrapper; - if (!GetWrapper(isolate).ToLocal(&wrapper)) { - if (callback) { - // We must always invoke the callback if present. - gin_helper::internal::ReplyChannel::Create(isolate, std::move(callback)) - ->SendError("WebContents was destroyed"); - } - return {}; - } - gin::Handle event = - gin_helper::internal::Event::New(isolate); - gin_helper::Dictionary dict(isolate, event.ToV8().As()); - dict.Set("type", "frame"); - if (callback) - dict.Set("_replyChannel", gin_helper::internal::ReplyChannel::Create( - isolate, std::move(callback))); - if (frame) { - dict.SetGetter("senderFrame", frame); - dict.Set("frameId", frame->GetRoutingID()); - dict.Set("processId", frame->GetProcess()->GetID().GetUnsafeValue()); - dict.Set("frameTreeNodeId", frame->GetFrameTreeNodeId()); - } - return event; -} - -void WebContents::ReceivePostMessage( - const std::string& channel, - blink::TransferableMessage message, - content::RenderFrameHost* render_frame_host) { - v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - auto wrapped_ports = - MessagePort::EntanglePorts(isolate, std::move(message.ports)); - v8::Local message_value = - electron::DeserializeV8Value(isolate, message); - EmitWithSender("-ipc-ports", render_frame_host, - electron::mojom::ElectronApiIPC::InvokeCallback(), false, - channel, message_value, std::move(wrapped_ports)); -} - -void WebContents::MessageSync( - bool internal, - const std::string& channel, - blink::CloneableMessage arguments, - electron::mojom::ElectronApiIPC::MessageSyncCallback callback, - content::RenderFrameHost* render_frame_host) { - TRACE_EVENT1("electron", "WebContents::MessageSync", "channel", channel); - // webContents.emit('-ipc-message-sync', new Event(sender, message), internal, - // channel, arguments); - EmitWithSender("-ipc-message-sync", render_frame_host, std::move(callback), - internal, channel, std::move(arguments)); -} - -void WebContents::MessageHost(const std::string& channel, - blink::CloneableMessage arguments, - content::RenderFrameHost* render_frame_host) { - TRACE_EVENT1("electron", "WebContents::MessageHost", "channel", channel); - // webContents.emit('ipc-message-host', new Event(), channel, args); - EmitWithSender("ipc-message-host", render_frame_host, - electron::mojom::ElectronApiIPC::InvokeCallback(), channel, - std::move(arguments)); -} - void WebContents::DraggableRegionsChanged( const std::vector& regions, content::WebContents* contents) { diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h index 1ae4e56e5a8f..58a6de68d24e 100644 --- a/shell/browser/api/electron_api_web_contents.h +++ b/shell/browser/api/electron_api_web_contents.h @@ -393,29 +393,6 @@ class WebContents final : public ExclusiveAccessContext, bool EmitNavigationEvent(const std::string& event, content::NavigationHandle* navigation_handle); - // this.emit(name, new Event(sender, message), args...); - template - bool EmitWithSender(const std::string_view name, - content::RenderFrameHost* frame, - electron::mojom::ElectronApiIPC::InvokeCallback callback, - Args&&... args) { - DCHECK_CURRENTLY_ON(content::BrowserThread::UI); - v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - - gin::Handle event = - MakeEventWithSender(isolate, frame, std::move(callback)); - if (event.IsEmpty()) - return false; - EmitWithoutEvent(name, event, std::forward(args)...); - return event->GetDefaultPrevented(); - } - - gin::Handle MakeEventWithSender( - v8::Isolate* isolate, - content::RenderFrameHost* frame, - electron::mojom::ElectronApiIPC::InvokeCallback callback); - WebContents* embedder() { return embedder_; } #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) @@ -448,29 +425,6 @@ class WebContents final : public ExclusiveAccessContext, fullscreen_frame_ = rfh; } - // mojom::ElectronApiIPC - void Message(bool internal, - const std::string& channel, - blink::CloneableMessage arguments, - content::RenderFrameHost* render_frame_host); - void Invoke(bool internal, - const std::string& channel, - blink::CloneableMessage arguments, - electron::mojom::ElectronApiIPC::InvokeCallback callback, - content::RenderFrameHost* render_frame_host); - void ReceivePostMessage(const std::string& channel, - blink::TransferableMessage message, - content::RenderFrameHost* render_frame_host); - void MessageSync( - bool internal, - const std::string& channel, - blink::CloneableMessage arguments, - electron::mojom::ElectronApiIPC::MessageSyncCallback callback, - content::RenderFrameHost* render_frame_host); - void MessageHost(const std::string& channel, - blink::CloneableMessage arguments, - content::RenderFrameHost* render_frame_host); - // mojom::ElectronWebContentsUtility void OnFirstNonEmptyLayout(content::RenderFrameHost* render_frame_host); void SetTemporaryZoomLevel(double level); diff --git a/shell/browser/api/ipc_dispatcher.h b/shell/browser/api/ipc_dispatcher.h index 37c3d166d70c..b4b013f936b6 100644 --- a/shell/browser/api/ipc_dispatcher.h +++ b/shell/browser/api/ipc_dispatcher.h @@ -35,15 +35,8 @@ class IpcDispatcher { void Invoke(gin::Handle& event, const std::string& channel, - blink::CloneableMessage arguments, - electron::mojom::ElectronApiIPC::InvokeCallback callback) { - TRACE_EVENT1("electron", "IpcHelper::Invoke", "channel", channel); - - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - gin_helper::Dictionary dict(isolate, event.ToV8().As()); - dict.Set("_replyChannel", gin_helper::internal::ReplyChannel::Create( - isolate, std::move(callback))); - + blink::CloneableMessage arguments) { + TRACE_EVENT1("electron", "IpcDispatcher::Invoke", "channel", channel); emitter()->EmitWithoutEvent("-ipc-invoke", event, channel, std::move(arguments)); } @@ -51,6 +44,8 @@ class IpcDispatcher { void ReceivePostMessage(gin::Handle& event, const std::string& channel, blink::TransferableMessage message) { + TRACE_EVENT1("electron", "IpcDispatcher::ReceivePostMessage", "channel", + channel); v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); v8::HandleScope handle_scope(isolate); auto wrapped_ports = @@ -61,22 +56,22 @@ class IpcDispatcher { std::move(wrapped_ports)); } - void MessageSync( - gin::Handle& event, - const std::string& channel, - blink::CloneableMessage arguments, - electron::mojom::ElectronApiIPC::MessageSyncCallback callback) { - TRACE_EVENT1("electron", "IpcHelper::MessageSync", "channel", channel); - - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - gin_helper::Dictionary dict(isolate, event.ToV8().As()); - dict.Set("_replyChannel", gin_helper::internal::ReplyChannel::Create( - isolate, std::move(callback))); - + void MessageSync(gin::Handle& event, + const std::string& channel, + blink::CloneableMessage arguments) { + TRACE_EVENT1("electron", "IpcDispatcher::MessageSync", "channel", channel); emitter()->EmitWithoutEvent("-ipc-message-sync", event, channel, std::move(arguments)); } + void MessageHost(gin::Handle& event, + const std::string& channel, + blink::CloneableMessage arguments) { + TRACE_EVENT1("electron", "IpcDispatcher::MessageHost", "channel", channel); + emitter()->EmitWithoutEvent("-ipc-message-host", event, channel, + std::move(arguments)); + } + private: inline T* emitter() { // T must inherit from gin_helper::EventEmitterMixin diff --git a/shell/browser/electron_api_ipc_handler_impl.cc b/shell/browser/electron_api_ipc_handler_impl.cc index 8902622a786c..4d1d2658320a 100644 --- a/shell/browser/electron_api_ipc_handler_impl.cc +++ b/shell/browser/electron_api_ipc_handler_impl.cc @@ -8,7 +8,12 @@ #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_process_host.h" +#include "gin/handle.h" #include "mojo/public/cpp/bindings/self_owned_receiver.h" +#include "shell/browser/api/electron_api_session.h" +#include "shell/common/gin_converters/content_converter.h" +#include "shell/common/gin_converters/frame_converter.h" +#include "shell/common/gin_helper/event.h" namespace electron { ElectronApiIPCHandlerImpl::ElectronApiIPCHandlerImpl( @@ -38,57 +43,128 @@ void ElectronApiIPCHandlerImpl::OnConnectionError() { void ElectronApiIPCHandlerImpl::Message(bool internal, const std::string& channel, blink::CloneableMessage arguments) { - api::WebContents* api_web_contents = api::WebContents::From(web_contents()); - if (api_web_contents) { - api_web_contents->Message(internal, channel, std::move(arguments), - GetRenderFrameHost()); - } + auto* session = GetSession(); + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session, internal); + if (event.IsEmpty()) + return; + session->Message(event, channel, std::move(arguments)); } void ElectronApiIPCHandlerImpl::Invoke(bool internal, const std::string& channel, blink::CloneableMessage arguments, InvokeCallback callback) { - api::WebContents* api_web_contents = api::WebContents::From(web_contents()); - if (api_web_contents) { - api_web_contents->Invoke(internal, channel, std::move(arguments), - std::move(callback), GetRenderFrameHost()); - } + auto* session = GetSession(); + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session, internal, std::move(callback)); + if (event.IsEmpty()) + return; + session->Invoke(event, channel, std::move(arguments)); } void ElectronApiIPCHandlerImpl::ReceivePostMessage( const std::string& channel, blink::TransferableMessage message) { - api::WebContents* api_web_contents = api::WebContents::From(web_contents()); - if (api_web_contents) { - api_web_contents->ReceivePostMessage(channel, std::move(message), - GetRenderFrameHost()); - } + auto* session = GetSession(); + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session, false); + if (event.IsEmpty()) + return; + session->ReceivePostMessage(event, channel, std::move(message)); } void ElectronApiIPCHandlerImpl::MessageSync(bool internal, const std::string& channel, blink::CloneableMessage arguments, MessageSyncCallback callback) { - api::WebContents* api_web_contents = api::WebContents::From(web_contents()); - if (api_web_contents) { - api_web_contents->MessageSync(internal, channel, std::move(arguments), - std::move(callback), GetRenderFrameHost()); - } + auto* session = GetSession(); + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session, internal, std::move(callback)); + if (event.IsEmpty()) + return; + session->MessageSync(event, channel, std::move(arguments)); } void ElectronApiIPCHandlerImpl::MessageHost(const std::string& channel, blink::CloneableMessage arguments) { - api::WebContents* api_web_contents = api::WebContents::From(web_contents()); - if (api_web_contents) { - api_web_contents->MessageHost(channel, std::move(arguments), - GetRenderFrameHost()); - } + auto* session = GetSession(); + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session, false); + if (event.IsEmpty()) + return; + session->MessageHost(event, channel, std::move(arguments)); } content::RenderFrameHost* ElectronApiIPCHandlerImpl::GetRenderFrameHost() { return content::RenderFrameHost::FromID(render_frame_host_id_); } +api::Session* ElectronApiIPCHandlerImpl::GetSession() { + auto* rfh = GetRenderFrameHost(); + return rfh ? api::Session::FromBrowserContext(rfh->GetBrowserContext()) + : nullptr; +} + +gin::Handle +ElectronApiIPCHandlerImpl::MakeIPCEvent( + v8::Isolate* isolate, + api::Session* session, + bool internal, + electron::mojom::ElectronApiIPC::InvokeCallback callback) { + if (!session) { + if (callback) { + // We must always invoke the callback if present. + gin_helper::internal::ReplyChannel::Create(isolate, std::move(callback)) + ->SendError("Session does not exist"); + } + return {}; + } + + api::WebContents* api_web_contents = api::WebContents::From(web_contents()); + if (!api_web_contents) { + if (callback) { + // We must always invoke the callback if present. + gin_helper::internal::ReplyChannel::Create(isolate, std::move(callback)) + ->SendError("WebContents does not exist"); + } + return {}; + } + + v8::Local wrapper; + if (!api_web_contents->GetWrapper(isolate).ToLocal(&wrapper)) { + if (callback) { + // We must always invoke the callback if present. + gin_helper::internal::ReplyChannel::Create(isolate, std::move(callback)) + ->SendError("WebContents was destroyed"); + } + return {}; + } + + content::RenderFrameHost* frame = GetRenderFrameHost(); + gin::Handle event = + gin_helper::internal::Event::New(isolate); + gin_helper::Dictionary dict(isolate, event.ToV8().As()); + dict.Set("type", "frame"); + dict.Set("sender", web_contents()); + if (internal) + dict.SetHidden("internal", internal); + if (callback) + dict.Set("_replyChannel", gin_helper::internal::ReplyChannel::Create( + isolate, std::move(callback))); + if (frame) { + dict.SetGetter("senderFrame", frame); + dict.Set("frameId", frame->GetRoutingID()); + dict.Set("processId", frame->GetProcess()->GetID().GetUnsafeValue()); + dict.Set("frameTreeNodeId", frame->GetFrameTreeNodeId()); + } + return event; +} + // static void ElectronApiIPCHandlerImpl::Create( content::RenderFrameHost* frame_host, diff --git a/shell/browser/electron_api_ipc_handler_impl.h b/shell/browser/electron_api_ipc_handler_impl.h index 819407a0de7b..973d20473481 100644 --- a/shell/browser/electron_api_ipc_handler_impl.h +++ b/shell/browser/electron_api_ipc_handler_impl.h @@ -65,6 +65,14 @@ class ElectronApiIPCHandlerImpl : public mojom::ElectronApiIPC, void OnConnectionError(); content::RenderFrameHost* GetRenderFrameHost(); + api::Session* GetSession(); + + gin::Handle MakeIPCEvent( + v8::Isolate* isolate, + api::Session* session, + bool internal, + electron::mojom::ElectronApiIPC::InvokeCallback callback = + electron::mojom::ElectronApiIPC::InvokeCallback()); content::GlobalRenderFrameHostId render_frame_host_id_; diff --git a/shell/browser/electron_api_sw_ipc_handler_impl.cc b/shell/browser/electron_api_sw_ipc_handler_impl.cc index 6c9bf803f015..da41fd53557a 100644 --- a/shell/browser/electron_api_sw_ipc_handler_impl.cc +++ b/shell/browser/electron_api_sw_ipc_handler_impl.cc @@ -71,13 +71,12 @@ void ElectronApiSWIPCHandlerImpl::Message(bool internal, const std::string& channel, blink::CloneableMessage arguments) { auto* session = GetSession(); - if (session) { - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - gin::Handle event = - MakeIPCEvent(isolate, internal); - session->Message(event, channel, std::move(arguments)); - } + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session, internal); + if (event.IsEmpty()) + return; + session->Message(event, channel, std::move(arguments)); } void ElectronApiSWIPCHandlerImpl::Invoke(bool internal, @@ -85,26 +84,24 @@ void ElectronApiSWIPCHandlerImpl::Invoke(bool internal, blink::CloneableMessage arguments, InvokeCallback callback) { auto* session = GetSession(); - if (session) { - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - gin::Handle event = - MakeIPCEvent(isolate, internal); - session->Invoke(event, channel, std::move(arguments), std::move(callback)); - } + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session, internal, std::move(callback)); + if (event.IsEmpty()) + return; + session->Invoke(event, channel, std::move(arguments)); } void ElectronApiSWIPCHandlerImpl::ReceivePostMessage( const std::string& channel, blink::TransferableMessage message) { auto* session = GetSession(); - if (session) { - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - gin::Handle event = - MakeIPCEvent(isolate, false); - session->ReceivePostMessage(event, channel, std::move(message)); - } + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session, false); + if (event.IsEmpty()) + return; + session->ReceivePostMessage(event, channel, std::move(message)); } void ElectronApiSWIPCHandlerImpl::MessageSync(bool internal, @@ -112,14 +109,12 @@ void ElectronApiSWIPCHandlerImpl::MessageSync(bool internal, blink::CloneableMessage arguments, MessageSyncCallback callback) { auto* session = GetSession(); - if (session) { - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - gin::Handle event = - MakeIPCEvent(isolate, internal); - session->MessageSync(event, channel, std::move(arguments), - std::move(callback)); - } + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session, internal, std::move(callback)); + if (event.IsEmpty()) + return; + session->MessageSync(event, channel, std::move(arguments)); } void ElectronApiSWIPCHandlerImpl::MessageHost( @@ -139,7 +134,20 @@ api::Session* ElectronApiSWIPCHandlerImpl::GetSession() { } gin::Handle -ElectronApiSWIPCHandlerImpl::MakeIPCEvent(v8::Isolate* isolate, bool internal) { +ElectronApiSWIPCHandlerImpl::MakeIPCEvent( + v8::Isolate* isolate, + api::Session* session, + bool internal, + electron::mojom::ElectronApiIPC::InvokeCallback callback) { + if (!session) { + if (callback) { + // We must always invoke the callback if present. + gin_helper::internal::ReplyChannel::Create(isolate, std::move(callback)) + ->SendError("Session does not exist"); + } + return {}; + } + gin::Handle event = gin_helper::internal::Event::New(isolate); v8::Local event_object = event.ToV8().As(); @@ -150,7 +158,11 @@ ElectronApiSWIPCHandlerImpl::MakeIPCEvent(v8::Isolate* isolate, bool internal) { dict.Set("processId", render_process_host_->GetID().GetUnsafeValue()); // Set session to provide context for getting preloads - dict.Set("session", GetSession()); + dict.Set("session", session); + + if (callback) + dict.Set("_replyChannel", gin_helper::internal::ReplyChannel::Create( + isolate, std::move(callback))); if (internal) dict.SetHidden("internal", internal); diff --git a/shell/browser/electron_api_sw_ipc_handler_impl.h b/shell/browser/electron_api_sw_ipc_handler_impl.h index b86f4fabac2a..d3cf6d95090c 100644 --- a/shell/browser/electron_api_sw_ipc_handler_impl.h +++ b/shell/browser/electron_api_sw_ipc_handler_impl.h @@ -70,8 +70,12 @@ class ElectronApiSWIPCHandlerImpl : public mojom::ElectronApiIPC, ElectronBrowserContext* GetBrowserContext(); api::Session* GetSession(); - gin::Handle MakeIPCEvent(v8::Isolate* isolate, - bool internal); + gin::Handle MakeIPCEvent( + v8::Isolate* isolate, + api::Session* session, + bool internal, + electron::mojom::ElectronApiIPC::InvokeCallback callback = + electron::mojom::ElectronApiIPC::InvokeCallback()); // content::RenderProcessHostObserver void RenderProcessExited(