api-ipc-renderer-spec.js 7.2 KB

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