123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- import { BrowserWindow, screen } from 'electron';
- import { expect, assert } from 'chai';
- import { once } from 'node:events';
- import * as http from 'node:http';
- import { HexColors, ScreenCapture, hasCapturableScreen } from './lib/screen-helpers';
- import { ifit, listen } from './lib/spec-helpers';
- import { closeAllWindows } from './lib/window-helpers';
- describe('webContents.setWindowOpenHandler', () => {
- describe('native window', () => {
- let browserWindow: BrowserWindow;
- beforeEach(async () => {
- browserWindow = new BrowserWindow({ show: false });
- await browserWindow.loadURL('about:blank');
- });
- afterEach(closeAllWindows);
- it('does not fire window creation events if the handler callback throws an error', (done) => {
- const error = new Error('oh no');
- const listeners = process.listeners('uncaughtException');
- process.removeAllListeners('uncaughtException');
- process.on('uncaughtException', (thrown) => {
- try {
- expect(thrown).to.equal(error);
- done();
- } catch (e) {
- done(e);
- } finally {
- process.removeAllListeners('uncaughtException');
- for (const listener of listeners) {
- process.on('uncaughtException', listener);
- }
- }
- });
- browserWindow.webContents.on('did-create-window', () => {
- assert.fail('did-create-window should not be called with an overridden window.open');
- });
- browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
- browserWindow.webContents.setWindowOpenHandler(() => {
- throw error;
- });
- });
- it('does not fire window creation events if the handler callback returns a bad result', async () => {
- const bad = new Promise((resolve) => {
- browserWindow.webContents.setWindowOpenHandler(() => {
- setTimeout(resolve);
- return [1, 2, 3] as any;
- });
- });
- browserWindow.webContents.on('did-create-window', () => {
- assert.fail('did-create-window should not be called with an overridden window.open');
- });
- browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
- await bad;
- });
- it('does not fire window creation events if an override returns action: deny', async () => {
- const denied = new Promise((resolve) => {
- browserWindow.webContents.setWindowOpenHandler(() => {
- setTimeout(resolve);
- return { action: 'deny' };
- });
- });
- browserWindow.webContents.on('did-create-window', () => {
- assert.fail('did-create-window should not be called with an overridden window.open');
- });
- browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
- await denied;
- });
- it('is called when clicking on a target=_blank link', async () => {
- const denied = new Promise((resolve) => {
- browserWindow.webContents.setWindowOpenHandler(() => {
- setTimeout(resolve);
- return { action: 'deny' };
- });
- });
- browserWindow.webContents.on('did-create-window', () => {
- assert.fail('did-create-window should not be called with an overridden window.open');
- });
- 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>');
- browserWindow.webContents.sendInputEvent({ type: 'mouseDown', x: 10, y: 10, button: 'left', clickCount: 1 });
- browserWindow.webContents.sendInputEvent({ type: 'mouseUp', x: 10, y: 10, button: 'left', clickCount: 1 });
- await denied;
- });
- it('is called when shift-clicking on a link', async () => {
- const denied = new Promise((resolve) => {
- browserWindow.webContents.setWindowOpenHandler(() => {
- setTimeout(resolve);
- return { action: 'deny' };
- });
- });
- browserWindow.webContents.on('did-create-window', () => {
- assert.fail('did-create-window should not be called with an overridden window.open');
- });
- 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>');
- browserWindow.webContents.sendInputEvent({ type: 'mouseDown', x: 10, y: 10, button: 'left', clickCount: 1, modifiers: ['shift'] });
- browserWindow.webContents.sendInputEvent({ type: 'mouseUp', x: 10, y: 10, button: 'left', clickCount: 1, modifiers: ['shift'] });
- await denied;
- });
- it('fires handler with correct params', async () => {
- const testFrameName = 'test-frame-name';
- const testFeatures = 'top=10&left=10&something-unknown&show=no';
- const testUrl = 'app://does-not-exist/';
- const details = await new Promise<Electron.HandlerDetails>(resolve => {
- browserWindow.webContents.setWindowOpenHandler((details) => {
- setTimeout(() => resolve(details));
- return { action: 'deny' };
- });
- browserWindow.webContents.executeJavaScript(`window.open('${testUrl}', '${testFrameName}', '${testFeatures}') && true`);
- });
- const { url, frameName, features, disposition, referrer } = details;
- expect(url).to.equal(testUrl);
- expect(frameName).to.equal(testFrameName);
- expect(features).to.equal(testFeatures);
- expect(disposition).to.equal('new-window');
- expect(referrer).to.deep.equal({
- policy: 'strict-origin-when-cross-origin',
- url: ''
- });
- });
- it('includes post body', async () => {
- const details = await new Promise<Electron.HandlerDetails>(resolve => {
- browserWindow.webContents.setWindowOpenHandler((details) => {
- setTimeout(() => resolve(details));
- return { action: 'deny' };
- });
- browserWindow.webContents.loadURL(`data:text/html,${encodeURIComponent(`
- <form action="http://example.com" target="_blank" method="POST" id="form">
- <input name="key" value="value"></input>
- </form>
- <script>form.submit()</script>
- `)}`);
- });
- const { url, frameName, features, disposition, referrer, postBody } = details;
- expect(url).to.equal('http://example.com/');
- expect(frameName).to.equal('');
- expect(features).to.deep.equal('');
- expect(disposition).to.equal('foreground-tab');
- expect(referrer).to.deep.equal({
- policy: 'strict-origin-when-cross-origin',
- url: ''
- });
- expect(postBody).to.deep.equal({
- contentType: 'application/x-www-form-urlencoded',
- data: [{
- type: 'rawData',
- bytes: Buffer.from('key=value')
- }]
- });
- });
- it('does fire window creation events if an override returns action: allow', async () => {
- browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
- setImmediate(() => {
- browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
- });
- await once(browserWindow.webContents, 'did-create-window');
- });
- it('can change webPreferences of child windows', async () => {
- browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { defaultFontSize: 30 } } }));
- const didCreateWindow = once(browserWindow.webContents, 'did-create-window') as Promise<[BrowserWindow, Electron.DidCreateWindowDetails]>;
- browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
- const [childWindow] = await didCreateWindow;
- await childWindow.webContents.executeJavaScript("document.write('hello')");
- const size = await childWindow.webContents.executeJavaScript("getComputedStyle(document.querySelector('body')).fontSize");
- expect(size).to.equal('30px');
- });
- it('does not hang parent window when denying window.open', async () => {
- browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
- browserWindow.webContents.executeJavaScript("window.open('https://127.0.0.1')");
- expect(await browserWindow.webContents.executeJavaScript('42')).to.equal(42);
- });
- ifit(hasCapturableScreen())('should not make child window background transparent', async () => {
- browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
- const didCreateWindow = once(browserWindow.webContents, 'did-create-window');
- browserWindow.webContents.executeJavaScript("window.open('about:blank') && true");
- const [childWindow] = await didCreateWindow;
- const display = screen.getPrimaryDisplay();
- childWindow.setBounds(display.bounds);
- await childWindow.webContents.executeJavaScript("const meta = document.createElement('meta'); meta.name = 'color-scheme'; meta.content = 'dark'; document.head.appendChild(meta); true;");
- const screenCapture = new ScreenCapture(display);
- // color-scheme is set to dark so background should not be white
- await screenCapture.expectColorAtCenterDoesNotMatch(HexColors.WHITE);
- });
- });
- describe('custom window', () => {
- let browserWindow: BrowserWindow;
- let server: http.Server;
- let url: string;
- before(async () => {
- server = http.createServer((request, response) => {
- switch (request.url) {
- case '/index':
- response.statusCode = 200;
- response.end('<title>Index page</title>');
- break;
- case '/child':
- response.statusCode = 200;
- response.end('<title>Child page</title>');
- break;
- case '/test':
- response.statusCode = 200;
- response.end('<title>Test page</title>');
- break;
- default:
- throw new Error(`Unsupported endpoint: ${request.url}`);
- }
- });
- url = (await listen(server)).url;
- });
- after(() => {
- server.close();
- });
- beforeEach(async () => {
- browserWindow = new BrowserWindow({ show: false });
- await browserWindow.loadURL(`${url}/index`);
- });
- afterEach(closeAllWindows);
- it('throws error when created window uses invalid webcontents', async () => {
- const listeners = process.listeners('uncaughtException');
- process.removeAllListeners('uncaughtException');
- const uncaughtExceptionEmitted = new Promise<void>((resolve, reject) => {
- process.on('uncaughtException', (thrown) => {
- try {
- expect(thrown.message).to.equal('Invalid webContents. Created window should be connected to webContents passed with options object.');
- resolve();
- } catch (e) {
- reject(e);
- } finally {
- process.removeAllListeners('uncaughtException');
- listeners.forEach((listener) => process.on('uncaughtException', listener));
- }
- });
- });
- browserWindow.webContents.setWindowOpenHandler(() => {
- return {
- action: 'allow',
- createWindow: () => {
- const childWindow = new BrowserWindow({ title: 'New window' });
- return childWindow.webContents;
- }
- };
- });
- browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
- await uncaughtExceptionEmitted;
- });
- it('spawns browser window when createWindow is provided', async () => {
- const browserWindowTitle = 'Child browser window';
- const childWindow = await new Promise<Electron.BrowserWindow>(resolve => {
- browserWindow.webContents.setWindowOpenHandler(() => {
- return {
- action: 'allow',
- createWindow: (options) => {
- const childWindow = new BrowserWindow({ ...options, title: browserWindowTitle });
- resolve(childWindow);
- return childWindow.webContents;
- }
- };
- });
- browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
- });
- expect(childWindow.title).to.equal(browserWindowTitle);
- });
- it('should be able to access the child window document when createWindow is provided', async () => {
- browserWindow.webContents.setWindowOpenHandler(() => {
- return {
- action: 'allow',
- createWindow: (options) => {
- const child = new BrowserWindow(options);
- return child.webContents;
- }
- };
- });
- const aboutBlankTitle = await browserWindow.webContents.executeJavaScript(`
- const win1 = window.open('about:blank', '', 'show=no');
- win1.document.title = 'about-blank-title';
- win1.document.title;
- `);
- expect(aboutBlankTitle).to.equal('about-blank-title');
- const serverPageTitle = await browserWindow.webContents.executeJavaScript(`
- const win2 = window.open('${url}/child', '', 'show=no');
- win2.document.title = 'server-page-title';
- win2.document.title;
- `);
- expect(serverPageTitle).to.equal('server-page-title');
- });
- it('spawns browser window with overridden options', async () => {
- const childWindow = await new Promise<Electron.BrowserWindow>(resolve => {
- browserWindow.webContents.setWindowOpenHandler(() => {
- return {
- action: 'allow',
- overrideBrowserWindowOptions: {
- width: 640,
- height: 480
- },
- createWindow: (options) => {
- expect(options.width).to.equal(640);
- expect(options.height).to.equal(480);
- const childWindow = new BrowserWindow(options);
- resolve(childWindow);
- return childWindow.webContents;
- }
- };
- });
- browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
- });
- const size = childWindow.getSize();
- expect(size[0]).to.equal(640);
- expect(size[1]).to.equal(480);
- });
- it('spawns browser window with access to opener property', async () => {
- const childWindow = await new Promise<Electron.BrowserWindow>(resolve => {
- browserWindow.webContents.setWindowOpenHandler(() => {
- return {
- action: 'allow',
- createWindow: (options) => {
- const childWindow = new BrowserWindow(options);
- resolve(childWindow);
- return childWindow.webContents;
- }
- };
- });
- browserWindow.webContents.executeJavaScript(`window.open('${url}/child', '', 'show=no') && true`);
- });
- await once(childWindow.webContents, 'ready-to-show');
- const childWindowOpenerTitle = await childWindow.webContents.executeJavaScript('window.opener.document.title');
- expect(childWindowOpenerTitle).to.equal(browserWindow.title);
- });
- it('spawns browser window without access to opener property because of noopener attribute ', async () => {
- const childWindow = await new Promise<Electron.BrowserWindow>(resolve => {
- browserWindow.webContents.setWindowOpenHandler(() => {
- return {
- action: 'allow',
- createWindow: (options) => {
- const childWindow = new BrowserWindow(options);
- resolve(childWindow);
- return childWindow.webContents;
- }
- };
- });
- browserWindow.webContents.executeJavaScript(`window.open('${url}/child', '', 'noopener,show=no') && true`);
- });
- await once(childWindow.webContents, 'ready-to-show');
- await expect(childWindow.webContents.executeJavaScript('window.opener.document.title')).to.be.rejectedWith('Script failed to execute, this normally means an error was thrown. Check the renderer console for the error.');
- });
- });
- });
|