rpc-server.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. 'use strict';
  2. const electron = require('electron');
  3. const { EventEmitter } = require('events');
  4. const fs = require('fs');
  5. const v8Util = process.electronBinding('v8_util');
  6. const eventBinding = process.electronBinding('event');
  7. const clipboard = process.electronBinding('clipboard');
  8. const features = process.electronBinding('features');
  9. const { getContentScripts } = require('@electron/internal/browser/chrome-extension');
  10. const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init');
  11. const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
  12. const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
  13. const objectsRegistry = require('@electron/internal/browser/objects-registry');
  14. const guestViewManager = require('@electron/internal/browser/guest-view-manager');
  15. const bufferUtils = require('@electron/internal/common/buffer-utils');
  16. const errorUtils = require('@electron/internal/common/error-utils');
  17. const typeUtils = require('@electron/internal/common/type-utils');
  18. const { isPromise } = require('@electron/internal/common/is-promise');
  19. const hasProp = {}.hasOwnProperty;
  20. // The internal properties of Function.
  21. const FUNCTION_PROPERTIES = [
  22. 'length', 'name', 'arguments', 'caller', 'prototype'
  23. ];
  24. // The remote functions in renderer processes.
  25. // id => Function
  26. const rendererFunctions = v8Util.createDoubleIDWeakMap();
  27. // Return the description of object's members:
  28. const getObjectMembers = function (object) {
  29. let names = Object.getOwnPropertyNames(object);
  30. // For Function, we should not override following properties even though they
  31. // are "own" properties.
  32. if (typeof object === 'function') {
  33. names = names.filter((name) => {
  34. return !FUNCTION_PROPERTIES.includes(name);
  35. });
  36. }
  37. // Map properties to descriptors.
  38. return names.map((name) => {
  39. const descriptor = Object.getOwnPropertyDescriptor(object, name);
  40. const member = { name, enumerable: descriptor.enumerable, writable: false };
  41. if (descriptor.get === undefined && typeof object[name] === 'function') {
  42. member.type = 'method';
  43. } else {
  44. if (descriptor.set || descriptor.writable) member.writable = true;
  45. member.type = 'get';
  46. }
  47. return member;
  48. });
  49. };
  50. // Return the description of object's prototype.
  51. const getObjectPrototype = function (object) {
  52. const proto = Object.getPrototypeOf(object);
  53. if (proto === null || proto === Object.prototype) return null;
  54. return {
  55. members: getObjectMembers(proto),
  56. proto: getObjectPrototype(proto)
  57. };
  58. };
  59. // Convert a real value into meta data.
  60. const valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) {
  61. // Determine the type of value.
  62. const meta = { type: typeof value };
  63. if (meta.type === 'object') {
  64. // Recognize certain types of objects.
  65. if (value === null) {
  66. meta.type = 'value';
  67. } else if (bufferUtils.isBuffer(value)) {
  68. meta.type = 'buffer';
  69. } else if (Array.isArray(value)) {
  70. meta.type = 'array';
  71. } else if (value instanceof Error) {
  72. meta.type = 'error';
  73. } else if (value instanceof Date) {
  74. meta.type = 'date';
  75. } else if (isPromise(value)) {
  76. meta.type = 'promise';
  77. } else if (hasProp.call(value, 'callee') && value.length != null) {
  78. // Treat the arguments object as array.
  79. meta.type = 'array';
  80. } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
  81. // Treat simple objects as value.
  82. meta.type = 'value';
  83. }
  84. }
  85. // Fill the meta object according to value's type.
  86. if (meta.type === 'array') {
  87. meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject));
  88. } else if (meta.type === 'object' || meta.type === 'function') {
  89. meta.name = value.constructor ? value.constructor.name : '';
  90. // Reference the original value if it's an object, because when it's
  91. // passed to renderer we would assume the renderer keeps a reference of
  92. // it.
  93. meta.id = objectsRegistry.add(sender, contextId, value);
  94. meta.members = getObjectMembers(value);
  95. meta.proto = getObjectPrototype(value);
  96. } else if (meta.type === 'buffer') {
  97. meta.value = bufferUtils.bufferToMeta(value);
  98. } else if (meta.type === 'promise') {
  99. // Add default handler to prevent unhandled rejections in main process
  100. // Instead they should appear in the renderer process
  101. value.then(function () {}, function () {});
  102. meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) {
  103. value.then(onFulfilled, onRejected);
  104. });
  105. } else if (meta.type === 'error') {
  106. meta.members = plainObjectToMeta(value);
  107. // Error.name is not part of own properties.
  108. meta.members.push({
  109. name: 'name',
  110. value: value.name
  111. });
  112. } else if (meta.type === 'date') {
  113. meta.value = value.getTime();
  114. } else {
  115. meta.type = 'value';
  116. meta.value = value;
  117. }
  118. return meta;
  119. };
  120. // Convert object to meta by value.
  121. const plainObjectToMeta = function (obj) {
  122. return Object.getOwnPropertyNames(obj).map(function (name) {
  123. return {
  124. name: name,
  125. value: obj[name]
  126. };
  127. });
  128. };
  129. // Convert Error into meta data.
  130. const exceptionToMeta = function (error) {
  131. return {
  132. type: 'exception',
  133. value: errorUtils.serialize(error)
  134. };
  135. };
  136. const throwRPCError = function (message) {
  137. const error = new Error(message);
  138. error.code = 'EBADRPC';
  139. error.errno = -72;
  140. throw error;
  141. };
  142. const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
  143. const location = v8Util.getHiddenValue(callIntoRenderer, 'location');
  144. let message = `Attempting to call a function in a renderer window that has been closed or released.` +
  145. `\nFunction provided here: ${location}`;
  146. if (sender instanceof EventEmitter) {
  147. const remoteEvents = sender.eventNames().filter((eventName) => {
  148. return sender.listeners(eventName).includes(callIntoRenderer);
  149. });
  150. if (remoteEvents.length > 0) {
  151. message += `\nRemote event names: ${remoteEvents.join(', ')}`;
  152. remoteEvents.forEach((eventName) => {
  153. sender.removeListener(eventName, callIntoRenderer);
  154. });
  155. }
  156. }
  157. console.warn(message);
  158. };
  159. // Convert array of meta data from renderer into array of real values.
  160. const unwrapArgs = function (sender, frameId, contextId, args) {
  161. const metaToValue = function (meta) {
  162. switch (meta.type) {
  163. case 'value':
  164. return meta.value;
  165. case 'remote-object':
  166. return objectsRegistry.get(meta.id);
  167. case 'array':
  168. return unwrapArgs(sender, frameId, contextId, meta.value);
  169. case 'buffer':
  170. return bufferUtils.metaToBuffer(meta.value);
  171. case 'date':
  172. return new Date(meta.value);
  173. case 'promise':
  174. return Promise.resolve({
  175. then: metaToValue(meta.then)
  176. });
  177. case 'object': {
  178. const ret = {};
  179. Object.defineProperty(ret.constructor, 'name', { value: meta.name });
  180. for (const { name, value } of meta.members) {
  181. ret[name] = metaToValue(value);
  182. }
  183. return ret;
  184. }
  185. case 'function-with-return-value':
  186. const returnValue = metaToValue(meta.value);
  187. return function () {
  188. return returnValue;
  189. };
  190. case 'function': {
  191. // Merge contextId and meta.id, since meta.id can be the same in
  192. // different webContents.
  193. const objectId = [contextId, meta.id];
  194. // Cache the callbacks in renderer.
  195. if (rendererFunctions.has(objectId)) {
  196. return rendererFunctions.get(objectId);
  197. }
  198. const callIntoRenderer = function (...args) {
  199. let succeed = false;
  200. if (!sender.isDestroyed()) {
  201. succeed = sender._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args));
  202. }
  203. if (!succeed) {
  204. removeRemoteListenersAndLogWarning(this, callIntoRenderer);
  205. }
  206. };
  207. v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location);
  208. Object.defineProperty(callIntoRenderer, 'length', { value: meta.length });
  209. v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender);
  210. rendererFunctions.set(objectId, callIntoRenderer);
  211. return callIntoRenderer;
  212. }
  213. default:
  214. throw new TypeError(`Unknown type: ${meta.type}`);
  215. }
  216. };
  217. return args.map(metaToValue);
  218. };
  219. const isRemoteModuleEnabledCache = new WeakMap();
  220. const isRemoteModuleEnabled = function (contents) {
  221. if (!isRemoteModuleEnabledCache.has(contents)) {
  222. isRemoteModuleEnabledCache.set(contents, contents._isRemoteModuleEnabled());
  223. }
  224. return isRemoteModuleEnabledCache.get(contents);
  225. };
  226. const handleRemoteCommand = function (channel, handler) {
  227. ipcMainInternal.on(channel, (event, contextId, ...args) => {
  228. let returnValue;
  229. if (!isRemoteModuleEnabled(event.sender)) {
  230. event.returnValue = null;
  231. return;
  232. }
  233. try {
  234. returnValue = handler(event, contextId, ...args);
  235. } catch (error) {
  236. returnValue = exceptionToMeta(error);
  237. }
  238. if (returnValue !== undefined) {
  239. event.returnValue = returnValue;
  240. }
  241. });
  242. };
  243. const emitCustomEvent = function (contents, eventName, ...args) {
  244. const event = eventBinding.createWithSender(contents);
  245. electron.app.emit(eventName, event, contents, ...args);
  246. contents.emit(eventName, event, ...args);
  247. return event;
  248. };
  249. handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) {
  250. const objectId = [passedContextId, id];
  251. if (!rendererFunctions.has(objectId)) {
  252. // Do nothing if the error has already been reported before.
  253. return;
  254. }
  255. removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId));
  256. });
  257. handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName) {
  258. const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName);
  259. if (customEvent.returnValue === undefined) {
  260. if (customEvent.defaultPrevented) {
  261. throw new Error(`Blocked remote.require('${moduleName}')`);
  262. } else {
  263. customEvent.returnValue = process.mainModule.require(moduleName);
  264. }
  265. }
  266. return valueToMeta(event.sender, contextId, customEvent.returnValue);
  267. });
  268. handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName) {
  269. const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName);
  270. if (customEvent.returnValue === undefined) {
  271. if (customEvent.defaultPrevented) {
  272. throw new Error(`Blocked remote.getBuiltin('${moduleName}')`);
  273. } else {
  274. customEvent.returnValue = electron[moduleName];
  275. }
  276. }
  277. return valueToMeta(event.sender, contextId, customEvent.returnValue);
  278. });
  279. handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName) {
  280. const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName);
  281. if (customEvent.returnValue === undefined) {
  282. if (customEvent.defaultPrevented) {
  283. throw new Error(`Blocked remote.getGlobal('${globalName}')`);
  284. } else {
  285. customEvent.returnValue = global[globalName];
  286. }
  287. }
  288. return valueToMeta(event.sender, contextId, customEvent.returnValue);
  289. });
  290. handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
  291. const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window');
  292. if (customEvent.returnValue === undefined) {
  293. if (customEvent.defaultPrevented) {
  294. throw new Error('Blocked remote.getCurrentWindow()');
  295. } else {
  296. customEvent.returnValue = event.sender.getOwnerBrowserWindow();
  297. }
  298. }
  299. return valueToMeta(event.sender, contextId, customEvent.returnValue);
  300. });
  301. handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
  302. const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents');
  303. if (customEvent.returnValue === undefined) {
  304. if (customEvent.defaultPrevented) {
  305. throw new Error('Blocked remote.getCurrentWebContents()');
  306. } else {
  307. customEvent.returnValue = event.sender;
  308. }
  309. }
  310. return valueToMeta(event.sender, contextId, customEvent.returnValue);
  311. });
  312. handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
  313. args = unwrapArgs(event.sender, event.frameId, contextId, args);
  314. const constructor = objectsRegistry.get(id);
  315. if (constructor == null) {
  316. throwRPCError(`Cannot call constructor on missing remote object ${id}`);
  317. }
  318. return valueToMeta(event.sender, contextId, new constructor(...args));
  319. });
  320. handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
  321. args = unwrapArgs(event.sender, event.frameId, contextId, args);
  322. const func = objectsRegistry.get(id);
  323. if (func == null) {
  324. throwRPCError(`Cannot call function on missing remote object ${id}`);
  325. }
  326. try {
  327. return valueToMeta(event.sender, contextId, func(...args), true);
  328. } catch (error) {
  329. 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`);
  330. err.cause = error;
  331. throw err;
  332. }
  333. });
  334. handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
  335. args = unwrapArgs(event.sender, event.frameId, contextId, args);
  336. const object = objectsRegistry.get(id);
  337. if (object == null) {
  338. throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`);
  339. }
  340. return valueToMeta(event.sender, contextId, new object[method](...args));
  341. });
  342. handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
  343. args = unwrapArgs(event.sender, event.frameId, contextId, args);
  344. const object = objectsRegistry.get(id);
  345. if (object == null) {
  346. throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`);
  347. }
  348. try {
  349. return valueToMeta(event.sender, contextId, object[method](...args), true);
  350. } catch (error) {
  351. 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`);
  352. err.cause = error;
  353. throw err;
  354. }
  355. });
  356. handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
  357. args = unwrapArgs(event.sender, event.frameId, contextId, args);
  358. const obj = objectsRegistry.get(id);
  359. if (obj == null) {
  360. throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`);
  361. }
  362. obj[name] = args[0];
  363. return null;
  364. });
  365. handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
  366. const obj = objectsRegistry.get(id);
  367. if (obj == null) {
  368. throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`);
  369. }
  370. return valueToMeta(event.sender, contextId, obj[name]);
  371. });
  372. handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id, rendererSideRefCount) {
  373. objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount);
  374. });
  375. handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
  376. objectsRegistry.clear(event.sender, contextId);
  377. });
  378. handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
  379. const guest = guestViewManager.getGuestForWebContents(guestInstanceId, event.sender);
  380. const customEvent = emitCustomEvent(event.sender, 'remote-get-guest-web-contents', guest);
  381. if (customEvent.returnValue === undefined) {
  382. if (customEvent.defaultPrevented) {
  383. throw new Error(`Blocked remote.getGuestForWebContents()`);
  384. } else {
  385. customEvent.returnValue = guest;
  386. }
  387. }
  388. return valueToMeta(event.sender, contextId, customEvent.returnValue);
  389. });
  390. // Implements window.close()
  391. ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
  392. const window = event.sender.getOwnerBrowserWindow();
  393. if (window) {
  394. window.close();
  395. }
  396. event.returnValue = null;
  397. });
  398. ipcMainUtils.handle('ELECTRON_CRASH_REPORTER_INIT', function (event, options) {
  399. return crashReporterInit(options);
  400. });
  401. ipcMainUtils.handle('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) {
  402. return event.sender.getLastWebPreferences();
  403. });
  404. // Methods not listed in this set are called directly in the renderer process.
  405. const allowedClipboardMethods = (() => {
  406. switch (process.platform) {
  407. case 'darwin':
  408. return new Set(['readFindText', 'writeFindText']);
  409. case 'linux':
  410. return new Set(Object.keys(clipboard));
  411. default:
  412. return new Set();
  413. }
  414. })();
  415. ipcMainUtils.handle('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...args) {
  416. if (!allowedClipboardMethods.has(method)) {
  417. throw new Error(`Invalid method: ${method}`);
  418. }
  419. return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args)));
  420. });
  421. if (features.isDesktopCapturerEnabled()) {
  422. const desktopCapturer = require('@electron/internal/browser/desktop-capturer');
  423. ipcMainUtils.handle('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, ...args) {
  424. const customEvent = emitCustomEvent(event.sender, 'desktop-capturer-get-sources');
  425. if (customEvent.defaultPrevented) {
  426. console.error('Blocked desktopCapturer.getSources()');
  427. return [];
  428. }
  429. return desktopCapturer.getSources(event, ...args);
  430. });
  431. }
  432. const getPreloadScript = async function (preloadPath) {
  433. let preloadSrc = null;
  434. let preloadError = null;
  435. try {
  436. preloadSrc = (await fs.promises.readFile(preloadPath)).toString();
  437. } catch (err) {
  438. preloadError = errorUtils.serialize(err);
  439. }
  440. return { preloadPath, preloadSrc, preloadError };
  441. };
  442. ipcMainUtils.handle('ELECTRON_GET_CONTENT_SCRIPTS', () => getContentScripts());
  443. ipcMainUtils.handle('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) {
  444. const preloadPaths = event.sender._getPreloadPaths();
  445. const webPreferences = event.sender.getLastWebPreferences() || {};
  446. return {
  447. contentScripts: getContentScripts(),
  448. preloadScripts: await Promise.all(preloadPaths.map(path => getPreloadScript(path))),
  449. isRemoteModuleEnabled: isRemoteModuleEnabled(event.sender),
  450. isWebViewTagEnabled: guestViewManager.isWebViewTagEnabled(event.sender),
  451. guestInstanceId: webPreferences.guestInstanceId,
  452. openerId: webPreferences.openerId,
  453. process: {
  454. arch: process.arch,
  455. platform: process.platform,
  456. env: process.env,
  457. version: process.version,
  458. versions: process.versions,
  459. execPath: process.helperExecPath
  460. }
  461. };
  462. });
  463. ipcMainInternal.on('ELECTRON_BROWSER_PRELOAD_ERROR', function (event, preloadPath, error) {
  464. event.sender.emit('preload-error', event, preloadPath, errorUtils.deserialize(error));
  465. });