Browse Source

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

Milan Burda 5 years ago
parent
commit
81e9dab52f

+ 1 - 0
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",

+ 2 - 34
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<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', () => {})

+ 4 - 3
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}'`)
     }

+ 9 - 8
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)

+ 5 - 4
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 = {

+ 25 - 6
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(`<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}`)
   }
 

+ 36 - 13
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 (!webContentsMethodsAsync.has(method)) {
+    console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`)
+    throw new Error(`Invalid method: ${method}`)
   }
 
-  if (!webContentsMethods.has(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}`)
   }

+ 33 - 0
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<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)
+  }
+}

+ 9 - 20
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
-
-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
-      }
-    })
+type IPCHandler = (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any
+
+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)]
+    }
   })
 }
 

+ 3 - 5
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', () => {})

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

+ 2 - 2
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<ElectronInternal.GetSourcesResult[]>('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', {
+  const sources = await ipcRendererInternal.invoke<ElectronInternal.GetSourcesResult[]>('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', {
     captureWindow,
     captureScreen,
     thumbnailSize,

+ 6 - 5
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

+ 3 - 3
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(),

+ 3 - 3
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<string>('CHROME_STORAGE_READ', storageType, extensionId)
+  ipcRendererInternal.invoke<string>('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<string, any>, 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()
     })

+ 5 - 4
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<number>('ELECTRON_INSPECTOR_CONTEXT_MENU', items, isEditMenu).then(id => {
+  ipcRendererInternal.invoke<number>('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))
     }

+ 8 - 28
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 <T extends IPCHandler> (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<T> (command: string, ...args: any[]) {
-  return new Promise<T>((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<T> (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)

+ 8 - 0
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<T> (channel: string, ...args: any[]) {
+  const { error, result } = await ipc.invoke<T>(internal, channel, args)
+  if (error) {
+    throw new Error(`Error invoking remote method '${channel}': ${error}`)
+  }
+  return result
+}

+ 2 - 2
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.WebPreferences>('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES')
+    return ipcRendererInternal.invoke<Electron.WebPreferences>('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES')
   } catch (error) {
     console.warn(`getLastWebPreferences() failed: ${error}`)
   }

+ 4 - 4
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<string, any>): Promise<number> {
-  return invoke('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params)
+  return ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', params)
 }
 
 export function createGuestSync (params: Record<string, any>): 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 = {

+ 2 - 2
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)
   }
 }
 

+ 2 - 1
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<any>) {
-      return ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args)
+      return ipcRendererInternal.invoke('ELECTRON_GUEST_VIEW_MANAGER_CALL', this.getWebContentsId(), method, args)
     }
   }
 

+ 4 - 4
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)
   }
 }
 

+ 4 - 3
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,

+ 2 - 1
shell/browser/api/atom_api_web_contents.h

@@ -491,7 +491,8 @@ class WebContents : public mate::TrackableObject<WebContents>,
   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,

+ 1 - 0
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);
 

+ 2 - 1
shell/renderer/api/atom_api_renderer_ipc.cc

@@ -73,13 +73,14 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
   }
 
   v8::Local<v8::Promise> Invoke(mate::Arguments* args,
+                                bool internal,
                                 const std::string& channel,
                                 const base::Value& arguments) {
     electron::util::Promise<base::Value> p(args->isolate());
     auto handle = p.GetHandle();
 
     electron_browser_ptr_->get()->Invoke(
-        channel, arguments.Clone(),
+        internal, channel, arguments.Clone(),
         base::BindOnce([](electron::util::Promise<base::Value> p,
                           base::Value result) { p.Resolve(result); },
                        std::move(p)));

+ 1 - 1
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<T>(channel: string, args: any[]): Promise<{ error: string, result: T }>;
+    invoke<T>(internal: boolean, channel: string, args: any[]): Promise<{ error: string, result: T }>;
   }
 
   interface V8UtilBinding {

+ 2 - 0
typings/internal-electron.d.ts

@@ -63,6 +63,7 @@ declare namespace Electron {
   }
 
   interface IpcRendererInternal extends Electron.IpcRenderer {
+    invoke<T>(channel: string, ...args: any[]): Promise<T>;
     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> | any): void;
     on(channel: string, listener: (event: IpcMainInternalEvent, ...args: any[]) => void): this;
     once(channel: string, listener: (event: IpcMainInternalEvent, ...args: any[]) => void): this;
   }