2b04af4349
All remote objects are now cached in renderer process, so there is always only one reference to one remote object for each webContents.
379 lines
12 KiB
JavaScript
379 lines
12 KiB
JavaScript
'use strict';
|
|
|
|
const electron = require('electron');
|
|
const ipcMain = electron.ipcMain;
|
|
const objectsRegistry = require('./objects-registry');
|
|
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, i, len, meta;
|
|
if (optimizeSimpleObject == null) {
|
|
optimizeSimpleObject = false;
|
|
}
|
|
meta = {
|
|
type: typeof value
|
|
};
|
|
if (Buffer.isBuffer(value)) {
|
|
meta.type = 'buffer';
|
|
}
|
|
if (value === null) {
|
|
meta.type = 'value';
|
|
}
|
|
if (Array.isArray(value)) {
|
|
meta.type = 'array';
|
|
}
|
|
if (value instanceof Error) {
|
|
meta.type = 'error';
|
|
}
|
|
if (value instanceof Date) {
|
|
meta.type = 'date';
|
|
}
|
|
if ((value != null ? value.constructor.name : void 0) === 'Promise') {
|
|
meta.type = 'promise';
|
|
}
|
|
|
|
// Treat simple objects as value.
|
|
if (optimizeSimpleObject && meta.type === 'object' && v8Util.getHiddenValue(value, 'simple')) {
|
|
meta.type = 'value';
|
|
}
|
|
|
|
// Treat the arguments object as array.
|
|
if (meta.type === 'object' && (value.hasOwnProperty('callee')) && (value.length != null)) {
|
|
meta.type = 'array';
|
|
}
|
|
if (meta.type === 'array') {
|
|
meta.members = [];
|
|
for (i = 0, len = value.length; i < len; i++) {
|
|
el = value[i];
|
|
meta.members.push(valueToMeta(sender, el));
|
|
}
|
|
} else if (meta.type === 'object' || meta.type === 'function') {
|
|
meta.name = 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.getId(), value);
|
|
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') {
|
|
meta.then = valueToMeta(sender, value.then.bind(value));
|
|
} 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.
|
|
var plainObjectToMeta = function(obj) {
|
|
return Object.getOwnPropertyNames(obj).map(function(name) {
|
|
return {
|
|
name: name,
|
|
value: obj[name]
|
|
};
|
|
});
|
|
};
|
|
|
|
// Convert Error into meta data.
|
|
var exceptionToMeta = function(error) {
|
|
return {
|
|
type: 'exception',
|
|
message: error.message,
|
|
stack: error.stack || error
|
|
};
|
|
};
|
|
|
|
// Convert array of meta data from renderer into array of real values.
|
|
var unwrapArgs = function(sender, args) {
|
|
var metaToValue;
|
|
metaToValue = function(meta) {
|
|
var i, len, member, ref, returnValue;
|
|
switch (meta.type) {
|
|
case 'value':
|
|
return meta.value;
|
|
case 'remote-object':
|
|
return objectsRegistry.get(meta.id);
|
|
case 'array':
|
|
return unwrapArgs(sender, meta.value);
|
|
case 'buffer':
|
|
return new Buffer(meta.value);
|
|
case 'date':
|
|
return new Date(meta.value);
|
|
case 'promise':
|
|
return Promise.resolve({
|
|
then: metaToValue(meta.then)
|
|
});
|
|
case 'object': {
|
|
let ret = {};
|
|
Object.defineProperty(ret.constructor, 'name', { value: meta.name });
|
|
|
|
ref = meta.members;
|
|
for (i = 0, len = ref.length; i < len; i++) {
|
|
member = ref[i];
|
|
ret[member.name] = metaToValue(member.value);
|
|
}
|
|
return ret;
|
|
}
|
|
case 'function-with-return-value':
|
|
returnValue = metaToValue(meta.value);
|
|
return function() {
|
|
return returnValue;
|
|
};
|
|
case 'function': {
|
|
// Cache the callbacks in renderer.
|
|
if (!sender.callbacks) {
|
|
sender.callbacks = new IDWeakMap;
|
|
sender.on('render-view-deleted', function() {
|
|
return this.callbacks.clear();
|
|
});
|
|
}
|
|
|
|
if (sender.callbacks.has(meta.id))
|
|
return sender.callbacks.get(meta.id);
|
|
|
|
// Prevent the callback from being called when its page is gone.
|
|
rendererReleased = false;
|
|
sender.once('render-view-deleted', function() {
|
|
rendererReleased = true;
|
|
});
|
|
|
|
let callIntoRenderer = function(...args) {
|
|
if (rendererReleased || sender.isDestroyed())
|
|
throw new Error(`Attempting to call a function in a renderer window that has been closed or released. Function provided here: ${meta.location}.`);
|
|
sender.send('ATOM_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args));
|
|
};
|
|
v8Util.setDestructor(callIntoRenderer, function() {
|
|
if (!rendererReleased && !sender.isDestroyed())
|
|
sender.send('ATOM_RENDERER_RELEASE_CALLBACK', meta.id);
|
|
});
|
|
sender.callbacks.set(meta.id, callIntoRenderer);
|
|
return callIntoRenderer;
|
|
}
|
|
default:
|
|
throw new TypeError("Unknown type: " + meta.type);
|
|
}
|
|
};
|
|
return args.map(metaToValue);
|
|
};
|
|
|
|
// Call a function and send reply asynchronously if it's a an asynchronous
|
|
// style function and the caller didn't pass a callback.
|
|
var callFunction = function(event, func, caller, args) {
|
|
var funcMarkedAsync, funcName, funcPassedCallback, ref, ret;
|
|
funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous');
|
|
funcPassedCallback = typeof args[args.length - 1] === 'function';
|
|
try {
|
|
if (funcMarkedAsync && !funcPassedCallback) {
|
|
args.push(function(ret) {
|
|
return event.returnValue = valueToMeta(event.sender, ret, true);
|
|
});
|
|
return func.apply(caller, args);
|
|
} else {
|
|
ret = func.apply(caller, args);
|
|
return event.returnValue = valueToMeta(event.sender, ret, true);
|
|
}
|
|
} catch (error) {
|
|
// Catch functions thrown further down in function invocation and wrap
|
|
// them with the function name so it's easier to trace things like
|
|
// `Error processing argument -1.`
|
|
funcName = (ref = func.name) != null ? ref : "anonymous";
|
|
throw new Error("Could not call remote function `" + funcName + "`. Check that the function signature is correct. Underlying error: " + error.message);
|
|
}
|
|
};
|
|
|
|
// Send by BrowserWindow when its render view is deleted.
|
|
process.on('ATOM_BROWSER_RELEASE_RENDER_VIEW', function(id) {
|
|
return objectsRegistry.clear(id);
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_REQUIRE', function(event, module) {
|
|
try {
|
|
return event.returnValue = valueToMeta(event.sender, process.mainModule.require(module));
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_GET_BUILTIN', function(event, module) {
|
|
try {
|
|
return event.returnValue = valueToMeta(event.sender, electron[module]);
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_GLOBAL', function(event, name) {
|
|
try {
|
|
return event.returnValue = valueToMeta(event.sender, global[name]);
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_CURRENT_WINDOW', function(event) {
|
|
try {
|
|
return event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow());
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_CURRENT_WEB_CONTENTS', function(event) {
|
|
return event.returnValue = valueToMeta(event.sender, event.sender);
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_CONSTRUCTOR', function(event, id, args) {
|
|
var constructor, obj;
|
|
try {
|
|
args = unwrapArgs(event.sender, args);
|
|
constructor = objectsRegistry.get(id);
|
|
|
|
// Call new with array of arguments.
|
|
// http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
|
|
obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)));
|
|
return event.returnValue = valueToMeta(event.sender, obj);
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_FUNCTION_CALL', function(event, id, args) {
|
|
var func;
|
|
try {
|
|
args = unwrapArgs(event.sender, args);
|
|
func = objectsRegistry.get(id);
|
|
return callFunction(event, func, global, args);
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_MEMBER_CONSTRUCTOR', function(event, id, method, args) {
|
|
var constructor, obj;
|
|
try {
|
|
args = unwrapArgs(event.sender, args);
|
|
constructor = objectsRegistry.get(id)[method];
|
|
|
|
// Call new with array of arguments.
|
|
obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)));
|
|
return event.returnValue = valueToMeta(event.sender, obj);
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_MEMBER_CALL', function(event, id, method, args) {
|
|
var obj;
|
|
try {
|
|
args = unwrapArgs(event.sender, args);
|
|
obj = objectsRegistry.get(id);
|
|
return callFunction(event, obj[method], obj, args);
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_MEMBER_SET', function(event, id, name, value) {
|
|
var obj;
|
|
try {
|
|
obj = objectsRegistry.get(id);
|
|
obj[name] = value;
|
|
return event.returnValue = null;
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_MEMBER_GET', function(event, id, name) {
|
|
var obj;
|
|
try {
|
|
obj = objectsRegistry.get(id);
|
|
return event.returnValue = valueToMeta(event.sender, obj[name]);
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_DEREFERENCE', function(event, id) {
|
|
return objectsRegistry.remove(event.sender.getId(), id);
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_GUEST_WEB_CONTENTS', function(event, guestInstanceId) {
|
|
var guestViewManager;
|
|
try {
|
|
guestViewManager = require('./guest-view-manager');
|
|
return event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId));
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|
|
|
|
ipcMain.on('ATOM_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function() {
|
|
var args, event, guest, guestInstanceId, guestViewManager, method;
|
|
event = arguments[0], guestInstanceId = arguments[1], method = arguments[2], args = 4 <= arguments.length ? slice.call(arguments, 3) : [];
|
|
try {
|
|
guestViewManager = require('./guest-view-manager');
|
|
guest = guestViewManager.getGuest(guestInstanceId);
|
|
return guest[method].apply(guest, args);
|
|
} catch (error) {
|
|
return event.returnValue = exceptionToMeta(error);
|
|
}
|
|
});
|