api-remote-spec.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. 'use strict'
  2. const assert = require('assert')
  3. const chai = require('chai')
  4. const dirtyChai = require('dirty-chai')
  5. const path = require('path')
  6. const { closeWindow } = require('./window-helpers')
  7. const { resolveGetters } = require('./assert-helpers')
  8. const { remote, ipcRenderer } = require('electron')
  9. const { ipcMain, BrowserWindow } = remote
  10. const { expect } = chai
  11. chai.use(dirtyChai)
  12. const comparePaths = (path1, path2) => {
  13. if (process.platform === 'win32') {
  14. path1 = path1.toLowerCase()
  15. path2 = path2.toLowerCase()
  16. }
  17. assert.strictEqual(path1, path2)
  18. }
  19. describe('remote module', () => {
  20. const fixtures = path.join(__dirname, 'fixtures')
  21. let w = null
  22. afterEach(() => closeWindow(w).then(() => { w = null }))
  23. describe('remote.getGlobal filtering', () => {
  24. it('can return custom values', () => {
  25. ipcRenderer.send('handle-next-remote-get-global', { test: 'Hello World!' })
  26. expect(remote.getGlobal('test')).to.be.equal('Hello World!')
  27. })
  28. it('throws when no returnValue set', () => {
  29. ipcRenderer.send('handle-next-remote-get-global')
  30. expect(() => remote.getGlobal('test')).to.throw(`Blocked remote.getGlobal('test')`)
  31. })
  32. })
  33. describe('remote.getBuiltin filtering', () => {
  34. it('can return custom values', () => {
  35. ipcRenderer.send('handle-next-remote-get-builtin', { test: 'Hello World!' })
  36. expect(remote.getBuiltin('test')).to.be.equal('Hello World!')
  37. })
  38. it('throws when no returnValue set', () => {
  39. ipcRenderer.send('handle-next-remote-get-builtin')
  40. expect(() => remote.getBuiltin('test')).to.throw(`Blocked remote.getBuiltin('test')`)
  41. })
  42. })
  43. describe('remote.require filtering', () => {
  44. it('can return custom values', () => {
  45. ipcRenderer.send('handle-next-remote-require', { test: 'Hello World!' })
  46. expect(remote.require('test')).to.be.equal('Hello World!')
  47. })
  48. it('throws when no returnValue set', () => {
  49. ipcRenderer.send('handle-next-remote-require')
  50. expect(() => remote.require('test')).to.throw(`Blocked remote.require('test')`)
  51. })
  52. })
  53. describe('remote.require', () => {
  54. it('should returns same object for the same module', () => {
  55. const dialog1 = remote.require('electron')
  56. const dialog2 = remote.require('electron')
  57. assert.strictEqual(dialog1, dialog2)
  58. })
  59. it('should work when object contains id property', () => {
  60. const a = remote.require(path.join(fixtures, 'module', 'id.js'))
  61. assert.strictEqual(a.id, 1127)
  62. })
  63. it('should work when object has no prototype', () => {
  64. const a = remote.require(path.join(fixtures, 'module', 'no-prototype.js'))
  65. assert.strictEqual(a.foo.constructor.name, '')
  66. assert.strictEqual(a.foo.bar, 'baz')
  67. assert.strictEqual(a.foo.baz, false)
  68. assert.strictEqual(a.bar, 1234)
  69. assert.strictEqual(a.anonymous.constructor.name, '')
  70. assert.strictEqual(a.getConstructorName(Object.create(null)), '')
  71. assert.strictEqual(a.getConstructorName(new (class {})()), '')
  72. })
  73. it('should search module from the user app', () => {
  74. comparePaths(path.normalize(remote.process.mainModule.filename), path.resolve(__dirname, 'static', 'main.js'))
  75. comparePaths(path.normalize(remote.process.mainModule.paths[0]), path.resolve(__dirname, 'static', 'node_modules'))
  76. })
  77. it('should work with function properties', () => {
  78. let a = remote.require(path.join(fixtures, 'module', 'export-function-with-properties.js'))
  79. assert.strictEqual(typeof a, 'function')
  80. assert.strictEqual(a.bar, 'baz')
  81. a = remote.require(path.join(fixtures, 'module', 'function-with-properties.js'))
  82. assert.strictEqual(typeof a, 'object')
  83. assert.strictEqual(a.foo(), 'hello')
  84. assert.strictEqual(a.foo.bar, 'baz')
  85. assert.strictEqual(a.foo.nested.prop, 'yes')
  86. assert.strictEqual(a.foo.method1(), 'world')
  87. assert.strictEqual(a.foo.method1.prop1(), 123)
  88. assert.ok(Object.keys(a.foo).includes('bar'))
  89. assert.ok(Object.keys(a.foo).includes('nested'))
  90. assert.ok(Object.keys(a.foo).includes('method1'))
  91. a = remote.require(path.join(fixtures, 'module', 'function-with-missing-properties.js')).setup()
  92. assert.strictEqual(a.bar(), true)
  93. assert.strictEqual(a.bar.baz, undefined)
  94. })
  95. it('should work with static class members', () => {
  96. const a = remote.require(path.join(fixtures, 'module', 'remote-static.js'))
  97. assert.strictEqual(typeof a.Foo, 'function')
  98. assert.strictEqual(a.Foo.foo(), 3)
  99. assert.strictEqual(a.Foo.bar, 'baz')
  100. const foo = new a.Foo()
  101. assert.strictEqual(foo.baz(), 123)
  102. })
  103. it('includes the length of functions specified as arguments', () => {
  104. const a = remote.require(path.join(fixtures, 'module', 'function-with-args.js'))
  105. assert.strictEqual(a((a, b, c, d, f) => {}), 5)
  106. assert.strictEqual(a((a) => {}), 1)
  107. assert.strictEqual(a((...args) => {}), 0)
  108. })
  109. it('handles circular references in arrays and objects', () => {
  110. const a = remote.require(path.join(fixtures, 'module', 'circular.js'))
  111. let arrayA = ['foo']
  112. const arrayB = [arrayA, 'bar']
  113. arrayA.push(arrayB)
  114. assert.deepStrictEqual(a.returnArgs(arrayA, arrayB), [
  115. ['foo', [null, 'bar']],
  116. [['foo', null], 'bar']
  117. ])
  118. let objectA = { foo: 'bar' }
  119. const objectB = { baz: objectA }
  120. objectA.objectB = objectB
  121. assert.deepStrictEqual(a.returnArgs(objectA, objectB), [
  122. { foo: 'bar', objectB: { baz: null } },
  123. { baz: { foo: 'bar', objectB: null } }
  124. ])
  125. arrayA = [1, 2, 3]
  126. assert.deepStrictEqual(a.returnArgs({ foo: arrayA }, { bar: arrayA }), [
  127. { foo: [1, 2, 3] },
  128. { bar: [1, 2, 3] }
  129. ])
  130. objectA = { foo: 'bar' }
  131. assert.deepStrictEqual(a.returnArgs({ foo: objectA }, { bar: objectA }), [
  132. { foo: { foo: 'bar' } },
  133. { bar: { foo: 'bar' } }
  134. ])
  135. arrayA = []
  136. arrayA.push(arrayA)
  137. assert.deepStrictEqual(a.returnArgs(arrayA), [
  138. [null]
  139. ])
  140. objectA = {}
  141. objectA.foo = objectA
  142. objectA.bar = 'baz'
  143. assert.deepStrictEqual(a.returnArgs(objectA), [
  144. { foo: null, bar: 'baz' }
  145. ])
  146. objectA = {}
  147. objectA.foo = { bar: objectA }
  148. objectA.bar = 'baz'
  149. assert.deepStrictEqual(a.returnArgs(objectA), [
  150. { foo: { bar: null }, bar: 'baz' }
  151. ])
  152. })
  153. })
  154. describe('remote.createFunctionWithReturnValue', () => {
  155. it('should be called in browser synchronously', () => {
  156. const buf = Buffer.from('test')
  157. const call = remote.require(path.join(fixtures, 'module', 'call.js'))
  158. const result = call.call(remote.createFunctionWithReturnValue(buf))
  159. assert.strictEqual(result.constructor.name, 'Buffer')
  160. })
  161. })
  162. describe('remote modules', () => {
  163. it('includes browser process modules as properties', () => {
  164. assert.strictEqual(typeof remote.app.getPath, 'function')
  165. assert.strictEqual(typeof remote.webContents.getFocusedWebContents, 'function')
  166. assert.strictEqual(typeof remote.clipboard.readText, 'function')
  167. assert.strictEqual(typeof remote.shell.openExternal, 'function')
  168. })
  169. it('returns toString() of original function via toString()', () => {
  170. const { readText } = remote.clipboard
  171. assert(readText.toString().startsWith('function'))
  172. const { functionWithToStringProperty } = remote.require(path.join(fixtures, 'module', 'to-string-non-function.js'))
  173. assert.strictEqual(functionWithToStringProperty.toString, 'hello')
  174. })
  175. })
  176. describe('remote object in renderer', () => {
  177. it('can change its properties', () => {
  178. const property = remote.require(path.join(fixtures, 'module', 'property.js'))
  179. assert.strictEqual(property.property, 1127)
  180. property.property = null
  181. assert.strictEqual(property.property, null)
  182. property.property = undefined
  183. assert.strictEqual(property.property, undefined)
  184. property.property = 1007
  185. assert.strictEqual(property.property, 1007)
  186. assert.strictEqual(property.getFunctionProperty(), 'foo-browser')
  187. property.func.property = 'bar'
  188. assert.strictEqual(property.getFunctionProperty(), 'bar-browser')
  189. property.func.property = 'foo' // revert back
  190. const property2 = remote.require(path.join(fixtures, 'module', 'property.js'))
  191. assert.strictEqual(property2.property, 1007)
  192. property.property = 1127
  193. })
  194. it('rethrows errors getting/setting properties', () => {
  195. const foo = remote.require(path.join(fixtures, 'module', 'error-properties.js'))
  196. assert.throws(() => {
  197. // eslint-disable-next-line
  198. foo.bar
  199. }, /getting error/)
  200. assert.throws(() => {
  201. foo.bar = 'test'
  202. }, /setting error/)
  203. })
  204. it('can set a remote property with a remote object', () => {
  205. const foo = remote.require(path.join(fixtures, 'module', 'remote-object-set.js'))
  206. assert.doesNotThrow(() => {
  207. foo.bar = remote.getCurrentWindow()
  208. })
  209. })
  210. it('can construct an object from its member', () => {
  211. const call = remote.require(path.join(fixtures, 'module', 'call.js'))
  212. const obj = new call.constructor()
  213. assert.strictEqual(obj.test, 'test')
  214. })
  215. it('can reassign and delete its member functions', () => {
  216. const remoteFunctions = remote.require(path.join(fixtures, 'module', 'function.js'))
  217. assert.strictEqual(remoteFunctions.aFunction(), 1127)
  218. remoteFunctions.aFunction = () => { return 1234 }
  219. assert.strictEqual(remoteFunctions.aFunction(), 1234)
  220. assert.strictEqual(delete remoteFunctions.aFunction, true)
  221. })
  222. it('is referenced by its members', () => {
  223. const stringify = remote.getGlobal('JSON').stringify
  224. global.gc()
  225. stringify({})
  226. })
  227. })
  228. describe('remote value in browser', () => {
  229. const print = path.join(fixtures, 'module', 'print_name.js')
  230. const printName = remote.require(print)
  231. it('converts NaN to undefined', () => {
  232. assert.strictEqual(printName.getNaN(), undefined)
  233. assert.strictEqual(printName.echo(NaN), undefined)
  234. })
  235. it('converts Infinity to undefined', () => {
  236. assert.strictEqual(printName.getInfinity(), undefined)
  237. assert.strictEqual(printName.echo(Infinity), undefined)
  238. })
  239. it('keeps its constructor name for objects', () => {
  240. const buf = Buffer.from('test')
  241. assert.strictEqual(printName.print(buf), 'Buffer')
  242. })
  243. it('supports instanceof Date', () => {
  244. const now = new Date()
  245. assert.strictEqual(printName.print(now), 'Date')
  246. assert.deepStrictEqual(printName.echo(now), now)
  247. })
  248. it('supports instanceof Buffer', () => {
  249. const buffer = Buffer.from('test')
  250. assert.ok(buffer.equals(printName.echo(buffer)))
  251. const objectWithBuffer = { a: 'foo', b: Buffer.from('bar') }
  252. assert.ok(objectWithBuffer.b.equals(printName.echo(objectWithBuffer).b))
  253. const arrayWithBuffer = [1, 2, Buffer.from('baz')]
  254. assert.ok(arrayWithBuffer[2].equals(printName.echo(arrayWithBuffer)[2]))
  255. })
  256. it('supports instanceof ArrayBuffer', () => {
  257. const buffer = new ArrayBuffer(8)
  258. const view = new DataView(buffer)
  259. view.setFloat64(0, Math.PI)
  260. assert.deepStrictEqual(printName.echo(buffer), buffer)
  261. assert.strictEqual(printName.print(buffer), 'ArrayBuffer')
  262. })
  263. it('supports instanceof Int8Array', () => {
  264. const values = [1, 2, 3, 4]
  265. assert.deepStrictEqual([...printName.typedArray('Int8Array', values)], values)
  266. const int8values = new Int8Array(values)
  267. assert.deepStrictEqual(printName.typedArray('Int8Array', int8values), int8values)
  268. assert.strictEqual(printName.print(int8values), 'Int8Array')
  269. })
  270. it('supports instanceof Uint8Array', () => {
  271. const values = [1, 2, 3, 4]
  272. assert.deepStrictEqual([...printName.typedArray('Uint8Array', values)], values)
  273. const uint8values = new Uint8Array(values)
  274. assert.deepStrictEqual(printName.typedArray('Uint8Array', uint8values), uint8values)
  275. assert.strictEqual(printName.print(uint8values), 'Uint8Array')
  276. })
  277. it('supports instanceof Uint8ClampedArray', () => {
  278. const values = [1, 2, 3, 4]
  279. assert.deepStrictEqual([...printName.typedArray('Uint8ClampedArray', values)], values)
  280. const uint8values = new Uint8ClampedArray(values)
  281. assert.deepStrictEqual(printName.typedArray('Uint8ClampedArray', uint8values), uint8values)
  282. assert.strictEqual(printName.print(uint8values), 'Uint8ClampedArray')
  283. })
  284. it('supports instanceof Int16Array', () => {
  285. const values = [0x1234, 0x2345, 0x3456, 0x4567]
  286. assert.deepStrictEqual([...printName.typedArray('Int16Array', values)], values)
  287. const int16values = new Int16Array(values)
  288. assert.deepStrictEqual(printName.typedArray('Int16Array', int16values), int16values)
  289. assert.strictEqual(printName.print(int16values), 'Int16Array')
  290. })
  291. it('supports instanceof Uint16Array', () => {
  292. const values = [0x1234, 0x2345, 0x3456, 0x4567]
  293. assert.deepStrictEqual([...printName.typedArray('Uint16Array', values)], values)
  294. const uint16values = new Uint16Array(values)
  295. assert.deepStrictEqual(printName.typedArray('Uint16Array', uint16values), uint16values)
  296. assert.strictEqual(printName.print(uint16values), 'Uint16Array')
  297. })
  298. it('supports instanceof Int32Array', () => {
  299. const values = [0x12345678, 0x23456789]
  300. assert.deepStrictEqual([...printName.typedArray('Int32Array', values)], values)
  301. const int32values = new Int32Array(values)
  302. assert.deepStrictEqual(printName.typedArray('Int32Array', int32values), int32values)
  303. assert.strictEqual(printName.print(int32values), 'Int32Array')
  304. })
  305. it('supports instanceof Uint32Array', () => {
  306. const values = [0x12345678, 0x23456789]
  307. assert.deepStrictEqual([...printName.typedArray('Uint32Array', values)], values)
  308. const uint32values = new Uint32Array(values)
  309. assert.deepStrictEqual(printName.typedArray('Uint32Array', uint32values), uint32values)
  310. assert.strictEqual(printName.print(uint32values), 'Uint32Array')
  311. })
  312. it('supports instanceof Float32Array', () => {
  313. const values = [0.5, 1.0, 1.5]
  314. assert.deepStrictEqual([...printName.typedArray('Float32Array', values)], values)
  315. const float32values = new Float32Array()
  316. assert.deepStrictEqual(printName.typedArray('Float32Array', float32values), float32values)
  317. assert.strictEqual(printName.print(float32values), 'Float32Array')
  318. })
  319. it('supports instanceof Float64Array', () => {
  320. const values = [0.5, 1.0, 1.5]
  321. assert.deepStrictEqual([...printName.typedArray('Float64Array', values)], values)
  322. const float64values = new Float64Array([0.5, 1.0, 1.5])
  323. assert.deepStrictEqual(printName.typedArray('Float64Array', float64values), float64values)
  324. assert.strictEqual(printName.print(float64values), 'Float64Array')
  325. })
  326. })
  327. describe('remote promise', () => {
  328. it('can be used as promise in each side', (done) => {
  329. const promise = remote.require(path.join(fixtures, 'module', 'promise.js'))
  330. promise.twicePromise(Promise.resolve(1234)).then((value) => {
  331. assert.strictEqual(value, 2468)
  332. done()
  333. })
  334. })
  335. it('handles rejections via catch(onRejected)', (done) => {
  336. const promise = remote.require(path.join(fixtures, 'module', 'rejected-promise.js'))
  337. promise.reject(Promise.resolve(1234)).catch((error) => {
  338. assert.strictEqual(error.message, 'rejected')
  339. done()
  340. })
  341. })
  342. it('handles rejections via then(onFulfilled, onRejected)', (done) => {
  343. const promise = remote.require(path.join(fixtures, 'module', 'rejected-promise.js'))
  344. promise.reject(Promise.resolve(1234)).then(() => {}, (error) => {
  345. assert.strictEqual(error.message, 'rejected')
  346. done()
  347. })
  348. })
  349. it('does not emit unhandled rejection events in the main process', (done) => {
  350. remote.process.once('unhandledRejection', function (reason) {
  351. done(reason)
  352. })
  353. const promise = remote.require(path.join(fixtures, 'module', 'unhandled-rejection.js'))
  354. promise.reject().then(() => {
  355. done(new Error('Promise was not rejected'))
  356. }).catch((error) => {
  357. assert.strictEqual(error.message, 'rejected')
  358. done()
  359. })
  360. })
  361. it('emits unhandled rejection events in the renderer process', (done) => {
  362. window.addEventListener('unhandledrejection', function (event) {
  363. event.preventDefault()
  364. assert.strictEqual(event.reason.message, 'rejected')
  365. done()
  366. })
  367. const promise = remote.require(path.join(fixtures, 'module', 'unhandled-rejection.js'))
  368. promise.reject().then(() => {
  369. done(new Error('Promise was not rejected'))
  370. })
  371. })
  372. })
  373. describe('remote webContents', () => {
  374. it('can return same object with different getters', () => {
  375. const contents1 = remote.getCurrentWindow().webContents
  376. const contents2 = remote.getCurrentWebContents()
  377. assert(contents1 === contents2)
  378. })
  379. })
  380. describe('remote.getCurrentWindow filtering', () => {
  381. it('can return custom value', () => {
  382. ipcRenderer.send('handle-next-remote-get-current-window', 'Hello World!')
  383. expect(remote.getCurrentWindow()).to.be.equal('Hello World!')
  384. })
  385. it('throws when no returnValue set', () => {
  386. ipcRenderer.send('handle-next-remote-get-current-window')
  387. expect(() => remote.getCurrentWindow()).to.throw('Blocked remote.getCurrentWindow()')
  388. })
  389. })
  390. describe('remote.getCurrentWebContents filtering', () => {
  391. it('can return custom value', () => {
  392. ipcRenderer.send('handle-next-remote-get-current-web-contents', 'Hello World!')
  393. expect(remote.getCurrentWebContents()).to.be.equal('Hello World!')
  394. })
  395. it('throws when no returnValue set', () => {
  396. ipcRenderer.send('handle-next-remote-get-current-web-contents')
  397. expect(() => remote.getCurrentWebContents()).to.throw('Blocked remote.getCurrentWebContents()')
  398. })
  399. })
  400. describe('remote class', () => {
  401. const cl = remote.require(path.join(fixtures, 'module', 'class.js'))
  402. const base = cl.base
  403. let derived = cl.derived
  404. it('can get methods', () => {
  405. assert.strictEqual(base.method(), 'method')
  406. })
  407. it('can get properties', () => {
  408. assert.strictEqual(base.readonly, 'readonly')
  409. })
  410. it('can change properties', () => {
  411. assert.strictEqual(base.value, 'old')
  412. base.value = 'new'
  413. assert.strictEqual(base.value, 'new')
  414. base.value = 'old'
  415. })
  416. it('has unenumerable methods', () => {
  417. assert(!base.hasOwnProperty('method'))
  418. assert(Object.getPrototypeOf(base).hasOwnProperty('method'))
  419. })
  420. it('keeps prototype chain in derived class', () => {
  421. assert.strictEqual(derived.method(), 'method')
  422. assert.strictEqual(derived.readonly, 'readonly')
  423. assert(!derived.hasOwnProperty('method'))
  424. const proto = Object.getPrototypeOf(derived)
  425. assert(!proto.hasOwnProperty('method'))
  426. assert(Object.getPrototypeOf(proto).hasOwnProperty('method'))
  427. })
  428. it('is referenced by methods in prototype chain', () => {
  429. const method = derived.method
  430. derived = null
  431. global.gc()
  432. assert.strictEqual(method(), 'method')
  433. })
  434. })
  435. describe('remote exception', () => {
  436. const throwFunction = remote.require(path.join(fixtures, 'module', 'exception.js'))
  437. it('throws errors from the main process', () => {
  438. assert.throws(() => {
  439. throwFunction()
  440. })
  441. })
  442. it('throws custom errors from the main process', () => {
  443. const err = new Error('error')
  444. err.cause = new Error('cause')
  445. err.prop = 'error prop'
  446. try {
  447. throwFunction(err)
  448. } catch (error) {
  449. assert.ok(error.from)
  450. assert.deepStrictEqual(error.cause, ...resolveGetters(err))
  451. }
  452. })
  453. })
  454. describe('remote function in renderer', () => {
  455. afterEach(() => {
  456. ipcMain.removeAllListeners('done')
  457. })
  458. it('works when created in preload script', (done) => {
  459. ipcMain.once('done', () => w.close())
  460. const preload = path.join(fixtures, 'module', 'preload-remote-function.js')
  461. w = new BrowserWindow({
  462. show: false,
  463. webPreferences: {
  464. preload: preload
  465. }
  466. })
  467. w.once('closed', () => done())
  468. w.loadURL('about:blank')
  469. })
  470. })
  471. })