api-ipc-renderer-spec.ts 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import { expect } from 'chai'
  2. import * as path from 'path'
  3. import { ipcMain, BrowserWindow, WebContents, WebPreferences, webContents } from 'electron'
  4. import { emittedOnce } from './events-helpers'
  5. import { closeWindow } from './window-helpers';
  6. describe('ipcRenderer module', () => {
  7. const fixtures = path.join(__dirname, '..', 'spec', 'fixtures')
  8. let w: BrowserWindow
  9. before(async () => {
  10. w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nativeWindowOpen: true, nodeIntegrationInSubFrames: true } })
  11. await w.loadURL('about:blank')
  12. })
  13. after(async () => {
  14. await closeWindow(w)
  15. w = null as unknown as BrowserWindow
  16. })
  17. describe('send()', () => {
  18. it('should work when sending an object containing id property', async () => {
  19. const obj = {
  20. id: 1,
  21. name: 'ly'
  22. }
  23. w.webContents.executeJavaScript(`{
  24. const { ipcRenderer } = require('electron')
  25. ipcRenderer.send('message', ${JSON.stringify(obj)})
  26. }`)
  27. const [, received] = await emittedOnce(ipcMain, 'message')
  28. expect(received).to.deep.equal(obj)
  29. })
  30. it('can send instances of Date as Dates', async () => {
  31. const isoDate = new Date().toISOString()
  32. w.webContents.executeJavaScript(`{
  33. const { ipcRenderer } = require('electron')
  34. ipcRenderer.send('message', new Date(${JSON.stringify(isoDate)}))
  35. }`)
  36. const [, received] = await emittedOnce(ipcMain, 'message')
  37. expect(received.toISOString()).to.equal(isoDate)
  38. })
  39. it('can send instances of Buffer', async () => {
  40. const data = 'hello'
  41. w.webContents.executeJavaScript(`{
  42. const { ipcRenderer } = require('electron')
  43. ipcRenderer.send('message', Buffer.from(${JSON.stringify(data)}))
  44. }`)
  45. const [, received] = await emittedOnce(ipcMain, 'message')
  46. expect(received).to.be.an.instanceOf(Uint8Array)
  47. expect(Buffer.from(data).equals(received)).to.be.true()
  48. })
  49. // TODO(nornagon): Change this test to expect an exception to be thrown in
  50. // Electron 9.
  51. it('can send objects with DOM class prototypes', async () => {
  52. w.webContents.executeJavaScript(`{
  53. const { ipcRenderer } = require('electron')
  54. ipcRenderer.send('message', document.location)
  55. }`)
  56. const [, value] = await emittedOnce(ipcMain, 'message')
  57. expect(value.protocol).to.equal('about:')
  58. expect(value.hostname).to.equal('')
  59. })
  60. // TODO(nornagon): Change this test to expect an exception to be thrown in
  61. // Electron 9.
  62. it('does not crash when sending external objects', async () => {
  63. w.webContents.executeJavaScript(`{
  64. const { ipcRenderer } = require('electron')
  65. const http = require('http')
  66. const request = http.request({ port: 5000, hostname: '127.0.0.1', method: 'GET', path: '/' })
  67. const stream = request.agent.sockets['127.0.0.1:5000:'][0]._handle._externalStream
  68. ipcRenderer.send('message', stream)
  69. }`)
  70. const [, externalStreamValue] = await emittedOnce(ipcMain, 'message')
  71. expect(externalStreamValue).to.be.null()
  72. })
  73. it('can send objects that both reference the same object', async () => {
  74. w.webContents.executeJavaScript(`{
  75. const { ipcRenderer } = require('electron')
  76. const child = { hello: 'world' }
  77. const foo = { name: 'foo', child: child }
  78. const bar = { name: 'bar', child: child }
  79. const array = [foo, bar]
  80. ipcRenderer.send('message', array, foo, bar, child)
  81. }`)
  82. const child = { hello: 'world' }
  83. const foo = { name: 'foo', child: child }
  84. const bar = { name: 'bar', child: child }
  85. const array = [foo, bar]
  86. const [, arrayValue, fooValue, barValue, childValue] = await emittedOnce(ipcMain, 'message')
  87. expect(arrayValue).to.deep.equal(array)
  88. expect(fooValue).to.deep.equal(foo)
  89. expect(barValue).to.deep.equal(bar)
  90. expect(childValue).to.deep.equal(child)
  91. })
  92. it('can handle cyclic references', async () => {
  93. w.webContents.executeJavaScript(`{
  94. const { ipcRenderer } = require('electron')
  95. const array = [5]
  96. array.push(array)
  97. const child = { hello: 'world' }
  98. child.child = child
  99. ipcRenderer.send('message', array, child)
  100. }`)
  101. const [, arrayValue, childValue] = await emittedOnce(ipcMain, 'message')
  102. expect(arrayValue[0]).to.equal(5)
  103. expect(arrayValue[1]).to.equal(arrayValue)
  104. expect(childValue.hello).to.equal('world')
  105. expect(childValue.child).to.equal(childValue)
  106. })
  107. })
  108. describe('sendSync()', () => {
  109. it('can be replied to by setting event.returnValue', async () => {
  110. ipcMain.once('echo', (event, msg) => {
  111. event.returnValue = msg
  112. })
  113. const msg = await w.webContents.executeJavaScript(`new Promise(resolve => {
  114. const { ipcRenderer } = require('electron')
  115. resolve(ipcRenderer.sendSync('echo', 'test'))
  116. })`)
  117. expect(msg).to.equal('test')
  118. })
  119. })
  120. describe('sendTo()', () => {
  121. const generateSpecs = (description: string, webPreferences: WebPreferences) => {
  122. describe(description, () => {
  123. let contents: WebContents
  124. const payload = 'Hello World!'
  125. before(async () => {
  126. contents = (webContents as any).create({
  127. preload: path.join(fixtures, 'module', 'preload-ipc-ping-pong.js'),
  128. ...webPreferences
  129. })
  130. await contents.loadURL('about:blank')
  131. })
  132. after(() => {
  133. (contents as any).destroy()
  134. contents = null as unknown as WebContents
  135. })
  136. it('sends message to WebContents', async () => {
  137. const data = await w.webContents.executeJavaScript(`new Promise(resolve => {
  138. const { ipcRenderer } = require('electron')
  139. ipcRenderer.sendTo(${contents.id}, 'ping', ${JSON.stringify(payload)})
  140. ipcRenderer.once('pong', (event, data) => resolve(data))
  141. })`)
  142. expect(data).to.equal(payload)
  143. })
  144. it('sends message on channel with non-ASCII characters to WebContents', async () => {
  145. const data = await w.webContents.executeJavaScript(`new Promise(resolve => {
  146. const { ipcRenderer } = require('electron')
  147. ipcRenderer.sendTo(${contents.id}, 'ping-æøåü', ${JSON.stringify(payload)})
  148. ipcRenderer.once('pong-æøåü', (event, data) => resolve(data))
  149. })`)
  150. expect(data).to.equal(payload)
  151. })
  152. })
  153. }
  154. generateSpecs('without sandbox', {})
  155. generateSpecs('with sandbox', { sandbox: true })
  156. generateSpecs('with contextIsolation', { contextIsolation: true })
  157. generateSpecs('with contextIsolation + sandbox', { contextIsolation: true, sandbox: true })
  158. })
  159. describe('ipcRenderer.on', () => {
  160. it('is not used for internals', async () => {
  161. const result = await w.webContents.executeJavaScript(`
  162. require('electron').ipcRenderer.eventNames()
  163. `)
  164. expect(result).to.deep.equal([])
  165. })
  166. })
  167. describe('after context is released', () => {
  168. it('throws an exception', async () => {
  169. const error = await w.webContents.executeJavaScript(`(${() => {
  170. const child = window.open('', 'child', 'show=no,nodeIntegration=yes')! as any;
  171. const childIpc = child.require('electron').ipcRenderer;
  172. child.close();
  173. return new Promise(resolve => {
  174. setTimeout(() => {
  175. try {
  176. childIpc.send('hello');
  177. } catch (e) {
  178. resolve(e);
  179. }
  180. resolve(false);
  181. }, 100);
  182. });
  183. }})()`);
  184. expect(error).to.have.property('message', 'IPC method called after context was released');
  185. });
  186. });
  187. });