remote.js 11 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) {
  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 (meta.type in types) {
  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)