const v8Util = process.atomBinding('v8_util');
const IDWeakMap = process.atomBinding('id_weak_map').IDWeakMap;
+// The internal properties of Function.
+const FUNCTION_PROPERTIES = [
+ 'length', 'name', 'arguments', 'caller', 'prototype',
+];
+
var slice = [].slice;
+// Return the description of object's members:
+let getObjectMemebers = 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) => {
+ let descriptor = Object.getOwnPropertyDescriptor(object, name);
+ let 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.
+let getObjectPrototype = function(object) {
+ let proto = Object.getPrototypeOf(object);
+ if (proto === null || proto === Object.prototype)
+ return null;
+ return {
+ members: getObjectMemebers(proto),
+ proto: getObjectPrototype(proto),
+ };
+};
+
// Convert a real value into meta data.
var valueToMeta = function(sender, value, optimizeSimpleObject) {
- var el, field, i, len, meta, name;
+ var el, i, len, meta;
if (optimizeSimpleObject == null) {
optimizeSimpleObject = false;
}
// passed to renderer we would assume the renderer keeps a reference of
// it.
meta.id = objectsRegistry.add(sender.getId(), value);
- meta.members = (function() {
- var results;
- results = [];
- for (name in value) {
- field = value[name];
- results.push({
- name: name,
- type: typeof field
- });
- }
- return results;
- })();
+ meta.members = getObjectMemebers(value);
+ meta.proto = getObjectPrototype(value);
} else if (meta.type === 'buffer') {
meta.value = Array.prototype.slice.call(value, 0);
} else if (meta.type === 'promise') {
+'use strict';
+
const ipcRenderer = require('electron').ipcRenderer;
const CallbacksRegistry = require('electron').CallbacksRegistry;
const v8Util = process.atomBinding('v8_util');
return Array.prototype.slice.call(args).map(valueToMeta);
};
+// Populate object's members from descriptors.
+// This matches |getObjectMemebers| in rpc-server.
+let setObjectMembers = function(object, metaId, members) {
+ for (let member of members) {
+ if (object.hasOwnProperty(member.name))
+ continue;
+
+ let descriptor = { enumerable: member.enumerable };
+ if (member.type === 'method') {
+ let remoteMemberFunction = function() {
+ if (this && this.constructor === remoteMemberFunction) {
+ // Constructor call.
+ let ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CONSTRUCTOR', metaId, member.name, wrapArgs(arguments));
+ return metaToValue(ret);
+ } else {
+ // Call member function.
+ let ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CALL', metaId, member.name, wrapArgs(arguments));
+ return metaToValue(ret);
+ }
+ };
+ descriptor.value = remoteMemberFunction;
+ } else if (member.type === 'get') {
+ descriptor.get = function() {
+ return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_GET', metaId, member.name));
+ };
+
+ // Only set setter when it is writable.
+ if (member.writable) {
+ descriptor.set = function(value) {
+ ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_SET', metaId, member.name, value);
+ return value;
+ };
+ }
+ }
+
+ Object.defineProperty(object, member.name, descriptor);
+ }
+};
+
+// Populate object's prototype from descriptor.
+// This matches |getObjectPrototype| in rpc-server.
+let setObjectPrototype = function(object, metaId, descriptor) {
+ if (descriptor === null)
+ return;
+ let proto = {};
+ setObjectMembers(proto, metaId, descriptor.members);
+ setObjectPrototype(proto, metaId, descriptor.proto);
+ Object.setPrototypeOf(object, proto);
+};
+
// Convert meta data from browser into real value.
var metaToValue = function(meta) {
- var el, i, j, len, len1, member, ref1, ref2, results, ret;
+ var el, i, len, ref1, results, ret;
switch (meta.type) {
case 'value':
return meta.value;
case 'exception':
throw new Error(meta.message + "\n" + meta.stack);
default:
+ if (remoteObjectCache.has(meta.id))
+ return remoteObjectCache.get(meta.id);
+
if (meta.type === 'function') {
// A shadow class to represent the remote function object.
- ret = (function() {
- function RemoteFunction() {
- var obj;
- if (this.constructor === RemoteFunction) {
-
- // Constructor call.
- obj = ipcRenderer.sendSync('ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments));
-
- /*
- 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.
- obj = ipcRenderer.sendSync('ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments));
- return metaToValue(obj);
- }
+ let remoteFunction = function() {
+ if (this && this.constructor === remoteFunction) {
+ // Constructor call.
+ let obj = ipcRenderer.sendSync('ATOM_BROWSER_CONSTRUCTOR', meta.id, wrapArgs(arguments));
+ // 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('ATOM_BROWSER_FUNCTION_CALL', meta.id, wrapArgs(arguments));
+ return metaToValue(obj);
}
-
- return RemoteFunction;
-
- })();
+ };
+ ret = remoteFunction;
} else {
- ret = v8Util.createObjectWithName(meta.name);
+ ret = {};
}
- // Polulate delegate members.
- ref2 = meta.members;
- for (j = 0, len1 = ref2.length; j < len1; j++) {
- member = ref2[j];
- if (member.type === 'function') {
- ret[member.name] = createRemoteMemberFunction(meta.id, member.name);
- } else {
- Object.defineProperty(ret, member.name, createRemoteMemberProperty(meta.id, member.name));
- }
- }
+ // Populate delegate members.
+ setObjectMembers(ret, meta.id, meta.members);
+ // Populate delegate prototype.
+ setObjectPrototype(ret, meta.id, meta.proto);
- if (remoteObjectCache.has(meta.id))
- return remoteObjectCache.get(meta.id);
+ // 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.setDestructor(ret, function() {
- return ipcRenderer.send('ATOM_BROWSER_DEREFERENCE', meta.id);
+ ipcRenderer.send('ATOM_BROWSER_DEREFERENCE', meta.id);
});
// Remember object's id.
return obj;
};
-// Create a RemoteMemberFunction instance.
-// This function's content should not be inlined into metaToValue, otherwise V8
-// may consider it circular reference.
-var createRemoteMemberFunction = function(metaId, name) {
- return (function() {
- function RemoteMemberFunction() {
- var ret;
- if (this.constructor === RemoteMemberFunction) {
-
- // Constructor call.
- ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CONSTRUCTOR', metaId, name, wrapArgs(arguments));
- return metaToValue(ret);
- } else {
-
- // Call member function.
- ret = ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_CALL', metaId, name, wrapArgs(arguments));
- return metaToValue(ret);
- }
- }
-
- return RemoteMemberFunction;
-
- })();
-};
-
-// Create configuration for defineProperty.
-// This function's content should not be inlined into metaToValue, otherwise V8
-// may consider it circular reference.
-var createRemoteMemberProperty = function(metaId, name) {
- return {
- enumerable: true,
- configurable: false,
- set: function(value) {
-
- // Set member data.
- ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_SET', metaId, name, value);
- return value;
- },
- get: function() {
-
- // Get member data.
- return metaToValue(ipcRenderer.sendSync('ATOM_BROWSER_MEMBER_GET', metaId, name));
- }
- };
-};
-
// Browser calls a callback in renderer.
ipcRenderer.on('ATOM_RENDERER_CALLBACK', function(event, id, args) {
return callbacksRegistry.apply(id, metaToValue(args));