Browse Source

build: add enable_remote_module build flag (#19821)

Milan Burda 5 years ago
parent
commit
11cd0db86b

+ 11 - 0
BUILD.gn

@@ -579,6 +579,17 @@ source_set("electron_lib") {
     ]
   }
 
+  if (enable_remote_module) {
+    sources += [
+      "shell/common/api/remote/object_life_monitor.cc",
+      "shell/common/api/remote/object_life_monitor.h",
+      "shell/common/api/remote/remote_callback_freer.cc",
+      "shell/common/api/remote/remote_callback_freer.h",
+      "shell/common/api/remote/remote_object_freer.cc",
+      "shell/common/api/remote/remote_object_freer.h",
+    ]
+  }
+
   if (enable_desktop_capturer) {
     if (is_component_build && !is_linux) {
       # On windows the implementation relies on unexported

+ 1 - 0
buildflags/BUILD.gn

@@ -12,6 +12,7 @@ buildflag_header("buildflags") {
     "ENABLE_DESKTOP_CAPTURER=$enable_desktop_capturer",
     "ENABLE_RUN_AS_NODE=$enable_run_as_node",
     "ENABLE_OSR=$enable_osr",
+    "ENABLE_REMOTE_MODULE=$enable_remote_module",
     "ENABLE_VIEW_API=$enable_view_api",
     "ENABLE_PEPPER_FLASH=$enable_pepper_flash",
     "ENABLE_PDF_VIEWER=$enable_pdf_viewer",

+ 2 - 0
buildflags/buildflags.gni

@@ -10,6 +10,8 @@ declare_args() {
 
   enable_osr = true
 
+  enable_remote_module = true
+
   enable_view_api = false
 
   enable_pdf_viewer = false

+ 13 - 12
filenames.auto.gni

@@ -133,20 +133,19 @@ auto_filenames = {
     "lib/common/api/module-list.ts",
     "lib/common/api/native-image.js",
     "lib/common/api/shell.js",
-    "lib/common/buffer-utils.ts",
     "lib/common/clipboard-utils.ts",
     "lib/common/crash-reporter.js",
     "lib/common/define-properties.ts",
     "lib/common/electron-binding-setup.ts",
     "lib/common/error-utils.ts",
-    "lib/common/is-promise.ts",
+    "lib/common/remote/buffer-utils.ts",
+    "lib/common/remote/is-promise.ts",
     "lib/common/web-view-methods.ts",
     "lib/renderer/api/crash-reporter.js",
     "lib/renderer/api/desktop-capturer.ts",
     "lib/renderer/api/ipc-renderer.ts",
     "lib/renderer/api/remote.js",
     "lib/renderer/api/web-frame.ts",
-    "lib/renderer/callbacks-registry.ts",
     "lib/renderer/chrome-api.ts",
     "lib/renderer/content-scripts-injector.ts",
     "lib/renderer/extensions/event.ts",
@@ -156,6 +155,7 @@ auto_filenames = {
     "lib/renderer/inspector.ts",
     "lib/renderer/ipc-renderer-internal-utils.ts",
     "lib/renderer/ipc-renderer-internal.ts",
+    "lib/renderer/remote/callbacks-registry.ts",
     "lib/renderer/security-warnings.ts",
     "lib/renderer/web-frame-init.ts",
     "lib/renderer/web-view/guest-view-internal.ts",
@@ -258,7 +258,8 @@ auto_filenames = {
     "lib/browser/ipc-main-internal-utils.ts",
     "lib/browser/ipc-main-internal.ts",
     "lib/browser/navigation-controller.js",
-    "lib/browser/objects-registry.js",
+    "lib/browser/remote/objects-registry.js",
+    "lib/browser/remote/server.js",
     "lib/browser/rpc-server.js",
     "lib/browser/utils.ts",
     "lib/common/api/clipboard.js",
@@ -266,15 +267,15 @@ auto_filenames = {
     "lib/common/api/module-list.ts",
     "lib/common/api/native-image.js",
     "lib/common/api/shell.js",
-    "lib/common/buffer-utils.ts",
     "lib/common/clipboard-utils.ts",
     "lib/common/crash-reporter.js",
     "lib/common/define-properties.ts",
     "lib/common/electron-binding-setup.ts",
     "lib/common/error-utils.ts",
     "lib/common/init.ts",
-    "lib/common/is-promise.ts",
     "lib/common/parse-features-string.js",
+    "lib/common/remote/buffer-utils.ts",
+    "lib/common/remote/is-promise.ts",
     "lib/common/reset-search-paths.ts",
     "lib/common/web-view-methods.ts",
     "lib/renderer/ipc-renderer-internal-utils.ts",
@@ -291,14 +292,14 @@ auto_filenames = {
     "lib/common/api/module-list.ts",
     "lib/common/api/native-image.js",
     "lib/common/api/shell.js",
-    "lib/common/buffer-utils.ts",
     "lib/common/clipboard-utils.ts",
     "lib/common/crash-reporter.js",
     "lib/common/define-properties.ts",
     "lib/common/electron-binding-setup.ts",
     "lib/common/error-utils.ts",
     "lib/common/init.ts",
-    "lib/common/is-promise.ts",
+    "lib/common/remote/buffer-utils.ts",
+    "lib/common/remote/is-promise.ts",
     "lib/common/reset-search-paths.ts",
     "lib/common/web-view-methods.ts",
     "lib/renderer/api/crash-reporter.js",
@@ -308,7 +309,6 @@ auto_filenames = {
     "lib/renderer/api/module-list.ts",
     "lib/renderer/api/remote.js",
     "lib/renderer/api/web-frame.ts",
-    "lib/renderer/callbacks-registry.ts",
     "lib/renderer/chrome-api.ts",
     "lib/renderer/content-scripts-injector.ts",
     "lib/renderer/extensions/event.ts",
@@ -319,6 +319,7 @@ auto_filenames = {
     "lib/renderer/inspector.ts",
     "lib/renderer/ipc-renderer-internal-utils.ts",
     "lib/renderer/ipc-renderer-internal.ts",
+    "lib/renderer/remote/callbacks-registry.ts",
     "lib/renderer/security-warnings.ts",
     "lib/renderer/web-frame-init.ts",
     "lib/renderer/web-view/guest-view-internal.ts",
@@ -341,14 +342,14 @@ auto_filenames = {
     "lib/common/api/module-list.ts",
     "lib/common/api/native-image.js",
     "lib/common/api/shell.js",
-    "lib/common/buffer-utils.ts",
     "lib/common/clipboard-utils.ts",
     "lib/common/crash-reporter.js",
     "lib/common/define-properties.ts",
     "lib/common/electron-binding-setup.ts",
     "lib/common/error-utils.ts",
     "lib/common/init.ts",
-    "lib/common/is-promise.ts",
+    "lib/common/remote/buffer-utils.ts",
+    "lib/common/remote/is-promise.ts",
     "lib/common/reset-search-paths.ts",
     "lib/renderer/api/crash-reporter.js",
     "lib/renderer/api/desktop-capturer.ts",
@@ -357,9 +358,9 @@ auto_filenames = {
     "lib/renderer/api/module-list.ts",
     "lib/renderer/api/remote.js",
     "lib/renderer/api/web-frame.ts",
-    "lib/renderer/callbacks-registry.ts",
     "lib/renderer/ipc-renderer-internal-utils.ts",
     "lib/renderer/ipc-renderer-internal.ts",
+    "lib/renderer/remote/callbacks-registry.ts",
     "lib/renderer/webpack-provider.ts",
     "lib/worker/init.js",
     "package.json",

+ 0 - 6
filenames.gni

@@ -440,12 +440,6 @@ filenames = {
     "shell/common/api/features.cc",
     "shell/common/api/locker.cc",
     "shell/common/api/locker.h",
-    "shell/common/api/object_life_monitor.cc",
-    "shell/common/api/object_life_monitor.h",
-    "shell/common/api/remote_callback_freer.cc",
-    "shell/common/api/remote_callback_freer.h",
-    "shell/common/api/remote_object_freer.cc",
-    "shell/common/api/remote_object_freer.h",
     "shell/common/asar/archive.cc",
     "shell/common/asar/archive.h",
     "shell/common/asar/asar_util.cc",

+ 7 - 1
lib/browser/init.ts

@@ -151,11 +151,17 @@ app._setDefaultAppPaths(packagePath)
 // Load the chrome devtools support.
 require('@electron/internal/browser/devtools')
 
+const features = process.electronBinding('features')
+
 // Load the chrome extension support.
-if (!process.electronBinding('features').isExtensionsEnabled()) {
+if (!features.isExtensionsEnabled()) {
   require('@electron/internal/browser/chrome-extension')
 }
 
+if (features.isRemoteModuleEnabled()) {
+  require('@electron/internal/browser/remote/server')
+}
+
 // Load protocol module to ensure it is populated on app ready
 require('@electron/internal/browser/api/protocol')
 

+ 0 - 0
lib/browser/objects-registry.js → lib/browser/remote/objects-registry.js


+ 471 - 0
lib/browser/remote/server.js

@@ -0,0 +1,471 @@
+'use strict'
+
+const electron = require('electron')
+const { EventEmitter } = require('events')
+
+const v8Util = process.electronBinding('v8_util')
+const eventBinding = process.electronBinding('event')
+const features = process.electronBinding('features')
+
+if (!features.isRemoteModuleEnabled()) {
+  throw new Error('remote module is disabled')
+}
+
+const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
+const objectsRegistry = require('@electron/internal/browser/remote/objects-registry')
+const guestViewManager = require('@electron/internal/browser/guest-view-manager')
+const bufferUtils = require('@electron/internal/common/remote/buffer-utils')
+const errorUtils = require('@electron/internal/common/error-utils')
+const { isPromise } = require('@electron/internal/common/remote/is-promise')
+
+const hasProp = {}.hasOwnProperty
+
+// The internal properties of Function.
+const FUNCTION_PROPERTIES = [
+  'length', 'name', 'arguments', 'caller', 'prototype'
+]
+
+// The remote functions in renderer processes.
+// id => Function
+const rendererFunctions = v8Util.createDoubleIDWeakMap()
+
+// Return the description of object's members:
+const getObjectMembers = function (object) {
+  let names = Object.getOwnPropertyNames(object)
+  // For Function, we should not override following properties even though they
+  // are "own" properties.
+  if (typeof object === 'function') {
+    names = names.filter((name) => {
+      return !FUNCTION_PROPERTIES.includes(name)
+    })
+  }
+  // Map properties to descriptors.
+  return names.map((name) => {
+    const descriptor = Object.getOwnPropertyDescriptor(object, name)
+    const member = { name, enumerable: descriptor.enumerable, writable: false }
+    if (descriptor.get === undefined && typeof object[name] === 'function') {
+      member.type = 'method'
+    } else {
+      if (descriptor.set || descriptor.writable) member.writable = true
+      member.type = 'get'
+    }
+    return member
+  })
+}
+
+// Return the description of object's prototype.
+const getObjectPrototype = function (object) {
+  const proto = Object.getPrototypeOf(object)
+  if (proto === null || proto === Object.prototype) return null
+  return {
+    members: getObjectMembers(proto),
+    proto: getObjectPrototype(proto)
+  }
+}
+
+// Convert a real value into meta data.
+const valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) {
+  // Determine the type of value.
+  const meta = { type: typeof value }
+  if (meta.type === 'object') {
+    // Recognize certain types of objects.
+    if (value === null) {
+      meta.type = 'value'
+    } else if (bufferUtils.isBuffer(value)) {
+      meta.type = 'buffer'
+    } else if (Array.isArray(value)) {
+      meta.type = 'array'
+    } else if (value instanceof Error) {
+      meta.type = 'error'
+    } else if (value instanceof Date) {
+      meta.type = 'date'
+    } else if (isPromise(value)) {
+      meta.type = 'promise'
+    } else if (hasProp.call(value, 'callee') && value.length != null) {
+      // Treat the arguments object as array.
+      meta.type = 'array'
+    } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
+      // Treat simple objects as value.
+      meta.type = 'value'
+    }
+  }
+
+  // Fill the meta object according to value's type.
+  if (meta.type === 'array') {
+    meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
+  } else if (meta.type === 'object' || meta.type === 'function') {
+    meta.name = value.constructor ? value.constructor.name : ''
+
+    // Reference the original value if it's an object, because when it's
+    // passed to renderer we would assume the renderer keeps a reference of
+    // it.
+    meta.id = objectsRegistry.add(sender, contextId, value)
+    meta.members = getObjectMembers(value)
+    meta.proto = getObjectPrototype(value)
+  } else if (meta.type === 'buffer') {
+    meta.value = bufferUtils.bufferToMeta(value)
+  } else if (meta.type === 'promise') {
+    // Add default handler to prevent unhandled rejections in main process
+    // Instead they should appear in the renderer process
+    value.then(function () {}, function () {})
+
+    meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) {
+      value.then(onFulfilled, onRejected)
+    })
+  } else if (meta.type === 'error') {
+    meta.members = plainObjectToMeta(value)
+
+    // Error.name is not part of own properties.
+    meta.members.push({
+      name: 'name',
+      value: value.name
+    })
+  } else if (meta.type === 'date') {
+    meta.value = value.getTime()
+  } else {
+    meta.type = 'value'
+    meta.value = value
+  }
+  return meta
+}
+
+// Convert object to meta by value.
+const plainObjectToMeta = function (obj) {
+  return Object.getOwnPropertyNames(obj).map(function (name) {
+    return {
+      name: name,
+      value: obj[name]
+    }
+  })
+}
+
+// Convert Error into meta data.
+const exceptionToMeta = function (error) {
+  return {
+    type: 'exception',
+    value: errorUtils.serialize(error)
+  }
+}
+
+const throwRPCError = function (message) {
+  const error = new Error(message)
+  error.code = 'EBADRPC'
+  error.errno = -72
+  throw error
+}
+
+const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
+  const location = v8Util.getHiddenValue(callIntoRenderer, 'location')
+  let message = `Attempting to call a function in a renderer window that has been closed or released.` +
+    `\nFunction provided here: ${location}`
+
+  if (sender instanceof EventEmitter) {
+    const remoteEvents = sender.eventNames().filter((eventName) => {
+      return sender.listeners(eventName).includes(callIntoRenderer)
+    })
+
+    if (remoteEvents.length > 0) {
+      message += `\nRemote event names: ${remoteEvents.join(', ')}`
+      remoteEvents.forEach((eventName) => {
+        sender.removeListener(eventName, callIntoRenderer)
+      })
+    }
+  }
+
+  console.warn(message)
+}
+
+// Convert array of meta data from renderer into array of real values.
+const unwrapArgs = function (sender, frameId, contextId, args) {
+  const metaToValue = function (meta) {
+    switch (meta.type) {
+      case 'value':
+        return meta.value
+      case 'remote-object':
+        return objectsRegistry.get(meta.id)
+      case 'array':
+        return unwrapArgs(sender, frameId, contextId, meta.value)
+      case 'buffer':
+        return bufferUtils.metaToBuffer(meta.value)
+      case 'date':
+        return new Date(meta.value)
+      case 'promise':
+        return Promise.resolve({
+          then: metaToValue(meta.then)
+        })
+      case 'object': {
+        const ret = {}
+        Object.defineProperty(ret.constructor, 'name', { value: meta.name })
+
+        for (const { name, value } of meta.members) {
+          ret[name] = metaToValue(value)
+        }
+        return ret
+      }
+      case 'function-with-return-value':
+        const returnValue = metaToValue(meta.value)
+        return function () {
+          return returnValue
+        }
+      case 'function': {
+        // Merge contextId and meta.id, since meta.id can be the same in
+        // different webContents.
+        const objectId = [contextId, meta.id]
+
+        // Cache the callbacks in renderer.
+        if (rendererFunctions.has(objectId)) {
+          return rendererFunctions.get(objectId)
+        }
+
+        const callIntoRenderer = function (...args) {
+          let succeed = false
+          if (!sender.isDestroyed()) {
+            succeed = sender._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
+          }
+          if (!succeed) {
+            removeRemoteListenersAndLogWarning(this, callIntoRenderer)
+          }
+        }
+        v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location)
+        Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
+
+        v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender)
+        rendererFunctions.set(objectId, callIntoRenderer)
+        return callIntoRenderer
+      }
+      default:
+        throw new TypeError(`Unknown type: ${meta.type}`)
+    }
+  }
+  return args.map(metaToValue)
+}
+
+const isRemoteModuleEnabledImpl = function (contents) {
+  const webPreferences = contents.getLastWebPreferences() || {}
+  return !!webPreferences.enableRemoteModule
+}
+
+const isRemoteModuleEnabledCache = new WeakMap()
+
+const isRemoteModuleEnabled = function (contents) {
+  if (!isRemoteModuleEnabledCache.has(contents)) {
+    isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents))
+  }
+
+  return isRemoteModuleEnabledCache.get(contents)
+}
+
+const handleRemoteCommand = function (channel, handler) {
+  ipcMainInternal.on(channel, (event, contextId, ...args) => {
+    let returnValue
+    if (!isRemoteModuleEnabled(event.sender)) {
+      event.returnValue = null
+      return
+    }
+
+    try {
+      returnValue = handler(event, contextId, ...args)
+    } catch (error) {
+      returnValue = exceptionToMeta(error)
+    }
+
+    if (returnValue !== undefined) {
+      event.returnValue = returnValue
+    }
+  })
+}
+
+const emitCustomEvent = function (contents, eventName, ...args) {
+  const event = eventBinding.createWithSender(contents)
+
+  electron.app.emit(eventName, event, contents, ...args)
+  contents.emit(eventName, event, ...args)
+
+  return event
+}
+
+handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) {
+  const objectId = [passedContextId, id]
+  if (!rendererFunctions.has(objectId)) {
+    // Do nothing if the error has already been reported before.
+    return
+  }
+  removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId))
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName) {
+  const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName)
+
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error(`Blocked remote.require('${moduleName}')`)
+    } else {
+      customEvent.returnValue = process.mainModule.require(moduleName)
+    }
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName) {
+  const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName)
+
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error(`Blocked remote.getBuiltin('${moduleName}')`)
+    } else {
+      customEvent.returnValue = electron[moduleName]
+    }
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName) {
+  const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName)
+
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error(`Blocked remote.getGlobal('${globalName}')`)
+    } else {
+      customEvent.returnValue = global[globalName]
+    }
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
+  const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window')
+
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error('Blocked remote.getCurrentWindow()')
+    } else {
+      customEvent.returnValue = event.sender.getOwnerBrowserWindow()
+    }
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
+  const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents')
+
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error('Blocked remote.getCurrentWebContents()')
+    } else {
+      customEvent.returnValue = event.sender
+    }
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
+  args = unwrapArgs(event.sender, event.frameId, contextId, args)
+  const constructor = objectsRegistry.get(id)
+
+  if (constructor == null) {
+    throwRPCError(`Cannot call constructor on missing remote object ${id}`)
+  }
+
+  return valueToMeta(event.sender, contextId, new constructor(...args))
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
+  args = unwrapArgs(event.sender, event.frameId, contextId, args)
+  const func = objectsRegistry.get(id)
+
+  if (func == null) {
+    throwRPCError(`Cannot call function on missing remote object ${id}`)
+  }
+
+  try {
+    return valueToMeta(event.sender, contextId, func(...args), true)
+  } catch (error) {
+    const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`)
+    err.cause = error
+    throw err
+  }
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
+  args = unwrapArgs(event.sender, event.frameId, contextId, args)
+  const object = objectsRegistry.get(id)
+
+  if (object == null) {
+    throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
+  }
+
+  return valueToMeta(event.sender, contextId, new object[method](...args))
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
+  args = unwrapArgs(event.sender, event.frameId, contextId, args)
+  const object = objectsRegistry.get(id)
+
+  if (object == null) {
+    throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`)
+  }
+
+  try {
+    return valueToMeta(event.sender, contextId, object[method](...args), true)
+  } catch (error) {
+    const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`)
+    err.cause = error
+    throw err
+  }
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
+  args = unwrapArgs(event.sender, event.frameId, contextId, args)
+  const obj = objectsRegistry.get(id)
+
+  if (obj == null) {
+    throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`)
+  }
+
+  obj[name] = args[0]
+  return null
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
+  const obj = objectsRegistry.get(id)
+
+  if (obj == null) {
+    throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`)
+  }
+
+  return valueToMeta(event.sender, contextId, obj[name])
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id, rendererSideRefCount) {
+  objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount)
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
+  objectsRegistry.clear(event.sender, contextId)
+  return null
+})
+
+handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
+  const guest = guestViewManager.getGuestForWebContents(guestInstanceId, event.sender)
+
+  const customEvent = emitCustomEvent(event.sender, 'remote-get-guest-web-contents', guest)
+
+  if (customEvent.returnValue === undefined) {
+    if (customEvent.defaultPrevented) {
+      throw new Error(`Blocked remote.getGuestForWebContents()`)
+    } else {
+      customEvent.returnValue = guest
+    }
+  }
+
+  return valueToMeta(event.sender, contextId, customEvent.returnValue)
+})
+
+module.exports = {
+  isRemoteModuleEnabled
+}

+ 6 - 446
lib/browser/rpc-server.js

@@ -1,10 +1,8 @@
 'use strict'
 
 const electron = require('electron')
-const { EventEmitter } = require('events')
 const fs = require('fs')
 
-const v8Util = process.electronBinding('v8_util')
 const eventBinding = process.electronBinding('event')
 const clipboard = process.electronBinding('clipboard')
 const features = process.electronBinding('features')
@@ -12,269 +10,9 @@ const features = process.electronBinding('features')
 const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init')
 const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
 const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
-const objectsRegistry = require('@electron/internal/browser/objects-registry')
 const guestViewManager = require('@electron/internal/browser/guest-view-manager')
-const bufferUtils = require('@electron/internal/common/buffer-utils')
 const errorUtils = require('@electron/internal/common/error-utils')
 const clipboardUtils = require('@electron/internal/common/clipboard-utils')
-const { isPromise } = require('@electron/internal/common/is-promise')
-
-const hasProp = {}.hasOwnProperty
-
-// The internal properties of Function.
-const FUNCTION_PROPERTIES = [
-  'length', 'name', 'arguments', 'caller', 'prototype'
-]
-
-// The remote functions in renderer processes.
-// id => Function
-const rendererFunctions = v8Util.createDoubleIDWeakMap()
-
-// Return the description of object's members:
-const getObjectMembers = function (object) {
-  let names = Object.getOwnPropertyNames(object)
-  // For Function, we should not override following properties even though they
-  // are "own" properties.
-  if (typeof object === 'function') {
-    names = names.filter((name) => {
-      return !FUNCTION_PROPERTIES.includes(name)
-    })
-  }
-  // Map properties to descriptors.
-  return names.map((name) => {
-    const descriptor = Object.getOwnPropertyDescriptor(object, name)
-    const member = { name, enumerable: descriptor.enumerable, writable: false }
-    if (descriptor.get === undefined && typeof object[name] === 'function') {
-      member.type = 'method'
-    } else {
-      if (descriptor.set || descriptor.writable) member.writable = true
-      member.type = 'get'
-    }
-    return member
-  })
-}
-
-// Return the description of object's prototype.
-const getObjectPrototype = function (object) {
-  const proto = Object.getPrototypeOf(object)
-  if (proto === null || proto === Object.prototype) return null
-  return {
-    members: getObjectMembers(proto),
-    proto: getObjectPrototype(proto)
-  }
-}
-
-// Convert a real value into meta data.
-const valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) {
-  // Determine the type of value.
-  const meta = { type: typeof value }
-  if (meta.type === 'object') {
-    // Recognize certain types of objects.
-    if (value === null) {
-      meta.type = 'value'
-    } else if (bufferUtils.isBuffer(value)) {
-      meta.type = 'buffer'
-    } else if (Array.isArray(value)) {
-      meta.type = 'array'
-    } else if (value instanceof Error) {
-      meta.type = 'error'
-    } else if (value instanceof Date) {
-      meta.type = 'date'
-    } else if (isPromise(value)) {
-      meta.type = 'promise'
-    } else if (hasProp.call(value, 'callee') && value.length != null) {
-      // Treat the arguments object as array.
-      meta.type = 'array'
-    } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
-      // Treat simple objects as value.
-      meta.type = 'value'
-    }
-  }
-
-  // Fill the meta object according to value's type.
-  if (meta.type === 'array') {
-    meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
-  } else if (meta.type === 'object' || meta.type === 'function') {
-    meta.name = value.constructor ? value.constructor.name : ''
-
-    // Reference the original value if it's an object, because when it's
-    // passed to renderer we would assume the renderer keeps a reference of
-    // it.
-    meta.id = objectsRegistry.add(sender, contextId, value)
-    meta.members = getObjectMembers(value)
-    meta.proto = getObjectPrototype(value)
-  } else if (meta.type === 'buffer') {
-    meta.value = bufferUtils.bufferToMeta(value)
-  } else if (meta.type === 'promise') {
-    // Add default handler to prevent unhandled rejections in main process
-    // Instead they should appear in the renderer process
-    value.then(function () {}, function () {})
-
-    meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) {
-      value.then(onFulfilled, onRejected)
-    })
-  } else if (meta.type === 'error') {
-    meta.members = plainObjectToMeta(value)
-
-    // Error.name is not part of own properties.
-    meta.members.push({
-      name: 'name',
-      value: value.name
-    })
-  } else if (meta.type === 'date') {
-    meta.value = value.getTime()
-  } else {
-    meta.type = 'value'
-    meta.value = value
-  }
-  return meta
-}
-
-// Convert object to meta by value.
-const plainObjectToMeta = function (obj) {
-  return Object.getOwnPropertyNames(obj).map(function (name) {
-    return {
-      name: name,
-      value: obj[name]
-    }
-  })
-}
-
-// Convert Error into meta data.
-const exceptionToMeta = function (error) {
-  return {
-    type: 'exception',
-    value: errorUtils.serialize(error)
-  }
-}
-
-const throwRPCError = function (message) {
-  const error = new Error(message)
-  error.code = 'EBADRPC'
-  error.errno = -72
-  throw error
-}
-
-const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
-  const location = v8Util.getHiddenValue(callIntoRenderer, 'location')
-  let message = `Attempting to call a function in a renderer window that has been closed or released.` +
-    `\nFunction provided here: ${location}`
-
-  if (sender instanceof EventEmitter) {
-    const remoteEvents = sender.eventNames().filter((eventName) => {
-      return sender.listeners(eventName).includes(callIntoRenderer)
-    })
-
-    if (remoteEvents.length > 0) {
-      message += `\nRemote event names: ${remoteEvents.join(', ')}`
-      remoteEvents.forEach((eventName) => {
-        sender.removeListener(eventName, callIntoRenderer)
-      })
-    }
-  }
-
-  console.warn(message)
-}
-
-// Convert array of meta data from renderer into array of real values.
-const unwrapArgs = function (sender, frameId, contextId, args) {
-  const metaToValue = function (meta) {
-    switch (meta.type) {
-      case 'value':
-        return meta.value
-      case 'remote-object':
-        return objectsRegistry.get(meta.id)
-      case 'array':
-        return unwrapArgs(sender, frameId, contextId, meta.value)
-      case 'buffer':
-        return bufferUtils.metaToBuffer(meta.value)
-      case 'date':
-        return new Date(meta.value)
-      case 'promise':
-        return Promise.resolve({
-          then: metaToValue(meta.then)
-        })
-      case 'object': {
-        const ret = {}
-        Object.defineProperty(ret.constructor, 'name', { value: meta.name })
-
-        for (const { name, value } of meta.members) {
-          ret[name] = metaToValue(value)
-        }
-        return ret
-      }
-      case 'function-with-return-value':
-        const returnValue = metaToValue(meta.value)
-        return function () {
-          return returnValue
-        }
-      case 'function': {
-        // Merge contextId and meta.id, since meta.id can be the same in
-        // different webContents.
-        const objectId = [contextId, meta.id]
-
-        // Cache the callbacks in renderer.
-        if (rendererFunctions.has(objectId)) {
-          return rendererFunctions.get(objectId)
-        }
-
-        const callIntoRenderer = function (...args) {
-          let succeed = false
-          if (!sender.isDestroyed()) {
-            succeed = sender._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
-          }
-          if (!succeed) {
-            removeRemoteListenersAndLogWarning(this, callIntoRenderer)
-          }
-        }
-        v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location)
-        Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
-
-        v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender)
-        rendererFunctions.set(objectId, callIntoRenderer)
-        return callIntoRenderer
-      }
-      default:
-        throw new TypeError(`Unknown type: ${meta.type}`)
-    }
-  }
-  return args.map(metaToValue)
-}
-
-const isRemoteModuleEnabledImpl = function (contents) {
-  const webPreferences = contents.getLastWebPreferences() || {}
-  return !!webPreferences.enableRemoteModule
-}
-
-const isRemoteModuleEnabledCache = new WeakMap()
-
-const isRemoteModuleEnabled = function (contents) {
-  if (!isRemoteModuleEnabledCache.has(contents)) {
-    isRemoteModuleEnabledCache.set(contents, isRemoteModuleEnabledImpl(contents))
-  }
-
-  return isRemoteModuleEnabledCache.get(contents)
-}
-
-const handleRemoteCommand = function (channel, handler) {
-  ipcMainInternal.on(channel, (event, contextId, ...args) => {
-    let returnValue
-    if (!isRemoteModuleEnabled(event.sender)) {
-      event.returnValue = null
-      return
-    }
-
-    try {
-      returnValue = handler(event, contextId, ...args)
-    } catch (error) {
-      returnValue = exceptionToMeta(error)
-    }
-
-    if (returnValue !== undefined) {
-      event.returnValue = returnValue
-    }
-  })
-}
 
 const emitCustomEvent = function (contents, eventName, ...args) {
   const event = eventBinding.createWithSender(contents)
@@ -285,188 +23,6 @@ const emitCustomEvent = function (contents, eventName, ...args) {
   return event
 }
 
-handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) {
-  const objectId = [passedContextId, id]
-  if (!rendererFunctions.has(objectId)) {
-    // Do nothing if the error has already been reported before.
-    return
-  }
-  removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId))
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName) {
-  const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName)
-
-  if (customEvent.returnValue === undefined) {
-    if (customEvent.defaultPrevented) {
-      throw new Error(`Blocked remote.require('${moduleName}')`)
-    } else {
-      customEvent.returnValue = process.mainModule.require(moduleName)
-    }
-  }
-
-  return valueToMeta(event.sender, contextId, customEvent.returnValue)
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName) {
-  const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName)
-
-  if (customEvent.returnValue === undefined) {
-    if (customEvent.defaultPrevented) {
-      throw new Error(`Blocked remote.getBuiltin('${moduleName}')`)
-    } else {
-      customEvent.returnValue = electron[moduleName]
-    }
-  }
-
-  return valueToMeta(event.sender, contextId, customEvent.returnValue)
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName) {
-  const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName)
-
-  if (customEvent.returnValue === undefined) {
-    if (customEvent.defaultPrevented) {
-      throw new Error(`Blocked remote.getGlobal('${globalName}')`)
-    } else {
-      customEvent.returnValue = global[globalName]
-    }
-  }
-
-  return valueToMeta(event.sender, contextId, customEvent.returnValue)
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
-  const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window')
-
-  if (customEvent.returnValue === undefined) {
-    if (customEvent.defaultPrevented) {
-      throw new Error('Blocked remote.getCurrentWindow()')
-    } else {
-      customEvent.returnValue = event.sender.getOwnerBrowserWindow()
-    }
-  }
-
-  return valueToMeta(event.sender, contextId, customEvent.returnValue)
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
-  const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents')
-
-  if (customEvent.returnValue === undefined) {
-    if (customEvent.defaultPrevented) {
-      throw new Error('Blocked remote.getCurrentWebContents()')
-    } else {
-      customEvent.returnValue = event.sender
-    }
-  }
-
-  return valueToMeta(event.sender, contextId, customEvent.returnValue)
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
-  args = unwrapArgs(event.sender, event.frameId, contextId, args)
-  const constructor = objectsRegistry.get(id)
-
-  if (constructor == null) {
-    throwRPCError(`Cannot call constructor on missing remote object ${id}`)
-  }
-
-  return valueToMeta(event.sender, contextId, new constructor(...args))
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
-  args = unwrapArgs(event.sender, event.frameId, contextId, args)
-  const func = objectsRegistry.get(id)
-
-  if (func == null) {
-    throwRPCError(`Cannot call function on missing remote object ${id}`)
-  }
-
-  try {
-    return valueToMeta(event.sender, contextId, func(...args), true)
-  } catch (error) {
-    const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`)
-    err.cause = error
-    throw err
-  }
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
-  args = unwrapArgs(event.sender, event.frameId, contextId, args)
-  const object = objectsRegistry.get(id)
-
-  if (object == null) {
-    throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
-  }
-
-  return valueToMeta(event.sender, contextId, new object[method](...args))
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
-  args = unwrapArgs(event.sender, event.frameId, contextId, args)
-  const object = objectsRegistry.get(id)
-
-  if (object == null) {
-    throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`)
-  }
-
-  try {
-    return valueToMeta(event.sender, contextId, object[method](...args), true)
-  } catch (error) {
-    const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`)
-    err.cause = error
-    throw err
-  }
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
-  args = unwrapArgs(event.sender, event.frameId, contextId, args)
-  const obj = objectsRegistry.get(id)
-
-  if (obj == null) {
-    throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`)
-  }
-
-  obj[name] = args[0]
-  return null
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
-  const obj = objectsRegistry.get(id)
-
-  if (obj == null) {
-    throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`)
-  }
-
-  return valueToMeta(event.sender, contextId, obj[name])
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id, rendererSideRefCount) {
-  objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount)
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
-  objectsRegistry.clear(event.sender, contextId)
-  return null
-})
-
-handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
-  const guest = guestViewManager.getGuestForWebContents(guestInstanceId, event.sender)
-
-  const customEvent = emitCustomEvent(event.sender, 'remote-get-guest-web-contents', guest)
-
-  if (customEvent.returnValue === undefined) {
-    if (customEvent.defaultPrevented) {
-      throw new Error(`Blocked remote.getGuestForWebContents()`)
-    } else {
-      customEvent.returnValue = guest
-    }
-  }
-
-  return valueToMeta(event.sender, contextId, customEvent.returnValue)
-})
-
 // Implements window.close()
 ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
   const window = event.sender.getOwnerBrowserWindow()
