api-ipc-renderer-spec.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import { expect } from 'chai';
  2. import * as path from 'path';
  3. import { ipcMain, BrowserWindow, WebContents, WebPreferences, webContents } from 'electron/main';
  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, contextIsolation: false } });
  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. it('throws when sending objects with DOM class prototypes', async () => {
  50. await expect(w.webContents.executeJavaScript(`{
  51. const { ipcRenderer } = require('electron')
  52. ipcRenderer.send('message', document.location)
  53. }`)).to.eventually.be.rejected();
  54. });
  55. it('does not crash when sending external objects', async () => {
  56. await expect(w.webContents.executeJavaScript(`{
  57. const { ipcRenderer } = require('electron')
  58. const http = require('http')
  59. const request = http.request({ port: 5000, hostname: '127.0.0.1', method: 'GET', path: '/' })
  60. const stream = request.agent.sockets['127.0.0.1:5000:'][0]._handle._externalStream
  61. ipcRenderer.send('message', stream)
  62. }`)).to.eventually.be.rejected();
  63. });
  64. it('can send objects that both reference the same object', async () => {
  65. w.webContents.executeJavaScript(`{
  66. const { ipcRenderer } = require('electron')
  67. const child = { hello: 'world' }
  68. const foo = { name: 'foo', child: child }
  69. const bar = { name: 'bar', child: child }
  70. const array = [foo, bar]
  71. ipcRenderer.send('message', array, foo, bar, child)
  72. }`);
  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. const [, arrayValue, fooValue, barValue, childValue] = await emittedOnce(ipcMain, 'message');
  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. });
  83. it('can handle cyclic references', async () => {
  84. w.webContents.executeJavaScript(`{
  85. const { ipcRenderer } = require('electron')
  86. const array = [5]
  87. array.push(array)
  88. const child = { hello: 'world' }
  89. child.child = child
  90. ipcRenderer.send('message', array, child)
  91. }`);
  92. const [, arrayValue, childValue] = await emittedOnce(ipcMain, 'message');
  93. expect(arrayValue[0]).to.equal(5);
  94. expect(arrayValue[1]).to.equal(arrayValue);
  95. expect(childValue.hello).to.equal('world');
  96. expect(childValue.child).to.equal(childValue);
  97. });
  98. });
  99. describe('sendSync()', () => {
  100. it('can be replied to by setting event.returnValue', async () => {
  101. ipcMain.once('echo', (event, msg) => {
  102. event.returnValue = msg;
  103. });
  104. const msg = await w.webContents.executeJavaScript(`new Promise(resolve => {
  105. const { ipcRenderer } = require('electron')
  106. resolve(ipcRenderer.sendSync('echo', 'test'))
  107. })`);
  108. expect(msg).to.equal('test');
  109. });
  110. });
  111. describe('sendTo()', () => {
  112. const generateSpecs = (description: string, webPreferences: WebPreferences) => {
  113. describe(description, () => {
  114. let contents: WebContents;
  115. const payload = 'Hello World!';
  116. before(async () => {
  117. contents = (webContents as any).create({
  118. preload: path.join(fixtures, 'module', 'preload-ipc-ping-pong.js'),
  119. ...webPreferences
  120. });
  121. await contents.loadURL('about:blank');
  122. });
  123. after(() => {
  124. (contents as any).destroy();
  125. contents = null as unknown as WebContents;
  126. });
  127. it('sends message to WebContents', async () => {
  128. const data = await w.webContents.executeJavaScript(`new Promise(resolve => {
  129. const { ipcRenderer } = require('electron')
  130. ipcRenderer.sendTo(${contents.id}, 'ping', ${JSON.stringify(payload)})
  131. ipcRenderer.once('pong', (event, data) => resolve(data))
  132. })`);
  133. expect(data).to.equal(payload);
  134. });
  135. it('sends message on channel with non-ASCII characters to WebContents', async () => {
  136. const data = await w.webContents.executeJavaScript(`new Promise(resolve => {
  137. const { ipcRenderer } = require('electron')
  138. ipcRenderer.sendTo(${contents.id}, 'ping-æøåü', ${JSON.stringify(payload)})
  139. ipcRenderer.once('pong-æøåü', (event, data) => resolve(data))
  140. })`);
  141. expect(data).to.equal(payload);
  142. });
  143. });
  144. };
  145. generateSpecs('without sandbox', {});
  146. generateSpecs('with sandbox', { sandbox: true });
  147. generateSpecs('with contextIsolation', { contextIsolation: true });
  148. generateSpecs('with contextIsolation + sandbox', { contextIsolation: true, sandbox: true });
  149. });
  150. describe('ipcRenderer.on', () => {
  151. it('is not used for internals', async () => {
  152. const result = await w.webContents.executeJavaScript(`
  153. require('electron').ipcRenderer.eventNames()
  154. `);
  155. expect(result).to.deep.equal([]);
  156. });
  157. });
  158. describe('after context is released', () => {
  159. it('throws an exception', async () => {
  160. const error = await w.webContents.executeJavaScript(`(${() => {
  161. const child = window.open('', 'child', 'show=no,nodeIntegration=yes')! as any;
  162. const childIpc = child.require('electron').ipcRenderer;
  163. child.close();
  164. return new Promise(resolve => {
  165. setInterval(() => {
  166. try {
  167. childIpc.send('hello');
  168. } catch (e) {
  169. resolve(e);
  170. }
  171. }, 16);
  172. });
  173. }})()`);
  174. expect(error).to.have.property('message', 'IPC method called after context was released');
  175. });
  176. });
  177. });