refactor: replace ipcRendererUtils.invoke() with ipcRendererInternal.invoke() (#19574)

This commit is contained in:
Milan Burda 2019-08-24 00:45:50 +02:00 committed by Jeremy Apthorp
parent 698120daf0
commit 81e9dab52f
29 changed files with 195 additions and 164 deletions

View file

@ -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<string, (e: IpcMainInvokeEvent, ...args: any[]) => 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', () => {})

View file

@ -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}'`)
}

View file

@ -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)

View file

@ -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 = {

View file

@ -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(`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`)
throw new Error('<webview> 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}`)
}

View file

@ -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}`)
}

View file

@ -0,0 +1,33 @@
import { EventEmitter } from 'events'
import { IpcMainInvokeEvent } from 'electron'
export class IpcMainImpl extends EventEmitter {
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => 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)
}
}

View file

@ -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 <T extends IPCHandler> (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 <T extends IPCHandler> (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)]
}
})
}

View file

@ -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', () => {})

View file

@ -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 = []