remote.js 10 KB

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