diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index fdc95cdb5cdd..a4f8072ed576 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -9,16 +9,11 @@ const {ipcRenderer, isPromise, CallbacksRegistry} = require('electron') const resolvePromise = Promise.resolve.bind(Promise) const callbacksRegistry = new CallbacksRegistry() - const remoteObjectCache = v8Util.createIDWeakMap() // Convert the arguments object into an array of meta data. -const wrapArgs = function (args, visited) { - if (visited == null) { - visited = new Set() - } - - const valueToMeta = function (value) { +function wrapArgs (args, visited = new Set()) { + const valueToMeta = (value) => { // Check for circular reference. if (visited.has(value)) { return { @@ -62,7 +57,7 @@ const wrapArgs = function (args, visited) { let meta = { type: 'object', - name: value.constructor != null ? value.constructor.name : '', + name: value.constructor ? value.constructor.name : '', members: [] } visited.add(value) @@ -99,7 +94,7 @@ const wrapArgs = function (args, visited) { // Populate object's members from descriptors. // The |ref| will be kept referenced by |members|. // This matches |getObjectMemebers| in rpc-server. -const setObjectMembers = function (ref, object, metaId, members) { +function setObjectMembers (ref, object, metaId, members) { if (!Array.isArray(members)) return for (let member of members) { @@ -108,44 +103,41 @@ const setObjectMembers = function (ref, object, metaId, members) { let descriptor = { enumerable: member.enumerable } if (member.type === 'method') { const remoteMemberFunction = function (...args) { + let command if (this && this.constructor === remoteMemberFunction) { - // Constructor call. - let ret = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', metaId, member.name, wrapArgs(args)) - return metaToValue(ret) + command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR' } else { - // Call member function. - let ret = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_CALL', metaId, member.name, wrapArgs(args)) - return metaToValue(ret) + command = 'ELECTRON_BROWSER_MEMBER_CALL' } + const ret = ipcRenderer.sendSync(command, metaId, member.name, wrapArgs(args)) + return metaToValue(ret) } let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name) - descriptor.get = function () { + descriptor.get = () => { descriptorFunction.ref = ref // The member should reference its object. return descriptorFunction } // Enable monkey-patch the method - descriptor.set = function (value) { + descriptor.set = (value) => { descriptorFunction = value return value } descriptor.configurable = true } else if (member.type === 'get') { - descriptor.get = function () { - return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_GET', metaId, member.name)) + descriptor.get = () => { + const command = 'ELECTRON_BROWSER_MEMBER_GET' + const meta = ipcRenderer.sendSync(command, metaId, member.name) + return metaToValue(meta) } - // Only set setter when it is writable. if (member.writable) { - descriptor.set = function (value) { + descriptor.set = (value) => { const args = wrapArgs([value]) - const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_SET', metaId, member.name, args) - // Meta will be non-null when a setter error occurred so parse it - // to a value so it gets re-thrown. - if (meta != null) { - metaToValue(meta) - } + const command = 'ELECTRON_BROWSER_MEMBER_SET' + const meta = ipcRenderer.sendSync(command, metaId, member.name, args) + if (meta != null) metaToValue(meta) return value } } @@ -157,7 +149,7 @@ const setObjectMembers = function (ref, object, metaId, members) { // Populate object's prototype from descriptor. // This matches |getObjectPrototype| in rpc-server. -const setObjectPrototype = function (ref, object, metaId, descriptor) { +function setObjectPrototype (ref, object, metaId, descriptor) { if (descriptor === null) return let proto = {} setObjectMembers(ref, proto, metaId, descriptor.members) @@ -166,14 +158,15 @@ const setObjectPrototype = function (ref, object, metaId, descriptor) { } // Wrap function in Proxy for accessing remote properties -const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) { +function proxyFunctionProperties (remoteMemberFunction, metaId, name) { let loaded = false // Lazily load function properties const loadRemoteProperties = () => { if (loaded) return loaded = true - const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_MEMBER_GET', metaId, name) + const command = 'ELECTRON_BROWSER_MEMBER_GET' + const meta = ipcRenderer.sendSync(command, metaId, name) setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members) } @@ -186,13 +179,9 @@ const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) { get: (target, property, receiver) => { if (!target.hasOwnProperty(property)) loadRemoteProperties() const value = target[property] - - // Bind toString to target if it is a function to avoid - // Function.prototype.toString is not generic errors if (property === 'toString' && typeof value === 'function') { return value.bind(target) } - return value }, ownKeys: (target) => { @@ -201,7 +190,7 @@ const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) { }, getOwnPropertyDescriptor: (target, property) => { let descriptor = Object.getOwnPropertyDescriptor(target, property) - if (descriptor != null) return descriptor + if (descriptor) return descriptor loadRemoteProperties() return Object.getOwnPropertyDescriptor(target, property) } @@ -209,155 +198,130 @@ const proxyFunctionProperties = function (remoteMemberFunction, metaId, name) { } // Convert meta data from browser into real value. -const metaToValue = function (meta) { - var el, i, len, ref1, results, ret - switch (meta.type) { - case 'value': - return meta.value - case 'array': - ref1 = meta.members - results = [] - for (i = 0, len = ref1.length; i < len; i++) { - el = ref1[i] - results.push(metaToValue(el)) - } - return results - case 'buffer': - return Buffer.from(meta.value) - case 'promise': - return resolvePromise({then: metaToValue(meta.then)}) - case 'error': - return metaToPlainObject(meta) - case 'date': - return new Date(meta.value) - case 'exception': - throw new Error(meta.message + '\n' + meta.stack) - default: - if (remoteObjectCache.has(meta.id)) return remoteObjectCache.get(meta.id) +function metaToValue (meta) { + const types = { + value: () => meta.value, + array: () => meta.members.map((member) => metaToValue(member)), + buffer: () => Buffer.from(meta.value), + promise: () => resolvePromise({then: metaToValue(meta.then)}), + error: () => metaToPlainObject(meta), + date: () => new Date(meta.value), + exception: () => { throw new Error(`${meta.message}\n${meta.stack}`) } + } - if (meta.type === 'function') { - // A shadow class to represent the remote function object. - let remoteFunction = function (...args) { - if (this && this.constructor === remoteFunction) { - // Constructor call. - let obj = ipcRenderer.sendSync('ELECTRON_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(args)) - // Returning object in constructor will replace constructed object - // with the returned object. - // http://stackoverflow.com/questions/1978049/what-values-can-a-constructor-return-to-avoid-returning-this - return metaToValue(obj) - } else { - // Function call. - let obj = ipcRenderer.sendSync('ELECTRON_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(args)) - return metaToValue(obj) - } + if (meta.type in types) { + return types[meta.type]() + } else { + let ret + if (remoteObjectCache.has(meta.id)) { + return remoteObjectCache.get(meta.id) + } + + // A shadow class to represent the remote function object. + if (meta.type === 'function') { + let remoteFunction = function (...args) { + let command + if (this && this.constructor === remoteFunction) { + command = 'ELECTRON_BROWSER_CONSTRUCTOR' + } else { + command = 'ELECTRON_BROWSER_FUNCTION_CALL' } - ret = remoteFunction - } else { - ret = {} + const obj = ipcRenderer.sendSync(command, meta.id, wrapArgs(args)) + return metaToValue(obj) } + ret = remoteFunction + } else { + ret = {} + } - // Populate delegate members. - setObjectMembers(ret, ret, meta.id, meta.members) - // Populate delegate prototype. - setObjectPrototype(ret, ret, meta.id, meta.proto) + setObjectMembers(ret, ret, meta.id, meta.members) + setObjectPrototype(ret, ret, meta.id, meta.proto) + Object.defineProperty(ret.constructor, 'name', { value: meta.name }) - // Set constructor.name to object's name. - Object.defineProperty(ret.constructor, 'name', { value: meta.name }) - - // Track delegate object's life time, and tell the browser to clean up - // when the object is GCed. - v8Util.setRemoteObjectFreer(ret, meta.id) - - // Remember object's id. - v8Util.setHiddenValue(ret, 'atomId', meta.id) - remoteObjectCache.set(meta.id, ret) - return ret + // Track delegate obj's lifetime & tell browser to clean up when object is GCed. + v8Util.setRemoteObjectFreer(ret, meta.id) + v8Util.setHiddenValue(ret, 'atomId', meta.id) + remoteObjectCache.set(meta.id, ret) + return ret } } // Construct a plain object from the meta. -const metaToPlainObject = function (meta) { - var i, len, obj, ref1 - obj = (function () { - switch (meta.type) { - case 'error': - return new Error() - default: - return {} - } - })() - ref1 = meta.members - for (i = 0, len = ref1.length; i < len; i++) { - let {name, value} = ref1[i] +function metaToPlainObject (meta) { + const obj = (() => meta.type === 'error' ? new Error() : {})() + for (let i = 0; i < meta.members.length; i++) { + let {name, value} = meta.members[i] obj[name] = value } return obj } // Browser calls a callback in renderer. -ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', function (event, id, args) { +ipcRenderer.on('ELECTRON_RENDERER_CALLBACK', (event, id, args) => { callbacksRegistry.apply(id, metaToValue(args)) }) // A callback in browser is released. -ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', function (event, id) { +ipcRenderer.on('ELECTRON_RENDERER_RELEASE_CALLBACK', (event, id) => { callbacksRegistry.remove(id) }) process.on('exit', () => { - ipcRenderer.sendSync('ELECTRON_BROWSER_CONTEXT_RELEASE') + const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE' + ipcRenderer.sendSync(command) }) -// Get remote module. -exports.require = function (module) { - return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_REQUIRE', module)) +exports.require = (module) => { + const command = 'ELECTRON_BROWSER_REQUIRE' + const meta = ipcRenderer.sendSync(command, module) + return metaToValue(meta) } // Alias to remote.require('electron').xxx. -exports.getBuiltin = function (module) { - return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_GET_BUILTIN', module)) +exports.getBuiltin = (module) => { + const command = 'ELECTRON_BROWSER_GET_BUILTIN' + const meta = ipcRenderer.sendSync(command, module) + return metaToValue(meta) } -// Get current BrowserWindow. -exports.getCurrentWindow = function () { - return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WINDOW')) +exports.getCurrentWindow = () => { + const command = 'ELECTRON_BROWSER_CURRENT_WINDOW' + const meta = ipcRenderer.sendSync(command) + return metaToValue(meta) } // Get current WebContents object. -exports.getCurrentWebContents = function () { +exports.getCurrentWebContents = () => { return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS')) } // Get a global object in browser. -exports.getGlobal = function (name) { - return metaToValue(ipcRenderer.sendSync('ELECTRON_BROWSER_GLOBAL', name)) +exports.getGlobal = (name) => { + const command = 'ELECTRON_BROWSER_GLOBAL' + const meta = ipcRenderer.sendSync(command, name) + return metaToValue(meta) } // Get the process object in browser. -exports.__defineGetter__('process', function () { - return exports.getGlobal('process') -}) +exports.__defineGetter__('process', () => exports.getGlobal('process')) -// Create a funtion that will return the specifed value when called in browser. -exports.createFunctionWithReturnValue = function (returnValue) { - const func = function () { - return returnValue - } +// Create a function that will return the specified value when called in browser. +exports.createFunctionWithReturnValue = (returnValue) => { + const func = () => returnValue v8Util.setHiddenValue(func, 'returnValue', true) return func } // Get the guest WebContents from guestInstanceId. -exports.getGuestWebContents = function (guestInstanceId) { - const meta = ipcRenderer.sendSync('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', guestInstanceId) +exports.getGuestWebContents = (guestInstanceId) => { + const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS' + const meta = ipcRenderer.sendSync(command, guestInstanceId) return metaToValue(meta) } const addBuiltinProperty = (name) => { Object.defineProperty(exports, name, { - get: function () { - return exports.getBuiltin(name) - } + get: () => exports.getBuiltin(name) }) }