feat: remove nativeWindowOpen option (#29405)

Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
Co-authored-by: Milan Burda <milan.burda@gmail.com>
This commit is contained in:
Jeremy Rose 2022-01-06 09:28:03 -08:00 committed by GitHub
parent 2f9fd06534
commit d44a187d0b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 316 additions and 1164 deletions

View file

@ -692,8 +692,8 @@ WebContents.prototype._init = function () {
// TODO(zcbenz): The features string is parsed twice: here where it is
// passed to C++, and in |makeBrowserWindowOptions| later where it is
// not actually used since the WebContents is created here.
// We should be able to remove the latter once the |nativeWindowOpen|
// option is removed.
// We should be able to remove the latter once the |new-window| event
// is removed.
const { webPreferences: parsedWebPreferences } = parseFeatures(rawFeatures);
// Parameters should keep same with |makeBrowserWindowOptions|.
const webPreferences = makeWebPreferences({
@ -705,8 +705,7 @@ WebContents.prototype._init = function () {
}
});
// Create a new browser window for the native implementation of
// "window.open", used in sandbox and nativeWindowOpen mode.
// Create a new browser window for "window.open"
this.on('-add-new-contents' as any, (event: ElectronInternal.Event, webContents: Electron.WebContents, disposition: string,
_userGesture: boolean, _left: number, _top: number, _width: number, _height: number, url: string, frameName: string,
referrer: Electron.Referrer, rawFeatures: string, postData: PostData) => {

View file

@ -56,7 +56,6 @@ function makeWebPreferences (embedder: Electron.WebContents, params: Record<stri
const inheritedWebPreferences = new Map([
['contextIsolation', true],
['javascript', false],
['nativeWindowOpen', true],
['nodeIntegration', false],
['sandbox', true],
['nodeIntegrationInSubFrames', false],

View file

@ -1,14 +1,13 @@
/**
* Create and minimally track guest windows at the direction of the renderer
* (via window.open). Here, "guest" roughly means "child" it's not necessarily
* emblematic of its process status; both in-process (same-origin
* nativeWindowOpen) and out-of-process (cross-origin nativeWindowOpen and
* BrowserWindowProxy) are created here. "Embedder" roughly means "parent."
* emblematic of its process status; both in-process (same-origin) and
* out-of-process (cross-origin) are created here. "Embedder" roughly means
* "parent."
*/
import { BrowserWindow } from 'electron/main';
import type { BrowserWindowConstructorOptions, Referrer, WebContents, LoadURLOptions } from 'electron/main';
import { parseFeatures } from '@electron/internal/browser/parse-features-string';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
type PostData = LoadURLOptions['postData']
export type WindowOpenArgs = {
@ -23,13 +22,12 @@ const unregisterFrameName = (name: string) => frameNamesToWindow.delete(name);
const getGuestWindowByFrameName = (name: string) => frameNamesToWindow.get(name);
/**
* `openGuestWindow` is called for both implementations of window.open
* (BrowserWindowProxy and nativeWindowOpen) to create and setup event handling
* for the new window.
* `openGuestWindow` is called to create and setup event handling for the new
* window.
*
* Until its removal in 12.0.0, the `new-window` event is fired, allowing the
* user to preventDefault() on the passed event (which ends up calling
* DestroyWebContents in the nativeWindowOpen code path).
* DestroyWebContents).
*/
export function openGuestWindow ({ event, embedder, guest, referrer, disposition, postData, overrideBrowserWindowOptions, windowOpenArgs }: {
event: { sender: WebContents, defaultPrevented: boolean },
@ -78,22 +76,6 @@ export function openGuestWindow ({ event, embedder, guest, referrer, disposition
webContents: guest,
...browserWindowOptions
});
if (!guest) {
// We should only call `loadURL` if the webContents was constructed by us in
// the case of BrowserWindowProxy (non-sandboxed, nativeWindowOpen: false),
// as navigating to the url when creating the window from an existing
// webContents is not necessary (it will navigate there anyway).
// This can also happen if we enter this function from OpenURLFromTab, in
// which case the browser process is responsible for initiating navigation
// in the new window.
window.loadURL(url, {
httpReferrer: referrer,
...(postData && {
postData,
extraHeaders: formatPostDataHeaders(postData as Electron.UploadRawData[])
})
});
}
handleWindowLifecycleEvents({ embedder, frameName, guest: window });
@ -118,9 +100,7 @@ const handleWindowLifecycleEvents = function ({ embedder, guest, frameName }: {
guest.destroy();
};
const cachedGuestId = guest.webContents.id;
const closedByUser = function () {
embedder._sendInternal(`${IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_CLOSED}_${cachedGuestId}`);
embedder.removeListener('current-render-view-deleted' as any, closedByEmbedder);
};
embedder.once('current-render-view-deleted' as any, closedByEmbedder);
@ -195,7 +175,6 @@ function emitDeprecatedNewWindowEvent ({ event, embedder, guest, windowOpenArgs,
const securityWebPreferences: { [key: string]: boolean } = {
contextIsolation: true,
javascript: false,
nativeWindowOpen: true,
nodeIntegration: false,
sandbox: true,
webviewTag: false,
@ -217,10 +196,10 @@ function makeBrowserWindowOptions ({ embedder, features, overrideOptions }: {
height: 600,
...parsedOptions,
...overrideOptions,
// Note that for |nativeWindowOpen: true| the WebContents is created in
// |api::WebContents::WebContentsCreatedWithFullParams|, with prefs
// parsed in the |-will-add-new-contents| event.
// The |webPreferences| here is only used by |nativeWindowOpen: false|.
// Note that for normal code path an existing WebContents created by
// Chromium will be used, with web preferences parsed in the
// |-will-add-new-contents| event.
// The |webPreferences| here is only used by the |new-window| event.
webPreferences: makeWebPreferences({
embedder,
insecureParsedWebPreferences: parsedWebPreferences,
@ -245,7 +224,6 @@ export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {
}
return map;
}, {} as Electron.WebPreferences));
const openerId = parentWebPreferences.nativeWindowOpen ? null : embedder.id;
return {
...parsedWebPreferences,
@ -253,22 +231,10 @@ export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {
// ability to change important security options but allow main (via
// setWindowOpenHandler) to change them.
...securityWebPreferencesFromParent,
...secureOverrideWebPreferences,
// Sets correct openerId here to give correct options to 'new-window' event handler
// TODO: Figure out another way to pass this?
openerId
...secureOverrideWebPreferences
};
}
function formatPostDataHeaders (postData: PostData) {
if (!postData) return;
const { contentType, boundary } = parseContentTypeFormat(postData);
if (boundary != null) { return `content-type: ${contentType}; boundary=${boundary}`; }
return `content-type: ${contentType}`;
}
const MULTIPART_CONTENT_TYPE = 'multipart/form-data';
const URL_ENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded';

View file

@ -1,213 +0,0 @@
/**
* Manage guest windows when using the default BrowserWindowProxy version of the
* renderer's window.open (i.e. nativeWindowOpen off). This module mostly
* consists of marshaling IPC requests from the BrowserWindowProxy to the
* WebContents.
*/
import { webContents, BrowserWindow } from 'electron/main';
import type { WebContents } from 'electron/main';
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
import { openGuestWindow } from '@electron/internal/browser/guest-window-manager';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
const { isSameOrigin } = process._linkedBinding('electron_common_v8_util');
const getGuestWindow = function (guestContents: WebContents) {
let guestWindow = BrowserWindow.fromWebContents(guestContents);
if (guestWindow == null) {
const hostContents = guestContents.hostWebContents;
if (hostContents != null) {
guestWindow = BrowserWindow.fromWebContents(hostContents);
}
}
if (!guestWindow) {
throw new Error('getGuestWindow failed');
}
return guestWindow;
};
const isChildWindow = function (sender: WebContents, target: WebContents) {
return target.getLastWebPreferences()!.openerId === sender.id;
};
const isRelatedWindow = function (sender: WebContents, target: WebContents) {
return isChildWindow(sender, target) || isChildWindow(target, sender);
};
const isScriptableWindow = function (sender: WebContents, target: WebContents) {
return (
isRelatedWindow(sender, target) &&
isSameOrigin(sender.getURL(), target.getURL())
);
};
const isNodeIntegrationEnabled = function (sender: WebContents) {
return sender.getLastWebPreferences()!.nodeIntegration === true;
};
// Checks whether |sender| can access the |target|:
const canAccessWindow = function (sender: WebContents, target: WebContents) {
return (
isChildWindow(sender, target) ||
isScriptableWindow(sender, target) ||
isNodeIntegrationEnabled(sender)
);
};
// Routed window.open messages with raw options
ipcMainInternal.on(
IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_OPEN,
(
event,
url: string,
frameName: string,
features: string
) => {
// This should only be allowed for senders that have nativeWindowOpen: false
const lastWebPreferences = event.sender.getLastWebPreferences()!;
if (lastWebPreferences.nativeWindowOpen || lastWebPreferences.sandbox) {
event.returnValue = null;
throw new Error(
'GUEST_WINDOW_MANAGER_WINDOW_OPEN denied: expected native window.open'
);
}
const referrer: Electron.Referrer = { url: '', policy: 'strict-origin-when-cross-origin' };
const browserWindowOptions = event.sender._callWindowOpenHandler(event, { url, frameName, features, disposition: 'new-window', referrer });
if (event.defaultPrevented) {
event.returnValue = null;
return;
}
const guest = openGuestWindow({
event,
embedder: event.sender,
referrer,
disposition: 'new-window',
overrideBrowserWindowOptions: browserWindowOptions!,
windowOpenArgs: {
url: url || 'about:blank',
frameName: frameName || '',
features: features || ''
}
});
event.returnValue = guest ? guest.webContents.id : null;
}
);
type IpcHandler<T, Event> = (event: Event, guestContents: Electron.WebContents, ...args: any[]) => T;
const makeSafeHandler = function<T, Event> (handler: IpcHandler<T, Event>) {
return (event: Event, guestId: number, ...args: any[]) => {
// Access webContents via electron to prevent circular require.
const guestContents = webContents.fromId(guestId);
if (!guestContents) {
throw new Error(`Invalid guestId: ${guestId}`);
}
return handler(event, guestContents as Electron.WebContents, ...args);
};
};
const handleMessage = function (channel: string, handler: IpcHandler<any, Electron.IpcMainInvokeEvent>) {
ipcMainInternal.handle(channel, makeSafeHandler(handler));
};
const handleMessageSync = function (channel: string, handler: IpcHandler<any, ElectronInternal.IpcMainInternalEvent>) {
ipcMainUtils.handleSync(channel, makeSafeHandler(handler));
};
type ContentsCheck = (contents: WebContents, guestContents: WebContents) => boolean;
const securityCheck = function (contents: WebContents, guestContents: WebContents, check: ContentsCheck) {
if (!check(contents, guestContents)) {
console.error(
`Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`
);
throw new Error(`Access denied to guestId: ${guestContents.id}`);
}
};
const windowMethods = new Set(['destroy', 'focus', 'blur']);
handleMessage(
IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_METHOD,
(event, guestContents, method, ...args) => {
securityCheck(event.sender, guestContents, canAccessWindow);
if (!windowMethods.has(method)) {
console.error(
`Blocked ${event.senderFrame.url} from calling method: ${method}`
);
throw new Error(`Invalid method: ${method}`);
}
return (getGuestWindow(guestContents) as any)[method](...args);
}
);
handleMessage(
IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE,
(event, guestContents, message, targetOrigin, sourceOrigin) => {
if (targetOrigin == null) {
targetOrigin = '*';
}
// The W3C does not seem to have word on how postMessage should work when the
// origins do not match, so we do not do |canAccessWindow| check here since
// postMessage across origins is useful and not harmful.
securityCheck(event.sender, guestContents, isRelatedWindow);
if (
targetOrigin === '*' ||
isSameOrigin(guestContents.getURL(), targetOrigin)
) {
const sourceId = event.sender.id;
guestContents._sendInternal(
IPC_MESSAGES.GUEST_WINDOW_POSTMESSAGE,
sourceId,
message,
sourceOrigin
);
}
}
);
const webContentsMethodsAsync = new Set([
'loadURL',
'executeJavaScript',
'print'
]);
handleMessage(
IPC_MESSAGES.GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD,
(event, guestContents, method, ...args) => {
securityCheck(event.sender, guestContents, canAccessWindow);
if (!webContentsMethodsAsync.has(method)) {
console.error(
`Blocked ${event.sender.getURL()} from calling method: ${method}`
);
throw new Error(`Invalid method: ${method}`);
}
return (guestContents as any)[method](...args);
}
);
const webContentsMethodsSync = new Set(['getURL']);
handleMessageSync(
IPC_MESSAGES.GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD,
(event, guestContents, method, ...args) => {
securityCheck(event.sender, guestContents, canAccessWindow);
if (!webContentsMethodsSync.has(method)) {
console.error(
`Blocked ${event.sender.getURL()} from calling method: ${method}`
);
throw new Error(`Invalid method: ${method}`);
}
return (guestContents as any)[method](...args);
}
);

View file

@ -78,7 +78,6 @@ require('@electron/internal/browser/rpc-server');
// Load the guest view manager.
require('@electron/internal/browser/guest-view-manager');
require('@electron/internal/browser/guest-window-proxy');
// Now we try to load app's package.json.
const v8Util = process._linkedBinding('electron_common_v8_util');

View file

@ -18,13 +18,6 @@ export const enum IPC_MESSAGES {
GUEST_VIEW_MANAGER_PROPERTY_GET = 'GUEST_VIEW_MANAGER_PROPERTY_GET',
GUEST_VIEW_MANAGER_PROPERTY_SET = 'GUEST_VIEW_MANAGER_PROPERTY_SET',
GUEST_WINDOW_MANAGER_WINDOW_OPEN = 'GUEST_WINDOW_MANAGER_WINDOW_OPEN',
GUEST_WINDOW_MANAGER_WINDOW_CLOSED = 'GUEST_WINDOW_MANAGER_WINDOW_CLOSED',
GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE = 'GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE',
GUEST_WINDOW_MANAGER_WINDOW_METHOD = 'GUEST_WINDOW_MANAGER_WINDOW_METHOD',
GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD = 'GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD',
GUEST_WINDOW_POSTMESSAGE = 'GUEST_WINDOW_POSTMESSAGE',
RENDERER_WEB_FRAME_METHOD = 'RENDERER_WEB_FRAME_METHOD',
INSPECTOR_CONFIRM = 'INSPECTOR_CONFIRM',

View file

@ -12,9 +12,7 @@ const v8Util = process._linkedBinding('electron_common_v8_util');
const nodeIntegration = mainFrame.getWebPreference('nodeIntegration');
const webviewTag = mainFrame.getWebPreference('webviewTag');
const isHiddenPage = mainFrame.getWebPreference('hiddenPage');
const nativeWindowOpen = mainFrame.getWebPreference('nativeWindowOpen') || process.sandboxed;
const isWebView = mainFrame.getWebPreference('isWebView');
const openerId = mainFrame.getWebPreference('openerId');
// ElectronApiServiceImpl will look for the "ipcNative" hidden object when
// invoking the 'onMessage' callback.
@ -44,7 +42,7 @@ switch (window.location.protocol) {
default: {
// Override default web functions.
const { windowSetup } = require('@electron/internal/renderer/window-setup') as typeof windowSetupModule;
windowSetup(isWebView, openerId, isHiddenPage, nativeWindowOpen);
windowSetup(isWebView, isHiddenPage);
}
}

View file

@ -1,249 +1,10 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
import { internalContextBridge } from '@electron/internal/renderer/api/context-bridge';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
const { contextIsolationEnabled } = internalContextBridge;
// This file implements the following APIs over the ctx bridge:
// - window.open()
// - window.opener.blur()
// - window.opener.close()
// - window.opener.eval()
// - window.opener.focus()
// - window.opener.location
// - window.opener.print()
// - window.opener.closed
// - window.opener.postMessage()
// - window.history.back()
// - window.history.forward()
// - window.history.go()
// - window.history.length
// - window.prompt()
// - document.hidden
// - document.visibilityState
// Helper function to resolve relative url.
const resolveURL = (url: string, base: string) => new URL(url, base).href;
// Use this method to ensure values expected as strings in the main process
// are convertible to strings in the renderer process. This ensures exceptions
// converting values to strings are thrown in this process.
const toString = (value: any) => {
return value != null ? `${value}` : value;
};
const windowProxies = new Map<number, BrowserWindowProxy>();
const getOrCreateProxy = (guestId: number): SafelyBoundBrowserWindowProxy => {
let proxy = windowProxies.get(guestId);
if (proxy == null) {
proxy = new BrowserWindowProxy(guestId);
windowProxies.set(guestId, proxy);
}
return proxy.getSafe();
};
const removeProxy = (guestId: number) => {
windowProxies.delete(guestId);
};
type LocationProperties = 'hash' | 'href' | 'host' | 'hostname' | 'origin' | 'pathname' | 'port' | 'protocol' | 'search'
class LocationProxy {
@LocationProxy.ProxyProperty public hash!: string;
@LocationProxy.ProxyProperty public href!: string;
@LocationProxy.ProxyProperty public host!: string;
@LocationProxy.ProxyProperty public hostname!: string;
@LocationProxy.ProxyProperty public origin!: string;
@LocationProxy.ProxyProperty public pathname!: string;
@LocationProxy.ProxyProperty public port!: string;
@LocationProxy.ProxyProperty public protocol!: string;
@LocationProxy.ProxyProperty public search!: URLSearchParams;
private guestId: number;
/**
* Beware: This decorator will have the _prototype_ as the `target`. It defines properties
* commonly found in URL on the LocationProxy.
*/
private static ProxyProperty<T> (target: LocationProxy, propertyKey: LocationProperties) {
Object.defineProperty(target, propertyKey, {
enumerable: true,
configurable: true,
get: function (this: LocationProxy): T | string {
const guestURL = this.getGuestURL();
const value = guestURL ? guestURL[propertyKey] : '';
return value === undefined ? '' : value;
},
set: function (this: LocationProxy, newVal: T) {
const guestURL = this.getGuestURL();
if (guestURL) {
// TypeScript doesn't want us to assign to read-only variables.
// It's right, that's bad, but we're doing it anyway.
(guestURL as any)[propertyKey] = newVal;
return this._invokeWebContentsMethod('loadURL', guestURL.toString());
}
}
});
}
public getSafe = () => {
const that = this;
return {
get href () { return that.href; },
set href (newValue) { that.href = newValue; },
get hash () { return that.hash; },
set hash (newValue) { that.hash = newValue; },
get host () { return that.host; },
set host (newValue) { that.host = newValue; },
get hostname () { return that.hostname; },
set hostname (newValue) { that.hostname = newValue; },
get origin () { return that.origin; },
set origin (newValue) { that.origin = newValue; },
get pathname () { return that.pathname; },
set pathname (newValue) { that.pathname = newValue; },
get port () { return that.port; },
set port (newValue) { that.port = newValue; },
get protocol () { return that.protocol; },
set protocol (newValue) { that.protocol = newValue; },
get search () { return that.search; },
set search (newValue) { that.search = newValue; }
};
}
constructor (guestId: number) {
// eslint will consider the constructor "useless"
// unless we assign them in the body. It's fine, that's what
// TS would do anyway.
this.guestId = guestId;
this.getGuestURL = this.getGuestURL.bind(this);
}
public toString (): string {
return this.href;
}
private getGuestURL (): URL | null {
const maybeURL = this._invokeWebContentsMethodSync('getURL') as string;
// When there's no previous frame the url will be blank, so account for that here
// to prevent url parsing errors on an empty string.
const urlString = maybeURL !== '' ? maybeURL : 'about:blank';
try {
return new URL(urlString);
} catch (e) {
console.error('LocationProxy: failed to parse string', urlString, e);
}
return null;
}
private _invokeWebContentsMethod (method: string, ...args: any[]) {
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD, this.guestId, method, ...args);
}
private _invokeWebContentsMethodSync (method: string, ...args: any[]) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD, this.guestId, method, ...args);
}
}
interface SafelyBoundBrowserWindowProxy {
location: WindowProxy['location'];
blur: WindowProxy['blur'];
close: WindowProxy['close'];
eval: typeof eval; // eslint-disable-line no-eval
focus: WindowProxy['focus'];
print: WindowProxy['print'];
postMessage: WindowProxy['postMessage'];
closed: boolean;
}
class BrowserWindowProxy {
public closed: boolean = false
private _location: LocationProxy
private guestId: number
// TypeScript doesn't allow getters/accessors with different types,
// so for now, we'll have to make do with an "any" in the mix.
// https://github.com/Microsoft/TypeScript/issues/2521
public get location (): LocationProxy | any {
return this._location.getSafe();
}
public set location (url: string | any) {
url = resolveURL(url, this.location.href);
this._invokeWebContentsMethod('loadURL', url);
}
constructor (guestId: number) {
this.guestId = guestId;
this._location = new LocationProxy(guestId);
ipcRendererInternal.once(`${IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_CLOSED}_${guestId}`, () => {
removeProxy(guestId);
this.closed = true;
});
}
public getSafe = (): SafelyBoundBrowserWindowProxy => {
const that = this;
return {
postMessage: this.postMessage,
blur: this.blur,
close: this.close,
focus: this.focus,
print: this.print,
eval: this.eval,
get location () {
return that.location;
},
set location (url: string | any) {
that.location = url;
},
get closed () {
return that.closed;
}
};
}
public close = () => {
this._invokeWindowMethod('destroy');
}
public focus = () => {
this._invokeWindowMethod('focus');
}
public blur = () => {
this._invokeWindowMethod('blur');
}
public print = () => {
this._invokeWebContentsMethod('print');
}
public postMessage = (message: any, targetOrigin: string) => {
ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE, this.guestId, message, toString(targetOrigin), window.location.origin);
}
public eval = (code: string) => {
this._invokeWebContentsMethod('executeJavaScript', code);
}
private _invokeWindowMethod (method: string, ...args: any[]) {
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_METHOD, this.guestId, method, ...args);
}
private _invokeWebContentsMethod (method: string, ...args: any[]) {
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD, this.guestId, method, ...args);
}
}
export const windowSetup = (
isWebView: boolean, openerId: number, isHiddenPage: boolean, usesNativeWindowOpen: boolean) => {
export const windowSetup = (isWebView: boolean, isHiddenPage: boolean) => {
if (!process.sandboxed && !isWebView) {
// Override default window.close.
window.close = function () {
@ -252,72 +13,12 @@ export const windowSetup = (
if (contextIsolationEnabled) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['close'], window.close);
}
if (!usesNativeWindowOpen) {
// TODO(MarshallOfSound): Make compatible with ctx isolation without hole-punch
// Make the browser window or guest view emit "new-window" event.
window.open = function (url?: string, frameName?: string, features?: string) {
if (url != null && url !== '') {
url = resolveURL(url, location.href);
}
const guestId = ipcRendererInternal.sendSync(IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_OPEN, url, toString(frameName), toString(features));
if (guestId != null) {
return getOrCreateProxy(guestId) as any as WindowProxy;
} else {
return null;
}
};
if (contextIsolationEnabled) internalContextBridge.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['open'], window.open);
}
// If this window uses nativeWindowOpen, but its opener window does not, we
// need to proxy window.opener in order to let the page communicate with its
// opener.
// Additionally, windows opened from a nativeWindowOpen child of a
// non-nativeWindowOpen parent will initially have their WebPreferences
// copied from their opener before having them updated, meaning openerId is
// initially incorrect. We detect this situation by checking for
// window.opener, which will be non-null for a natively-opened child, so we
// can ignore the openerId in that case, since it's incorrectly copied from
// the parent. This is, uh, confusing, so here's a diagram that will maybe
// help?
//
// [ grandparent window ] --> [ parent window ] --> [ child window ]
// n.W.O = false n.W.O = true n.W.O = true
// id = 1 id = 2 id = 3
// openerId = 0 openerId = 1 openerId = 1 <- !!wrong!!
// opener = null opener = null opener = [parent window]
if (openerId && !window.opener) {
window.opener = getOrCreateProxy(openerId);
if (contextIsolationEnabled) internalContextBridge.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['opener'], window.opener);
}
// But we do not support prompt().
window.prompt = function () {
throw new Error('prompt() is and will not be supported.');
};
if (contextIsolationEnabled) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['prompt'], window.prompt);
if (!usesNativeWindowOpen || openerId) {
ipcRendererInternal.on(IPC_MESSAGES.GUEST_WINDOW_POSTMESSAGE, function (
_event, sourceId: number, message: any, sourceOrigin: string
) {
// Manually dispatch event instead of using postMessage because we also need to
// set event.source.
//
// Why any? We can't construct a MessageEvent and we can't
// use `as MessageEvent` because you're not supposed to override
// data, origin, and source
const event: any = document.createEvent('Event');
event.initEvent('message', false, false);
event.data = message;
event.origin = sourceOrigin;
event.source = getOrCreateProxy(sourceId);
window.dispatchEvent(event as MessageEvent);
});
}
if (isWebView) {
// Webview `document.visibilityState` tracks window visibility (and ignores
// the actual <webview> element visibility) for backwards compatibility.