@electron/remote
Version:
[](https://circleci.com/gh/electron/remote/tree/main) [](https://npmjs.org/package/@electron/remote
411 lines (410 loc) • 15.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createFunctionWithReturnValue = exports.getGlobal = exports.getCurrentWebContents = exports.getCurrentWindow = exports.getBuiltin = void 0;
const callbacks_registry_1 = require("./callbacks-registry");
const type_utils_1 = require("../common/type-utils");
const electron_1 = require("electron");
const module_names_1 = require("../common/module-names");
const get_electron_binding_1 = require("../common/get-electron-binding");
const { Promise } = global;
const callbacksRegistry = new callbacks_registry_1.CallbacksRegistry();
const remoteObjectCache = new Map();
const finalizationRegistry = new FinalizationRegistry((id) => {
const ref = remoteObjectCache.get(id);
if (ref !== undefined && ref.deref() === undefined) {
remoteObjectCache.delete(id);
electron_1.ipcRenderer.send("REMOTE_BROWSER_DEREFERENCE" /* BROWSER_DEREFERENCE */, contextId, id, 0);
}
});
const electronIds = new WeakMap();
const isReturnValue = new WeakSet();
function getCachedRemoteObject(id) {
const ref = remoteObjectCache.get(id);
if (ref !== undefined) {
const deref = ref.deref();
if (deref !== undefined)
return deref;
}
}
function setCachedRemoteObject(id, value) {
const wr = new WeakRef(value);
remoteObjectCache.set(id, wr);
finalizationRegistry.register(value, id);
return value;
}
function getContextId() {
const v8Util = get_electron_binding_1.getElectronBinding('v8_util');
if (v8Util) {
return v8Util.getHiddenValue(global, 'contextId');
}
else {
throw new Error('Electron >=v13.0.0-beta.6 required to support sandboxed renderers');
}
}
// An unique ID that can represent current context.
const contextId = process.contextId || getContextId();
// Notify the main process when current context is going to be released.
// Note that when the renderer process is destroyed, the message may not be
// sent, we also listen to the "render-view-deleted" event in the main process
// to guard that situation.
process.on('exit', () => {
const command = "REMOTE_BROWSER_CONTEXT_RELEASE" /* BROWSER_CONTEXT_RELEASE */;
electron_1.ipcRenderer.send(command, contextId);
});
const IS_REMOTE_PROXY = Symbol('is-remote-proxy');
// Convert the arguments object into an array of meta data.
function wrapArgs(args, visited = new Set()) {
const valueToMeta = (value) => {
// Check for circular reference.
if (visited.has(value)) {
return {
type: 'value',
value: null
};
}
if (value && value.constructor && value.constructor.name === 'NativeImage') {
return { type: 'nativeimage', value: type_utils_1.serialize(value) };
}
else if (Array.isArray(value)) {
visited.add(value);
const meta = {
type: 'array',
value: wrapArgs(value, visited)
};
visited.delete(value);
return meta;
}
else if (value instanceof Buffer) {
return {
type: 'buffer',
value
};
}
else if (type_utils_1.isSerializableObject(value)) {
return {
type: 'value',
value
};
}
else if (typeof value === 'object') {
if (type_utils_1.isPromise(value)) {
return {
type: 'promise',
then: valueToMeta(function (onFulfilled, onRejected) {
value.then(onFulfilled, onRejected);
})
};
}
else if (electronIds.has(value)) {
return {
type: 'remote-object',
id: electronIds.get(value)
};
}
const meta = {
type: 'object',
name: value.constructor ? value.constructor.name : '',
members: []
};
visited.add(value);
for (const prop in value) { // eslint-disable-line guard-for-in
meta.members.push({
name: prop,
value: valueToMeta(value[prop])
});
}
visited.delete(value);
return meta;
}
else if (typeof value === 'function' && isReturnValue.has(value)) {
return {
type: 'function-with-return-value',
value: valueToMeta(value())
};
}
else if (typeof value === 'function') {
return {
type: 'function',
id: callbacksRegistry.add(value),
location: callbacksRegistry.getLocation(value),
length: value.length
};
}
else {
return {
type: 'value',
value
};
}
};
return args.map(valueToMeta);
}
// Populate object's members from descriptors.
// The |ref| will be kept referenced by |members|.
// This matches |getObjectMemebers| in rpc-server.
function setObjectMembers(ref, object, metaId, members) {
if (!Array.isArray(members))
return;
for (const member of members) {
if (Object.prototype.hasOwnProperty.call(object, member.name))
continue;
const descriptor = { enumerable: member.enumerable };
if (member.type === 'method') {
const remoteMemberFunction = function (...args) {
let command;
if (this && this.constructor === remoteMemberFunction) {
command = "REMOTE_BROWSER_MEMBER_CONSTRUCTOR" /* BROWSER_MEMBER_CONSTRUCTOR */;
}
else {
command = "REMOTE_BROWSER_MEMBER_CALL" /* BROWSER_MEMBER_CALL */;
}
const ret = electron_1.ipcRenderer.sendSync(command, contextId, metaId, member.name, wrapArgs(args));
return metaToValue(ret);
};
let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name);
descriptor.get = () => {
descriptorFunction.ref = ref; // The member should reference its object.
return descriptorFunction;
};
// Enable monkey-patch the method
descriptor.set = (value) => {
descriptorFunction = value;
return value;
};
descriptor.configurable = true;
}
else if (member.type === 'get') {
descriptor.get = () => {
const command = "REMOTE_BROWSER_MEMBER_GET" /* BROWSER_MEMBER_GET */;
const meta = electron_1.ipcRenderer.sendSync(command, contextId, metaId, member.name);
return metaToValue(meta);
};
if (member.writable) {
descriptor.set = (value) => {
const args = wrapArgs([value]);
const command = "REMOTE_BROWSER_MEMBER_SET" /* BROWSER_MEMBER_SET */;
const meta = electron_1.ipcRenderer.sendSync(command, contextId, metaId, member.name, args);
if (meta != null)
metaToValue(meta);
return value;
};
}
}
Object.defineProperty(object, member.name, descriptor);
}
}
// Populate object's prototype from descriptor.
// This matches |getObjectPrototype| in rpc-server.
function setObjectPrototype(ref, object, metaId, descriptor) {
if (descriptor === null)
return;
const proto = {};
setObjectMembers(ref, proto, metaId, descriptor.members);
setObjectPrototype(ref, proto, metaId, descriptor.proto);
Object.setPrototypeOf(object, proto);
}
// Wrap function in Proxy for accessing remote properties
function proxyFunctionProperties(remoteMemberFunction, metaId, name) {
let loaded = false;
// Lazily load function properties
const loadRemoteProperties = () => {
if (loaded)
return;
loaded = true;
const command = "REMOTE_BROWSER_MEMBER_GET" /* BROWSER_MEMBER_GET */;
const meta = electron_1.ipcRenderer.sendSync(command, contextId, metaId, name);
setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members);
};
return new Proxy(remoteMemberFunction, {
set: (target, property, value) => {
if (property !== 'ref')
loadRemoteProperties();
target[property] = value;
return true;
},
get: (target, property) => {
if (property === IS_REMOTE_PROXY)
return true;
if (!Object.prototype.hasOwnProperty.call(target, property))
loadRemoteProperties();
const value = target[property];
if (property === 'toString' && typeof value === 'function') {
return value.bind(target);
}
return value;
},
ownKeys: (target) => {
loadRemoteProperties();
return Object.getOwnPropertyNames(target);
},
getOwnPropertyDescriptor: (target, property) => {
const descriptor = Object.getOwnPropertyDescriptor(target, property);
if (descriptor)
return descriptor;
loadRemoteProperties();
return Object.getOwnPropertyDescriptor(target, property);
}
});
}
// Convert meta data from browser into real value.
function metaToValue(meta) {
if (!meta)
return {};
if (meta.type === 'value') {
return meta.value;
}
else if (meta.type === 'array') {
return meta.members.map((member) => metaToValue(member));
}
else if (meta.type === 'nativeimage') {
return type_utils_1.deserialize(meta.value);
}
else if (meta.type === 'buffer') {
return Buffer.from(meta.value.buffer, meta.value.byteOffset, meta.value.byteLength);
}
else if (meta.type === 'promise') {
return Promise.resolve({ then: metaToValue(meta.then) });
}
else if (meta.type === 'error') {
return metaToError(meta);
}
else if (meta.type === 'exception') {
if (meta.value.type === 'error') {
throw metaToError(meta.value);
}
else {
throw new Error(`Unexpected value type in exception: ${meta.value.type}`);
}
}
else {
let ret;
if ('id' in meta) {
const cached = getCachedRemoteObject(meta.id);
if (cached !== undefined) {
return cached;
}
}
// A shadow class to represent the remote function object.
if (meta.type === 'function') {
const remoteFunction = function (...args) {
let command;
if (this && this.constructor === remoteFunction) {
command = "REMOTE_BROWSER_CONSTRUCTOR" /* BROWSER_CONSTRUCTOR */;
}
else {
command = "REMOTE_BROWSER_FUNCTION_CALL" /* BROWSER_FUNCTION_CALL */;
}
const obj = electron_1.ipcRenderer.sendSync(command, contextId, meta.id, wrapArgs(args));
return metaToValue(obj);
};
ret = remoteFunction;
}
else {
ret = {};
}
setObjectMembers(ret, ret, meta.id, meta.members);
setObjectPrototype(ret, ret, meta.id, meta.proto);
if (ret.constructor && ret.constructor[IS_REMOTE_PROXY]) {
Object.defineProperty(ret.constructor, 'name', { value: meta.name });
}
// Track delegate obj's lifetime & tell browser to clean up when object is GCed.
electronIds.set(ret, meta.id);
setCachedRemoteObject(meta.id, ret);
return ret;
}
}
function metaToError(meta) {
const obj = meta.value;
for (const { name, value } of meta.members) {
obj[name] = metaToValue(value);
}
return obj;
}
function hasSenderId(input) {
return typeof input.senderId === "number";
}
function handleMessage(channel, handler) {
electron_1.ipcRenderer.on(channel, (event, passedContextId, id, ...args) => {
if (hasSenderId(event)) {
if (event.senderId !== 0 && event.senderId !== undefined) {
console.error(`Message ${channel} sent by unexpected WebContents (${event.senderId})`);
return;
}
}
if (passedContextId === contextId) {
handler(id, ...args);
}
else {
// Message sent to an un-exist context, notify the error to main process.
electron_1.ipcRenderer.send("REMOTE_BROWSER_WRONG_CONTEXT_ERROR" /* BROWSER_WRONG_CONTEXT_ERROR */, contextId, passedContextId, id);
}
});
}
const enableStacks = process.argv.includes('--enable-api-filtering-logging');
function getCurrentStack() {
const target = { stack: undefined };
if (enableStacks) {
Error.captureStackTrace(target, getCurrentStack);
}
return target.stack;
}
// Browser calls a callback in renderer.
handleMessage("REMOTE_RENDERER_CALLBACK" /* RENDERER_CALLBACK */, (id, args) => {
callbacksRegistry.apply(id, metaToValue(args));
});
// A callback in browser is released.
handleMessage("REMOTE_RENDERER_RELEASE_CALLBACK" /* RENDERER_RELEASE_CALLBACK */, (id) => {
callbacksRegistry.remove(id);
});
exports.require = (module) => {
const command = "REMOTE_BROWSER_REQUIRE" /* BROWSER_REQUIRE */;
const meta = electron_1.ipcRenderer.sendSync(command, contextId, module, getCurrentStack());
return metaToValue(meta);
};
// Alias to remote.require('electron').xxx.
function getBuiltin(module) {
const command = "REMOTE_BROWSER_GET_BUILTIN" /* BROWSER_GET_BUILTIN */;
const meta = electron_1.ipcRenderer.sendSync(command, contextId, module, getCurrentStack());
return metaToValue(meta);
}
exports.getBuiltin = getBuiltin;
function getCurrentWindow() {
const command = "REMOTE_BROWSER_GET_CURRENT_WINDOW" /* BROWSER_GET_CURRENT_WINDOW */;
const meta = electron_1.ipcRenderer.sendSync(command, contextId, getCurrentStack());
return metaToValue(meta);
}
exports.getCurrentWindow = getCurrentWindow;
// Get current WebContents object.
function getCurrentWebContents() {
const command = "REMOTE_BROWSER_GET_CURRENT_WEB_CONTENTS" /* BROWSER_GET_CURRENT_WEB_CONTENTS */;
const meta = electron_1.ipcRenderer.sendSync(command, contextId, getCurrentStack());
return metaToValue(meta);
}
exports.getCurrentWebContents = getCurrentWebContents;
// Get a global object in browser.
function getGlobal(name) {
const command = "REMOTE_BROWSER_GET_GLOBAL" /* BROWSER_GET_GLOBAL */;
const meta = electron_1.ipcRenderer.sendSync(command, contextId, name, getCurrentStack());
return metaToValue(meta);
}
exports.getGlobal = getGlobal;
// Get the process object in browser.
Object.defineProperty(exports, 'process', {
enumerable: true,
get: () => exports.getGlobal('process')
});
// Create a function that will return the specified value when called in browser.
function createFunctionWithReturnValue(returnValue) {
const func = () => returnValue;
isReturnValue.add(func);
return func;
}
exports.createFunctionWithReturnValue = createFunctionWithReturnValue;
const addBuiltinProperty = (name) => {
Object.defineProperty(exports, name, {
enumerable: true,
get: () => exports.getBuiltin(name)
});
};
module_names_1.browserModuleNames
.forEach(addBuiltinProperty);