feat: add ability to configure if window should close when opener closes (#31314)
* feat: Added ability to configure if window should close when opener closes * fix: check if embedder is destroyed * fix: correctly take over closeWithOpener property * chore: Added documentation * Update docs/api/window-open.md Co-authored-by: John Kleinschmidt <jkleinsc@github.com> * chore: refactor Co-authored-by: Jeremy Rose <nornagon@nornagon.net> * chore: changed property name from `closeWithOpener` to `outlivesOpener` * dummy change to kick lint * undo above Co-authored-by: John Kleinschmidt <jkleinsc@github.com> Co-authored-by: Jeremy Rose <nornagon@nornagon.net>
This commit is contained in:
parent
bcf060fab6
commit
41b2945ced
4 changed files with 53 additions and 21 deletions
|
@ -73,6 +73,11 @@ creating the window. Note that this is more powerful than passing options
|
|||
through the feature string, as the renderer has more limited privileges in
|
||||
deciding security preferences than the main process.
|
||||
|
||||
In addition to passing in `action` and `overrideBrowserWindowOptions`,
|
||||
`outlivesOpener` can be passed like: `{ action: 'allow', outlivesOpener: true,
|
||||
overrideBrowserWindowOptions: { ... } }`. If set to `true`, the newly created
|
||||
window will not close when the opener window closes. The default value is `false`.
|
||||
|
||||
### Native `Window` example
|
||||
|
||||
```javascript
|
||||
|
|
|
@ -492,41 +492,51 @@ WebContents.prototype.loadURL = function (url, options) {
|
|||
return p;
|
||||
};
|
||||
|
||||
WebContents.prototype.setWindowOpenHandler = function (handler: (details: Electron.HandlerDetails) => ({action: 'allow'} | {action: 'deny', overrideBrowserWindowOptions?: BrowserWindowConstructorOptions})) {
|
||||
WebContents.prototype.setWindowOpenHandler = function (handler: (details: Electron.HandlerDetails) => ({action: 'deny'} | {action: 'allow', overrideBrowserWindowOptions?: BrowserWindowConstructorOptions, outlivesOpener?: boolean})) {
|
||||
this._windowOpenHandler = handler;
|
||||
};
|
||||
|
||||
WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event, details: Electron.HandlerDetails): BrowserWindowConstructorOptions | null {
|
||||
WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event, details: Electron.HandlerDetails): {browserWindowConstructorOptions: BrowserWindowConstructorOptions | null, outlivesOpener: boolean} {
|
||||
const defaultResponse = {
|
||||
browserWindowConstructorOptions: null,
|
||||
outlivesOpener: false
|
||||
};
|
||||
if (!this._windowOpenHandler) {
|
||||
return null;
|
||||
return defaultResponse;
|
||||
}
|
||||
const response = this._windowOpenHandler(details);
|
||||
|
||||
if (typeof response !== 'object') {
|
||||
event.preventDefault();
|
||||
console.error(`The window open handler response must be an object, but was instead of type '${typeof response}'.`);
|
||||
return null;
|
||||
return defaultResponse;
|
||||
}
|
||||
|
||||
if (response === null) {
|
||||
event.preventDefault();
|
||||
console.error('The window open handler response must be an object, but was instead null.');
|
||||
return null;
|
||||
return defaultResponse;
|
||||
}
|
||||
|
||||
if (response.action === 'deny') {
|
||||
event.preventDefault();
|
||||
return null;
|
||||
return defaultResponse;
|
||||
} else if (response.action === 'allow') {
|
||||
if (typeof response.overrideBrowserWindowOptions === 'object' && response.overrideBrowserWindowOptions !== null) {
|
||||
return response.overrideBrowserWindowOptions;
|
||||
return {
|
||||
browserWindowConstructorOptions: response.overrideBrowserWindowOptions,
|
||||
outlivesOpener: typeof response.outlivesOpener === 'boolean' ? response.outlivesOpener : false
|
||||
};
|
||||
} else {
|
||||
return {};
|
||||
return {
|
||||
browserWindowConstructorOptions: {},
|
||||
outlivesOpener: typeof response.outlivesOpener === 'boolean' ? response.outlivesOpener : false
|
||||
};
|
||||
}
|
||||
} else {
|
||||
event.preventDefault();
|
||||
console.error('The window open handler response must be an object with an \'action\' property of \'allow\' or \'deny\'.');
|
||||
return null;
|
||||
return defaultResponse;
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -651,7 +661,8 @@ WebContents.prototype._init = function () {
|
|||
postBody,
|
||||
disposition
|
||||
};
|
||||
const options = this._callWindowOpenHandler(event, details);
|
||||
const result = this._callWindowOpenHandler(event, details);
|
||||
const options = result.browserWindowConstructorOptions;
|
||||
if (!event.defaultPrevented) {
|
||||
openGuestWindow({
|
||||
event,
|
||||
|
@ -660,12 +671,14 @@ WebContents.prototype._init = function () {
|
|||
referrer,
|
||||
postData,
|
||||
overrideBrowserWindowOptions: options || {},
|
||||
windowOpenArgs: details
|
||||
windowOpenArgs: details,
|
||||
outlivesOpener: result.outlivesOpener
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let windowOpenOverriddenOptions: BrowserWindowConstructorOptions | null = null;
|
||||
let windowOpenOutlivesOpenerOption: boolean = false;
|
||||
this.on('-will-add-new-contents' as any, (event: ElectronInternal.Event, url: string, frameName: string, rawFeatures: string, disposition: Electron.HandlerDetails['disposition'], referrer: Electron.Referrer, postData: PostData) => {
|
||||
const postBody = postData ? {
|
||||
data: postData,
|
||||
|
@ -679,7 +692,9 @@ WebContents.prototype._init = function () {
|
|||
referrer,
|
||||
postBody
|
||||
};
|
||||
windowOpenOverriddenOptions = this._callWindowOpenHandler(event, details);
|
||||
const result = this._callWindowOpenHandler(event, details);
|
||||
windowOpenOutlivesOpenerOption = result.outlivesOpener;
|
||||
windowOpenOverriddenOptions = result.browserWindowConstructorOptions;
|
||||
if (!event.defaultPrevented) {
|
||||
const secureOverrideWebPreferences = windowOpenOverriddenOptions ? {
|
||||
// Allow setting of backgroundColor as a webPreference even though
|
||||
|
@ -710,7 +725,10 @@ WebContents.prototype._init = function () {
|
|||
_userGesture: boolean, _left: number, _top: number, _width: number, _height: number, url: string, frameName: string,
|
||||
referrer: Electron.Referrer, rawFeatures: string, postData: PostData) => {
|
||||
const overriddenOptions = windowOpenOverriddenOptions || undefined;
|
||||
const outlivesOpener = windowOpenOutlivesOpenerOption;
|
||||
windowOpenOverriddenOptions = null;
|
||||
// false is the default
|
||||
windowOpenOutlivesOpenerOption = false;
|
||||
|
||||
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
|
||||
disposition !== 'background-tab')) {
|
||||
|
@ -730,7 +748,8 @@ WebContents.prototype._init = function () {
|
|||
url,
|
||||
frameName,
|
||||
features: rawFeatures
|
||||
}
|
||||
},
|
||||
outlivesOpener
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ const getGuestWindowByFrameName = (name: string) => frameNamesToWindow.get(name)
|
|||
* user to preventDefault() on the passed event (which ends up calling
|
||||
* DestroyWebContents).
|
||||
*/
|
||||
export function openGuestWindow ({ event, embedder, guest, referrer, disposition, postData, overrideBrowserWindowOptions, windowOpenArgs }: {
|
||||
export function openGuestWindow ({ event, embedder, guest, referrer, disposition, postData, overrideBrowserWindowOptions, windowOpenArgs, outlivesOpener }: {
|
||||
event: { sender: WebContents, defaultPrevented: boolean },
|
||||
embedder: WebContents,
|
||||
guest?: WebContents,
|
||||
|
@ -38,6 +38,7 @@ export function openGuestWindow ({ event, embedder, guest, referrer, disposition
|
|||
postData?: PostData,
|
||||
overrideBrowserWindowOptions?: BrowserWindowConstructorOptions,
|
||||
windowOpenArgs: WindowOpenArgs,
|
||||
outlivesOpener: boolean,
|
||||
}): BrowserWindow | undefined {
|
||||
const { url, frameName, features } = windowOpenArgs;
|
||||
const { options: browserWindowOptions } = makeBrowserWindowOptions({
|
||||
|
@ -77,7 +78,7 @@ export function openGuestWindow ({ event, embedder, guest, referrer, disposition
|
|||
...browserWindowOptions
|
||||
});
|
||||
|
||||
handleWindowLifecycleEvents({ embedder, frameName, guest: window });
|
||||
handleWindowLifecycleEvents({ embedder, frameName, guest: window, outlivesOpener });
|
||||
|
||||
embedder.emit('did-create-window', window, { url, frameName, options: browserWindowOptions, disposition, referrer, postData });
|
||||
|
||||
|
@ -90,10 +91,11 @@ export function openGuestWindow ({ event, embedder, guest, referrer, disposition
|
|||
* too is the guest destroyed; this is Electron convention and isn't based in
|
||||
* browser behavior.
|
||||
*/
|
||||
const handleWindowLifecycleEvents = function ({ embedder, guest, frameName }: {
|
||||
const handleWindowLifecycleEvents = function ({ embedder, guest, frameName, outlivesOpener }: {
|
||||
embedder: WebContents,
|
||||
guest: BrowserWindow,
|
||||
frameName: string
|
||||
frameName: string,
|
||||
outlivesOpener: boolean
|
||||
}) {
|
||||
const closedByEmbedder = function () {
|
||||
guest.removeListener('closed', closedByUser);
|
||||
|
@ -101,9 +103,14 @@ const handleWindowLifecycleEvents = function ({ embedder, guest, frameName }: {
|
|||
};
|
||||
|
||||
const closedByUser = function () {
|
||||
embedder.removeListener('current-render-view-deleted' as any, closedByEmbedder);
|
||||
// Embedder might have been closed
|
||||
if (!embedder.isDestroyed() && !outlivesOpener) {
|
||||
embedder.removeListener('current-render-view-deleted' as any, closedByEmbedder);
|
||||
}
|
||||
};
|
||||
embedder.once('current-render-view-deleted' as any, closedByEmbedder);
|
||||
if (!outlivesOpener) {
|
||||
embedder.once('current-render-view-deleted' as any, closedByEmbedder);
|
||||
}
|
||||
guest.once('closed', closedByUser);
|
||||
|
||||
if (frameName) {
|
||||
|
@ -163,7 +170,8 @@ function emitDeprecatedNewWindowEvent ({ event, embedder, guest, windowOpenArgs,
|
|||
handleWindowLifecycleEvents({
|
||||
embedder: event.sender,
|
||||
guest: newGuest,
|
||||
frameName
|
||||
frameName,
|
||||
outlivesOpener: false
|
||||
});
|
||||
}
|
||||
return true;
|
||||
|
|
2
typings/internal-electron.d.ts
vendored
2
typings/internal-electron.d.ts
vendored
|
@ -63,7 +63,7 @@ declare namespace Electron {
|
|||
equal(other: WebContents): boolean;
|
||||
browserWindowOptions: BrowserWindowConstructorOptions;
|
||||
_windowOpenHandler: ((details: Electron.HandlerDetails) => any) | null;
|
||||
_callWindowOpenHandler(event: any, details: Electron.HandlerDetails): Electron.BrowserWindowConstructorOptions | null;
|
||||
_callWindowOpenHandler(event: any, details: Electron.HandlerDetails): {browserWindowConstructorOptions: Electron.BrowserWindowConstructorOptions | null, outlivesOpener: boolean};
|
||||
_setNextChildWebPreferences(prefs: Partial<Electron.BrowserWindowConstructorOptions['webPreferences']> & Pick<Electron.BrowserWindowConstructorOptions, 'backgroundColor'>): void;
|
||||
_send(internal: boolean, channel: string, args: any): boolean;
|
||||
_sendToFrameInternal(frameId: number | [number, number], channel: string, ...args: any[]): boolean;
|
||||
|
|
Loading…
Reference in a new issue