guest-window-manager-spec.ts 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import { BrowserWindow, screen } from 'electron';
  2. import { expect, assert } from 'chai';
  3. import { areColorsSimilar, captureScreen, HexColors, getPixelColor } from './lib/screen-helpers';
  4. import { ifit } from './lib/spec-helpers';
  5. import { closeAllWindows } from './lib/window-helpers';
  6. import { once } from 'node:events';
  7. import { setTimeout as setTimeoutAsync } from 'node:timers/promises';
  8. describe('webContents.setWindowOpenHandler', () => {
  9. let browserWindow: BrowserWindow;
  10. beforeEach(async () => {
  11. browserWindow = new BrowserWindow({ show: false });
  12. await browserWindow.loadURL('about:blank');
  13. });
  14. afterEach(closeAllWindows);
  15. it('does not fire window creation events if the handler callback throws an error', (done) => {
  16. const error = new Error('oh no');
  17. const listeners = process.listeners('uncaughtException');
  18. process.removeAllListeners('uncaughtException');
  19. process.on('uncaughtException', (thrown) => {
  20. try {
  21. expect(thrown).to.equal(error);
  22. done();
  23. } catch (e) {
  24. done(e);
  25. } finally {
  26. process.removeAllListeners('uncaughtException');
  27. listeners.forEach((listener) => process.on('uncaughtException', listener));
  28. }
  29. });
  30. browserWindow.webContents.on('did-create-window', () => {
  31. assert.fail('did-create-window should not be called with an overridden window.open');
  32. });
  33. browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
  34. browserWindow.webContents.setWindowOpenHandler(() => {
  35. throw error;
  36. });
  37. });
  38. it('does not fire window creation events if the handler callback returns a bad result', async () => {
  39. const bad = new Promise((resolve) => {
  40. browserWindow.webContents.setWindowOpenHandler(() => {
  41. setTimeout(resolve);
  42. return [1, 2, 3] as any;
  43. });
  44. });
  45. browserWindow.webContents.on('did-create-window', () => {
  46. assert.fail('did-create-window should not be called with an overridden window.open');
  47. });
  48. browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
  49. await bad;
  50. });
  51. it('does not fire window creation events if an override returns action: deny', async () => {
  52. const denied = new Promise((resolve) => {
  53. browserWindow.webContents.setWindowOpenHandler(() => {
  54. setTimeout(resolve);
  55. return { action: 'deny' };
  56. });
  57. });
  58. browserWindow.webContents.on('did-create-window', () => {
  59. assert.fail('did-create-window should not be called with an overridden window.open');
  60. });
  61. browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
  62. await denied;
  63. });
  64. it('is called when clicking on a target=_blank link', async () => {
  65. const denied = new Promise((resolve) => {
  66. browserWindow.webContents.setWindowOpenHandler(() => {
  67. setTimeout(resolve);
  68. return { action: 'deny' };
  69. });
  70. });
  71. browserWindow.webContents.on('did-create-window', () => {
  72. assert.fail('did-create-window should not be called with an overridden window.open');
  73. });
  74. await browserWindow.webContents.loadURL('data:text/html,<a target="_blank" href="http://example.com" style="display: block; width: 100%; height: 100%; position: fixed; left: 0; top: 0;">link</a>');
  75. browserWindow.webContents.sendInputEvent({ type: 'mouseDown', x: 10, y: 10, button: 'left', clickCount: 1 });
  76. browserWindow.webContents.sendInputEvent({ type: 'mouseUp', x: 10, y: 10, button: 'left', clickCount: 1 });
  77. await denied;
  78. });
  79. it('is called when shift-clicking on a link', async () => {
  80. const denied = new Promise((resolve) => {
  81. browserWindow.webContents.setWindowOpenHandler(() => {
  82. setTimeout(resolve);
  83. return { action: 'deny' };
  84. });
  85. });
  86. browserWindow.webContents.on('did-create-window', () => {
  87. assert.fail('did-create-window should not be called with an overridden window.open');
  88. });
  89. await browserWindow.webContents.loadURL('data:text/html,<a href="http://example.com" style="display: block; width: 100%; height: 100%; position: fixed; left: 0; top: 0;">link</a>');
  90. browserWindow.webContents.sendInputEvent({ type: 'mouseDown', x: 10, y: 10, button: 'left', clickCount: 1, modifiers: ['shift'] });
  91. browserWindow.webContents.sendInputEvent({ type: 'mouseUp', x: 10, y: 10, button: 'left', clickCount: 1, modifiers: ['shift'] });
  92. await denied;
  93. });
  94. it('fires handler with correct params', async () => {
  95. const testFrameName = 'test-frame-name';
  96. const testFeatures = 'top=10&left=10&something-unknown&show=no';
  97. const testUrl = 'app://does-not-exist/';
  98. const details = await new Promise<Electron.HandlerDetails>(resolve => {
  99. browserWindow.webContents.setWindowOpenHandler((details) => {
  100. setTimeout(() => resolve(details));
  101. return { action: 'deny' };
  102. });
  103. browserWindow.webContents.executeJavaScript(`window.open('${testUrl}', '${testFrameName}', '${testFeatures}') && true`);
  104. });
  105. const { url, frameName, features, disposition, referrer } = details;
  106. expect(url).to.equal(testUrl);
  107. expect(frameName).to.equal(testFrameName);
  108. expect(features).to.equal(testFeatures);
  109. expect(disposition).to.equal('new-window');
  110. expect(referrer).to.deep.equal({
  111. policy: 'strict-origin-when-cross-origin',
  112. url: ''
  113. });
  114. });
  115. it('includes post body', async () => {
  116. const details = await new Promise<Electron.HandlerDetails>(resolve => {
  117. browserWindow.webContents.setWindowOpenHandler((details) => {
  118. setTimeout(() => resolve(details));
  119. return { action: 'deny' };
  120. });
  121. browserWindow.webContents.loadURL(`data:text/html,${encodeURIComponent(`
  122. <form action="http://example.com" target="_blank" method="POST" id="form">
  123. <input name="key" value="value"></input>
  124. </form>
  125. <script>form.submit()</script>
  126. `)}`);
  127. });
  128. const { url, frameName, features, disposition, referrer, postBody } = details;
  129. expect(url).to.equal('http://example.com/');
  130. expect(frameName).to.equal('');
  131. expect(features).to.deep.equal('');
  132. expect(disposition).to.equal('foreground-tab');
  133. expect(referrer).to.deep.equal({
  134. policy: 'strict-origin-when-cross-origin',
  135. url: ''
  136. });
  137. expect(postBody).to.deep.equal({
  138. contentType: 'application/x-www-form-urlencoded',
  139. data: [{
  140. type: 'rawData',
  141. bytes: Buffer.from('key=value')
  142. }]
  143. });
  144. });
  145. it('does fire window creation events if an override returns action: allow', async () => {
  146. browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
  147. setImmediate(() => {
  148. browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
  149. });
  150. await once(browserWindow.webContents, 'did-create-window');
  151. });
  152. it('can change webPreferences of child windows', async () => {
  153. browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { defaultFontSize: 30 } } }));
  154. const didCreateWindow = once(browserWindow.webContents, 'did-create-window') as Promise<[BrowserWindow, Electron.DidCreateWindowDetails]>;
  155. browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
  156. const [childWindow] = await didCreateWindow;
  157. await childWindow.webContents.executeJavaScript("document.write('hello')");
  158. const size = await childWindow.webContents.executeJavaScript("getComputedStyle(document.querySelector('body')).fontSize");
  159. expect(size).to.equal('30px');
  160. });
  161. it('does not hang parent window when denying window.open', async () => {
  162. browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
  163. browserWindow.webContents.executeJavaScript("window.open('https://127.0.0.1')");
  164. expect(await browserWindow.webContents.executeJavaScript('42')).to.equal(42);
  165. });
  166. // Linux and arm64 platforms (WOA and macOS) do not return any capture sources
  167. ifit(process.platform === 'darwin' && process.arch === 'x64')('should not make child window background transparent', (done) => {
  168. browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
  169. browserWindow.webContents.once('did-create-window', async (childWindow) => {
  170. const display = screen.getPrimaryDisplay();
  171. childWindow.setBounds(display.bounds);
  172. await childWindow.webContents.executeJavaScript("const meta = document.createElement('meta'); meta.name = 'color-scheme'; meta.content = 'dark'; document.head.appendChild(meta); true;");
  173. await setTimeoutAsync(1000);
  174. const screenCapture = await captureScreen();
  175. const centerColor = getPixelColor(screenCapture, {
  176. x: display.size.width / 2,
  177. y: display.size.height / 2
  178. });
  179. try {
  180. // color-scheme is set to dark so background should not be white
  181. expect(areColorsSimilar(centerColor, HexColors.WHITE)).to.be.false();
  182. done();
  183. } catch (err) {
  184. done(err);
  185. }
  186. });
  187. browserWindow.webContents.executeJavaScript("window.open('about:blank') && true");
  188. });
  189. });