rpc-server.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. 'use strict'
  2. const electron = require('electron')
  3. const {EventEmitter} = require('events')
  4. const fs = require('fs')
  5. const v8Util = process.atomBinding('v8_util')
  6. const {ipcMain, isPromise, webContents} = electron
  7. const objectsRegistry = require('./objects-registry')
  8. const bufferUtils = require('../common/buffer-utils')
  9. const hasProp = {}.hasOwnProperty
  10. // The internal properties of Function.
  11. const FUNCTION_PROPERTIES = [
  12. 'length', 'name', 'arguments', 'caller', 'prototype'
  13. ]
  14. // The remote functions in renderer processes.
  15. // id => Function
  16. let rendererFunctions = v8Util.createDoubleIDWeakMap()
  17. // Return the description of object's members:
  18. let getObjectMembers = function (object) {
  19. let names = Object.getOwnPropertyNames(object)
  20. // For Function, we should not override following properties even though they
  21. // are "own" properties.
  22. if (typeof object === 'function') {
  23. names = names.filter((name) => {
  24. return !FUNCTION_PROPERTIES.includes(name)
  25. })
  26. }
  27. // Map properties to descriptors.
  28. return names.map((name) => {
  29. let descriptor = Object.getOwnPropertyDescriptor(object, name)
  30. let member = {name, enumerable: descriptor.enumerable, writable: false}
  31. if (descriptor.get === undefined && typeof object[name] === 'function') {
  32. member.type = 'method'
  33. } else {
  34. if (descriptor.set || descriptor.writable) member.writable = true
  35. member.type = 'get'
  36. }
  37. return member
  38. })
  39. }
  40. // Return the description of object's prototype.
  41. let getObjectPrototype = function (object) {
  42. let proto = Object.getPrototypeOf(object)
  43. if (proto === null || proto === Object.prototype) return null
  44. return {
  45. members: getObjectMembers(proto),
  46. proto: getObjectPrototype(proto)
  47. }
  48. }
  49. // Convert a real value into meta data.
  50. let valueToMeta = function (sender, value, optimizeSimpleObject = false) {
  51. // Determine the type of value.
  52. const meta = { type: typeof value }
  53. if (meta.type === 'object') {
  54. // Recognize certain types of objects.
  55. if (value === null) {
  56. meta.type = 'value'
  57. } else if (bufferUtils.isBuffer(value)) {
  58. meta.type = 'buffer'
  59. } else if (Array.isArray(value)) {
  60. meta.type = 'array'
  61. } else if (value instanceof Error) {
  62. meta.type = 'error'
  63. } else if (value instanceof Date) {
  64. meta.type = 'date'
  65. } else if (isPromise(value)) {
  66. meta.type = 'promise'
  67. } else if (hasProp.call(value, 'callee') && value.length != null) {
  68. // Treat the arguments object as array.
  69. meta.type = 'array'
  70. } else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
  71. // Treat simple objects as value.
  72. meta.type = 'value'
  73. }
  74. }
  75. // Fill the meta object according to value's type.
  76. if (meta.type === 'array') {
  77. meta.members = value.map((el) => valueToMeta(sender, el, optimizeSimpleObject))
  78. } else if (meta.type === 'object' || meta.type === 'function') {
  79. meta.name = value.constructor ? value.constructor.name : ''
  80. // Reference the original value if it's an object, because when it's
  81. // passed to renderer we would assume the renderer keeps a reference of
  82. // it.
  83. meta.id = objectsRegistry.add(sender, value)
  84. meta.members = getObjectMembers(value)
  85. meta.proto = getObjectPrototype(value)
  86. } else if (meta.type === 'buffer') {
  87. meta.value = bufferUtils.bufferToMeta(value)
  88. } else if (meta.type === 'promise') {
  89. // Add default handler to prevent unhandled rejections in main process
  90. // Instead they should appear in the renderer process
  91. value.then(function () {}, function () {})
  92. meta.then = valueToMeta(sender, function (onFulfilled, onRejected) {
  93. value.then(onFulfilled, onRejected)
  94. })
  95. } else if (meta.type === 'error') {
  96. meta.members = plainObjectToMeta(value)
  97. // Error.name is not part of own properties.
  98. meta.members.push({
  99. name: 'name',
  100. value: value.name
  101. })
  102. } else if (meta.type === 'date') {
  103. meta.value = value.getTime()
  104. } else {
  105. meta.type = 'value'
  106. meta.value = value
  107. }
  108. return meta
  109. }
  110. // Convert object to meta by value.
  111. const plainObjectToMeta = function (obj) {
  112. return Object.getOwnPropertyNames(obj).map(function (name) {
  113. return {
  114. name: name,
  115. value: obj[name]
  116. }
  117. })
  118. }
  119. // Convert Error into meta data.
  120. const exceptionToMeta = function (sender, error) {
  121. return {
  122. type: 'exception',
  123. message: error.message,
  124. stack: error.stack || error,
  125. cause: valueToMeta(sender, error.cause)
  126. }
  127. }
  128. const throwRPCError = function (message) {
  129. const error = new Error(message)
  130. error.code = 'EBADRPC'
  131. error.errno = -72
  132. throw error
  133. }
  134. const removeRemoteListenersAndLogWarning = (sender, meta, callIntoRenderer) => {
  135. let message = `Attempting to call a function in a renderer window that has been closed or released.` +
  136. `\nFunction provided here: ${meta.location}`
  137. if (sender instanceof EventEmitter) {
  138. const remoteEvents = sender.eventNames().filter((eventName) => {
  139. return sender.listeners(eventName).includes(callIntoRenderer)
  140. })
  141. if (remoteEvents.length > 0) {
  142. message += `\nRemote event names: ${remoteEvents.join(', ')}`
  143. remoteEvents.forEach((eventName) => {
  144. sender.removeListener(eventName, callIntoRenderer)
  145. })
  146. }
  147. }
  148. console.warn(message)
  149. }
  150. // Convert array of meta data from renderer into array of real values.
  151. const unwrapArgs = function (sender, args) {
  152. const metaToValue = function (meta) {
  153. let i, len, member, ref, returnValue
  154. switch (meta.type) {
  155. case 'value':
  156. return meta.value
  157. case 'remote-object':
  158. return objectsRegistry.get(meta.id)
  159. case 'array':
  160. return unwrapArgs(sender, meta.value)
  161. case 'buffer':
  162. return bufferUtils.metaToBuffer(meta.value)
  163. case 'date':
  164. return new Date(meta.value)
  165. case 'promise':
  166. return Promise.resolve({
  167. then: metaToValue(meta.then)
  168. })
  169. case 'object': {
  170. let ret = {}
  171. Object.defineProperty(ret.constructor, 'name', { value: meta.name })
  172. ref = meta.members
  173. for (i = 0, len = ref.length; i < len; i++) {
  174. member = ref[i]
  175. ret[member.name] = metaToValue(member.value)
  176. }
  177. return ret
  178. }
  179. case 'function-with-return-value':
  180. returnValue = metaToValue(meta.value)
  181. return function () {
  182. return returnValue
  183. }
  184. case 'function': {
  185. // Merge webContentsId and meta.id, since meta.id can be the same in
  186. // different webContents.
  187. const webContentsId = sender.getId()
  188. const objectId = [webContentsId, meta.id]
  189. // Cache the callbacks in renderer.
  190. if (rendererFunctions.has(objectId)) {
  191. return rendererFunctions.get(objectId)
  192. }
  193. let callIntoRenderer = function (...args) {
  194. if (!sender.isDestroyed() && webContentsId === sender.getId()) {
  195. sender.send('ELECTRON_RENDERER_CALLBACK', meta.id, valueToMeta(sender, args))
  196. } else {
  197. removeRemoteListenersAndLogWarning(this, meta, callIntoRenderer)
  198. }
  199. }
  200. Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
  201. v8Util.setRemoteCallbackFreer(callIntoRenderer, meta.id, sender)
  202. rendererFunctions.set(objectId, callIntoRenderer)
  203. return callIntoRenderer
  204. }
  205. default:
  206. throw new TypeError(`Unknown type: ${meta.type}`)
  207. }
  208. }
  209. return args.map(metaToValue)
  210. }
  211. // Call a function and send reply asynchronously if it's a an asynchronous
  212. // style function and the caller didn't pass a callback.
  213. const callFunction = function (event, func, caller, args) {
  214. let err, funcMarkedAsync, funcName, funcPassedCallback, ref, ret
  215. funcMarkedAsync = v8Util.getHiddenValue(func, 'asynchronous')
  216. funcPassedCallback = typeof args[args.length - 1] === 'function'
  217. try {
  218. if (funcMarkedAsync && !funcPassedCallback) {
  219. args.push(function (ret) {
  220. event.returnValue = valueToMeta(event.sender, ret, true)
  221. })
  222. func.apply(caller, args)
  223. } else {
  224. ret = func.apply(caller, args)
  225. event.returnValue = valueToMeta(event.sender, ret, true)
  226. }
  227. } catch (error) {
  228. // Catch functions thrown further down in function invocation and wrap
  229. // them with the function name so it's easier to trace things like
  230. // `Error processing argument -1.`
  231. funcName = ((ref = func.name) != null) ? ref : 'anonymous'
  232. err = new Error(`Could not call remote function '${funcName}'. Check that the function signature is correct. Underlying error: ${error.message}`)
  233. err.cause = error
  234. throw err
  235. }
  236. }
  237. ipcMain.on('ELECTRON_BROWSER_REQUIRE', function (event, module) {
  238. try {
  239. event.returnValue = valueToMeta(event.sender, process.mainModule.require(module))
  240. } catch (error) {
  241. event.returnValue = exceptionToMeta(event.sender, error)
  242. }
  243. })
  244. ipcMain.on('ELECTRON_BROWSER_GET_BUILTIN', function (event, module) {
  245. try {
  246. event.returnValue = valueToMeta(event.sender, electron[module])
  247. } catch (error) {
  248. event.returnValue = exceptionToMeta(event.sender, error)
  249. }
  250. })
  251. ipcMain.on('ELECTRON_BROWSER_GLOBAL', function (event, name) {
  252. try {
  253. event.returnValue = valueToMeta(event.sender, global[name])
  254. } catch (error) {
  255. event.returnValue = exceptionToMeta(event.sender, error)
  256. }
  257. })
  258. ipcMain.on('ELECTRON_BROWSER_CURRENT_WINDOW', function (event) {
  259. try {
  260. event.returnValue = valueToMeta(event.sender, event.sender.getOwnerBrowserWindow())
  261. } catch (error) {
  262. event.returnValue = exceptionToMeta(event.sender, error)
  263. }
  264. })
  265. ipcMain.on('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event) {
  266. event.returnValue = valueToMeta(event.sender, event.sender)
  267. })
  268. ipcMain.on('ELECTRON_BROWSER_CONSTRUCTOR', function (event, id, args) {
  269. try {
  270. args = unwrapArgs(event.sender, args)
  271. let constructor = objectsRegistry.get(id)
  272. if (constructor == null) {
  273. throwRPCError(`Cannot call constructor on missing remote object ${id}`)
  274. }
  275. // Call new with array of arguments.
  276. // http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
  277. let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))()
  278. event.returnValue = valueToMeta(event.sender, obj)
  279. } catch (error) {
  280. event.returnValue = exceptionToMeta(event.sender, error)
  281. }
  282. })
  283. ipcMain.on('ELECTRON_BROWSER_FUNCTION_CALL', function (event, id, args) {
  284. try {
  285. args = unwrapArgs(event.sender, args)
  286. let func = objectsRegistry.get(id)
  287. if (func == null) {
  288. throwRPCError(`Cannot call function on missing remote object ${id}`)
  289. }
  290. callFunction(event, func, global, args)
  291. } catch (error) {
  292. event.returnValue = exceptionToMeta(event.sender, error)
  293. }
  294. })
  295. ipcMain.on('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, id, method, args) {
  296. try {
  297. args = unwrapArgs(event.sender, args)
  298. let object = objectsRegistry.get(id)
  299. if (object == null) {
  300. throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
  301. }
  302. // Call new with array of arguments.
  303. let constructor = object[method]
  304. let obj = new (Function.prototype.bind.apply(constructor, [null].concat(args)))()
  305. event.returnValue = valueToMeta(event.sender, obj)
  306. } catch (error) {
  307. event.returnValue = exceptionToMeta(event.sender, error)
  308. }
  309. })
  310. ipcMain.on('ELECTRON_BROWSER_MEMBER_CALL', function (event, id, method, args) {
  311. try {
  312. args = unwrapArgs(event.sender, args)
  313. let obj = objectsRegistry.get(id)
  314. if (obj == null) {
  315. throwRPCError(`Cannot call function '${method}' on missing remote object ${id}`)
  316. }
  317. callFunction(event, obj[method], obj, args)
  318. } catch (error) {
  319. event.returnValue = exceptionToMeta(event.sender, error)
  320. }
  321. })
  322. ipcMain.on('ELECTRON_BROWSER_MEMBER_SET', function (event, id, name, args) {
  323. try {
  324. args = unwrapArgs(event.sender, args)
  325. let obj = objectsRegistry.get(id)
  326. if (obj == null) {
  327. throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`)
  328. }
  329. obj[name] = args[0]
  330. event.returnValue = null
  331. } catch (error) {
  332. event.returnValue = exceptionToMeta(event.sender, error)
  333. }
  334. })
  335. ipcMain.on('ELECTRON_BROWSER_MEMBER_GET', function (event, id, name) {
  336. try {
  337. let obj = objectsRegistry.get(id)
  338. if (obj == null) {
  339. throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`)
  340. }
  341. event.returnValue = valueToMeta(event.sender, obj[name])
  342. } catch (error) {
  343. event.returnValue = exceptionToMeta(event.sender, error)
  344. }
  345. })
  346. ipcMain.on('ELECTRON_BROWSER_DEREFERENCE', function (event, id) {
  347. objectsRegistry.remove(event.sender.getId(), id)
  348. })
  349. ipcMain.on('ELECTRON_BROWSER_CONTEXT_RELEASE', (e, contextId) => {
  350. objectsRegistry.clear(contextId)
  351. e.returnValue = null
  352. })
  353. ipcMain.on('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, guestInstanceId) {
  354. try {
  355. let guestViewManager = require('./guest-view-manager')
  356. event.returnValue = valueToMeta(event.sender, guestViewManager.getGuest(guestInstanceId))
  357. } catch (error) {
  358. event.returnValue = exceptionToMeta(event.sender, error)
  359. }
  360. })
  361. ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, requestId, guestInstanceId, method, ...args) {
  362. try {
  363. let guestViewManager = require('./guest-view-manager')
  364. let guest = guestViewManager.getGuest(guestInstanceId)
  365. if (requestId) {
  366. const responseCallback = function (result) {
  367. event.sender.send(`ELECTRON_RENDERER_ASYNC_CALL_TO_GUEST_VIEW_RESPONSE_${requestId}`, result)
  368. }
  369. args.push(responseCallback)
  370. }
  371. guest[method].apply(guest, args)
  372. } catch (error) {
  373. event.returnValue = exceptionToMeta(event.sender, error)
  374. }
  375. })
  376. ipcMain.on('ELECTRON_BROWSER_SEND_TO', function (event, sendToAll, webContentsId, channel, ...args) {
  377. let contents = webContents.fromId(webContentsId)
  378. if (!contents) {
  379. console.error(`Sending message to WebContents with unknown ID ${webContentsId}`)
  380. return
  381. }
  382. if (sendToAll) {
  383. contents.sendToAll(channel, ...args)
  384. } else {
  385. contents.send(channel, ...args)
  386. }
  387. })
  388. // Implements window.close()
  389. ipcMain.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
  390. const window = event.sender.getOwnerBrowserWindow()
  391. if (window) {
  392. window.close()
  393. }
  394. event.returnValue = null
  395. })
  396. ipcMain.on('ELECTRON_BROWSER_SANDBOX_LOAD', function (event, preloadPath) {
  397. let preloadSrc = null
  398. let preloadError = null
  399. if (preloadPath) {
  400. try {
  401. preloadSrc = fs.readFileSync(preloadPath).toString()
  402. } catch (err) {
  403. preloadError = {stack: err ? err.stack : (new Error(`Failed to load "${preloadPath}"`)).stack}
  404. }
  405. }
  406. event.returnValue = {
  407. preloadSrc: preloadSrc,
  408. preloadError: preloadError,
  409. webContentsId: event.sender.getId(),
  410. platform: process.platform,
  411. execPath: process.execPath,
  412. env: process.env
  413. }
  414. })