feat: Allow creation of new window to be customizable. (#41432)
This commit is contained in:
parent
04df5ce492
commit
a0dad83ded
7 changed files with 441 additions and 220 deletions
7
docs/api/structures/window-open-handler-response.md
Normal file
7
docs/api/structures/window-open-handler-response.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# WindowOpenHandlerResponse Object
|
||||||
|
|
||||||
|
* `action` string - Can be `allow` or `deny`. Controls whether new window should be created.
|
||||||
|
* `overrideBrowserWindowOptions` BrowserWindowConstructorOptions (optional) - Allows customization of the created window.
|
||||||
|
* `outlivesOpener` boolean (optional) - By default, child windows are closed when their opener is closed. This can be
|
||||||
|
changed by specifying `outlivesOpener: true`, in which case the opened window will not be closed when its opener is closed.
|
||||||
|
* `createWindow` (options: BrowserWindowConstructorOptions) => WebContents (optional) - If specified, will be called instead of `new BrowserWindow` to create the new child window and event [`did-create-window`](../web-contents.md#event-did-create-window) will not be emitted. Constructed child window should use passed `options` object. This can be used for example to have the new window open as a BrowserView instead of in a separate window.
|
|
@ -1288,7 +1288,7 @@ Ignore application menu shortcuts while this web contents is focused.
|
||||||
|
|
||||||
#### `contents.setWindowOpenHandler(handler)`
|
#### `contents.setWindowOpenHandler(handler)`
|
||||||
|
|
||||||
* `handler` Function<{action: 'deny'} | {action: 'allow', outlivesOpener?: boolean, overrideBrowserWindowOptions?: BrowserWindowConstructorOptions}>
|
* `handler` Function<[WindowOpenHandlerResponse](structures/window-open-handler-response.md)>
|
||||||
* `details` Object
|
* `details` Object
|
||||||
* `url` string - The _resolved_ version of the URL passed to `window.open()`. e.g. opening a window with `window.open('foo')` will yield something like `https://the-origin/the/current/path/foo`.
|
* `url` string - The _resolved_ version of the URL passed to `window.open()`. e.g. opening a window with `window.open('foo')` will yield something like `https://the-origin/the/current/path/foo`.
|
||||||
* `frameName` string - Name of the window provided in `window.open()`
|
* `frameName` string - Name of the window provided in `window.open()`
|
||||||
|
@ -1303,11 +1303,8 @@ Ignore application menu shortcuts while this web contents is focused.
|
||||||
be set. If no post data is to be sent, the value will be `null`. Only defined
|
be set. If no post data is to be sent, the value will be `null`. Only defined
|
||||||
when the window is being created by a form that set `target=_blank`.
|
when the window is being created by a form that set `target=_blank`.
|
||||||
|
|
||||||
Returns `{action: 'deny'} | {action: 'allow', outlivesOpener?: boolean, overrideBrowserWindowOptions?: BrowserWindowConstructorOptions}` - `deny` cancels the creation of the new
|
Returns `WindowOpenHandlerResponse` - When set to `{ action: 'deny' }` cancels the creation of the new
|
||||||
window. `allow` will allow the new window to be created. Specifying `overrideBrowserWindowOptions` allows customization of the created window.
|
window. `{ action: 'allow' }` will allow the new window to be created.
|
||||||
By default, child windows are closed when their opener is closed. This can be
|
|
||||||
changed by specifying `outlivesOpener: true`, in which case the opened window
|
|
||||||
will not be closed when its opener is closed.
|
|
||||||
Returning an unrecognized value such as a null, undefined, or an object
|
Returning an unrecognized value such as a null, undefined, or an object
|
||||||
without a recognized 'action' value will result in a console error and have
|
without a recognized 'action' value will result in a console error and have
|
||||||
the same effect as returning `{action: 'deny'}`.
|
the same effect as returning `{action: 'deny'}`.
|
||||||
|
@ -1318,6 +1315,26 @@ submitting a form with `<form target="_blank">`. See
|
||||||
[`window.open()`](window-open.md) for more details and how to use this in
|
[`window.open()`](window-open.md) for more details and how to use this in
|
||||||
conjunction with `did-create-window`.
|
conjunction with `did-create-window`.
|
||||||
|
|
||||||
|
An example showing how to customize the process of new `BrowserWindow` creation to be `BrowserView` attached to main window instead:
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { BrowserView, BrowserWindow } = require('electron')
|
||||||
|
|
||||||
|
const mainWindow = new BrowserWindow()
|
||||||
|
|
||||||
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||||
|
return {
|
||||||
|
action: 'allow',
|
||||||
|
createWindow: (options) => {
|
||||||
|
const browserView = new BrowserView(options)
|
||||||
|
mainWindow.addBrowserView(browserView)
|
||||||
|
browserView.setBounds({ x: 0, y: 0, width: 640, height: 480 })
|
||||||
|
return browserView.webContents
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
#### `contents.setAudioMuted(muted)`
|
#### `contents.setAudioMuted(muted)`
|
||||||
|
|
||||||
* `muted` boolean
|
* `muted` boolean
|
||||||
|
|
|
@ -144,6 +144,7 @@ auto_filenames = {
|
||||||
"docs/api/structures/web-preferences.md",
|
"docs/api/structures/web-preferences.md",
|
||||||
"docs/api/structures/web-request-filter.md",
|
"docs/api/structures/web-request-filter.md",
|
||||||
"docs/api/structures/web-source.md",
|
"docs/api/structures/web-source.md",
|
||||||
|
"docs/api/structures/window-open-handler-response.md",
|
||||||
]
|
]
|
||||||
|
|
||||||
sandbox_bundle_deps = [
|
sandbox_bundle_deps = [
|
||||||
|
|
|
@ -435,14 +435,15 @@ WebContents.prototype.loadURL = function (url, options) {
|
||||||
return p;
|
return p;
|
||||||
};
|
};
|
||||||
|
|
||||||
WebContents.prototype.setWindowOpenHandler = function (handler: (details: Electron.HandlerDetails) => ({action: 'deny'} | {action: 'allow', overrideBrowserWindowOptions?: BrowserWindowConstructorOptions, outlivesOpener?: boolean})) {
|
WebContents.prototype.setWindowOpenHandler = function (handler: (details: Electron.HandlerDetails) => Electron.WindowOpenHandlerResponse) {
|
||||||
this._windowOpenHandler = handler;
|
this._windowOpenHandler = handler;
|
||||||
};
|
};
|
||||||
|
|
||||||
WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event, details: Electron.HandlerDetails): {browserWindowConstructorOptions: BrowserWindowConstructorOptions | null, outlivesOpener: boolean} {
|
WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event, details: Electron.HandlerDetails): {browserWindowConstructorOptions: BrowserWindowConstructorOptions | null, outlivesOpener: boolean, createWindow?: Electron.CreateWindowFunction} {
|
||||||
const defaultResponse = {
|
const defaultResponse = {
|
||||||
browserWindowConstructorOptions: null,
|
browserWindowConstructorOptions: null,
|
||||||
outlivesOpener: false
|
outlivesOpener: false,
|
||||||
|
createWindow: undefined
|
||||||
};
|
};
|
||||||
if (!this._windowOpenHandler) {
|
if (!this._windowOpenHandler) {
|
||||||
return defaultResponse;
|
return defaultResponse;
|
||||||
|
@ -468,7 +469,8 @@ WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event,
|
||||||
} else if (response.action === 'allow') {
|
} else if (response.action === 'allow') {
|
||||||
return {
|
return {
|
||||||
browserWindowConstructorOptions: typeof response.overrideBrowserWindowOptions === 'object' ? response.overrideBrowserWindowOptions : null,
|
browserWindowConstructorOptions: typeof response.overrideBrowserWindowOptions === 'object' ? response.overrideBrowserWindowOptions : null,
|
||||||
outlivesOpener: typeof response.outlivesOpener === 'boolean' ? response.outlivesOpener : false
|
outlivesOpener: typeof response.outlivesOpener === 'boolean' ? response.outlivesOpener : false,
|
||||||
|
createWindow: typeof response.createWindow === 'function' ? response.createWindow : undefined
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -655,13 +657,16 @@ WebContents.prototype._init = function () {
|
||||||
postData,
|
postData,
|
||||||
overrideBrowserWindowOptions: options || {},
|
overrideBrowserWindowOptions: options || {},
|
||||||
windowOpenArgs: details,
|
windowOpenArgs: details,
|
||||||
outlivesOpener: result.outlivesOpener
|
outlivesOpener: result.outlivesOpener,
|
||||||
|
createWindow: result.createWindow
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let windowOpenOverriddenOptions: BrowserWindowConstructorOptions | null = null;
|
let windowOpenOverriddenOptions: BrowserWindowConstructorOptions | null = null;
|
||||||
let windowOpenOutlivesOpenerOption: boolean = false;
|
let windowOpenOutlivesOpenerOption: boolean = false;
|
||||||
|
let createWindow: Electron.CreateWindowFunction | undefined;
|
||||||
|
|
||||||
this.on('-will-add-new-contents' as any, (event: Electron.Event, url: string, frameName: string, rawFeatures: string, disposition: Electron.HandlerDetails['disposition'], referrer: Electron.Referrer, postData: PostData) => {
|
this.on('-will-add-new-contents' as any, (event: Electron.Event, url: string, frameName: string, rawFeatures: string, disposition: Electron.HandlerDetails['disposition'], referrer: Electron.Referrer, postData: PostData) => {
|
||||||
const postBody = postData ? {
|
const postBody = postData ? {
|
||||||
data: postData,
|
data: postData,
|
||||||
|
@ -686,6 +691,7 @@ WebContents.prototype._init = function () {
|
||||||
|
|
||||||
windowOpenOutlivesOpenerOption = result.outlivesOpener;
|
windowOpenOutlivesOpenerOption = result.outlivesOpener;
|
||||||
windowOpenOverriddenOptions = result.browserWindowConstructorOptions;
|
windowOpenOverriddenOptions = result.browserWindowConstructorOptions;
|
||||||
|
createWindow = result.createWindow;
|
||||||
if (!event.defaultPrevented) {
|
if (!event.defaultPrevented) {
|
||||||
const secureOverrideWebPreferences = windowOpenOverriddenOptions ? {
|
const secureOverrideWebPreferences = windowOpenOverriddenOptions ? {
|
||||||
// Allow setting of backgroundColor as a webPreference even though
|
// Allow setting of backgroundColor as a webPreference even though
|
||||||
|
@ -715,6 +721,9 @@ WebContents.prototype._init = function () {
|
||||||
referrer: Electron.Referrer, rawFeatures: string, postData: PostData) => {
|
referrer: Electron.Referrer, rawFeatures: string, postData: PostData) => {
|
||||||
const overriddenOptions = windowOpenOverriddenOptions || undefined;
|
const overriddenOptions = windowOpenOverriddenOptions || undefined;
|
||||||
const outlivesOpener = windowOpenOutlivesOpenerOption;
|
const outlivesOpener = windowOpenOutlivesOpenerOption;
|
||||||
|
const windowOpenFunction = createWindow;
|
||||||
|
|
||||||
|
createWindow = undefined;
|
||||||
windowOpenOverriddenOptions = null;
|
windowOpenOverriddenOptions = null;
|
||||||
// false is the default
|
// false is the default
|
||||||
windowOpenOutlivesOpenerOption = false;
|
windowOpenOutlivesOpenerOption = false;
|
||||||
|
@ -737,7 +746,8 @@ WebContents.prototype._init = function () {
|
||||||
frameName,
|
frameName,
|
||||||
features: rawFeatures
|
features: rawFeatures
|
||||||
},
|
},
|
||||||
outlivesOpener
|
outlivesOpener,
|
||||||
|
createWindow: windowOpenFunction
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,16 +16,16 @@ export type WindowOpenArgs = {
|
||||||
features: string,
|
features: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
const frameNamesToWindow = new Map<string, BrowserWindow>();
|
const frameNamesToWindow = new Map<string, WebContents>();
|
||||||
const registerFrameNameToGuestWindow = (name: string, win: BrowserWindow) => frameNamesToWindow.set(name, win);
|
const registerFrameNameToGuestWindow = (name: string, webContents: WebContents) => frameNamesToWindow.set(name, webContents);
|
||||||
const unregisterFrameName = (name: string) => frameNamesToWindow.delete(name);
|
const unregisterFrameName = (name: string) => frameNamesToWindow.delete(name);
|
||||||
const getGuestWindowByFrameName = (name: string) => frameNamesToWindow.get(name);
|
const getGuestWebContentsByFrameName = (name: string) => frameNamesToWindow.get(name);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `openGuestWindow` is called to create and setup event handling for the new
|
* `openGuestWindow` is called to create and setup event handling for the new
|
||||||
* window.
|
* window.
|
||||||
*/
|
*/
|
||||||
export function openGuestWindow ({ embedder, guest, referrer, disposition, postData, overrideBrowserWindowOptions, windowOpenArgs, outlivesOpener }: {
|
export function openGuestWindow ({ embedder, guest, referrer, disposition, postData, overrideBrowserWindowOptions, windowOpenArgs, outlivesOpener, createWindow }: {
|
||||||
embedder: WebContents,
|
embedder: WebContents,
|
||||||
guest?: WebContents,
|
guest?: WebContents,
|
||||||
referrer: Referrer,
|
referrer: Referrer,
|
||||||
|
@ -34,7 +34,8 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||||
overrideBrowserWindowOptions?: BrowserWindowConstructorOptions,
|
overrideBrowserWindowOptions?: BrowserWindowConstructorOptions,
|
||||||
windowOpenArgs: WindowOpenArgs,
|
windowOpenArgs: WindowOpenArgs,
|
||||||
outlivesOpener: boolean,
|
outlivesOpener: boolean,
|
||||||
}): BrowserWindow | undefined {
|
createWindow?: Electron.CreateWindowFunction
|
||||||
|
}): void {
|
||||||
const { url, frameName, features } = windowOpenArgs;
|
const { url, frameName, features } = windowOpenArgs;
|
||||||
const { options: parsedOptions } = parseFeatures(features);
|
const { options: parsedOptions } = parseFeatures(features);
|
||||||
const browserWindowOptions = {
|
const browserWindowOptions = {
|
||||||
|
@ -48,17 +49,42 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||||
// To spec, subsequent window.open calls with the same frame name (`target` in
|
// To spec, subsequent window.open calls with the same frame name (`target` in
|
||||||
// spec parlance) will reuse the previous window.
|
// spec parlance) will reuse the previous window.
|
||||||
// https://html.spec.whatwg.org/multipage/window-object.html#apis-for-creating-and-navigating-browsing-contexts-by-name
|
// https://html.spec.whatwg.org/multipage/window-object.html#apis-for-creating-and-navigating-browsing-contexts-by-name
|
||||||
const existingWindow = getGuestWindowByFrameName(frameName);
|
const existingWebContents = getGuestWebContentsByFrameName(frameName);
|
||||||
if (existingWindow) {
|
if (existingWebContents) {
|
||||||
if (existingWindow.isDestroyed() || existingWindow.webContents.isDestroyed()) {
|
if (existingWebContents.isDestroyed()) {
|
||||||
// FIXME(t57ser): The webContents is destroyed for some reason, unregister the frame name
|
// FIXME(t57ser): The webContents is destroyed for some reason, unregister the frame name
|
||||||
unregisterFrameName(frameName);
|
unregisterFrameName(frameName);
|
||||||
} else {
|
} else {
|
||||||
existingWindow.loadURL(url);
|
existingWebContents.loadURL(url);
|
||||||
return existingWindow;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (createWindow) {
|
||||||
|
const webContents = createWindow({
|
||||||
|
webContents: guest,
|
||||||
|
...browserWindowOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
if (guest != null) {
|
||||||
|
if (webContents !== guest) {
|
||||||
|
throw new Error('Invalid webContents. Created window should be connected to webContents passed with options object.');
|
||||||
|
}
|
||||||
|
|
||||||
|
webContents.loadURL(url, {
|
||||||
|
httpReferrer: referrer,
|
||||||
|
...(postData && {
|
||||||
|
postData,
|
||||||
|
extraHeaders: formatPostDataHeaders(postData as Electron.UploadRawData[])
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
handleWindowLifecycleEvents({ embedder, frameName, guest, outlivesOpener });
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const window = new BrowserWindow({
|
const window = new BrowserWindow({
|
||||||
webContents: guest,
|
webContents: guest,
|
||||||
...browserWindowOptions
|
...browserWindowOptions
|
||||||
|
@ -77,11 +103,9 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWindowLifecycleEvents({ embedder, frameName, guest: window, outlivesOpener });
|
handleWindowLifecycleEvents({ embedder, frameName, guest: window.webContents, outlivesOpener });
|
||||||
|
|
||||||
embedder.emit('did-create-window', window, { url, frameName, options: browserWindowOptions, disposition, referrer, postData });
|
embedder.emit('did-create-window', window, { url, frameName, options: browserWindowOptions, disposition, referrer, postData });
|
||||||
|
|
||||||
return window;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -92,12 +116,12 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||||
*/
|
*/
|
||||||
const handleWindowLifecycleEvents = function ({ embedder, guest, frameName, outlivesOpener }: {
|
const handleWindowLifecycleEvents = function ({ embedder, guest, frameName, outlivesOpener }: {
|
||||||
embedder: WebContents,
|
embedder: WebContents,
|
||||||
guest: BrowserWindow,
|
guest: WebContents,
|
||||||
frameName: string,
|
frameName: string,
|
||||||
outlivesOpener: boolean
|
outlivesOpener: boolean
|
||||||
}) {
|
}) {
|
||||||
const closedByEmbedder = function () {
|
const closedByEmbedder = function () {
|
||||||
guest.removeListener('closed', closedByUser);
|
guest.removeListener('destroyed', closedByUser);
|
||||||
guest.destroy();
|
guest.destroy();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -110,11 +134,11 @@ const handleWindowLifecycleEvents = function ({ embedder, guest, frameName, outl
|
||||||
if (!outlivesOpener) {
|
if (!outlivesOpener) {
|
||||||
embedder.once('current-render-view-deleted' as any, closedByEmbedder);
|
embedder.once('current-render-view-deleted' as any, closedByEmbedder);
|
||||||
}
|
}
|
||||||
guest.once('closed', closedByUser);
|
guest.once('destroyed', closedByUser);
|
||||||
|
|
||||||
if (frameName) {
|
if (frameName) {
|
||||||
registerFrameNameToGuestWindow(frameName, guest);
|
registerFrameNameToGuestWindow(frameName, guest);
|
||||||
guest.once('closed', function () {
|
guest.once('destroyed', function () {
|
||||||
unregisterFrameName(frameName);
|
unregisterFrameName(frameName);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,216 +1,376 @@
|
||||||
import { BrowserWindow, screen } from 'electron';
|
import { BrowserWindow, screen } from 'electron';
|
||||||
import { expect, assert } from 'chai';
|
import { expect, assert } from 'chai';
|
||||||
import { HexColors, ScreenCapture } from './lib/screen-helpers';
|
import { HexColors, ScreenCapture } from './lib/screen-helpers';
|
||||||
import { ifit } from './lib/spec-helpers';
|
import { ifit, listen } from './lib/spec-helpers';
|
||||||
import { closeAllWindows } from './lib/window-helpers';
|
import { closeAllWindows } from './lib/window-helpers';
|
||||||
import { once } from 'node:events';
|
import { once } from 'node:events';
|
||||||
import { setTimeout as setTimeoutAsync } from 'node:timers/promises';
|
import { setTimeout as setTimeoutAsync } from 'node:timers/promises';
|
||||||
|
import * as http from 'node:http';
|
||||||
|
|
||||||
describe('webContents.setWindowOpenHandler', () => {
|
describe('webContents.setWindowOpenHandler', () => {
|
||||||
let browserWindow: BrowserWindow;
|
describe('native window', () => {
|
||||||
beforeEach(async () => {
|
let browserWindow: BrowserWindow;
|
||||||
browserWindow = new BrowserWindow({ show: false });
|
beforeEach(async () => {
|
||||||
await browserWindow.loadURL('about:blank');
|
browserWindow = new BrowserWindow({ show: false });
|
||||||
});
|
await browserWindow.loadURL('about:blank');
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(closeAllWindows);
|
afterEach(closeAllWindows);
|
||||||
|
|
||||||
it('does not fire window creation events if the handler callback throws an error', (done) => {
|
it('does not fire window creation events if the handler callback throws an error', (done) => {
|
||||||
const error = new Error('oh no');
|
const error = new Error('oh no');
|
||||||
const listeners = process.listeners('uncaughtException');
|
const listeners = process.listeners('uncaughtException');
|
||||||
process.removeAllListeners('uncaughtException');
|
process.removeAllListeners('uncaughtException');
|
||||||
process.on('uncaughtException', (thrown) => {
|
process.on('uncaughtException', (thrown) => {
|
||||||
try {
|
try {
|
||||||
expect(thrown).to.equal(error);
|
expect(thrown).to.equal(error);
|
||||||
done();
|
done();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
done(e);
|
done(e);
|
||||||
} finally {
|
} finally {
|
||||||
process.removeAllListeners('uncaughtException');
|
process.removeAllListeners('uncaughtException');
|
||||||
for (const listener of listeners) {
|
for (const listener of listeners) {
|
||||||
process.on('uncaughtException', listener);
|
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`);
|
browserWindow.webContents.on('did-create-window', () => {
|
||||||
});
|
assert.fail('did-create-window should not be called with an overridden window.open');
|
||||||
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");
|
browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
|
||||||
|
|
||||||
|
browserWindow.webContents.setWindowOpenHandler(() => {
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
await once(browserWindow.webContents, 'did-create-window');
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
||||||
|
ifit(process.platform === 'darwin' && process.arch === 'x64')('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;");
|
||||||
|
await setTimeoutAsync(1000);
|
||||||
|
const screenCapture = await ScreenCapture.createForDisplay(display);
|
||||||
|
// color-scheme is set to dark so background should not be white
|
||||||
|
await screenCapture.expectColorAtCenterDoesNotMatch(HexColors.WHITE);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can change webPreferences of child windows', async () => {
|
describe('custom window', () => {
|
||||||
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { defaultFontSize: 30 } } }));
|
let browserWindow: BrowserWindow;
|
||||||
|
|
||||||
const didCreateWindow = once(browserWindow.webContents, 'did-create-window') as Promise<[BrowserWindow, Electron.DidCreateWindowDetails]>;
|
let server: http.Server;
|
||||||
browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
|
let url: string;
|
||||||
const [childWindow] = await didCreateWindow;
|
|
||||||
|
|
||||||
await childWindow.webContents.executeJavaScript("document.write('hello')");
|
before(async () => {
|
||||||
const size = await childWindow.webContents.executeJavaScript("getComputedStyle(document.querySelector('body')).fontSize");
|
server = http.createServer((request, response) => {
|
||||||
expect(size).to.equal('30px');
|
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;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported endpoint: ${request.url}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it('does not hang parent window when denying window.open', async () => {
|
url = (await listen(server)).url;
|
||||||
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
|
});
|
||||||
browserWindow.webContents.executeJavaScript("window.open('https://127.0.0.1')");
|
|
||||||
expect(await browserWindow.webContents.executeJavaScript('42')).to.equal(42);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
|
after(() => {
|
||||||
ifit(process.platform === 'darwin' && process.arch === 'x64')('should not make child window background transparent', async () => {
|
server.close();
|
||||||
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
|
});
|
||||||
const didCreateWindow = once(browserWindow.webContents, 'did-create-window');
|
|
||||||
browserWindow.webContents.executeJavaScript("window.open('about:blank') && true");
|
beforeEach(async () => {
|
||||||
const [childWindow] = await didCreateWindow;
|
browserWindow = new BrowserWindow({ show: false });
|
||||||
const display = screen.getPrimaryDisplay();
|
await browserWindow.loadURL(`${url}/index`);
|
||||||
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;");
|
|
||||||
await setTimeoutAsync(1000);
|
afterEach(closeAllWindows);
|
||||||
const screenCapture = await ScreenCapture.createForDisplay(display);
|
|
||||||
// color-scheme is set to dark so background should not be white
|
it('throws error when created window uses invalid webcontents', async () => {
|
||||||
await screenCapture.expectColorAtCenterDoesNotMatch(HexColors.WHITE);
|
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('spawns browser window with overriden 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.');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
4
typings/internal-electron.d.ts
vendored
4
typings/internal-electron.d.ts
vendored
|
@ -77,7 +77,7 @@ declare namespace Electron {
|
||||||
equal(other: WebContents): boolean;
|
equal(other: WebContents): boolean;
|
||||||
browserWindowOptions: BrowserWindowConstructorOptions;
|
browserWindowOptions: BrowserWindowConstructorOptions;
|
||||||
_windowOpenHandler: ((details: Electron.HandlerDetails) => any) | null;
|
_windowOpenHandler: ((details: Electron.HandlerDetails) => any) | null;
|
||||||
_callWindowOpenHandler(event: any, details: Electron.HandlerDetails): {browserWindowConstructorOptions: Electron.BrowserWindowConstructorOptions | null, outlivesOpener: boolean};
|
_callWindowOpenHandler(event: any, details: Electron.HandlerDetails): {browserWindowConstructorOptions: Electron.BrowserWindowConstructorOptions | null, outlivesOpener: boolean, createWindow?: Electron.CreateWindowFunction};
|
||||||
_setNextChildWebPreferences(prefs: Partial<Electron.BrowserWindowConstructorOptions['webPreferences']> & Pick<Electron.BrowserWindowConstructorOptions, 'backgroundColor'>): void;
|
_setNextChildWebPreferences(prefs: Partial<Electron.BrowserWindowConstructorOptions['webPreferences']> & Pick<Electron.BrowserWindowConstructorOptions, 'backgroundColor'>): void;
|
||||||
_send(internal: boolean, channel: string, args: any): boolean;
|
_send(internal: boolean, channel: string, args: any): boolean;
|
||||||
_sendInternal(channel: string, ...args: any[]): void;
|
_sendInternal(channel: string, ...args: any[]): void;
|
||||||
|
@ -112,6 +112,8 @@ declare namespace Electron {
|
||||||
embedder?: Electron.WebContents;
|
embedder?: Electron.WebContents;
|
||||||
type?: 'backgroundPage' | 'window' | 'browserView' | 'remote' | 'webview' | 'offscreen';
|
type?: 'backgroundPage' | 'window' | 'browserView' | 'remote' | 'webview' | 'offscreen';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateWindowFunction = (options: BrowserWindowConstructorOptions) => WebContents;
|
||||||
|
|
||||||
interface Menu {
|
interface Menu {
|
||||||
_init(): void;
|
_init(): void;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue