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:
parent
2f9fd06534
commit
d44a187d0b
39 changed files with 316 additions and 1164 deletions
|
@ -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) => {
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
);
|
|
@ -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');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue