remote.js 11 KB

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