@@ -519,6 +75,10 @@ if (features.isDesktopCapturerEnabled()) {
   })
 }
 
+const isRemoteModuleEnabled = features.isRemoteModuleEnabled()
+  ? require('@electron/internal/browser/remote/server').isRemoteModuleEnabled
+  : () => false
+
 const getPreloadScript = async function (preloadPath) {
   let preloadSrc = null
   let preloadError = null
@@ -530,7 +90,7 @@ const getPreloadScript = async function (preloadPath) {
   return { preloadPath, preloadSrc, preloadError }
 }
 
-if (process.electronBinding('features').isExtensionsEnabled()) {
+if (features.isExtensionsEnabled()) {
   ipcMainUtils.handleSync('ELECTRON_GET_CONTENT_SCRIPTS', () => [])
 } else {
   const { getContentScripts } = require('@electron/internal/browser/chrome-extension')
@@ -541,7 +101,7 @@ ipcMainUtils.handleSync('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event)
   const preloadPaths = event.sender._getPreloadPaths()
 
   let contentScripts = []
-  if (!process.electronBinding('features').isExtensionsEnabled()) {
+  if (!features.isExtensionsEnabled()) {
     const { getContentScripts } = require('@electron/internal/browser/chrome-extension')
     contentScripts = getContentScripts()
   }

+ 0 - 0
lib/common/buffer-utils.ts → lib/common/remote/buffer-utils.ts


+ 0 - 0
lib/common/is-promise.ts → lib/common/remote/is-promise.ts


+ 1 - 1
lib/renderer/api/module-list.ts

@@ -14,6 +14,6 @@ if (features.isDesktopCapturerEnabled()) {
   rendererModuleList.push({ name: 'desktopCapturer', loader: () => require('./desktop-capturer') })
 }
 
-if (enableRemoteModule) {
+if (features.isRemoteModuleEnabled() && enableRemoteModule) {
   rendererModuleList.push({ name: 'remote', loader: () => require('./remote') })
 }

+ 3 - 3
lib/renderer/api/remote.js

@@ -2,10 +2,10 @@
 
 const v8Util = process.electronBinding('v8_util')
 
-const { CallbacksRegistry } = require('@electron/internal/renderer/callbacks-registry')
-const bufferUtils = require('@electron/internal/common/buffer-utils')
+const { CallbacksRegistry } = require('@electron/internal/renderer/remote/callbacks-registry')
+const bufferUtils = require('@electron/internal/common/remote/buffer-utils')
 const errorUtils = require('@electron/internal/common/error-utils')
-const { isPromise } = require('@electron/internal/common/is-promise')
+const { isPromise } = require('@electron/internal/common/remote/is-promise')
 const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal')
 
 const callbacksRegistry = new CallbacksRegistry()

+ 0 - 0
lib/renderer/callbacks-registry.ts → lib/renderer/remote/callbacks-registry.ts


+ 1 - 1
lib/sandboxed_renderer/api/module-list.ts

@@ -32,7 +32,7 @@ if (features.isDesktopCapturerEnabled()) {
   })
 }
 
-if (process.isRemoteModuleEnabled) {
+if (features.isRemoteModuleEnabled() && process.isRemoteModuleEnabled) {
   moduleList.push({
     name: 'remote',
     loader: () => require('@electron/internal/renderer/api/remote')

+ 5 - 0
shell/browser/web_contents_preferences.cc

@@ -18,6 +18,7 @@
 #include "content/public/browser/render_process_host.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/common/web_preferences.h"
+#include "electron/buildflags/buildflags.h"
 #include "native_mate/dictionary.h"
 #include "net/base/filename_util.h"
 #include "services/service_manager/sandbox/switches.h"
@@ -170,7 +171,9 @@ WebContentsPreferences::~WebContentsPreferences() {
 }
 
 void WebContentsPreferences::SetDefaults() {
+#if BUILDFLAG(ENABLE_REMOTE_MODULE)
   SetDefaultBoolIfUndefined(options::kEnableRemoteModule, true);
+#endif
 
   if (IsEnabled(options::kSandbox)) {
     SetBool(options::kNativeWindowOpen, true);
@@ -323,9 +326,11 @@ void WebContentsPreferences::AppendCommandLineSwitches(
     }
   }
 
+#if BUILDFLAG(ENABLE_REMOTE_MODULE)
   // Whether to enable the remote module
   if (IsEnabled(options::kEnableRemoteModule))
     command_line->AppendSwitch(switches::kEnableRemoteModule);
+#endif
 
   // Run Electron APIs and preload script in isolated world
   if (IsEnabled(options::kContextIsolation))

+ 9 - 3
shell/common/api/atom_api_v8_util.cc

@@ -6,16 +6,20 @@
 #include <utility>
 
 #include "base/hash/hash.h"
+#include "electron/buildflags/buildflags.h"
 #include "native_mate/dictionary.h"
-#include "shell/common/api/atom_api_key_weak_map.h"
-#include "shell/common/api/remote_callback_freer.h"
-#include "shell/common/api/remote_object_freer.h"
 #include "shell/common/native_mate_converters/content_converter.h"
 #include "shell/common/native_mate_converters/gurl_converter.h"
 #include "shell/common/node_includes.h"
 #include "url/origin.h"
 #include "v8/include/v8-profiler.h"
 
+#if BUILDFLAG(ENABLE_REMOTE_MODULE)
+#include "shell/common/api/atom_api_key_weak_map.h"
+#include "shell/common/api/remote/remote_callback_freer.h"
+#include "shell/common/api/remote/remote_object_freer.h"
+#endif
+
 namespace std {
 
 // The hash function used by DoubleIDWeakMap.
@@ -117,6 +121,7 @@ void Initialize(v8::Local<v8::Object> exports,
   dict.SetMethod("deleteHiddenValue", &DeleteHiddenValue);
   dict.SetMethod("getObjectHash", &GetObjectHash);
   dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot);
+#if BUILDFLAG(ENABLE_REMOTE_MODULE)
   dict.SetMethod("setRemoteCallbackFreer",
                  &electron::RemoteCallbackFreer::BindTo);
   dict.SetMethod("setRemoteObjectFreer", &electron::RemoteObjectFreer::BindTo);
@@ -126,6 +131,7 @@ void Initialize(v8::Local<v8::Object> exports,
   dict.SetMethod(
       "createDoubleIDWeakMap",
       &electron::api::KeyWeakMap<std::pair<std::string, int32_t>>::Create);
+#endif
   dict.SetMethod("requestGarbageCollectionForTesting",
                  &RequestGarbageCollectionForTesting);
   dict.SetMethod("isSameOrigin", &IsSameOrigin);

+ 5 - 0
shell/common/api/features.cc

@@ -17,6 +17,10 @@ bool IsOffscreenRenderingEnabled() {
   return BUILDFLAG(ENABLE_OSR);
 }
 
+bool IsRemoteModuleEnabled() {
+  return BUILDFLAG(ENABLE_REMOTE_MODULE);
+}
+
 bool IsPDFViewerEnabled() {
   return BUILDFLAG(ENABLE_PDF_VIEWER);
 }
@@ -64,6 +68,7 @@ void Initialize(v8::Local<v8::Object> exports,
   mate::Dictionary dict(context->GetIsolate(), exports);
   dict.SetMethod("isDesktopCapturerEnabled", &IsDesktopCapturerEnabled);
   dict.SetMethod("isOffscreenRenderingEnabled", &IsOffscreenRenderingEnabled);
+  dict.SetMethod("isRemoteModuleEnabled", &IsRemoteModuleEnabled);
   dict.SetMethod("isPDFViewerEnabled", &IsPDFViewerEnabled);
   dict.SetMethod("isRunAsNodeEnabled", &IsRunAsNodeEnabled);
   dict.SetMethod("isFakeLocationProviderEnabled",

+ 1 - 1
shell/common/api/object_life_monitor.cc → shell/common/api/remote/object_life_monitor.cc

@@ -3,7 +3,7 @@
 // Use of this source code is governed by the MIT license that can be
 // found in the LICENSE file.
 
-#include "shell/common/api/object_life_monitor.h"
+#include "shell/common/api/remote/object_life_monitor.h"
 
 #include "base/bind.h"
 #include "base/message_loop/message_loop.h"

+ 3 - 3
shell/common/api/object_life_monitor.h → shell/common/api/remote/object_life_monitor.h

@@ -2,8 +2,8 @@
 // Use of this source code is governed by the MIT license that can be
 // found in the LICENSE file.
 
-#ifndef SHELL_COMMON_API_OBJECT_LIFE_MONITOR_H_
-#define SHELL_COMMON_API_OBJECT_LIFE_MONITOR_H_
+#ifndef SHELL_COMMON_API_REMOTE_OBJECT_LIFE_MONITOR_H_
+#define SHELL_COMMON_API_REMOTE_OBJECT_LIFE_MONITOR_H_
 
 #include "base/macros.h"
 #include "base/memory/weak_ptr.h"
@@ -31,4 +31,4 @@ class ObjectLifeMonitor {
 
 }  // namespace electron
 
-#endif  // SHELL_COMMON_API_OBJECT_LIFE_MONITOR_H_
+#endif  // SHELL_COMMON_API_REMOTE_OBJECT_LIFE_MONITOR_H_

+ 1 - 1
shell/common/api/remote_callback_freer.cc → shell/common/api/remote/remote_callback_freer.cc

@@ -2,7 +2,7 @@
 // Use of this source code is governed by the MIT license that can be
 // found in the LICENSE file.
 
-#include "shell/common/api/remote_callback_freer.h"
+#include "shell/common/api/remote/remote_callback_freer.h"
 
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"

+ 4 - 4
shell/common/api/remote_callback_freer.h → shell/common/api/remote/remote_callback_freer.h

@@ -2,13 +2,13 @@
 // Use of this source code is governed by the MIT license that can be
 // found in the LICENSE file.
 
-#ifndef SHELL_COMMON_API_REMOTE_CALLBACK_FREER_H_
-#define SHELL_COMMON_API_REMOTE_CALLBACK_FREER_H_
+#ifndef SHELL_COMMON_API_REMOTE_REMOTE_CALLBACK_FREER_H_
+#define SHELL_COMMON_API_REMOTE_REMOTE_CALLBACK_FREER_H_
 
 #include <string>
 
 #include "content/public/browser/web_contents_observer.h"
-#include "shell/common/api/object_life_monitor.h"
+#include "shell/common/api/remote/object_life_monitor.h"
 
 namespace electron {
 
@@ -43,4 +43,4 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
 
 }  // namespace electron
 
-#endif  // SHELL_COMMON_API_REMOTE_CALLBACK_FREER_H_
+#endif  // SHELL_COMMON_API_REMOTE_REMOTE_CALLBACK_FREER_H_

+ 1 - 1
shell/common/api/remote_object_freer.cc → shell/common/api/remote/remote_object_freer.cc

@@ -2,7 +2,7 @@
 // Use of this source code is governed by the MIT license that can be
 // found in the LICENSE file.
 
-#include "shell/common/api/remote_object_freer.h"
+#include "shell/common/api/remote/remote_object_freer.h"
 
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"

+ 4 - 4
shell/common/api/remote_object_freer.h → shell/common/api/remote/remote_object_freer.h

@@ -2,13 +2,13 @@
 // Use of this source code is governed by the MIT license that can be
 // found in the LICENSE file.
 
-#ifndef SHELL_COMMON_API_REMOTE_OBJECT_FREER_H_
-#define SHELL_COMMON_API_REMOTE_OBJECT_FREER_H_
+#ifndef SHELL_COMMON_API_REMOTE_REMOTE_OBJECT_FREER_H_
+#define SHELL_COMMON_API_REMOTE_REMOTE_OBJECT_FREER_H_
 
 #include <map>
 #include <string>
 
-#include "shell/common/api/object_life_monitor.h"
+#include "shell/common/api/remote/object_life_monitor.h"
 
 namespace electron {
 
@@ -42,4 +42,4 @@ class RemoteObjectFreer : public ObjectLifeMonitor {
 
 }  // namespace electron
 
-#endif  // SHELL_COMMON_API_REMOTE_OBJECT_FREER_H_
+#endif  // SHELL_COMMON_API_REMOTE_REMOTE_OBJECT_FREER_H_

+ 8 - 4
shell/common/options_switches.cc

@@ -110,9 +110,6 @@ const char kPreloadURL[] = "preloadURL";
 // Enable the node integration.
 const char kNodeIntegration[] = "nodeIntegration";
 
-// Enable the remote module
-const char kEnableRemoteModule[] = "enableRemoteModule";
-
 // Enable context isolation of Electron APIs and preload script
 const char kContextIsolation[] = "contextIsolation";
 
@@ -176,6 +173,10 @@ const char kWebGL[] = "webgl";
 // navigation.
 const char kNavigateOnDragDrop[] = "navigateOnDragDrop";
 
+#if BUILDFLAG(ENABLE_REMOTE_MODULE)
+const char kEnableRemoteModule[] = "enableRemoteModule";
+#endif
+
 }  // namespace options
 
 namespace switches {
@@ -224,7 +225,6 @@ const char kBackgroundColor[] = "background-color";
 const char kPreloadScript[] = "preload";
 const char kPreloadScripts[] = "preload-scripts";
 const char kNodeIntegration[] = "node-integration";
-const char kEnableRemoteModule[] = "enable-remote-module";
 const char kContextIsolation[] = "context-isolation";
 const char kGuestInstanceID[] = "guest-instance-id";
 const char kOpenerID[] = "opener-id";
@@ -265,6 +265,10 @@ const char kAuthNegotiateDelegateWhitelist[] =
 // If set, include the port in generated Kerberos SPNs.
 const char kEnableAuthNegotiatePort[] = "enable-auth-negotiate-port";
 
+#if BUILDFLAG(ENABLE_REMOTE_MODULE)
+const char kEnableRemoteModule[] = "enable-remote-module";
+#endif
+
 }  // namespace switches
 
 }  // namespace electron

+ 10 - 2
shell/common/options_switches.h

@@ -5,6 +5,8 @@
 #ifndef SHELL_COMMON_OPTIONS_SWITCHES_H_
 #define SHELL_COMMON_OPTIONS_SWITCHES_H_
 
+#include "electron/buildflags/buildflags.h"
+
 namespace electron {
 
 namespace options {
@@ -58,7 +60,6 @@ extern const char kZoomFactor[];
 extern const char kPreloadScript[];
 extern const char kPreloadURL[];
 extern const char kNodeIntegration[];
-extern const char kEnableRemoteModule[];
 extern const char kContextIsolation[];
 extern const char kGuestInstanceID[];
 extern const char kExperimentalFeatures[];
@@ -83,6 +84,10 @@ extern const char kTextAreasAreResizable[];
 extern const char kWebGL[];
 extern const char kNavigateOnDragDrop[];
 
+#if BUILDFLAG(ENABLE_REMOTE_MODULE)
+extern const char kEnableRemoteModule[];
+#endif
+
 }  // namespace options
 
 // Following are actually command line switches, should be moved to other files.
@@ -107,7 +112,6 @@ extern const char kBackgroundColor[];
 extern const char kPreloadScript[];
 extern const char kPreloadScripts[];
 extern const char kNodeIntegration[];
-extern const char kEnableRemoteModule[];
 extern const char kContextIsolation[];
 extern const char kGuestInstanceID[];
 extern const char kOpenerID[];
@@ -129,6 +133,10 @@ extern const char kAuthServerWhitelist[];
 extern const char kAuthNegotiateDelegateWhitelist[];
 extern const char kEnableAuthNegotiatePort[];
 
+#if BUILDFLAG(ENABLE_REMOTE_MODULE)
+extern const char kEnableRemoteModule[];
+#endif
+
 }  // namespace switches
 
 }  // namespace electron

+ 2 - 0
shell/renderer/renderer_client_base.cc

@@ -117,10 +117,12 @@ void RendererClientBase::DidCreateScriptContext(
   gin_helper::Dictionary global(context->GetIsolate(), context->Global());
   global.SetHidden("contextId", context_id);
 
+#if BUILDFLAG(ENABLE_REMOTE_MODULE)
   auto* command_line = base::CommandLine::ForCurrentProcess();
   bool enableRemoteModule =
       command_line->HasSwitch(switches::kEnableRemoteModule);
   global.SetHidden("enableRemoteModule", enableRemoteModule);
+#endif
 }
 
 void RendererClientBase::AddRenderBindings(

+ 5 - 2
spec-main/api-callbacks-registry-spec.ts

@@ -1,7 +1,10 @@
 import { expect } from 'chai'
-import { CallbacksRegistry } from '../lib/renderer/callbacks-registry'
+import { CallbacksRegistry } from '../lib/renderer/remote/callbacks-registry'
+import { ifdescribe } from './spec-helpers';
 
-describe('CallbacksRegistry module', () => {
+const features = process.electronBinding('features')
+
+ifdescribe(features.isRemoteModuleEnabled())('CallbacksRegistry module', () => {
   let registry: CallbacksRegistry
 
   beforeEach(() => {

+ 4 - 1
spec-main/api-remote-spec.ts

@@ -1,9 +1,12 @@
 import { expect } from 'chai'
 import { closeWindow } from './window-helpers'
+import { ifdescribe } from './spec-helpers';
 
 import { BrowserWindow } from 'electron'
 
-describe('remote module', () => {
+const features = process.electronBinding('features')
+
+ifdescribe(features.isRemoteModuleEnabled())('remote module', () => {
   let w = null as unknown as BrowserWindow
   before(async () => {
     w = new BrowserWindow({show: false, webPreferences: {nodeIntegration: true}})

+ 4 - 1
spec/api-remote-spec.js

@@ -5,11 +5,14 @@ const dirtyChai = require('dirty-chai')
 const path = require('path')
 const { closeWindow } = require('./window-helpers')
 const { resolveGetters } = require('./expect-helpers')
+const { ifdescribe } = require('./spec-helpers')
 
 const { remote, ipcRenderer } = require('electron')
 const { ipcMain, BrowserWindow } = remote
 const { expect } = chai
 
+const features = process.electronBinding('features')
+
 chai.use(dirtyChai)
 
 const comparePaths = (path1, path2) => {
@@ -20,7 +23,7 @@ const comparePaths = (path1, path2) => {
   expect(path1).to.equal(path2)
 }
 
-describe('remote module', () => {
+ifdescribe(features.isRemoteModuleEnabled())('remote module', () => {
   const fixtures = path.join(__dirname, 'fixtures')
 
   describe('remote.require', () => {

+ 2 - 0
spec/spec-helpers.js

@@ -0,0 +1,2 @@
+exports.ifit = (condition) => (condition ? it : it.skip)
+exports.ifdescribe = (condition) => (condition ? describe : describe.skip)

+ 1 - 0
typings/internal-ambient.d.ts

@@ -4,6 +4,7 @@ declare namespace NodeJS {
   interface FeaturesBinding {
     isDesktopCapturerEnabled(): boolean;
     isOffscreenRenderingEnabled(): boolean;
+    isRemoteModuleEnabled(): boolean;
     isPDFViewerEnabled(): boolean;
     isRunAsNodeEnabled(): boolean;
     isFakeLocationProviderEnabled(): boolean;