api-ipc-renderer-spec.js 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. 'use strict'
  2. const chai = require('chai')
  3. const dirtyChai = require('dirty-chai')
  4. const http = require('http')
  5. const path = require('path')
  6. const { closeWindow } = require('./window-helpers')
  7. const { emittedOnce } = require('./events-helpers')
  8. const { expect } = chai
  9. chai.use(dirtyChai)
  10. const { ipcRenderer, remote } = require('electron')
  11. const { ipcMain, webContents, BrowserWindow } = remote
  12. describe('ipc renderer module', () => {
  13. const fixtures = path.join(__dirname, 'fixtures')
  14. let w = null
  15. afterEach(() => closeWindow(w).then(() => { w = null }))
  16. describe('ipc.sender.send', () => {
  17. it('should work when sending an object containing id property', done => {
  18. const obj = {
  19. id: 1,
  20. name: 'ly'
  21. }
  22. ipcRenderer.once('message', (event, message) => {
  23. expect(message).to.deep.equal(obj)
  24. done()
  25. })
  26. ipcRenderer.send('message', obj)
  27. })
  28. it('can send instances of Date', done => {
  29. const currentDate = new Date()
  30. ipcRenderer.once('message', (event, value) => {
  31. expect(value).to.equal(currentDate.toISOString())
  32. done()
  33. })
  34. ipcRenderer.send('message', currentDate)
  35. })
  36. it('can send instances of Buffer', done => {
  37. const buffer = Buffer.from('hello')
  38. ipcRenderer.once('message', (event, message) => {
  39. expect(buffer.equals(message)).to.be.true()
  40. done()
  41. })
  42. ipcRenderer.send('message', buffer)
  43. })
  44. it('can send objects with DOM class prototypes', done => {
  45. ipcRenderer.once('message', (event, value) => {
  46. expect(value.protocol).to.equal('file:')
  47. expect(value.hostname).to.equal('')
  48. done()
  49. })
  50. ipcRenderer.send('message', document.location)
  51. })
  52. it('can send Electron API objects', done => {
  53. const webContents = remote.getCurrentWebContents()
  54. ipcRenderer.once('message', (event, value) => {
  55. expect(value.browserWindowOptions).to.deep.equal(webContents.browserWindowOptions)
  56. done()
  57. })
  58. ipcRenderer.send('message', webContents)
  59. })
  60. it('does not crash on external objects (regression)', done => {
  61. const request = http.request({ port: 5000, hostname: '127.0.0.1', method: 'GET', path: '/' })
  62. const stream = request.agent.sockets['127.0.0.1:5000:'][0]._handle._externalStream
  63. request.on('error', () => {})
  64. ipcRenderer.once('message', (event, requestValue, externalStreamValue) => {
  65. expect(requestValue.method).to.equal('GET')
  66. expect(requestValue.path).to.equal('/')
  67. expect(externalStreamValue).to.be.null()
  68. done()
  69. })
  70. ipcRenderer.send('message', request, stream)
  71. })
  72. it('can send objects that both reference the same object', done => {
  73. const child = { hello: 'world' }
  74. const foo = { name: 'foo', child: child }
  75. const bar = { name: 'bar', child: child }
  76. const array = [foo, bar]
  77. ipcRenderer.once('message', (event, arrayValue, fooValue, barValue, childValue) => {
  78. expect(arrayValue).to.deep.equal(array)
  79. expect(fooValue).to.deep.equal(foo)
  80. expect(barValue).to.deep.equal(bar)
  81. expect(childValue).to.deep.equal(child)
  82. done()
  83. })
  84. ipcRenderer.send('message', array, foo, bar, child)
  85. })
  86. it('inserts null for cyclic references', done => {
  87. const array = [5]
  88. array.push(array)
  89. const child = { hello: 'world' }
  90. child.child = child
  91. ipcRenderer.once('message', (event, arrayValue, childValue) => {
  92. expect(arrayValue[0]).to.equal(5)
  93. expect(arrayValue[1]).to.be.null()
  94. expect(childValue.hello).to.equal('world')
  95. expect(childValue.child).to.be.null()
  96. done()
  97. })
  98. ipcRenderer.send('message', array, child)
  99. })
  100. })
  101. describe('ipc.sendSync', () => {
  102. afterEach(() => {
  103. ipcMain.removeAllListeners('send-sync-message')
  104. })
  105. it('can be replied by setting event.returnValue', () => {
  106. const msg = ipcRenderer.sendSync('echo', 'test')
  107. expect(msg).to.equal('test')
  108. })
  109. })
  110. describe('ipcRenderer.sendTo', () => {
  111. let contents = null
  112. afterEach(() => {
  113. ipcRenderer.removeAllListeners('pong')
  114. contents.destroy()
  115. contents = null
  116. })
  117. const generateSpecs = (description, webPreferences) => {
  118. describe(description, () => {
  119. it('sends message to WebContents', done => {
  120. contents = webContents.create({
  121. preload: path.join(fixtures, 'module', 'preload-ipc-ping-pong.js'),
  122. ...webPreferences
  123. })
  124. const payload = 'Hello World!'
  125. ipcRenderer.once('pong', (event, data) => {
  126. expect(payload).to.equal(data)
  127. done()
  128. })
  129. contents.once('did-finish-load', () => {
  130. ipcRenderer.sendTo(contents.id, 'ping', payload)
  131. })
  132. contents.loadFile(path.join(fixtures, 'pages', 'base-page.html'))
  133. })
  134. it('sends message to WebContents (channel has special chars)', done => {
  135. contents = webContents.create({
  136. preload: path.join(fixtures, 'module', 'preload-ipc-ping-pong.js'),
  137. ...webPreferences
  138. })
  139. const payload = 'Hello World!'
  140. ipcRenderer.once('pong-æøåü', (event, data) => {
  141. expect(payload).to.equal(data)
  142. done()
  143. })
  144. contents.once('did-finish-load', () => {
  145. ipcRenderer.sendTo(contents.id, 'ping-æøåü', payload)
  146. })
  147. contents.loadFile(path.join(fixtures, 'pages', 'base-page.html'))
  148. })
  149. })
  150. }
  151. generateSpecs('without sandbox', {})
  152. generateSpecs('with sandbox', { sandbox: true })
  153. generateSpecs('with contextIsolation', { contextIsolation: true })
  154. generateSpecs('with contextIsolation + sandbox', { contextIsolation: true, sandbox: true })
  155. })
  156. describe('remote listeners', () => {
  157. it('detaches listeners subscribed to destroyed renderers, and shows a warning', (done) => {
  158. w = new BrowserWindow({ show: false })
  159. w.webContents.once('did-finish-load', () => {
  160. w.webContents.once('did-finish-load', () => {
  161. const expectedMessage = [
  162. 'Attempting to call a function in a renderer window that has been closed or released.',
  163. 'Function provided here: remote-event-handler.html:11:33',
  164. 'Remote event names: remote-handler, other-remote-handler'
  165. ].join('\n')
  166. const results = ipcRenderer.sendSync('try-emit-web-contents-event', w.webContents.id, 'remote-handler')
  167. expect(results).to.deep.equal({
  168. warningMessage: expectedMessage,
  169. listenerCountBefore: 2,
  170. listenerCountAfter: 1
  171. })
  172. done()
  173. })
  174. w.webContents.reload()
  175. })
  176. w.loadFile(path.join(fixtures, 'api', 'remote-event-handler.html'))
  177. })
  178. })
  179. describe('ipcRenderer.on', () => {
  180. it('is not used for internals', async () => {
  181. w = new BrowserWindow({ show: false })
  182. w.loadURL('about:blank')
  183. await emittedOnce(w.webContents, 'did-finish-load')
  184. const script = `require('electron').ipcRenderer.eventNames()`
  185. const result = await w.webContents.executeJavaScript(script)
  186. expect(result).to.deep.equal([])
  187. })
  188. })
  189. })