From 81e9dab52f6f59a56c3f928156823dd482817539 Mon Sep 17 00:00:00 2001 From: Milan Burda Date: Sat, 24 Aug 2019 00:45:50 +0200 Subject: [PATCH] refactor: replace ipcRendererUtils.invoke() with ipcRendererInternal.invoke() (#19574) --- filenames.auto.gni | 1 + lib/browser/api/ipc-main.ts | 36 +------------- lib/browser/api/web-contents.js | 7 +-- lib/browser/chrome-extension.js | 17 ++++--- lib/browser/devtools.ts | 9 ++-- lib/browser/guest-view-manager.js | 31 +++++++++--- lib/browser/guest-window-manager.js | 51 ++++++++++++++------ lib/browser/ipc-main-impl.ts | 33 +++++++++++++ lib/browser/ipc-main-internal-utils.ts | 27 +++-------- lib/browser/ipc-main-internal.ts | 8 ++- lib/browser/rpc-server.js | 14 +++--- lib/renderer/api/desktop-capturer.ts | 4 +- lib/renderer/api/ipc-renderer.ts | 11 +++-- lib/renderer/chrome-api.ts | 6 +-- lib/renderer/extensions/storage.ts | 6 +-- lib/renderer/inspector.ts | 9 ++-- lib/renderer/ipc-renderer-internal-utils.ts | 36 +++----------- lib/renderer/ipc-renderer-internal.ts | 8 +++ lib/renderer/security-warnings.ts | 4 +- lib/renderer/web-view/guest-view-internal.ts | 8 +-- lib/renderer/web-view/web-view-attributes.ts | 4 +- lib/renderer/web-view/web-view-impl.ts | 3 +- lib/renderer/window-setup.ts | 8 +-- shell/browser/api/atom_api_web_contents.cc | 7 +-- shell/browser/api/atom_api_web_contents.h | 3 +- shell/common/api/api.mojom | 1 + shell/renderer/api/atom_api_renderer_ipc.cc | 3 +- typings/internal-ambient.d.ts | 2 +- typings/internal-electron.d.ts | 2 + 29 files changed, 195 insertions(+), 164 deletions(-) create mode 100644 lib/browser/ipc-main-impl.ts diff --git a/filenames.auto.gni b/filenames.auto.gni index b80f90500f8..28b36e7288d 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -254,6 +254,7 @@ auto_filenames = { "lib/browser/guest-view-manager.js", "lib/browser/guest-window-manager.js", "lib/browser/init.ts", + "lib/browser/ipc-main-impl.ts", "lib/browser/ipc-main-internal-utils.ts", "lib/browser/ipc-main-internal.ts", "lib/browser/navigation-controller.js", diff --git a/lib/browser/api/ipc-main.ts b/lib/browser/api/ipc-main.ts index d97e916149b..c71660d4538 100644 --- a/lib/browser/api/ipc-main.ts +++ b/lib/browser/api/ipc-main.ts @@ -1,38 +1,6 @@ -import { EventEmitter } from 'events' -import { IpcMainInvokeEvent } from 'electron' +import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl' -class IpcMain extends EventEmitter { - private _invokeHandlers: Map void> = new Map(); - - handle: Electron.IpcMain['handle'] = (method, fn) => { - if (this._invokeHandlers.has(method)) { - throw new Error(`Attempted to register a second handler for '${method}'`) - } - if (typeof fn !== 'function') { - throw new Error(`Expected handler to be a function, but found type '${typeof fn}'`) - } - this._invokeHandlers.set(method, async (e, ...args) => { - try { - (e as any)._reply(await Promise.resolve(fn(e, ...args))) - } catch (err) { - (e as any)._throw(err) - } - }) - } - - handleOnce: Electron.IpcMain['handleOnce'] = (method, fn) => { - this.handle(method, (e, ...args) => { - this.removeHandler(method) - return fn(e, ...args) - }) - } - - removeHandler (method: string) { - this._invokeHandlers.delete(method) - } -} - -const ipcMain = new IpcMain() +const ipcMain = new IpcMainImpl() // Do not throw exception when channel name is "error". ipcMain.on('error', () => {}) diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index 8f1be8bfddd..6a92a9a884c 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -327,14 +327,15 @@ WebContents.prototype._init = function () { } }) - this.on('-ipc-invoke', function (event, channel, args) { + this.on('-ipc-invoke', function (event, internal, channel, args) { event._reply = (result) => event.sendReply({ result }) event._throw = (error) => { console.error(`Error occurred in handler for '${channel}':`, error) event.sendReply({ error: error.toString() }) } - if (ipcMain._invokeHandlers.has(channel)) { - ipcMain._invokeHandlers.get(channel)(event, ...args) + const target = internal ? ipcMainInternal : ipcMain + if (target._invokeHandlers.has(channel)) { + target._invokeHandlers.get(channel)(event, ...args) } else { event._throw(`No handler registered for '${channel}'`) } diff --git a/lib/browser/chrome-extension.js b/lib/browser/chrome-extension.js index afcf47ed5f8..dd0101e08f5 100644 --- a/lib/browser/chrome-extension.js +++ b/lib/browser/chrome-extension.js @@ -6,6 +6,7 @@ if (process.electronBinding('features').isExtensionsEnabled()) { const { app, webContents, BrowserWindow } = require('electron') const { getAllWebContents } = process.electronBinding('web_contents') +const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal') const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') const { Buffer } = require('buffer') @@ -160,7 +161,7 @@ const hookWebContentsEvents = function (webContents) { // Handle the chrome.* API messages. let nextId = 0 -ipcMainUtils.handle('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) { +ipcMainUtils.handleSync('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) { if (isBackgroundPage(event.sender)) { throw new Error('chrome.runtime.connect is not supported in background page') } @@ -182,7 +183,7 @@ ipcMainUtils.handle('CHROME_RUNTIME_CONNECT', function (event, extensionId, conn return { tabId, portId } }) -ipcMainUtils.handle('CHROME_EXTENSION_MANIFEST', function (event, extensionId) { +ipcMainUtils.handleSync('CHROME_EXTENSION_MANIFEST', function (event, extensionId) { const manifest = manifestMap[extensionId] if (!manifest) { throw new Error(`Invalid extensionId: ${extensionId}`) @@ -190,7 +191,7 @@ ipcMainUtils.handle('CHROME_EXTENSION_MANIFEST', function (event, extensionId) { return manifest }) -ipcMainUtils.handle('CHROME_RUNTIME_SEND_MESSAGE', async function (event, extensionId, message) { +ipcMainInternal.handle('CHROME_RUNTIME_SEND_MESSAGE', async function (event, extensionId, message) { if (isBackgroundPage(event.sender)) { throw new Error('chrome.runtime.sendMessage is not supported in background page') } @@ -203,7 +204,7 @@ ipcMainUtils.handle('CHROME_RUNTIME_SEND_MESSAGE', async function (event, extens return ipcMainUtils.invokeInWebContents(page.webContents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message) }) -ipcMainUtils.handle('CHROME_TABS_SEND_MESSAGE', async function (event, tabId, extensionId, message) { +ipcMainInternal.handle('CHROME_TABS_SEND_MESSAGE', async function (event, tabId, extensionId, message) { const contents = webContents.fromId(tabId) if (!contents) { throw new Error(`Sending message to unknown tab ${tabId}`) @@ -237,7 +238,7 @@ const getMessagesPath = (extensionId) => { } } -ipcMainUtils.handle('CHROME_GET_MESSAGES', async function (event, extensionId) { +ipcMainUtils.handleSync('CHROME_GET_MESSAGES', async function (event, extensionId) { const messagesPath = getMessagesPath(extensionId) return fs.promises.readFile(messagesPath) }) @@ -256,7 +257,7 @@ const getChromeStoragePath = (storageType, extensionId) => { return path.join(app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`) } -ipcMainUtils.handle('CHROME_STORAGE_READ', async function (event, storageType, extensionId) { +ipcMainInternal.handle('CHROME_STORAGE_READ', async function (event, storageType, extensionId) { const filePath = getChromeStoragePath(storageType, extensionId) try { @@ -270,7 +271,7 @@ ipcMainUtils.handle('CHROME_STORAGE_READ', async function (event, storageType, e } }) -ipcMainUtils.handle('CHROME_STORAGE_WRITE', async function (event, storageType, extensionId, data) { +ipcMainInternal.handle('CHROME_STORAGE_WRITE', async function (event, storageType, extensionId, data) { const filePath = getChromeStoragePath(storageType, extensionId) try { @@ -295,7 +296,7 @@ const assertChromeExtension = function (contents, api) { } } -ipcMainUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', async function (event, tabId, extensionId, details) { +ipcMainInternal.handle('CHROME_TABS_EXECUTE_SCRIPT', async function (event, tabId, extensionId, details) { assertChromeExtension(event.sender, 'chrome.tabs.executeScript()') const contents = webContents.fromId(tabId) diff --git a/lib/browser/devtools.ts b/lib/browser/devtools.ts index c51e107edd0..090ed6be825 100644 --- a/lib/browser/devtools.ts +++ b/lib/browser/devtools.ts @@ -2,7 +2,8 @@ import { dialog, Menu } from 'electron' import * as fs from 'fs' import * as url from 'url' -const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils') +import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal' +import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils' const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) { return items.map(function (item) { @@ -59,7 +60,7 @@ const assertChromeDevTools = function (contents: Electron.WebContents, api: stri } } -ipcMainUtils.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event: Electron.IpcMainEvent, items: ContextMenuItem[], isEditMenu: boolean) { +ipcMainInternal.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event: Electron.IpcMainInvokeEvent, items: ContextMenuItem[], isEditMenu: boolean) { return new Promise(resolve => { assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()') @@ -71,7 +72,7 @@ ipcMainUtils.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event: Electron }) }) -ipcMainUtils.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event: Electron.IpcMainEvent) { +ipcMainInternal.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event: Electron.IpcMainInvokeEvent) { assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()') const result = await dialog.showOpenDialog({}) @@ -83,7 +84,7 @@ ipcMainUtils.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event: Ele return [path, data] }) -ipcMainUtils.handle('ELECTRON_INSPECTOR_CONFIRM', async function (event: Electron.IpcMainEvent, message: string = '', title: string = '') { +ipcMainUtils.handleSync('ELECTRON_INSPECTOR_CONFIRM', async function (event: Electron.IpcMainInvokeEvent, message: string = '', title: string = '') { assertChromeDevTools(event.sender, 'window.confirm()') const options = { diff --git a/lib/browser/guest-view-manager.js b/lib/browser/guest-view-manager.js index 03462d4e567..c75076fa108 100644 --- a/lib/browser/guest-view-manager.js +++ b/lib/browser/guest-view-manager.js @@ -312,21 +312,33 @@ const isWebViewTagEnabled = function (contents) { return isWebViewTagEnabledCache.get(contents) } -const handleMessage = function (channel, handler) { - ipcMainUtils.handle(channel, (event, ...args) => { +const makeSafeHandler = function (channel, handler) { + return (event, ...args) => { if (isWebViewTagEnabled(event.sender)) { return handler(event, ...args) } else { console.error(` IPC message ${channel} sent by WebContents with disabled (${event.sender.id})`) throw new Error(' disabled') } - }) + } +} + +const handleMessage = function (channel, handler) { + ipcMainInternal.handle(channel, makeSafeHandler(channel, handler)) +} + +const handleMessageSync = function (channel, handler) { + ipcMainUtils.handleSync(channel, makeSafeHandler(channel, handler)) } handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params) { return createGuest(event.sender, params) }) +handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params) { + return createGuest(event.sender, params) +}) + handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) { try { attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params) @@ -345,11 +357,18 @@ ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, } }) -const allMethods = new Set([ ...syncMethods, ...asyncMethods ]) - handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) { const guest = getGuestForWebContents(guestInstanceId, event.sender) - if (!allMethods.has(method)) { + if (!asyncMethods.has(method)) { + throw new Error(`Invalid method: ${method}`) + } + + return guest[method](...args) +}) + +handleMessageSync('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) { + const guest = getGuestForWebContents(guestInstanceId, event.sender) + if (!syncMethods.has(method)) { throw new Error(`Invalid method: ${method}`) } diff --git a/lib/browser/guest-window-manager.js b/lib/browser/guest-window-manager.js index c223b55f8ba..7a17de8fbc1 100644 --- a/lib/browser/guest-window-manager.js +++ b/lib/browser/guest-window-manager.js @@ -271,15 +271,30 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', functio } }) -const handleMessage = function (channel, handler) { - ipcMainUtils.handle(channel, (event, guestId, ...args) => { +const makeSafeHandler = function (handler) { + return (event, guestId, ...args) => { const guestContents = webContents.fromId(guestId) if (!guestContents) { throw new Error(`Invalid guestId: ${guestId}`) } return handler(event, guestContents, ...args) - }) + } +} + +const handleMessage = function (channel, handler) { + ipcMainInternal.handle(channel, makeSafeHandler(handler)) +} + +const handleMessageSync = function (channel, handler) { + ipcMainUtils.handleSync(channel, makeSafeHandler(handler)) +} + +const assertCanAccessWindow = function (contents, guestContents) { + if (!canAccessWindow(contents, guestContents)) { + console.error(`Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`) + throw new Error(`Access denied to guestId: ${guestContents.id}`) + } } const windowMethods = new Set([ @@ -289,10 +304,7 @@ const windowMethods = new Set([ ]) handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestContents, method, ...args) => { - if (!canAccessWindow(event.sender, guestContents)) { - console.error(`Blocked ${event.sender.getURL()} from accessing guestId: ${guestContents.id}`) - throw new Error(`Access denied to guestId: ${guestContents.id}`) - } + assertCanAccessWindow(event.sender, guestContents) if (!windowMethods.has(method)) { console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`) @@ -316,20 +328,31 @@ handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestC } }) -const webContentsMethods = new Set([ - 'getURL', +const webContentsMethodsAsync = new Set([ 'loadURL', 'executeJavaScript', 'print' ]) handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => { - if (!canAccessWindow(event.sender, guestContents)) { - console.error(`Blocked ${event.sender.getURL()} from accessing guestId: ${guestContents.id}`) - throw new Error(`Access denied to guestId: ${guestContents.id}`) - } + assertCanAccessWindow(event.sender, guestContents) - if (!webContentsMethods.has(method)) { + if (!webContentsMethodsAsync.has(method)) { + console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`) + throw new Error(`Invalid method: ${method}`) + } + + return guestContents[method](...args) +}) + +const webContentsMethodsSync = new Set([ + 'getURL' +]) + +handleMessageSync('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => { + assertCanAccessWindow(event.sender, guestContents) + + if (!webContentsMethodsSync.has(method)) { console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`) throw new Error(`Invalid method: ${method}`) } diff --git a/lib/browser/ipc-main-impl.ts b/lib/browser/ipc-main-impl.ts new file mode 100644 index 00000000000..8f3fc9acd62 --- /dev/null +++ b/lib/browser/ipc-main-impl.ts @@ -0,0 +1,33 @@ +import { EventEmitter } from 'events' +import { IpcMainInvokeEvent } from 'electron' + +export class IpcMainImpl extends EventEmitter { + private _invokeHandlers: Map void> = new Map(); + + handle: Electron.IpcMain['handle'] = (method, fn) => { + if (this._invokeHandlers.has(method)) { + throw new Error(`Attempted to register a second handler for '${method}'`) + } + if (typeof fn !== 'function') { + throw new Error(`Expected handler to be a function, but found type '${typeof fn}'`) + } + this._invokeHandlers.set(method, async (e, ...args) => { + try { + (e as any)._reply(await Promise.resolve(fn(e, ...args))) + } catch (err) { + (e as any)._throw(err) + } + }) + } + + handleOnce: Electron.IpcMain['handleOnce'] = (method, fn) => { + this.handle(method, (e, ...args) => { + this.removeHandler(method) + return fn(e, ...args) + }) + } + + removeHandler (method: string) { + this._invokeHandlers.delete(method) + } +} diff --git a/lib/browser/ipc-main-internal-utils.ts b/lib/browser/ipc-main-internal-utils.ts index 0b8eeee8d84..958ed2d1885 100644 --- a/lib/browser/ipc-main-internal-utils.ts +++ b/lib/browser/ipc-main-internal-utils.ts @@ -1,26 +1,15 @@ import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal' import * as errorUtils from '@electron/internal/common/error-utils' -type IPCHandler = (event: ElectronInternal.IpcMainInternalEvent, ...args: any[]) => any +type IPCHandler = (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any -const callHandler = async function (handler: IPCHandler, event: ElectronInternal.IpcMainInternalEvent, args: any[], reply: (args: any[]) => void) { - try { - const result = await handler(event, ...args) - reply([null, result]) - } catch (error) { - reply([errorUtils.serialize(error)]) - } -} - -export const handle = function (channel: string, handler: T) { - ipcMainInternal.on(channel, (event, requestId, ...args) => { - callHandler(handler, event, args, responseArgs => { - if (requestId) { - event._replyInternal(`${channel}_RESPONSE_${requestId}`, ...responseArgs) - } else { - event.returnValue = responseArgs - } - }) +export const handleSync = function (channel: string, handler: T) { + ipcMainInternal.on(channel, async (event, ...args) => { + try { + event.returnValue = [null, await handler(event, ...args)] + } catch (error) { + event.returnValue = [errorUtils.serialize(error)] + } }) } diff --git a/lib/browser/ipc-main-internal.ts b/lib/browser/ipc-main-internal.ts index 3f1a9f4205d..9f296453b42 100644 --- a/lib/browser/ipc-main-internal.ts +++ b/lib/browser/ipc-main-internal.ts @@ -1,8 +1,6 @@ -import { EventEmitter } from 'events' +import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl' -const emitter = new EventEmitter() +export const ipcMainInternal = new IpcMainImpl() as ElectronInternal.IpcMainInternal // Do not throw exception when channel name is "error". -emitter.on('error', () => {}) - -export const ipcMainInternal = emitter as ElectronInternal.IpcMainInternal +ipcMainInternal.on('error', () => {}) diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index aa66388610e..71810b52793 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -471,11 +471,11 @@ ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) { event.returnValue = null }) -ipcMainUtils.handle('ELECTRON_CRASH_REPORTER_INIT', function (event, options) { +ipcMainUtils.handleSync('ELECTRON_CRASH_REPORTER_INIT', function (event, options) { return crashReporterInit(options) }) -ipcMainUtils.handle('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) { +ipcMainInternal.handle('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) { return event.sender.getLastWebPreferences() }) @@ -491,7 +491,7 @@ const allowedClipboardMethods = (() => { } })() -ipcMainUtils.handle('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...args) { +ipcMainUtils.handleSync('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...args) { if (!allowedClipboardMethods.has(method)) { throw new Error(`Invalid method: ${method}`) } @@ -502,7 +502,7 @@ ipcMainUtils.handle('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...ar if (features.isDesktopCapturerEnabled()) { const desktopCapturer = require('@electron/internal/browser/desktop-capturer') - ipcMainUtils.handle('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, ...args) { + ipcMainInternal.handle('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, ...args) { const customEvent = emitCustomEvent(event.sender, 'desktop-capturer-get-sources') if (customEvent.defaultPrevented) { @@ -526,13 +526,13 @@ const getPreloadScript = async function (preloadPath) { } if (process.electronBinding('features').isExtensionsEnabled()) { - ipcMainUtils.handle('ELECTRON_GET_CONTENT_SCRIPTS', () => []) + ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => []) } else { const { getContentScripts } = require('@electron/internal/browser/chrome-extension') - ipcMainUtils.handle('ELECTRON_GET_CONTENT_SCRIPTS', () => getContentScripts()) + ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => getContentScripts()) } -ipcMainUtils.handle('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) { +ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) { const preloadPaths = event.sender._getPreloadPaths() let contentScripts = [] diff --git a/lib/renderer/api/desktop-capturer.ts b/lib/renderer/api/desktop-capturer.ts index f39cc007373..d8de9a35a45 100644 --- a/lib/renderer/api/desktop-capturer.ts +++ b/lib/renderer/api/desktop-capturer.ts @@ -1,5 +1,5 @@ import { nativeImage } from 'electron' -import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils' +import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' // |options.types| can't be empty and must be an array function isValid (options: Electron.SourcesOptions) { @@ -16,7 +16,7 @@ export async function getSources (options: Electron.SourcesOptions) { const { thumbnailSize = { width: 150, height: 150 } } = options const { fetchWindowIcons = false } = options - const sources = await ipcRendererUtils.invoke('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', { + const sources = await ipcRendererInternal.invoke('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', { captureWindow, captureScreen, thumbnailSize, diff --git a/lib/renderer/api/ipc-renderer.ts b/lib/renderer/api/ipc-renderer.ts index d631138b7b9..2a5085a8257 100644 --- a/lib/renderer/api/ipc-renderer.ts +++ b/lib/renderer/api/ipc-renderer.ts @@ -21,11 +21,12 @@ ipcRenderer.sendTo = function (webContentsId, channel, ...args) { return ipc.sendTo(internal, false, webContentsId, channel, args) } -ipcRenderer.invoke = function (channel, ...args) { - return ipc.invoke(channel, args).then(({ error, result }) => { - if (error) { throw new Error(`Error invoking remote method '${channel}': ${error}`) } - return result - }) +ipcRenderer.invoke = async function (channel, ...args) { + const { error, result } = await ipc.invoke(internal, channel, args) + if (error) { + throw new Error(`Error invoking remote method '${channel}': ${error}`) + } + return result } export default ipcRenderer diff --git a/lib/renderer/chrome-api.ts b/lib/renderer/chrome-api.ts index 19b5576569e..8c5538378c1 100644 --- a/lib/renderer/chrome-api.ts +++ b/lib/renderer/chrome-api.ts @@ -157,7 +157,7 @@ export function injectTo (extensionId: string, context: any) { console.error('options are not supported') } - ipcRendererUtils.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback) + ipcRendererInternal.invoke('CHROME_RUNTIME_SEND_MESSAGE', targetExtensionId, message).then(responseCallback) }, onConnect: new Event(), @@ -172,7 +172,7 @@ export function injectTo (extensionId: string, context: any) { details: Chrome.Tabs.ExecuteScriptDetails, resultCallback: Chrome.Tabs.ExecuteScriptCallback = () => {} ) { - ipcRendererUtils.invoke('CHROME_TABS_EXECUTE_SCRIPT', tabId, extensionId, details) + ipcRendererInternal.invoke('CHROME_TABS_EXECUTE_SCRIPT', tabId, extensionId, details) .then((result: any) => resultCallback([result])) }, @@ -183,7 +183,7 @@ export function injectTo (extensionId: string, context: any) { _options: Chrome.Tabs.SendMessageDetails, responseCallback: Chrome.Tabs.SendMessageCallback = () => {} ) { - ipcRendererUtils.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback) + ipcRendererInternal.invoke('CHROME_TABS_SEND_MESSAGE', tabId, extensionId, message).then(responseCallback) }, onUpdated: new Event(), diff --git a/lib/renderer/extensions/storage.ts b/lib/renderer/extensions/storage.ts index 5b39de4cc0a..f5ed8b69cda 100644 --- a/lib/renderer/extensions/storage.ts +++ b/lib/renderer/extensions/storage.ts @@ -1,9 +1,9 @@ -import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils' +import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' const getStorage = (storageType: string, extensionId: number, callback: Function) => { if (typeof callback !== 'function') throw new TypeError('No callback provided') - ipcRendererUtils.invoke('CHROME_STORAGE_READ', storageType, extensionId) + ipcRendererInternal.invoke('CHROME_STORAGE_READ', storageType, extensionId) .then(data => { if (data !== null) { callback(JSON.parse(data)) @@ -17,7 +17,7 @@ const getStorage = (storageType: string, extensionId: number, callback: Function const setStorage = (storageType: string, extensionId: number, storage: Record, callback: Function) => { const json = JSON.stringify(storage) - ipcRendererUtils.invoke('CHROME_STORAGE_WRITE', storageType, extensionId, json) + ipcRendererInternal.invoke('CHROME_STORAGE_WRITE', storageType, extensionId, json) .then(() => { if (callback) callback() }) diff --git a/lib/renderer/inspector.ts b/lib/renderer/inspector.ts index 9739f24c534..0f4927040cb 100644 --- a/lib/renderer/inspector.ts +++ b/lib/renderer/inspector.ts @@ -1,4 +1,5 @@ -import { invoke, invokeSync } from '@electron/internal/renderer/ipc-renderer-internal-utils' +import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' +import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils' window.onload = function () { // Use menu API to show context menu. @@ -19,7 +20,7 @@ function completeURL (project: string, path: string) { // The DOM implementation expects (message?: string) => boolean (window.confirm as any) = function (message: string, title: string) { - return invokeSync('ELECTRON_INSPECTOR_CONFIRM', message, title) as boolean + return ipcRendererUtils.invokeSync('ELECTRON_INSPECTOR_CONFIRM', message, title) as boolean } const useEditMenuItems = function (x: number, y: number, items: ContextMenuItem[]) { @@ -32,7 +33,7 @@ const useEditMenuItems = function (x: number, y: number, items: ContextMenuItem[ const createMenu = function (x: number, y: number, items: ContextMenuItem[]) { const isEditMenu = useEditMenuItems(x, y, items) - invoke('ELECTRON_INSPECTOR_CONTEXT_MENU', items, isEditMenu).then(id => { + ipcRendererInternal.invoke('ELECTRON_INSPECTOR_CONTEXT_MENU', items, isEditMenu).then(id => { if (typeof id === 'number') { window.DevToolsAPI!.contextMenuItemSelected(id) } @@ -41,7 +42,7 @@ const createMenu = function (x: number, y: number, items: ContextMenuItem[]) { } const showFileChooserDialog = function (callback: (blob: File) => void) { - invoke<[ string, any ]>('ELECTRON_INSPECTOR_SELECT_FILE').then(([path, data]) => { + ipcRendererInternal.invoke<[ string, any ]>('ELECTRON_INSPECTOR_SELECT_FILE').then(([path, data]) => { if (path && data) { callback(dataToHtml5FileObject(path, data)) } diff --git a/lib/renderer/ipc-renderer-internal-utils.ts b/lib/renderer/ipc-renderer-internal-utils.ts index 711701ddfe5..ec749c914cc 100644 --- a/lib/renderer/ipc-renderer-internal-utils.ts +++ b/lib/renderer/ipc-renderer-internal-utils.ts @@ -4,38 +4,18 @@ import * as errorUtils from '@electron/internal/common/error-utils' type IPCHandler = (event: Electron.IpcRendererEvent, ...args: any[]) => any export const handle = function (channel: string, handler: T) { - ipcRendererInternal.on(channel, (event, requestId, ...args) => { - new Promise(resolve => resolve(handler(event, ...args)) - ).then(result => { - return [null, result] - }, error => { - return [errorUtils.serialize(error)] - }).then(responseArgs => { - event.sender.send(`${channel}_RESPONSE_${requestId}`, ...responseArgs) - }) - }) -} - -let nextId = 0 - -export function invoke (command: string, ...args: any[]) { - return new Promise((resolve, reject) => { - const requestId = ++nextId - ipcRendererInternal.once(`${command}_RESPONSE_${requestId}`, ( - _event, error: Electron.SerializedError, result: any - ) => { - if (error) { - reject(errorUtils.deserialize(error)) - } else { - resolve(result) - } - }) - ipcRendererInternal.send(command, requestId, ...args) + ipcRendererInternal.on(channel, async (event, requestId, ...args) => { + const replyChannel = `${channel}_RESPONSE_${requestId}` + try { + event.sender.send(replyChannel, null, await handler(event, ...args)) + } catch (error) { + event.sender.send(replyChannel, errorUtils.serialize(error)) + } }) } export function invokeSync (command: string, ...args: any[]): T { - const [ error, result ] = ipcRendererInternal.sendSync(command, null, ...args) + const [ error, result ] = ipcRendererInternal.sendSync(command, ...args) if (error) { throw errorUtils.deserialize(error) diff --git a/lib/renderer/ipc-renderer-internal.ts b/lib/renderer/ipc-renderer-internal.ts index 8fcbe1920d4..661f7059d6a 100644 --- a/lib/renderer/ipc-renderer-internal.ts +++ b/lib/renderer/ipc-renderer-internal.ts @@ -20,3 +20,11 @@ ipcRendererInternal.sendTo = function (webContentsId, channel, ...args) { ipcRendererInternal.sendToAll = function (webContentsId, channel, ...args) { return ipc.sendTo(internal, true, webContentsId, channel, args) } + +ipcRendererInternal.invoke = async function (channel: string, ...args: any[]) { + const { error, result } = await ipc.invoke(internal, channel, args) + if (error) { + throw new Error(`Error invoking remote method '${channel}': ${error}`) + } + return result +} diff --git a/lib/renderer/security-warnings.ts b/lib/renderer/security-warnings.ts index 4e0ef4afd83..b917f8af3b5 100644 --- a/lib/renderer/security-warnings.ts +++ b/lib/renderer/security-warnings.ts @@ -1,5 +1,5 @@ import { webFrame } from 'electron' -import { invoke } from '@electron/internal/renderer/ipc-renderer-internal-utils' +import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' let shouldLog: boolean | null = null @@ -299,7 +299,7 @@ const logSecurityWarnings = function ( const getWebPreferences = async function () { try { - return invoke('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES') + return ipcRendererInternal.invoke('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES') } catch (error) { console.warn(`getLastWebPreferences() failed: ${error}`) } diff --git a/lib/renderer/web-view/guest-view-internal.ts b/lib/renderer/web-view/guest-view-internal.ts index 551e6f8539e..852826da158 100644 --- a/lib/renderer/web-view/guest-view-internal.ts +++ b/lib/renderer/web-view/guest-view-internal.ts @@ -1,6 +1,6 @@ import { webFrame, IpcMessageEvent } from 'electron' import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' -import { invoke, invokeSync } from '@electron/internal/renderer/ipc-renderer-internal-utils' +import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils' import { WebViewImpl } from '@electron/internal/renderer/web-view/web-view-impl' @@ -93,11 +93,11 @@ export function deregisterEvents (viewInstanceId: number) { } export function createGuest (params: Record): Promise { - return invoke('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params) + return ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params) } export function createGuestSync (params: Record): number { - return invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params) + return ipcRendererUtils.invokeSync('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params) } export function attachGuest ( @@ -107,7 +107,7 @@ export function attachGuest ( if (embedderFrameId < 0) { // this error should not happen. throw new Error('Invalid embedder frame') } - invoke('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', embedderFrameId, elementInstanceId, guestInstanceId, params) + ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', embedderFrameId, elementInstanceId, guestInstanceId, params) } export const guestViewInternalModule = { diff --git a/lib/renderer/web-view/web-view-attributes.ts b/lib/renderer/web-view/web-view-attributes.ts index 40e01aa3c75..4a6844acea0 100644 --- a/lib/renderer/web-view/web-view-attributes.ts +++ b/lib/renderer/web-view/web-view-attributes.ts @@ -1,4 +1,4 @@ -import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils' +import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' import { WebViewImpl } from '@electron/internal/renderer/web-view/web-view-impl' import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants' @@ -196,7 +196,7 @@ class SrcAttribute extends WebViewAttribute { const method = 'loadURL' const args = [this.getValue(), opts] - ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', guestInstanceId, method, args) + ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', guestInstanceId, method, args) } } diff --git a/lib/renderer/web-view/web-view-impl.ts b/lib/renderer/web-view/web-view-impl.ts index 014a66c3177..4cf47a7d310 100644 --- a/lib/renderer/web-view/web-view-impl.ts +++ b/lib/renderer/web-view/web-view-impl.ts @@ -1,5 +1,6 @@ import { remote, webFrame } from 'electron' +import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal' import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils' import * as guestViewInternal from '@electron/internal/renderer/web-view/guest-view-internal' import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants' @@ -253,7 +254,7 @@ export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElem const createNonBlockHandler = function (method: string) { return function (this: ElectronInternal.WebViewElement, ...args: Array) { - return ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args) + return ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args) } } diff --git a/lib/renderer/window-setup.ts b/lib/renderer/window-setup.ts index 49b6a8ba599..05d434d77a1 100644 --- a/lib/renderer/window-setup.ts +++ b/lib/renderer/window-setup.ts @@ -106,7 +106,7 @@ class LocationProxy { } private _invokeWebContentsMethod (method: string, ...args: any[]) { - return ipcRendererUtils.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, method, ...args) + return ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, method, ...args) } private _invokeWebContentsMethodSync (method: string, ...args: any[]) { @@ -158,7 +158,7 @@ class BrowserWindowProxy { } public postMessage (message: any, targetOrigin: string) { - ipcRendererUtils.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, toString(targetOrigin), window.location.origin) + ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', this.guestId, message, toString(targetOrigin), window.location.origin) } public eval (code: string) { @@ -166,11 +166,11 @@ class BrowserWindowProxy { } private _invokeWindowMethod (method: string, ...args: any[]) { - return ipcRendererUtils.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, method, ...args) + return ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', this.guestId, method, ...args) } private _invokeWebContentsMethod (method: string, ...args: any[]) { - return ipcRendererUtils.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, method, ...args) + return ipcRendererInternal.invoke('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', this.guestId, method, ...args) } } diff --git a/shell/browser/api/atom_api_web_contents.cc b/shell/browser/api/atom_api_web_contents.cc index ed3df93c899..641822fc83f 100644 --- a/shell/browser/api/atom_api_web_contents.cc +++ b/shell/browser/api/atom_api_web_contents.cc @@ -976,12 +976,13 @@ void WebContents::Message(bool internal, internal, channel, std::move(arguments)); } -void WebContents::Invoke(const std::string& channel, +void WebContents::Invoke(bool internal, + const std::string& channel, base::Value arguments, InvokeCallback callback) { - // webContents.emit('-ipc-invoke', new Event(), channel, arguments); + // webContents.emit('-ipc-invoke', new Event(), internal, channel, arguments); EmitWithSender("-ipc-invoke", bindings_.dispatch_context(), - std::move(callback), channel, std::move(arguments)); + std::move(callback), internal, channel, std::move(arguments)); } void WebContents::MessageSync(bool internal, diff --git a/shell/browser/api/atom_api_web_contents.h b/shell/browser/api/atom_api_web_contents.h index 6ad3bf5663a..aa63546f7d3 100644 --- a/shell/browser/api/atom_api_web_contents.h +++ b/shell/browser/api/atom_api_web_contents.h @@ -491,7 +491,8 @@ class WebContents : public mate::TrackableObject, void Message(bool internal, const std::string& channel, base::Value arguments) override; - void Invoke(const std::string& channel, + void Invoke(bool internal, + const std::string& channel, base::Value arguments, InvokeCallback callback) override; void MessageSync(bool internal, diff --git a/shell/common/api/api.mojom b/shell/common/api/api.mojom index 1ea6345399e..8bf04bfe92a 100644 --- a/shell/common/api/api.mojom +++ b/shell/common/api/api.mojom @@ -42,6 +42,7 @@ interface ElectronBrowser { // Emits an event on |channel| from the ipcMain JavaScript object in the main // process, and returns the response. Invoke( + bool internal, string channel, mojo_base.mojom.ListValue arguments) => (mojo_base.mojom.Value result); diff --git a/shell/renderer/api/atom_api_renderer_ipc.cc b/shell/renderer/api/atom_api_renderer_ipc.cc index 1e3045ab445..fe64d80d1a5 100644 --- a/shell/renderer/api/atom_api_renderer_ipc.cc +++ b/shell/renderer/api/atom_api_renderer_ipc.cc @@ -73,13 +73,14 @@ class IPCRenderer : public mate::Wrappable { } v8::Local Invoke(mate::Arguments* args, + bool internal, const std::string& channel, const base::Value& arguments) { electron::util::Promise p(args->isolate()); auto handle = p.GetHandle(); electron_browser_ptr_->get()->Invoke( - channel, arguments.Clone(), + internal, channel, arguments.Clone(), base::BindOnce([](electron::util::Promise p, base::Value result) { p.Resolve(result); }, std::move(p))); diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index 8dc8604526a..ecc7f406819 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -20,7 +20,7 @@ declare namespace NodeJS { sendSync(internal: boolean, channel: string, args: any[]): any; sendToHost(channel: string, args: any[]): void; sendTo(internal: boolean, sendToAll: boolean, webContentsId: number, channel: string, args: any[]): void; - invoke(channel: string, args: any[]): Promise<{ error: string, result: T }>; + invoke(internal: boolean, channel: string, args: any[]): Promise<{ error: string, result: T }>; } interface V8UtilBinding { diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 74a10a894d4..ac87f497cc7 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -63,6 +63,7 @@ declare namespace Electron { } interface IpcRendererInternal extends Electron.IpcRenderer { + invoke(channel: string, ...args: any[]): Promise; sendToAll(webContentsId: number, channel: string, ...args: any[]): void } @@ -126,6 +127,7 @@ declare namespace ElectronInternal { } interface IpcMainInternal extends NodeJS.EventEmitter { + handle(channel: string, listener: (event: Electron.IpcMainInvokeEvent, ...args: any[]) => Promise | any): void; on(channel: string, listener: (event: IpcMainInternalEvent, ...args: any[]) => void): this; once(channel: string, listener: (event: IpcMainInternalEvent, ...args: any[]) => void): this; }