api-ipc-renderer-spec.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import { ipcMain, BrowserWindow } from 'electron/main';
  2. import { expect } from 'chai';
  3. import { once } from 'node:events';
  4. import { closeWindow } from './lib/window-helpers';
  5. describe('ipcRenderer module', () => {
  6. let w: BrowserWindow;
  7. before(async () => {
  8. w = new BrowserWindow({
  9. show: false,
  10. webPreferences: {
  11. nodeIntegration: true,
  12. nodeIntegrationInSubFrames: true,
  13. contextIsolation: false
  14. }
  15. });
  16. await w.loadURL('about:blank');
  17. w.webContents.on('console-message', (event, ...args) => console.error(...args));
  18. });
  19. after(async () => {
  20. await closeWindow(w);
  21. w = null as unknown as BrowserWindow;
  22. });
  23. describe('send()', () => {
  24. it('should work when sending an object containing id property', async () => {
  25. const obj = {
  26. id: 1,
  27. name: 'ly'
  28. };
  29. w.webContents.executeJavaScript(`{
  30. const { ipcRenderer } = require('electron')
  31. ipcRenderer.send('message', ${JSON.stringify(obj)})
  32. }`);
  33. const [, received] = await once(ipcMain, 'message');
  34. expect(received).to.deep.equal(obj);
  35. });
  36. it('can send instances of Date as Dates', async () => {
  37. const isoDate = new Date().toISOString();
  38. w.webContents.executeJavaScript(`{
  39. const { ipcRenderer } = require('electron')
  40. ipcRenderer.send('message', new Date(${JSON.stringify(isoDate)}))
  41. }`);
  42. const [, received] = await once(ipcMain, 'message');
  43. expect(received.toISOString()).to.equal(isoDate);
  44. });
  45. it('can send instances of Buffer', async () => {
  46. const data = 'hello';
  47. w.webContents.executeJavaScript(`{
  48. const { ipcRenderer } = require('electron')
  49. ipcRenderer.send('message', Buffer.from(${JSON.stringify(data)}))
  50. }`);
  51. const [, received] = await once(ipcMain, 'message');
  52. expect(received).to.be.an.instanceOf(Uint8Array);
  53. expect(Buffer.from(data).equals(received)).to.be.true();
  54. });
  55. it('throws when sending objects with DOM class prototypes', async () => {
  56. await expect(w.webContents.executeJavaScript(`{
  57. const { ipcRenderer } = require('electron')
  58. ipcRenderer.send('message', document.location)
  59. }`)).to.eventually.be.rejected();
  60. });
  61. it('does not crash when sending external objects', async () => {
  62. await expect(w.webContents.executeJavaScript(`{
  63. const { ipcRenderer } = require('electron')
  64. const http = require('node:http')
  65. const request = http.request({ port: 5000, hostname: '127.0.0.1', method: 'GET', path: '/' })
  66. const stream = request.agent.sockets['127.0.0.1:5000:'][0]._handle._externalStream
  67. ipcRenderer.send('message', stream)
  68. }`)).to.eventually.be.rejected();
  69. });
  70. it('can send objects that both reference the same object', async () => {
  71. w.webContents.executeJavaScript(`{
  72. const { ipcRenderer } = require('electron')
  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.send('message', array, foo, bar, child)
  78. }`);
  79. const child = { hello: 'world' };
  80. const foo = { name: 'foo', child };
  81. const bar = { name: 'bar', child };
  82. const array = [foo, bar];
  83. const [, arrayValue, fooValue, barValue, childValue] = await once(ipcMain, 'message');
  84. expect(arrayValue).to.deep.equal(array);
  85. expect(fooValue).to.deep.equal(foo);
  86. expect(barValue).to.deep.equal(bar);
  87. expect(childValue).to.deep.equal(child);
  88. });
  89. it('can handle cyclic references', async () => {
  90. w.webContents.executeJavaScript(`{
  91. const { ipcRenderer } = require('electron')
  92. const array = [5]
  93. array.push(array)
  94. const child = { hello: 'world' }
  95. child.child = child
  96. ipcRenderer.send('message', array, child)
  97. }`);
  98. const [, arrayValue, childValue] = await once(ipcMain, 'message');
  99. expect(arrayValue[0]).to.equal(5);
  100. expect(arrayValue[1]).to.equal(arrayValue);
  101. expect(childValue.hello).to.equal('world');
  102. expect(childValue.child).to.equal(childValue);
  103. });
  104. });
  105. describe('sendSync()', () => {
  106. it('can be replied to by setting event.returnValue', async () => {
  107. ipcMain.once('echo', (event, msg) => {
  108. event.returnValue = msg;
  109. });
  110. const msg = await w.webContents.executeJavaScript(`new Promise(resolve => {
  111. const { ipcRenderer } = require('electron')
  112. resolve(ipcRenderer.sendSync('echo', 'test'))
  113. })`);
  114. expect(msg).to.equal('test');
  115. });
  116. });
  117. describe('ipcRenderer.on', () => {
  118. it('is not used for internals', async () => {
  119. const result = await w.webContents.executeJavaScript(`
  120. require('electron').ipcRenderer.eventNames()
  121. `);
  122. expect(result).to.deep.equal([]);
  123. });
  124. });
  125. describe('ipcRenderer.removeAllListeners', () => {
  126. it('removes only the given channel', async () => {
  127. const result = await w.webContents.executeJavaScript(`
  128. (() => {
  129. const { ipcRenderer } = require('electron');
  130. ipcRenderer.on('channel1', () => {});
  131. ipcRenderer.on('channel2', () => {});
  132. ipcRenderer.removeAllListeners('channel1');
  133. return ipcRenderer.eventNames();
  134. })()
  135. `);
  136. expect(result).to.deep.equal(['channel2']);
  137. });
  138. it('removes all channels if no channel is specified', async () => {
  139. const result = await w.webContents.executeJavaScript(`
  140. (() => {
  141. const { ipcRenderer } = require('electron');
  142. ipcRenderer.on('channel1', () => {});
  143. ipcRenderer.on('channel2', () => {});
  144. ipcRenderer.removeAllListeners();
  145. return ipcRenderer.eventNames();
  146. })()
  147. `);
  148. expect(result).to.deep.equal([]);
  149. });
  150. });
  151. describe('after context is released', () => {
  152. it('throws an exception', async () => {
  153. const error = await w.webContents.executeJavaScript(`(${() => {
  154. const child = window.open('', 'child', 'show=no,nodeIntegration=yes')! as any;
  155. const childIpc = child.require('electron').ipcRenderer;
  156. child.close();
  157. return new Promise(resolve => {
  158. setInterval(() => {
  159. try {
  160. childIpc.send('hello');
  161. } catch (e) {
  162. resolve(e);
  163. }
  164. }, 16);
  165. });
  166. }})()`);
  167. expect(error).to.have.property('message', 'IPC method called after context was released');
  168. });
  169. });
  170. });