remote.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. 'use strict';
  2. const v8Util = process.electronBinding('v8_util');
  3. const { CallbacksRegistry } = require('@electron/internal/renderer/callbacks-registry');
  4. const bufferUtils = require('@electron/internal/common/buffer-utils');
  5. const errorUtils = require('@electron/internal/common/error-utils');
  6. const { isPromise } = require('@electron/internal/common/is-promise');
  7. const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal');
  8. const callbacksRegistry = new CallbacksRegistry();
  9. const remoteObjectCache = v8Util.createIDWeakMap();
  10. // An unique ID that can represent current context.
  11. const contextId = v8Util.getHiddenValue(global, 'contextId');
  12. // Notify the main process when current context is going to be released.
  13. // Note that when the renderer process is destroyed, the message may not be
  14. // sent, we also listen to the "render-view-deleted" event in the main process
  15. // to guard that situation.
  16. process.on('exit', () => {
  17. const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE';
  18. ipcRendererInternal.send(command, contextId);
  19. });
  20. // Convert the arguments object into an array of meta data.
  21. function wrapArgs (args, visited = new Set()) {
  22. const valueToMeta = (value) => {
  23. // Check for circular reference.
  24. if (visited.has(value)) {
  25. return {
  26. type: 'value',
  27. value: null
  28. };
  29. }
  30. if (Array.isArray(value)) {
  31. visited.add(value);
  32. const meta = {
  33. type: 'array',
  34. value: wrapArgs(value, visited)
  35. };
  36. visited.delete(value);
  37. return meta;
  38. } else if (bufferUtils.isBuffer(value)) {
  39. return {
  40. type: 'buffer',
  41. value: bufferUtils.bufferToMeta(value)
  42. };
  43. } else if (value instanceof Date) {
  44. return {
  45. type: 'date',
  46. value: value.getTime()
  47. };
  48. } else if ((value != null) && typeof value === 'object') {
  49. if (isPromise(value)) {
  50. return {
  51. type: 'promise',
  52. then: valueToMeta(function (onFulfilled, onRejected) {
  53. value.then(onFulfilled, onRejected);
  54. })
  55. };
  56. } else if (v8Util.getHiddenValue(value, 'atomId')) {
  57. return {
  58. type: 'remote-object',
  59. id: v8Util.getHiddenValue(value, 'atomId')
  60. };
  61. }
  62. const meta = {
  63. type: 'object',
  64. name: value.constructor ? value.constructor.name : '',
  65. members: []
  66. };
  67. visited.add(value);
  68. for (const prop in value) { // eslint-disable-line guard-for-in
  69. meta.members.push({
  70. name: prop,
  71. value: valueToMeta(value[prop])
  72. });
  73. }
  74. visited.delete(value);
  75. return meta;
  76. } else if (typeof value === 'function' && v8Util.getHiddenValue(value, 'returnValue')) {
  77. return {
  78. type: 'function-with-return-value',
  79. value: valueToMeta(value())
  80. };
  81. } else if (typeof value === 'function') {
  82. return {
  83. type: 'function',
  84. id: callbacksRegistry.add(value),
  85. location: v8Util.getHiddenValue(value, 'location'),
  86. length: value.length
  87. };
  88. } else {
  89. return {
  90. type: 'value',
  91. value: value
  92. };
  93. }
  94. };
  95. return args.map(valueToMeta);
  96. }
  97. // Populate object's members from descriptors.
  98. // The |ref| will be kept referenced by |members|.
  99. // This matches |getObjectMemebers| in rpc-server.
  100. function setObjectMembers (ref, object, metaId, members) {
  101. if (!Array.isArray(members)) return;
  102. for (const member of members) {
  103. if (object.hasOwnProperty(member.name)) continue;
  104. const descriptor = { enumerable: member.enumerable };
  105. if (member.type === 'method') {
  106. const remoteMemberFunction = function (...args) {
  107. let command;
  108. if (this && this.constructor === remoteMemberFunction) {
  109. command = 'ELECTRON_BROWSER_MEMBER_CONSTRUCTOR';
  110. } else {
  111. command = 'ELECTRON_BROWSER_MEMBER_CALL';
  112. }
  113. const ret = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, wrapArgs(args));
  114. return metaToValue(ret);
  115. };
  116. let descriptorFunction = proxyFunctionProperties(remoteMemberFunction, metaId, member.name);
  117. descriptor.get = () => {
  118. descriptorFunction.ref = ref; // The member should reference its object.
  119. return descriptorFunction;
  120. };
  121. // Enable monkey-patch the method
  122. descriptor.set = (value) => {
  123. descriptorFunction = value;
  124. return value;
  125. };
  126. descriptor.configurable = true;
  127. } else if (member.type === 'get') {
  128. descriptor.get = () => {
  129. const command = 'ELECTRON_BROWSER_MEMBER_GET';
  130. const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name);
  131. return metaToValue(meta);
  132. };
  133. if (member.writable) {
  134. descriptor.set = (value) => {
  135. const args = wrapArgs([value]);
  136. const command = 'ELECTRON_BROWSER_MEMBER_SET';
  137. const meta = ipcRendererInternal.sendSync(command, contextId, metaId, member.name, args);
  138. if (meta != null) metaToValue(meta);
  139. return value;
  140. };
  141. }
  142. }
  143. Object.defineProperty(object, member.name, descriptor);
  144. }
  145. }
  146. // Populate object's prototype from descriptor.
  147. // This matches |getObjectPrototype| in rpc-server.
  148. function setObjectPrototype (ref, object, metaId, descriptor) {
  149. if (descriptor === null) return;
  150. const proto = {};
  151. setObjectMembers(ref, proto, metaId, descriptor.members);
  152. setObjectPrototype(ref, proto, metaId, descriptor.proto);
  153. Object.setPrototypeOf(object, proto);
  154. }
  155. // Wrap function in Proxy for accessing remote properties
  156. function proxyFunctionProperties (remoteMemberFunction, metaId, name) {
  157. let loaded = false;
  158. // Lazily load function properties
  159. const loadRemoteProperties = () => {
  160. if (loaded) return;
  161. loaded = true;
  162. const command = 'ELECTRON_BROWSER_MEMBER_GET';
  163. const meta = ipcRendererInternal.sendSync(command, contextId, metaId, name);
  164. setObjectMembers(remoteMemberFunction, remoteMemberFunction, meta.id, meta.members);
  165. };
  166. return new Proxy(remoteMemberFunction, {
  167. set: (target, property, value, receiver) => {
  168. if (property !== 'ref') loadRemoteProperties();
  169. target[property] = value;
  170. return true;
  171. },
  172. get: (target, property, receiver) => {
  173. if (!target.hasOwnProperty(property)) loadRemoteProperties();
  174. const value = target[property];
  175. if (property === 'toString' && typeof value === 'function') {
  176. return value.bind(target);
  177. }
  178. return value;
  179. },
  180. ownKeys: (target) => {
  181. loadRemoteProperties();
  182. return Object.getOwnPropertyNames(target);
  183. },
  184. getOwnPropertyDescriptor: (target, property) => {
  185. const descriptor = Object.getOwnPropertyDescriptor(target, property);
  186. if (descriptor) return descriptor;
  187. loadRemoteProperties();
  188. return Object.getOwnPropertyDescriptor(target, property);
  189. }
  190. });
  191. }
  192. // Convert meta data from browser into real value.
  193. function metaToValue (meta) {
  194. const types = {
  195. value: () => meta.value,
  196. array: () => meta.members.map((member) => metaToValue(member)),
  197. buffer: () => bufferUtils.metaToBuffer(meta.value),
  198. promise: () => Promise.resolve({ then: metaToValue(meta.then) }),
  199. error: () => metaToPlainObject(meta),
  200. date: () => new Date(meta.value),
  201. exception: () => { throw errorUtils.deserialize(meta.value); }
  202. };
  203. if (Object.prototype.hasOwnProperty.call(types, meta.type)) {
  204. return types[meta.type]();
  205. } else {
  206. let ret;
  207. if (remoteObjectCache.has(meta.id)) {
  208. v8Util.addRemoteObjectRef(contextId, meta.id);
  209. return remoteObjectCache.get(meta.id);
  210. }
  211. // A shadow class to represent the remote function object.
  212. if (meta.type === 'function') {
  213. const remoteFunction = function (...args) {
  214. let command;
  215. if (this && this.constructor === remoteFunction) {
  216. command = 'ELECTRON_BROWSER_CONSTRUCTOR';
  217. } else {
  218. command = 'ELECTRON_BROWSER_FUNCTION_CALL';
  219. }
  220. const obj = ipcRendererInternal.sendSync(command, contextId, meta.id, wrapArgs(args));
  221. return metaToValue(obj);
  222. };
  223. ret = remoteFunction;
  224. } else {
  225. ret = {};
  226. }
  227. setObjectMembers(ret, ret, meta.id, meta.members);
  228. setObjectPrototype(ret, ret, meta.id, meta.proto);
  229. Object.defineProperty(ret.constructor, 'name', { value: meta.name });
  230. // Track delegate obj's lifetime & tell browser to clean up when object is GCed.
  231. v8Util.setRemoteObjectFreer(ret, contextId, meta.id);
  232. v8Util.setHiddenValue(ret, 'atomId', meta.id);
  233. v8Util.addRemoteObjectRef(contextId, meta.id);
  234. remoteObjectCache.set(meta.id, ret);
  235. return ret;
  236. }
  237. }
  238. // Construct a plain object from the meta.
  239. function metaToPlainObject (meta) {
  240. const obj = (() => meta.type === 'error' ? new Error() : {})();
  241. for (let i = 0; i < meta.members.length; i++) {
  242. const { name, value } = meta.members[i];
  243. obj[name] = value;
  244. }
  245. return obj;
  246. }
  247. function handleMessage (channel, handler) {
  248. ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => {
  249. if (passedContextId === contextId) {
  250. handler(id, ...args);
  251. } else {
  252. // Message sent to an un-exist context, notify the error to main process.
  253. ipcRendererInternal.send('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', contextId, passedContextId, id);
  254. }
  255. });
  256. }
  257. // Browser calls a callback in renderer.
  258. handleMessage('ELECTRON_RENDERER_CALLBACK', (id, args) => {
  259. callbacksRegistry.apply(id, metaToValue(args));
  260. });
  261. // A callback in browser is released.
  262. handleMessage('ELECTRON_RENDERER_RELEASE_CALLBACK', (id) => {
  263. callbacksRegistry.remove(id);
  264. });
  265. exports.require = (module) => {
  266. const command = 'ELECTRON_BROWSER_REQUIRE';
  267. const meta = ipcRendererInternal.sendSync(command, contextId, module);
  268. return metaToValue(meta);
  269. };
  270. // Alias to remote.require('electron').xxx.
  271. exports.getBuiltin = (module) => {
  272. const command = 'ELECTRON_BROWSER_GET_BUILTIN';
  273. const meta = ipcRendererInternal.sendSync(command, contextId, module);
  274. return metaToValue(meta);
  275. };
  276. exports.getCurrentWindow = () => {
  277. const command = 'ELECTRON_BROWSER_CURRENT_WINDOW';
  278. const meta = ipcRendererInternal.sendSync(command, contextId);
  279. return metaToValue(meta);
  280. };
  281. // Get current WebContents object.
  282. exports.getCurrentWebContents = () => {
  283. const command = 'ELECTRON_BROWSER_CURRENT_WEB_CONTENTS';
  284. const meta = ipcRendererInternal.sendSync(command, contextId);
  285. return metaToValue(meta);
  286. };
  287. // Get a global object in browser.
  288. exports.getGlobal = (name) => {
  289. const command = 'ELECTRON_BROWSER_GLOBAL';
  290. const meta = ipcRendererInternal.sendSync(command, contextId, name);
  291. return metaToValue(meta);
  292. };
  293. // Get the process object in browser.
  294. Object.defineProperty(exports, 'process', {
  295. get: () => exports.getGlobal('process')
  296. });
  297. // Create a function that will return the specified value when called in browser.
  298. exports.createFunctionWithReturnValue = (returnValue) => {
  299. const func = () => returnValue;
  300. v8Util.setHiddenValue(func, 'returnValue', true);
  301. return func;
  302. };
  303. // Get the guest WebContents from guestInstanceId.
  304. exports.getGuestWebContents = (guestInstanceId) => {
  305. const command = 'ELECTRON_BROWSER_GUEST_WEB_CONTENTS';
  306. const meta = ipcRendererInternal.sendSync(command, contextId, guestInstanceId);
  307. return metaToValue(meta);
  308. };
  309. const addBuiltinProperty = (name) => {
  310. Object.defineProperty(exports, name, {
  311. get: () => exports.getBuiltin(name)
  312. });
  313. };
  314. const browserModules =
  315. require('@electron/internal/common/api/module-list').concat(
  316. require('@electron/internal/browser/api/module-keys'));
  317. // And add a helper receiver for each one.
  318. browserModules
  319. .filter((m) => !m.private)
  320. .map((m) => m.name)
  321. .forEach(addBuiltinProperty);