diff --git a/BUILD.gn b/BUILD.gn index 282a5bac3032..c791fafd327b 100644 --- a/BUILD.gn +++ b/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 diff --git a/buildflags/BUILD.gn b/buildflags/BUILD.gn index 03f1a508a0f5..2da174f3e9c6 100644 --- a/buildflags/BUILD.gn +++ b/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", diff --git a/buildflags/buildflags.gni b/buildflags/buildflags.gni index 3f7f3f2a62e2..a4d4a487144e 100644 --- a/buildflags/buildflags.gni +++ b/buildflags/buildflags.gni @@ -10,6 +10,8 @@ declare_args() { enable_osr = true + enable_remote_module = true + enable_view_api = false enable_pdf_viewer = false diff --git a/filenames.auto.gni b/filenames.auto.gni index 536a9bd270ec..7bdc3602c83a 100644 --- a/filenames.auto.gni +++ b/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", diff --git a/filenames.gni b/filenames.gni index 0bbc17f3b671..29f345e0a53b 100644 --- a/filenames.gni +++ b/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", diff --git a/lib/browser/init.ts b/lib/browser/init.ts index f75698b3da36..47a3924d4b9e 100644 --- a/lib/browser/init.ts +++ b/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') diff --git a/lib/browser/objects-registry.js b/lib/browser/remote/objects-registry.js similarity index 100% rename from lib/browser/objects-registry.js rename to lib/browser/remote/objects-registry.js diff --git a/lib/browser/remote/server.js b/lib/browser/remote/server.js new file mode 100644 index 000000000000..9b9f306777ec --- /dev/null +++ b/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 +} diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index 95deb097795f..a56c295e97e9 100644 --- a/lib/browser/rpc-server.js +++ b/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() } diff --git a/lib/common/buffer-utils.ts b/lib/common/remote/buffer-utils.ts similarity index 100% rename from lib/common/buffer-utils.ts rename to lib/common/remote/buffer-utils.ts diff --git a/lib/common/is-promise.ts b/lib/common/remote/is-promise.ts similarity index 100% rename from lib/common/is-promise.ts rename to lib/common/remote/is-promise.ts diff --git a/lib/renderer/api/module-list.ts b/lib/renderer/api/module-list.ts index f396540d5108..a29b4b4dcf08 100644 --- a/lib/renderer/api/module-list.ts +++ b/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') }) } diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index 680b748f2d8c..94c2bcce336e 100644 --- a/lib/renderer/api/remote.js +++ b/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() diff --git a/lib/renderer/callbacks-registry.ts b/lib/renderer/remote/callbacks-registry.ts similarity index 100% rename from lib/renderer/callbacks-registry.ts rename to lib/renderer/remote/callbacks-registry.ts diff --git a/lib/sandboxed_renderer/api/module-list.ts b/lib/sandboxed_renderer/api/module-list.ts index 5cd3c21b8c6e..defe19000072 100644 --- a/lib/sandboxed_renderer/api/module-list.ts +++ b/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') diff --git a/shell/browser/web_contents_preferences.cc b/shell/browser/web_contents_preferences.cc index 0b07b1402e8e..e24764177332 100644 --- a/shell/browser/web_contents_preferences.cc +++ b/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)) diff --git a/shell/common/api/atom_api_v8_util.cc b/shell/common/api/atom_api_v8_util.cc index 6cfd18287f6d..6de686f420d6 100644 --- a/shell/common/api/atom_api_v8_util.cc +++ b/shell/common/api/atom_api_v8_util.cc @@ -6,16 +6,20 @@ #include #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 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 exports, dict.SetMethod( "createDoubleIDWeakMap", &electron::api::KeyWeakMap>::Create); +#endif dict.SetMethod("requestGarbageCollectionForTesting", &RequestGarbageCollectionForTesting); dict.SetMethod("isSameOrigin", &IsSameOrigin); diff --git a/shell/common/api/features.cc b/shell/common/api/features.cc index bf3a82109d3e..b95e87ea43ca 100644 --- a/shell/common/api/features.cc +++ b/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 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", diff --git a/shell/common/api/object_life_monitor.cc b/shell/common/api/remote/object_life_monitor.cc similarity index 94% rename from shell/common/api/object_life_monitor.cc rename to shell/common/api/remote/object_life_monitor.cc index a1e93e14c858..17f3d18de30d 100644 --- a/shell/common/api/object_life_monitor.cc +++ b/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" diff --git a/shell/common/api/object_life_monitor.h b/shell/common/api/remote/object_life_monitor.h similarity index 81% rename from shell/common/api/object_life_monitor.h rename to shell/common/api/remote/object_life_monitor.h index 0b11d1cb9d71..bde07434a90a 100644 --- a/shell/common/api/object_life_monitor.h +++ b/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_ diff --git a/shell/common/api/remote_callback_freer.cc b/shell/common/api/remote/remote_callback_freer.cc similarity index 97% rename from shell/common/api/remote_callback_freer.cc rename to shell/common/api/remote/remote_callback_freer.cc index a4e5ef4328b3..8f468d9b8863 100644 --- a/shell/common/api/remote_callback_freer.cc +++ b/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" diff --git a/shell/common/api/remote_callback_freer.h b/shell/common/api/remote/remote_callback_freer.h similarity index 83% rename from shell/common/api/remote_callback_freer.h rename to shell/common/api/remote/remote_callback_freer.h index aa4278c24ecf..14c6e6c76357 100644 --- a/shell/common/api/remote_callback_freer.h +++ b/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 #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_ diff --git a/shell/common/api/remote_object_freer.cc b/shell/common/api/remote/remote_object_freer.cc similarity index 98% rename from shell/common/api/remote_object_freer.cc rename to shell/common/api/remote/remote_object_freer.cc index 813ba6c8b609..68627b8386b1 100644 --- a/shell/common/api/remote_object_freer.cc +++ b/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" diff --git a/shell/common/api/remote_object_freer.h b/shell/common/api/remote/remote_object_freer.h similarity index 82% rename from shell/common/api/remote_object_freer.h rename to shell/common/api/remote/remote_object_freer.h index 6eaab698d7d1..987911e78936 100644 --- a/shell/common/api/remote_object_freer.h +++ b/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 #include -#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_ diff --git a/shell/common/options_switches.cc b/shell/common/options_switches.cc index e3208e8e8991..7499ecfd246b 100644 --- a/shell/common/options_switches.cc +++ b/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 diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h index 4ece8e122b48..e562910ee00e 100644 --- a/shell/common/options_switches.h +++ b/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 diff --git a/shell/renderer/renderer_client_base.cc b/shell/renderer/renderer_client_base.cc index 8972e526b5c5..6a459781b2c5 100644 --- a/shell/renderer/renderer_client_base.cc +++ b/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( diff --git a/spec-main/api-callbacks-registry-spec.ts b/spec-main/api-callbacks-registry-spec.ts index ac527f192de6..db2b3efdf107 100644 --- a/spec-main/api-callbacks-registry-spec.ts +++ b/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(() => { diff --git a/spec-main/api-remote-spec.ts b/spec-main/api-remote-spec.ts index 6cce1e9eca8f..130576811c55 100644 --- a/spec-main/api-remote-spec.ts +++ b/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}}) diff --git a/spec/api-remote-spec.js b/spec/api-remote-spec.js index 4564560d74ea..5173464703d9 100644 --- a/spec/api-remote-spec.js +++ b/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', () => { diff --git a/spec/spec-helpers.js b/spec/spec-helpers.js new file mode 100644 index 000000000000..f54f043dccf6 --- /dev/null +++ b/spec/spec-helpers.js @@ -0,0 +1,2 @@ +exports.ifit = (condition) => (condition ? it : it.skip) +exports.ifdescribe = (condition) => (condition ? describe : describe.skip) diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index ecc7f4068197..dc79d6d45b07 100644 --- a/typings/internal-ambient.d.ts +++ b/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;