api-ipc-renderer-spec.ts 8.5 KB

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