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

@ -106,7 +106,6 @@ These individual tutorials expand on topics discussed in the guide above.
* [`File` Object](api/file-object.md) * [`File` Object](api/file-object.md)
* [`<webview>` Tag](api/webview-tag.md) * [`<webview>` Tag](api/webview-tag.md)
* [`window.open` Function](api/window-open.md) * [`window.open` Function](api/window-open.md)
* [`BrowserWindowProxy` Object](api/browser-window-proxy.md)
### Modules for the Main Process: ### Modules for the Main Process:

View file

@ -1,54 +0,0 @@
## Class: BrowserWindowProxy
> Manipulate the child browser window
Process: [Renderer](../glossary.md#renderer-process)<br />
_This class is not exported from the `'electron'` module. It is only available as a return value of other methods in the Electron API._
The `BrowserWindowProxy` object is returned from `window.open` and provides
limited functionality with the child window.
### Instance Methods
The `BrowserWindowProxy` object has the following instance methods:
#### `win.blur()`
Removes focus from the child window.
#### `win.close()`
Forcefully closes the child window without calling its unload event.
#### `win.eval(code)`
* `code` string
Evaluates the code in the child window.
#### `win.focus()`
Focuses the child window (brings the window to front).
#### `win.print()`
Invokes the print dialog on the child window.
#### `win.postMessage(message, targetOrigin)`
* `message` any
* `targetOrigin` string
Sends a message to the child window with the specified origin or `*` for no
origin preference.
In addition to these methods, the child window implements `window.opener` object
with no properties and a single method.
### Instance Properties
The `BrowserWindowProxy` object has the following instance properties:
#### `win.closed`
A `boolean` that is set to true after the child window gets closed.

View file

@ -341,9 +341,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
[Chrome Content Scripts][chrome-content-scripts]. You can access this [Chrome Content Scripts][chrome-content-scripts]. You can access this
context in the dev tools by selecting the 'Electron Isolated Context' context in the dev tools by selecting the 'Electron Isolated Context'
entry in the combo box at the top of the Console tab. entry in the combo box at the top of the Console tab.
* `nativeWindowOpen` boolean (optional) - Whether to use native
`window.open()`. Defaults to `true`. Child windows will always have node
integration disabled unless `nodeIntegrationInSubFrames` is true.
* `webviewTag` boolean (optional) - Whether to enable the [`<webview>` tag](webview-tag.md). * `webviewTag` boolean (optional) - Whether to enable the [`<webview>` tag](webview-tag.md).
Defaults to `false`. **Note:** The Defaults to `false`. **Note:** The
`preload` script configured for the `<webview>` will have node integration `preload` script configured for the `<webview>` will have node integration

View file

@ -7,17 +7,3 @@
the `enctype` attribute of the submitted HTML form. the `enctype` attribute of the submitted HTML form.
* `boundary` string (optional) - The boundary used to separate multiple parts of * `boundary` string (optional) - The boundary used to separate multiple parts of
the message. Only valid when `contentType` is `multipart/form-data`. the message. Only valid when `contentType` is `multipart/form-data`.
Note that keys starting with `--` are not currently supported. For example, this will errantly submit as `multipart/form-data` when `nativeWindowOpen` is set to `false` in webPreferences:
```html
<form
target="_blank"
method="POST"
enctype="application/x-www-form-urlencoded"
action="https://postman-echo.com/post"
>
<input type="text" name="--theKey">
<input type="submit">
</form>
```

View file

@ -12,10 +12,6 @@ useful for app sub-windows that act as preference panels, or similar, as the
parent can render to the sub-window directly, as if it were a `div` in the parent can render to the sub-window directly, as if it were a `div` in the
parent. This is the same behavior as in the browser. parent. This is the same behavior as in the browser.
When `nativeWindowOpen` is set to false, `window.open` instead results in the
creation of a [`BrowserWindowProxy`](browser-window-proxy.md), a light wrapper
around `BrowserWindow`.
Electron pairs this native Chrome `Window` with a BrowserWindow under the hood. Electron pairs this native Chrome `Window` with a BrowserWindow under the hood.
You can take advantage of all the customization available when creating a You can take advantage of all the customization available when creating a
BrowserWindow in the main process by using `webContents.setWindowOpenHandler()` BrowserWindow in the main process by using `webContents.setWindowOpenHandler()`
@ -34,7 +30,7 @@ because it is invoked in the main process.
* `frameName` string (optional) * `frameName` string (optional)
* `features` string (optional) * `features` string (optional)
Returns [`BrowserWindowProxy`](browser-window-proxy.md) | [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) Returns [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) | null
`features` is a comma-separated key-value list, following the standard format of `features` is a comma-separated key-value list, following the standard format of
the browser. Electron will parse `BrowserWindowConstructorOptions` out of this the browser. Electron will parse `BrowserWindowConstructorOptions` out of this
@ -108,33 +104,3 @@ mainWindow.webContents.setWindowOpenHandler(({ url }) => {
const childWindow = window.open('', 'modal') const childWindow = window.open('', 'modal')
childWindow.document.write('<h1>Hello</h1>') childWindow.document.write('<h1>Hello</h1>')
``` ```
### `BrowserWindowProxy` example
```javascript
// main.js
const mainWindow = new BrowserWindow({
webPreferences: { nativeWindowOpen: false }
})
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('https://github.com/')) {
return { action: 'allow' }
}
return { action: 'deny' }
})
mainWindow.webContents.on('did-create-window', (childWindow) => {
// For example...
childWindow.webContents.on('will-navigate', (e) => {
e.preventDefault()
})
})
```
```javascript
// renderer.js
const windowProxy = window.open('https://github.com/', null, 'minimizable=false')
windowProxy.postMessage('hi', '*')
```

View file

@ -12,6 +12,18 @@ This document uses the following convention to categorize breaking changes:
* **Deprecated:** An API was marked as deprecated. The API will continue to function, but will emit a deprecation warning, and will be removed in a future release. * **Deprecated:** An API was marked as deprecated. The API will continue to function, but will emit a deprecation warning, and will be removed in a future release.
* **Removed:** An API or feature was removed, and is no longer supported by Electron. * **Removed:** An API or feature was removed, and is no longer supported by Electron.
## Planned Breaking API Changes (18.0)
### Removed: `nativeWindowOpen`
Prior to Electron 15, `window.open` was by default shimmed to use
`BrowserWindowProxy`. This meant that `window.open('about:blank')` did not work
to open synchronously scriptable child windows, among other incompatibilities.
Since Electron 15, `nativeWindowOpen` has been enabled by default.
See the documentation for [window.open in Electron](api/window-open.md)
for more details.
## Planned Breaking API Changes (17.0) ## Planned Breaking API Changes (17.0)
### Removed: `desktopCapturer.getSources` in the renderer ### Removed: `desktopCapturer.getSources` in the renderer

View file

@ -5,7 +5,6 @@ auto_filenames = {
"docs/api/app.md", "docs/api/app.md",
"docs/api/auto-updater.md", "docs/api/auto-updater.md",
"docs/api/browser-view.md", "docs/api/browser-view.md",
"docs/api/browser-window-proxy.md",
"docs/api/browser-window.md", "docs/api/browser-window.md",
"docs/api/client-request.md", "docs/api/client-request.md",
"docs/api/clipboard.md", "docs/api/clipboard.md",
@ -226,7 +225,6 @@ auto_filenames = {
"lib/browser/devtools.ts", "lib/browser/devtools.ts",
"lib/browser/guest-view-manager.ts", "lib/browser/guest-view-manager.ts",
"lib/browser/guest-window-manager.ts", "lib/browser/guest-window-manager.ts",
"lib/browser/guest-window-proxy.ts",
"lib/browser/init.ts", "lib/browser/init.ts",
"lib/browser/ipc-main-impl.ts", "lib/browser/ipc-main-impl.ts",
"lib/browser/ipc-main-internal-utils.ts", "lib/browser/ipc-main-internal-utils.ts",

View file

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

View file

@ -1,14 +1,13 @@
/** /**
* Create and minimally track guest windows at the direction of the renderer * Create and minimally track guest windows at the direction of the renderer
* (via window.open). Here, "guest" roughly means "child" it's not necessarily * (via window.open). Here, "guest" roughly means "child" it's not necessarily
* emblematic of its process status; both in-process (same-origin * emblematic of its process status; both in-process (same-origin) and
* nativeWindowOpen) and out-of-process (cross-origin nativeWindowOpen and * out-of-process (cross-origin) are created here. "Embedder" roughly means
* BrowserWindowProxy) are created here. "Embedder" roughly means "parent." * "parent."
*/ */
import { BrowserWindow } from 'electron/main'; import { BrowserWindow } from 'electron/main';
import type { BrowserWindowConstructorOptions, Referrer, WebContents, LoadURLOptions } from 'electron/main'; import type { BrowserWindowConstructorOptions, Referrer, WebContents, LoadURLOptions } from 'electron/main';
import { parseFeatures } from '@electron/internal/browser/parse-features-string'; import { parseFeatures } from '@electron/internal/browser/parse-features-string';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
type PostData = LoadURLOptions['postData'] type PostData = LoadURLOptions['postData']
export type WindowOpenArgs = { export type WindowOpenArgs = {
@ -23,13 +22,12 @@ const unregisterFrameName = (name: string) => frameNamesToWindow.delete(name);
const getGuestWindowByFrameName = (name: string) => frameNamesToWindow.get(name); const getGuestWindowByFrameName = (name: string) => frameNamesToWindow.get(name);
/** /**
* `openGuestWindow` is called for both implementations of window.open * `openGuestWindow` is called to create and setup event handling for the new
* (BrowserWindowProxy and nativeWindowOpen) to create and setup event handling * window.
* for the new window.
* *
* Until its removal in 12.0.0, the `new-window` event is fired, allowing the * 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 * 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 }: { export function openGuestWindow ({ event, embedder, guest, referrer, disposition, postData, overrideBrowserWindowOptions, windowOpenArgs }: {
event: { sender: WebContents, defaultPrevented: boolean }, event: { sender: WebContents, defaultPrevented: boolean },
@ -78,22 +76,6 @@ export function openGuestWindow ({ event, embedder, guest, referrer, disposition
webContents: guest, webContents: guest,
...browserWindowOptions ...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 }); handleWindowLifecycleEvents({ embedder, frameName, guest: window });
@ -118,9 +100,7 @@ const handleWindowLifecycleEvents = function ({ embedder, guest, frameName }: {
guest.destroy(); guest.destroy();
}; };
const cachedGuestId = guest.webContents.id;
const closedByUser = function () { const closedByUser = function () {
embedder._sendInternal(`${IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_CLOSED}_${cachedGuestId}`);
embedder.removeListener('current-render-view-deleted' as any, closedByEmbedder); embedder.removeListener('current-render-view-deleted' as any, closedByEmbedder);
}; };
embedder.once('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 } = { const securityWebPreferences: { [key: string]: boolean } = {
contextIsolation: true, contextIsolation: true,
javascript: false, javascript: false,
nativeWindowOpen: true,
nodeIntegration: false, nodeIntegration: false,
sandbox: true, sandbox: true,
webviewTag: false, webviewTag: false,
@ -217,10 +196,10 @@ function makeBrowserWindowOptions ({ embedder, features, overrideOptions }: {
height: 600, height: 600,
...parsedOptions, ...parsedOptions,
...overrideOptions, ...overrideOptions,
// Note that for |nativeWindowOpen: true| the WebContents is created in // Note that for normal code path an existing WebContents created by
// |api::WebContents::WebContentsCreatedWithFullParams|, with prefs // Chromium will be used, with web preferences parsed in the
// parsed in the |-will-add-new-contents| event. // |-will-add-new-contents| event.
// The |webPreferences| here is only used by |nativeWindowOpen: false|. // The |webPreferences| here is only used by the |new-window| event.
webPreferences: makeWebPreferences({ webPreferences: makeWebPreferences({
embedder, embedder,
insecureParsedWebPreferences: parsedWebPreferences, insecureParsedWebPreferences: parsedWebPreferences,
@ -245,7 +224,6 @@ export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {
} }
return map; return map;
}, {} as Electron.WebPreferences)); }, {} as Electron.WebPreferences));
const openerId = parentWebPreferences.nativeWindowOpen ? null : embedder.id;
return { return {
...parsedWebPreferences, ...parsedWebPreferences,
@ -253,22 +231,10 @@ export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {
// ability to change important security options but allow main (via // ability to change important security options but allow main (via
// setWindowOpenHandler) to change them. // setWindowOpenHandler) to change them.
...securityWebPreferencesFromParent, ...securityWebPreferencesFromParent,
...secureOverrideWebPreferences, ...secureOverrideWebPreferences
// Sets correct openerId here to give correct options to 'new-window' event handler
// TODO: Figure out another way to pass this?
openerId
}; };
} }
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 MULTIPART_CONTENT_TYPE = 'multipart/form-data';
const URL_ENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded'; 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. // Load the guest view manager.
require('@electron/internal/browser/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. // Now we try to load app's package.json.
const v8Util = process._linkedBinding('electron_common_v8_util'); 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_GET = 'GUEST_VIEW_MANAGER_PROPERTY_GET',
GUEST_VIEW_MANAGER_PROPERTY_SET = 'GUEST_VIEW_MANAGER_PROPERTY_SET', 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', RENDERER_WEB_FRAME_METHOD = 'RENDERER_WEB_FRAME_METHOD',
INSPECTOR_CONFIRM = 'INSPECTOR_CONFIRM', INSPECTOR_CONFIRM = 'INSPECTOR_CONFIRM',

View file

@ -12,9 +12,7 @@ const v8Util = process._linkedBinding('electron_common_v8_util');
const nodeIntegration = mainFrame.getWebPreference('nodeIntegration'); const nodeIntegration = mainFrame.getWebPreference('nodeIntegration');
const webviewTag = mainFrame.getWebPreference('webviewTag'); const webviewTag = mainFrame.getWebPreference('webviewTag');
const isHiddenPage = mainFrame.getWebPreference('hiddenPage'); const isHiddenPage = mainFrame.getWebPreference('hiddenPage');
const nativeWindowOpen = mainFrame.getWebPreference('nativeWindowOpen') || process.sandboxed;
const isWebView = mainFrame.getWebPreference('isWebView'); const isWebView = mainFrame.getWebPreference('isWebView');
const openerId = mainFrame.getWebPreference('openerId');
// ElectronApiServiceImpl will look for the "ipcNative" hidden object when // ElectronApiServiceImpl will look for the "ipcNative" hidden object when
// invoking the 'onMessage' callback. // invoking the 'onMessage' callback.
@ -44,7 +42,7 @@ switch (window.location.protocol) {
default: { default: {
// Override default web functions. // Override default web functions.
const { windowSetup } = require('@electron/internal/renderer/window-setup') as typeof windowSetupModule; 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 { 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 { internalContextBridge } from '@electron/internal/renderer/api/context-bridge';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages'; import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
const { contextIsolationEnabled } = internalContextBridge; const { contextIsolationEnabled } = internalContextBridge;
// This file implements the following APIs over the ctx bridge: export const windowSetup = (isWebView: boolean, isHiddenPage: boolean) => {
// - 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) => {
if (!process.sandboxed && !isWebView) { if (!process.sandboxed && !isWebView) {
// Override default window.close. // Override default window.close.
window.close = function () { window.close = function () {
@ -252,72 +13,12 @@ export const windowSetup = (
if (contextIsolationEnabled) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['close'], window.close); 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(). // But we do not support prompt().
window.prompt = function () { window.prompt = function () {
throw new Error('prompt() is and will not be supported.'); throw new Error('prompt() is and will not be supported.');
}; };
if (contextIsolationEnabled) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['prompt'], window.prompt); 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) { if (isWebView) {
// Webview `document.visibilityState` tracks window visibility (and ignores // Webview `document.visibilityState` tracks window visibility (and ignores
// the actual <webview> element visibility) for backwards compatibility. // the actual <webview> element visibility) for backwards compatibility.

View file

@ -8,21 +8,19 @@ WebPreferences of in-process child windows, rather than relying on
process-level command line switches, as before. process-level command line switches, as before.
diff --git a/third_party/blink/common/web_preferences/web_preferences.cc b/third_party/blink/common/web_preferences/web_preferences.cc diff --git a/third_party/blink/common/web_preferences/web_preferences.cc b/third_party/blink/common/web_preferences/web_preferences.cc
index db2d0536ed7a8143a60cebf1c5d7fee1acf4d10d..6cea8d7ce6ff75ae80a4db03c25f913915624342 100644 index db2d0536ed7a8143a60cebf1c5d7fee1acf4d10d..0cad8646e8b733a3a2d4b1076fdb0276bcd5b7bf 100644
--- a/third_party/blink/common/web_preferences/web_preferences.cc --- a/third_party/blink/common/web_preferences/web_preferences.cc
+++ b/third_party/blink/common/web_preferences/web_preferences.cc +++ b/third_party/blink/common/web_preferences/web_preferences.cc
@@ -145,6 +145,22 @@ WebPreferences::WebPreferences() @@ -145,6 +145,20 @@ WebPreferences::WebPreferences()
fake_no_alloc_direct_call_for_testing_enabled(false), fake_no_alloc_direct_call_for_testing_enabled(false),
v8_cache_options(blink::mojom::V8CacheOptions::kDefault), v8_cache_options(blink::mojom::V8CacheOptions::kDefault),
record_whole_document(false), record_whole_document(false),
+ // Begin Electron-specific WebPreferences. + // Begin Electron-specific WebPreferences.
+ opener_id(0),
+ context_isolation(false), + context_isolation(false),
+ is_webview(false), + is_webview(false),
+ hidden_page(false), + hidden_page(false),
+ offscreen(false), + offscreen(false),
+ preload(base::FilePath::StringType()), + preload(base::FilePath::StringType()),
+ native_window_open(false),
+ node_integration(false), + node_integration(false),
+ node_integration_in_worker(false), + node_integration_in_worker(false),
+ node_integration_in_sub_frames(false), + node_integration_in_sub_frames(false),
@ -35,7 +33,7 @@ index db2d0536ed7a8143a60cebf1c5d7fee1acf4d10d..6cea8d7ce6ff75ae80a4db03c25f9139
accelerated_video_decode_enabled(false), accelerated_video_decode_enabled(false),
animation_policy( animation_policy(
diff --git a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc diff --git a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
index e9f2e215ee1220c66549112982df04201c68fe1a..a8e08adfdeaf3acde4d190766b65ad3fbacfdf58 100644 index e9f2e215ee1220c66549112982df04201c68fe1a..e9118530b395dbb13180365521dcf03d9ca211e5 100644
--- a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc --- a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
+++ b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc +++ b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc
@@ -23,6 +23,10 @@ bool StructTraits<blink::mojom::WebPreferencesDataView, @@ -23,6 +23,10 @@ bool StructTraits<blink::mojom::WebPreferencesDataView,
@ -49,17 +47,15 @@ index e9f2e215ee1220c66549112982df04201c68fe1a..a8e08adfdeaf3acde4d190766b65ad3f
!data.ReadLazyFrameLoadingDistanceThresholdsPx( !data.ReadLazyFrameLoadingDistanceThresholdsPx(
&out->lazy_frame_loading_distance_thresholds_px) || &out->lazy_frame_loading_distance_thresholds_px) ||
!data.ReadLazyImageLoadingDistanceThresholdsPx( !data.ReadLazyImageLoadingDistanceThresholdsPx(
@@ -158,6 +162,21 @@ bool StructTraits<blink::mojom::WebPreferencesDataView, @@ -158,6 +162,19 @@ bool StructTraits<blink::mojom::WebPreferencesDataView,
data.fake_no_alloc_direct_call_for_testing_enabled(); data.fake_no_alloc_direct_call_for_testing_enabled();
out->v8_cache_options = data.v8_cache_options(); out->v8_cache_options = data.v8_cache_options();
out->record_whole_document = data.record_whole_document(); out->record_whole_document = data.record_whole_document();
+ // Begin Electron-specific WebPreferences. + // Begin Electron-specific WebPreferences.
+ out->opener_id = data.opener_id();
+ out->context_isolation = data.context_isolation(); + out->context_isolation = data.context_isolation();
+ out->is_webview = data.is_webview(); + out->is_webview = data.is_webview();
+ out->hidden_page = data.hidden_page(); + out->hidden_page = data.hidden_page();
+ out->offscreen = data.offscreen(); + out->offscreen = data.offscreen();
+ out->native_window_open = data.native_window_open();
+ out->node_integration = data.node_integration(); + out->node_integration = data.node_integration();
+ out->node_integration_in_worker = data.node_integration_in_worker(); + out->node_integration_in_worker = data.node_integration_in_worker();
+ out->node_integration_in_sub_frames = data.node_integration_in_sub_frames(); + out->node_integration_in_sub_frames = data.node_integration_in_sub_frames();
@ -72,7 +68,7 @@ index e9f2e215ee1220c66549112982df04201c68fe1a..a8e08adfdeaf3acde4d190766b65ad3f
out->accelerated_video_decode_enabled = out->accelerated_video_decode_enabled =
data.accelerated_video_decode_enabled(); data.accelerated_video_decode_enabled();
diff --git a/third_party/blink/public/common/web_preferences/web_preferences.h b/third_party/blink/public/common/web_preferences/web_preferences.h diff --git a/third_party/blink/public/common/web_preferences/web_preferences.h b/third_party/blink/public/common/web_preferences/web_preferences.h
index 0d74719b2f8fb91f094772fab96a880cc8787c77..bc447e53a87852aac03fd2487b77aa1009472d36 100644 index 0d74719b2f8fb91f094772fab96a880cc8787c77..23126786a738c3fe83f7064bf8b597794d871ac5 100644
--- a/third_party/blink/public/common/web_preferences/web_preferences.h --- a/third_party/blink/public/common/web_preferences/web_preferences.h
+++ b/third_party/blink/public/common/web_preferences/web_preferences.h +++ b/third_party/blink/public/common/web_preferences/web_preferences.h
@@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
@ -83,19 +79,17 @@ index 0d74719b2f8fb91f094772fab96a880cc8787c77..bc447e53a87852aac03fd2487b77aa10
#include "net/nqe/effective_connection_type.h" #include "net/nqe/effective_connection_type.h"
#include "third_party/blink/public/common/common_export.h" #include "third_party/blink/public/common/common_export.h"
#include "third_party/blink/public/mojom/css/preferred_color_scheme.mojom-shared.h" #include "third_party/blink/public/mojom/css/preferred_color_scheme.mojom-shared.h"
@@ -163,6 +164,24 @@ struct BLINK_COMMON_EXPORT WebPreferences { @@ -163,6 +164,22 @@ struct BLINK_COMMON_EXPORT WebPreferences {
blink::mojom::V8CacheOptions v8_cache_options; blink::mojom::V8CacheOptions v8_cache_options;
bool record_whole_document; bool record_whole_document;
+ // Begin Electron-specific WebPreferences. + // Begin Electron-specific WebPreferences.
+ std::vector<base::FilePath> preloads; + std::vector<base::FilePath> preloads;
+ int opener_id;
+ bool context_isolation; + bool context_isolation;
+ bool is_webview; + bool is_webview;
+ bool hidden_page; + bool hidden_page;
+ bool offscreen; + bool offscreen;
+ base::FilePath preload; + base::FilePath preload;
+ bool native_window_open;
+ bool node_integration; + bool node_integration;
+ bool node_integration_in_worker; + bool node_integration_in_worker;
+ bool node_integration_in_sub_frames; + bool node_integration_in_sub_frames;
@ -109,7 +103,7 @@ index 0d74719b2f8fb91f094772fab96a880cc8787c77..bc447e53a87852aac03fd2487b77aa10
// only controls whether or not the "document.cookie" field is properly // only controls whether or not the "document.cookie" field is properly
// connected to the backing store, for instance if you wanted to be able to // connected to the backing store, for instance if you wanted to be able to
diff --git a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h diff --git a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
index e0084598921ddcb0cf2aeb33875f05da0b5457e9..90bf73d7a1f2daf921f5a0ae9e4b3c292efaaaa0 100644 index e0084598921ddcb0cf2aeb33875f05da0b5457e9..bcda6f35ad74b2b8aa9d439155048aab7bd02b21 100644
--- a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h --- a/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
+++ b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h +++ b/third_party/blink/public/common/web_preferences/web_preferences_mojom_traits.h
@@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
@ -120,7 +114,7 @@ index e0084598921ddcb0cf2aeb33875f05da0b5457e9..90bf73d7a1f2daf921f5a0ae9e4b3c29
#include "mojo/public/cpp/bindings/struct_traits.h" #include "mojo/public/cpp/bindings/struct_traits.h"
#include "net/nqe/effective_connection_type.h" #include "net/nqe/effective_connection_type.h"
#include "third_party/blink/public/common/common_export.h" #include "third_party/blink/public/common/common_export.h"
@@ -456,6 +457,68 @@ struct BLINK_COMMON_EXPORT StructTraits<blink::mojom::WebPreferencesDataView, @@ -456,6 +457,60 @@ struct BLINK_COMMON_EXPORT StructTraits<blink::mojom::WebPreferencesDataView,
return r.record_whole_document; return r.record_whole_document;
} }
@ -129,10 +123,6 @@ index e0084598921ddcb0cf2aeb33875f05da0b5457e9..90bf73d7a1f2daf921f5a0ae9e4b3c29
+ return r.preloads; + return r.preloads;
+ } + }
+ +
+ static int opener_id(const blink::web_pref::WebPreferences& r) {
+ return r.opener_id;
+ }
+
+ static bool context_isolation(const blink::web_pref::WebPreferences& r) { + static bool context_isolation(const blink::web_pref::WebPreferences& r) {
+ return r.context_isolation; + return r.context_isolation;
+ } + }
@ -153,10 +143,6 @@ index e0084598921ddcb0cf2aeb33875f05da0b5457e9..90bf73d7a1f2daf921f5a0ae9e4b3c29
+ return r.preload; + return r.preload;
+ } + }
+ +
+ static bool native_window_open(const blink::web_pref::WebPreferences& r) {
+ return r.native_window_open;
+ }
+
+ static bool node_integration(const blink::web_pref::WebPreferences& r) { + static bool node_integration(const blink::web_pref::WebPreferences& r) {
+ return r.node_integration; + return r.node_integration;
+ } + }
@ -190,7 +176,7 @@ index e0084598921ddcb0cf2aeb33875f05da0b5457e9..90bf73d7a1f2daf921f5a0ae9e4b3c29
return r.cookie_enabled; return r.cookie_enabled;
} }
diff --git a/third_party/blink/public/mojom/webpreferences/web_preferences.mojom b/third_party/blink/public/mojom/webpreferences/web_preferences.mojom diff --git a/third_party/blink/public/mojom/webpreferences/web_preferences.mojom b/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
index af5ba07466b95ab61befdd3994d731ed8f7ef0a6..2623a82a333767f789da2e952adb2bc1997ca4fd 100644 index af5ba07466b95ab61befdd3994d731ed8f7ef0a6..5ffa7ef2334da800267af3947e68477bf82f3526 100644
--- a/third_party/blink/public/mojom/webpreferences/web_preferences.mojom --- a/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
+++ b/third_party/blink/public/mojom/webpreferences/web_preferences.mojom +++ b/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
@@ -10,6 +10,7 @@ import "third_party/blink/public/mojom/v8_cache_options.mojom"; @@ -10,6 +10,7 @@ import "third_party/blink/public/mojom/v8_cache_options.mojom";
@ -201,19 +187,17 @@ index af5ba07466b95ab61befdd3994d731ed8f7ef0a6..2623a82a333767f789da2e952adb2bc1
enum PointerType { enum PointerType {
kPointerNone = 1, // 1 << 0 kPointerNone = 1, // 1 << 0
@@ -215,6 +216,24 @@ struct WebPreferences { @@ -215,6 +216,22 @@ struct WebPreferences {
V8CacheOptions v8_cache_options; V8CacheOptions v8_cache_options;
bool record_whole_document; bool record_whole_document;
+ // Begin Electron-specific WebPreferences. + // Begin Electron-specific WebPreferences.
+ array<mojo_base.mojom.FilePath> preloads; + array<mojo_base.mojom.FilePath> preloads;
+ int32 opener_id;
+ bool context_isolation; + bool context_isolation;
+ bool is_webview; + bool is_webview;
+ bool hidden_page; + bool hidden_page;
+ bool offscreen; + bool offscreen;
+ mojo_base.mojom.FilePath preload; + mojo_base.mojom.FilePath preload;
+ bool native_window_open;
+ bool node_integration; + bool node_integration;
+ bool node_integration_in_worker; + bool node_integration_in_worker;
+ bool node_integration_in_sub_frames; + bool node_integration_in_sub_frames;

View file

@ -81,8 +81,7 @@ BrowserView::BrowserView(gin::Arguments* args,
v8::Local<v8::Value> value; v8::Local<v8::Value> value;
// Copy the webContents option to webPreferences. This is only used internally // Copy the webContents option to webPreferences.
// to implement nativeWindowOpen option.
if (options.Get("webContents", &value)) { if (options.Get("webContents", &value)) {
web_preferences.SetHidden("webContents", value); web_preferences.SetHidden("webContents", value);
} }

View file

@ -78,8 +78,7 @@ BrowserWindow::BrowserWindow(gin::Arguments* args,
web_preferences.Set(options::kEnableBlinkFeatures, enabled_features); web_preferences.Set(options::kEnableBlinkFeatures, enabled_features);
} }
// Copy the webContents option to webPreferences. This is only used internally // Copy the webContents option to webPreferences.
// to implement nativeWindowOpen option.
if (options.Get("webContents", &value)) { if (options.Get("webContents", &value)) {
web_preferences.SetHidden("webContents", value); web_preferences.SetHidden("webContents", value);
} }

View file

@ -721,7 +721,7 @@ bool ElectronBrowserClient::CanCreateWindow(
content::WebContents* web_contents = content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(opener); content::WebContents::FromRenderFrameHost(opener);
WebContentsPreferences* prefs = WebContentsPreferences::From(web_contents); WebContentsPreferences* prefs = WebContentsPreferences::From(web_contents);
if (prefs && prefs->ShouldUseNativeWindowOpen()) { if (prefs) {
if (prefs->ShouldDisablePopups()) { if (prefs->ShouldDisablePopups()) {
// <webview> without allowpopups attribute should return // <webview> without allowpopups attribute should return
// null from window.open calls // null from window.open calls

View file

@ -130,7 +130,6 @@ void WebContentsPreferences::Clear() {
disable_html_fullscreen_window_resize_ = false; disable_html_fullscreen_window_resize_ = false;
webview_tag_ = false; webview_tag_ = false;
sandbox_ = absl::nullopt; sandbox_ = absl::nullopt;
native_window_open_ = true;
context_isolation_ = true; context_isolation_ = true;
javascript_ = true; javascript_ = true;
images_ = true; images_ = true;
@ -148,7 +147,6 @@ void WebContentsPreferences::Clear() {
default_monospace_font_size_ = absl::nullopt; default_monospace_font_size_ = absl::nullopt;
minimum_font_size_ = absl::nullopt; minimum_font_size_ = absl::nullopt;
default_encoding_ = absl::nullopt; default_encoding_ = absl::nullopt;
opener_id_ = 0;
is_webview_ = false; is_webview_ = false;
custom_args_.clear(); custom_args_.clear();
custom_switches_.clear(); custom_switches_.clear();
@ -194,7 +192,6 @@ void WebContentsPreferences::Merge(
bool sandbox; bool sandbox;
if (web_preferences.Get(options::kSandbox, &sandbox)) if (web_preferences.Get(options::kSandbox, &sandbox))
sandbox_ = sandbox; sandbox_ = sandbox;
web_preferences.Get(options::kNativeWindowOpen, &native_window_open_);
web_preferences.Get(options::kContextIsolation, &context_isolation_); web_preferences.Get(options::kContextIsolation, &context_isolation_);
web_preferences.Get(options::kJavaScript, &javascript_); web_preferences.Get(options::kJavaScript, &javascript_);
web_preferences.Get(options::kImages, &images_); web_preferences.Get(options::kImages, &images_);
@ -223,7 +220,6 @@ void WebContentsPreferences::Merge(
std::string encoding; std::string encoding;
if (web_preferences.Get("defaultEncoding", &encoding)) if (web_preferences.Get("defaultEncoding", &encoding))
default_encoding_ = encoding; default_encoding_ = encoding;
web_preferences.Get(options::kOpenerID, &opener_id_);
web_preferences.Get(options::kCustomArgs, &custom_args_); web_preferences.Get(options::kCustomArgs, &custom_args_);
web_preferences.Get("commandLineSwitches", &custom_switches_); web_preferences.Get("commandLineSwitches", &custom_switches_);
web_preferences.Get("disablePopups", &disable_popups_); web_preferences.Get("disablePopups", &disable_popups_);
@ -406,13 +402,10 @@ void WebContentsPreferences::AppendCommandLineSwitches(
void WebContentsPreferences::SaveLastPreferences() { void WebContentsPreferences::SaveLastPreferences() {
last_web_preferences_ = base::Value(base::Value::Type::DICTIONARY); last_web_preferences_ = base::Value(base::Value::Type::DICTIONARY);
last_web_preferences_.SetKey(options::kOpenerID, base::Value(opener_id_));
last_web_preferences_.SetKey(options::kNodeIntegration, last_web_preferences_.SetKey(options::kNodeIntegration,
base::Value(node_integration_)); base::Value(node_integration_));
last_web_preferences_.SetKey(options::kNodeIntegrationInSubFrames, last_web_preferences_.SetKey(options::kNodeIntegrationInSubFrames,
base::Value(node_integration_in_sub_frames_)); base::Value(node_integration_in_sub_frames_));
last_web_preferences_.SetKey(options::kNativeWindowOpen,
base::Value(native_window_open_));
last_web_preferences_.SetKey(options::kSandbox, base::Value(IsSandboxed())); last_web_preferences_.SetKey(options::kSandbox, base::Value(IsSandboxed()));
last_web_preferences_.SetKey(options::kContextIsolation, last_web_preferences_.SetKey(options::kContextIsolation,
base::Value(context_isolation_)); base::Value(context_isolation_));
@ -477,9 +470,6 @@ void WebContentsPreferences::OverrideWebkitPrefs(
if (default_encoding_) if (default_encoding_)
prefs->default_encoding = *default_encoding_; prefs->default_encoding = *default_encoding_;
// Pass the opener's window id.
prefs->opener_id = opener_id_;
// Run Electron APIs and preload script in isolated world // Run Electron APIs and preload script in isolated world
prefs->context_isolation = context_isolation_; prefs->context_isolation = context_isolation_;
prefs->is_webview = is_webview_; prefs->is_webview = is_webview_;
@ -507,7 +497,6 @@ void WebContentsPreferences::OverrideWebkitPrefs(
if (preload_path_) if (preload_path_)
prefs->preload = *preload_path_; prefs->preload = *preload_path_;
prefs->native_window_open = native_window_open_;
prefs->node_integration = node_integration_; prefs->node_integration = node_integration_;
prefs->node_integration_in_worker = node_integration_in_worker_; prefs->node_integration_in_worker = node_integration_in_worker_;
prefs->node_integration_in_sub_frames = node_integration_in_sub_frames_; prefs->node_integration_in_sub_frames = node_integration_in_sub_frames_;

View file

@ -75,7 +75,6 @@ class WebContentsPreferences
bool ShouldUseSafeDialogs() const { return safe_dialogs_; } bool ShouldUseSafeDialogs() const { return safe_dialogs_; }
bool GetSafeDialogsMessage(std::string* message) const; bool GetSafeDialogsMessage(std::string* message) const;
bool ShouldDisablePopups() const { return disable_popups_; } bool ShouldDisablePopups() const { return disable_popups_; }
bool ShouldUseNativeWindowOpen() const { return native_window_open_; }
bool IsWebSecurityEnabled() const { return web_security_; } bool IsWebSecurityEnabled() const { return web_security_; }
bool GetPreloadPath(base::FilePath* path) const; bool GetPreloadPath(base::FilePath* path) const;
bool IsSandboxed() const; bool IsSandboxed() const;
@ -100,7 +99,6 @@ class WebContentsPreferences
bool disable_html_fullscreen_window_resize_; bool disable_html_fullscreen_window_resize_;
bool webview_tag_; bool webview_tag_;
absl::optional<bool> sandbox_; absl::optional<bool> sandbox_;
bool native_window_open_;
bool context_isolation_; bool context_isolation_;
bool javascript_; bool javascript_;
bool images_; bool images_;
@ -118,7 +116,6 @@ class WebContentsPreferences
absl::optional<int> default_monospace_font_size_; absl::optional<int> default_monospace_font_size_;
absl::optional<int> minimum_font_size_; absl::optional<int> minimum_font_size_;
absl::optional<std::string> default_encoding_; absl::optional<std::string> default_encoding_;
int opener_id_;
bool is_webview_; bool is_webview_;
std::vector<std::string> custom_args_; std::vector<std::string> custom_args_;
std::vector<std::string> custom_switches_; std::vector<std::string> custom_switches_;

View file

@ -102,10 +102,6 @@ void RequestGarbageCollectionForTesting(v8::Isolate* isolate) {
v8::Isolate::GarbageCollectionType::kFullGarbageCollection); v8::Isolate::GarbageCollectionType::kFullGarbageCollection);
} }
bool IsSameOrigin(const GURL& l, const GURL& r) {
return url::Origin::Create(l).IsSameOriginWith(url::Origin::Create(r));
}
// This causes a fatal error by creating a circular extension dependency. // This causes a fatal error by creating a circular extension dependency.
void TriggerFatalErrorForTesting(v8::Isolate* isolate) { void TriggerFatalErrorForTesting(v8::Isolate* isolate) {
static const char* aDeps[] = {"B"}; static const char* aDeps[] = {"B"};
@ -132,7 +128,6 @@ void Initialize(v8::Local<v8::Object> exports,
dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot); dict.SetMethod("takeHeapSnapshot", &TakeHeapSnapshot);
dict.SetMethod("requestGarbageCollectionForTesting", dict.SetMethod("requestGarbageCollectionForTesting",
&RequestGarbageCollectionForTesting); &RequestGarbageCollectionForTesting);
dict.SetMethod("isSameOrigin", &IsSameOrigin);
dict.SetMethod("triggerFatalErrorForTesting", &TriggerFatalErrorForTesting); dict.SetMethod("triggerFatalErrorForTesting", &TriggerFatalErrorForTesting);
dict.SetMethod("runUntilIdle", &RunUntilIdle); dict.SetMethod("runUntilIdle", &RunUntilIdle);
} }

View file

@ -129,9 +129,6 @@ const char kContextIsolation[] = "contextIsolation";
// Web runtime features. // Web runtime features.
const char kExperimentalFeatures[] = "experimentalFeatures"; const char kExperimentalFeatures[] = "experimentalFeatures";
// Opener window's ID.
const char kOpenerID[] = "openerId";
// Enable the rubber banding effect. // Enable the rubber banding effect.
const char kScrollBounce[] = "scrollBounce"; const char kScrollBounce[] = "scrollBounce";
@ -147,8 +144,6 @@ const char kNodeIntegrationInWorker[] = "nodeIntegrationInWorker";
// Enable the web view tag. // Enable the web view tag.
const char kWebviewTag[] = "webviewTag"; const char kWebviewTag[] = "webviewTag";
const char kNativeWindowOpen[] = "nativeWindowOpen";
const char kCustomArgs[] = "additionalArguments"; const char kCustomArgs[] = "additionalArguments";
const char kPlugins[] = "plugins"; const char kPlugins[] = "plugins";

View file

@ -69,13 +69,11 @@ extern const char kPreloadURL[];
extern const char kNodeIntegration[]; extern const char kNodeIntegration[];
extern const char kContextIsolation[]; extern const char kContextIsolation[];
extern const char kExperimentalFeatures[]; extern const char kExperimentalFeatures[];
extern const char kOpenerID[];
extern const char kScrollBounce[]; extern const char kScrollBounce[];
extern const char kEnableBlinkFeatures[]; extern const char kEnableBlinkFeatures[];
extern const char kDisableBlinkFeatures[]; extern const char kDisableBlinkFeatures[];
extern const char kNodeIntegrationInWorker[]; extern const char kNodeIntegrationInWorker[];
extern const char kWebviewTag[]; extern const char kWebviewTag[];
extern const char kNativeWindowOpen[];
extern const char kCustomArgs[]; extern const char kCustomArgs[];
extern const char kPlugins[]; extern const char kPlugins[];
extern const char kSandbox[]; extern const char kSandbox[];

View file

@ -496,9 +496,6 @@ class WebFrameRenderer : public gin::Wrappable<WebFrameRenderer>,
if (pref_name == options::kPreloadScripts) { if (pref_name == options::kPreloadScripts) {
return gin::ConvertToV8(isolate, prefs.preloads); return gin::ConvertToV8(isolate, prefs.preloads);
} else if (pref_name == options::kOpenerID) {
// NOTE: openerId is internal-only.
return gin::ConvertToV8(isolate, prefs.opener_id);
} else if (pref_name == "isWebView") { } else if (pref_name == "isWebView") {
// FIXME(zcbenz): For child windows opened with window.open('') from // FIXME(zcbenz): For child windows opened with window.open('') from
// webview, the WebPreferences is inherited from webview and the value // webview, the WebPreferences is inherited from webview and the value
@ -516,8 +513,6 @@ class WebFrameRenderer : public gin::Wrappable<WebFrameRenderer>,
return gin::ConvertToV8(isolate, prefs.offscreen); return gin::ConvertToV8(isolate, prefs.offscreen);
} else if (pref_name == options::kPreloadScript) { } else if (pref_name == options::kPreloadScript) {
return gin::ConvertToV8(isolate, prefs.preload.value()); return gin::ConvertToV8(isolate, prefs.preload.value());
} else if (pref_name == options::kNativeWindowOpen) {
return gin::ConvertToV8(isolate, prefs.native_window_open);
} else if (pref_name == options::kNodeIntegration) { } else if (pref_name == options::kNodeIntegration) {
return gin::ConvertToV8(isolate, prefs.node_integration); return gin::ConvertToV8(isolate, prefs.node_integration);
} else if (pref_name == options::kNodeIntegrationInWorker) { } else if (pref_name == options::kNodeIntegrationInWorker) {

View file

@ -15,6 +15,7 @@ import { closeWindow, closeAllWindows } from './window-helpers';
const features = process._linkedBinding('electron_common_features'); const features = process._linkedBinding('electron_common_features');
const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures'); const fixtures = path.resolve(__dirname, '..', 'spec', 'fixtures');
const mainFixtures = path.resolve(__dirname, 'fixtures');
// Is the display's scale factor possibly causing rounding of pixel coordinate // Is the display's scale factor possibly causing rounding of pixel coordinate
// values? // values?
@ -2450,7 +2451,7 @@ describe('BrowserWindow module', () => {
} }
}); });
const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js'); const preloadPath = path.join(mainFixtures, 'api', 'new-window-preload.js');
w.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { preload: preloadPath } } })); w.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { preload: preloadPath } } }));
w.loadFile(path.join(fixtures, 'api', 'new-window.html')); w.loadFile(path.join(fixtures, 'api', 'new-window.html'));
const [, { argv }] = await emittedOnce(ipcMain, 'answer'); const [, { argv }] = await emittedOnce(ipcMain, 'answer');
@ -2465,7 +2466,7 @@ describe('BrowserWindow module', () => {
} }
}); });
const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js'); const preloadPath = path.join(mainFixtures, 'api', 'new-window-preload.js');
w.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { preload: preloadPath, contextIsolation: false } } })); w.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { preload: preloadPath, contextIsolation: false } } }));
w.loadFile(path.join(fixtures, 'api', 'new-window.html')); w.loadFile(path.join(fixtures, 'api', 'new-window.html'));
const [[, childWebContents]] = await Promise.all([ const [[, childWebContents]] = await Promise.all([
@ -2636,7 +2637,7 @@ describe('BrowserWindow module', () => {
}); });
}); });
describe('nativeWindowOpen option', () => { describe('child windows', () => {
let w: BrowserWindow = null as unknown as BrowserWindow; let w: BrowserWindow = null as unknown as BrowserWindow;
beforeEach(() => { beforeEach(() => {
@ -2644,7 +2645,6 @@ describe('BrowserWindow module', () => {
show: false, show: false,
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
nativeWindowOpen: true,
// tests relies on preloads in opened windows // tests relies on preloads in opened windows
nodeIntegrationInSubFrames: true, nodeIntegrationInSubFrames: true,
contextIsolation: false contextIsolation: false
@ -2676,6 +2676,17 @@ describe('BrowserWindow module', () => {
const [, content] = await answer; const [, content] = await answer;
expect(content).to.equal('Hello'); expect(content).to.equal('Hello');
}); });
it('opens window with cross-scripting enabled from isolated context', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
preload: path.join(fixtures, 'api', 'native-window-open-isolated-preload.js')
}
});
w.loadFile(path.join(fixtures, 'api', 'native-window-open-isolated.html'));
const [, content] = await emittedOnce(ipcMain, 'answer');
expect(content).to.equal('Hello');
});
ifit(!process.env.ELECTRON_SKIP_NATIVE_MODULE_TESTS)('loads native addons correctly after reload', async () => { ifit(!process.env.ELECTRON_SKIP_NATIVE_MODULE_TESTS)('loads native addons correctly after reload', async () => {
w.loadFile(path.join(__dirname, 'fixtures', 'api', 'native-window-open-native-addon.html')); w.loadFile(path.join(__dirname, 'fixtures', 'api', 'native-window-open-native-addon.html'));
{ {
@ -2695,7 +2706,6 @@ describe('BrowserWindow module', () => {
show: false, show: false,
webPreferences: { webPreferences: {
nodeIntegrationInSubFrames: true, nodeIntegrationInSubFrames: true,
nativeWindowOpen: true,
webviewTag: true, webviewTag: true,
contextIsolation: false, contextIsolation: false,
preload preload
@ -2703,7 +2713,7 @@ describe('BrowserWindow module', () => {
}); });
w.webContents.setWindowOpenHandler(() => ({ w.webContents.setWindowOpenHandler(() => ({
action: 'allow', action: 'allow',
overrideBrowserWindowOptions: { show: false, webPreferences: { contextIsolation: false, webviewTag: true, nativeWindowOpen: true, nodeIntegrationInSubFrames: true } } overrideBrowserWindowOptions: { show: false, webPreferences: { contextIsolation: false, webviewTag: true, nodeIntegrationInSubFrames: true } }
})); }));
w.webContents.once('new-window', (event, url, frameName, disposition, options) => { w.webContents.once('new-window', (event, url, frameName, disposition, options) => {
options.show = false; options.show = false;
@ -2713,23 +2723,8 @@ describe('BrowserWindow module', () => {
w.loadFile(path.join(fixtures, 'api', 'new-window-webview.html')); w.loadFile(path.join(fixtures, 'api', 'new-window-webview.html'));
await webviewLoaded; await webviewLoaded;
}); });
it('should inherit the nativeWindowOpen setting in opened windows', async () => {
const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js');
w.webContents.setWindowOpenHandler(() => ({
action: 'allow',
overrideBrowserWindowOptions: {
webPreferences: {
preload: preloadPath
}
}
}));
w.loadFile(path.join(fixtures, 'api', 'new-window.html'));
const [, { nativeWindowOpen }] = await emittedOnce(ipcMain, 'answer');
expect(nativeWindowOpen).to.be.true();
});
it('should open windows with the options configured via new-window event listeners', async () => { it('should open windows with the options configured via new-window event listeners', async () => {
const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js'); const preloadPath = path.join(mainFixtures, 'api', 'new-window-preload.js');
w.webContents.setWindowOpenHandler(() => ({ w.webContents.setWindowOpenHandler(() => ({
action: 'allow', action: 'allow',
overrideBrowserWindowOptions: { overrideBrowserWindowOptions: {
@ -2772,7 +2767,6 @@ describe('BrowserWindow module', () => {
const w = new BrowserWindow({ const w = new BrowserWindow({
show: false, show: false,
webPreferences: { webPreferences: {
nativeWindowOpen: true,
// test relies on preloads in opened window // test relies on preloads in opened window
nodeIntegrationInSubFrames: true, nodeIntegrationInSubFrames: true,
contextIsolation: false contextIsolation: false
@ -2783,7 +2777,7 @@ describe('BrowserWindow module', () => {
action: 'allow', action: 'allow',
overrideBrowserWindowOptions: { overrideBrowserWindowOptions: {
webPreferences: { webPreferences: {
preload: path.join(fixtures, 'api', 'window-open-preload.js'), preload: path.join(mainFixtures, 'api', 'window-open-preload.js'),
contextIsolation: false, contextIsolation: false,
nodeIntegrationInSubFrames: true nodeIntegrationInSubFrames: true
} }
@ -2791,9 +2785,8 @@ describe('BrowserWindow module', () => {
})); }));
w.loadFile(path.join(fixtures, 'api', 'window-open-location-open.html')); w.loadFile(path.join(fixtures, 'api', 'window-open-location-open.html'));
const [, { nodeIntegration, nativeWindowOpen, typeofProcess }] = await emittedOnce(ipcMain, 'answer'); const [, { nodeIntegration, typeofProcess }] = await emittedOnce(ipcMain, 'answer');
expect(nodeIntegration).to.be.false(); expect(nodeIntegration).to.be.false();
expect(nativeWindowOpen).to.be.true();
expect(typeofProcess).to.eql('undefined'); expect(typeofProcess).to.eql('undefined');
}); });
@ -2801,7 +2794,6 @@ describe('BrowserWindow module', () => {
const w = new BrowserWindow({ const w = new BrowserWindow({
show: false, show: false,
webPreferences: { webPreferences: {
nativeWindowOpen: true,
// test relies on preloads in opened window // test relies on preloads in opened window
nodeIntegrationInSubFrames: true nodeIntegrationInSubFrames: true
} }
@ -2811,7 +2803,7 @@ describe('BrowserWindow module', () => {
action: 'allow', action: 'allow',
overrideBrowserWindowOptions: { overrideBrowserWindowOptions: {
webPreferences: { webPreferences: {
preload: path.join(fixtures, 'api', 'window-open-preload.js') preload: path.join(mainFixtures, 'api', 'window-open-preload.js')
} }
} }
})); }));
@ -2840,23 +2832,6 @@ describe('BrowserWindow module', () => {
}); });
}); });
describe('nativeWindowOpen + contextIsolation options', () => {
afterEach(closeAllWindows);
it('opens window with cross-scripting enabled from isolated context', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
nativeWindowOpen: true,
contextIsolation: true,
preload: path.join(fixtures, 'api', 'native-window-open-isolated-preload.js')
}
});
w.loadFile(path.join(fixtures, 'api', 'native-window-open-isolated.html'));
const [, content] = await emittedOnce(ipcMain, 'answer');
expect(content).to.equal('Hello');
});
});
describe('beforeunload handler', function () { describe('beforeunload handler', function () {
let w: BrowserWindow = null as unknown as BrowserWindow; let w: BrowserWindow = null as unknown as BrowserWindow;
beforeEach(() => { beforeEach(() => {

View file

@ -9,7 +9,7 @@ describe('ipcRenderer module', () => {
let w: BrowserWindow; let w: BrowserWindow;
before(async () => { before(async () => {
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nativeWindowOpen: true, contextIsolation: false } }); w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
await w.loadURL('about:blank'); await w.loadURL('about:blank');
}); });
after(async () => { after(async () => {

View file

@ -1245,8 +1245,8 @@ describe('webContents module', () => {
expect(currentRenderViewDeletedEmitted).to.be.false('current-render-view-deleted was emitted'); expect(currentRenderViewDeletedEmitted).to.be.false('current-render-view-deleted was emitted');
}); });
it('does not emit current-render-view-deleted when speculative RVHs are deleted and nativeWindowOpen is set to true', async () => { it('does not emit current-render-view-deleted when speculative RVHs are deleted', async () => {
const parentWindow = new BrowserWindow({ show: false, webPreferences: { nativeWindowOpen: true } }); const parentWindow = new BrowserWindow({ show: false });
let currentRenderViewDeletedEmitted = false; let currentRenderViewDeletedEmitted = false;
let childWindow: BrowserWindow | null = null; let childWindow: BrowserWindow | null = null;
const destroyed = emittedOnce(parentWindow.webContents, 'destroyed'); const destroyed = emittedOnce(parentWindow.webContents, 'destroyed');
@ -2054,7 +2054,7 @@ describe('webContents module', () => {
describe('page-title-updated event', () => { describe('page-title-updated event', () => {
afterEach(closeAllWindows); afterEach(closeAllWindows);
it('is emitted with a full title for pages with no navigation', async () => { it('is emitted with a full title for pages with no navigation', async () => {
const bw = new BrowserWindow({ show: false, webPreferences: { nativeWindowOpen: true } }); const bw = new BrowserWindow({ show: false });
await bw.loadURL('about:blank'); await bw.loadURL('about:blank');
bw.webContents.executeJavaScript('child = window.open("", "", "show=no"); null'); bw.webContents.executeJavaScript('child = window.open("", "", "show=no"); null');
const [, child] = await emittedOnce(app, 'web-contents-created'); const [, child] = await emittedOnce(app, 'web-contents-created');

View file

@ -84,19 +84,15 @@ describe('window.postMessage', () => {
await closeAllWindows(); await closeAllWindows();
}); });
for (const nativeWindowOpen of [true, false]) { it('sets the source and origin correctly', async () => {
describe(`when nativeWindowOpen: ${nativeWindowOpen}`, () => { const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
it('sets the source and origin correctly', async () => { w.loadURL(`file://${fixturesPath}/pages/window-open-postMessage-driver.html`);
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nativeWindowOpen, contextIsolation: false } }); const [, message] = await emittedOnce(ipcMain, 'complete');
w.loadURL(`file://${fixturesPath}/pages/window-open-postMessage-driver.html`); expect(message.data).to.equal('testing');
const [, message] = await emittedOnce(ipcMain, 'complete'); expect(message.origin).to.equal('file://');
expect(message.data).to.equal('testing'); expect(message.sourceEqualsOpener).to.equal(true);
expect(message.origin).to.equal('file://'); expect(message.eventOrigin).to.equal('file://');
expect(message.sourceEqualsOpener).to.equal(true); });
expect(message.eventOrigin).to.equal('file://');
});
});
}
}); });
describe('focus handling', () => { describe('focus handling', () => {
@ -814,8 +810,8 @@ describe('chromium features', () => {
expect(typeofProcessGlobal).to.equal('undefined'); expect(typeofProcessGlobal).to.equal('undefined');
}); });
it('can disable node integration when it is enabled on the parent window with nativeWindowOpen: true', async () => { it('can disable node integration when it is enabled on the parent window', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nativeWindowOpen: true } }); const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
w.loadURL('about:blank'); w.loadURL('about:blank');
w.webContents.executeJavaScript(` w.webContents.executeJavaScript(`
{ b = window.open('about:blank', '', 'nodeIntegration=no,show=no'); null } { b = window.open('about:blank', '', 'nodeIntegration=no,show=no'); null }
@ -909,34 +905,6 @@ describe('chromium features', () => {
expect(frameName).to.equal('__proto__'); expect(frameName).to.equal('__proto__');
}); });
it('denies custom open when nativeWindowOpen: true', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
nativeWindowOpen: true
}
});
w.loadURL('about:blank');
const previousListeners = process.listeners('uncaughtException');
process.removeAllListeners('uncaughtException');
try {
const uncaughtException = new Promise<Error>(resolve => {
process.once('uncaughtException', resolve);
});
expect(await w.webContents.executeJavaScript(`(${function () {
const { ipc } = process._linkedBinding('electron_renderer_ipc');
return ipc.sendSync(true, 'GUEST_WINDOW_MANAGER_WINDOW_OPEN', ['', '', '']);
}})()`)).to.be.null();
const exception = await uncaughtException;
expect(exception.message).to.match(/denied: expected native window\.open/);
} finally {
previousListeners.forEach(l => process.on('uncaughtException', l));
}
});
}); });
describe('window.opener', () => { describe('window.opener', () => {
@ -1047,31 +1015,21 @@ describe('chromium features', () => {
const httpBlank = `${scheme}://origin1/blank`; const httpBlank = `${scheme}://origin1/blank`;
const table = [ const table = [
{ parent: fileBlank, child: httpUrl1, nodeIntegration: false, nativeWindowOpen: false, openerAccessible: false }, { parent: fileBlank, child: httpUrl1, nodeIntegration: false, openerAccessible: false },
{ parent: fileBlank, child: httpUrl1, nodeIntegration: false, nativeWindowOpen: true, openerAccessible: false }, { parent: fileBlank, child: httpUrl1, nodeIntegration: true, openerAccessible: false },
{ parent: fileBlank, child: httpUrl1, nodeIntegration: true, nativeWindowOpen: false, openerAccessible: true },
{ parent: fileBlank, child: httpUrl1, nodeIntegration: true, nativeWindowOpen: true, openerAccessible: false },
{ parent: httpBlank, child: fileUrl, nodeIntegration: false, nativeWindowOpen: false, openerAccessible: false }, // {parent: httpBlank, child: fileUrl, nodeIntegration: false, openerAccessible: false}, // can't window.open()
// {parent: httpBlank, child: fileUrl, nodeIntegration: false, nativeWindowOpen: true, openerAccessible: false}, // can't window.open() // {parent: httpBlank, child: fileUrl, nodeIntegration: true, openerAccessible: false}, // can't window.open()
{ parent: httpBlank, child: fileUrl, nodeIntegration: true, nativeWindowOpen: false, openerAccessible: true },
// {parent: httpBlank, child: fileUrl, nodeIntegration: true, nativeWindowOpen: true, openerAccessible: false}, // can't window.open()
// NB. this is different from Chrome's behavior, which isolates file: urls from each other // NB. this is different from Chrome's behavior, which isolates file: urls from each other
{ parent: fileBlank, child: fileUrl, nodeIntegration: false, nativeWindowOpen: false, openerAccessible: true }, { parent: fileBlank, child: fileUrl, nodeIntegration: false, openerAccessible: true },
{ parent: fileBlank, child: fileUrl, nodeIntegration: false, nativeWindowOpen: true, openerAccessible: true }, { parent: fileBlank, child: fileUrl, nodeIntegration: true, openerAccessible: true },
{ parent: fileBlank, child: fileUrl, nodeIntegration: true, nativeWindowOpen: false, openerAccessible: true },
{ parent: fileBlank, child: fileUrl, nodeIntegration: true, nativeWindowOpen: true, openerAccessible: true },
{ parent: httpBlank, child: httpUrl1, nodeIntegration: false, nativeWindowOpen: false, openerAccessible: true }, { parent: httpBlank, child: httpUrl1, nodeIntegration: false, openerAccessible: true },
{ parent: httpBlank, child: httpUrl1, nodeIntegration: false, nativeWindowOpen: true, openerAccessible: true }, { parent: httpBlank, child: httpUrl1, nodeIntegration: true, openerAccessible: true },
{ parent: httpBlank, child: httpUrl1, nodeIntegration: true, nativeWindowOpen: false, openerAccessible: true },
{ parent: httpBlank, child: httpUrl1, nodeIntegration: true, nativeWindowOpen: true, openerAccessible: true },
{ parent: httpBlank, child: httpUrl2, nodeIntegration: false, nativeWindowOpen: false, openerAccessible: false }, { parent: httpBlank, child: httpUrl2, nodeIntegration: false, openerAccessible: false },
{ parent: httpBlank, child: httpUrl2, nodeIntegration: false, nativeWindowOpen: true, openerAccessible: false }, { parent: httpBlank, child: httpUrl2, nodeIntegration: true, openerAccessible: false }
{ parent: httpBlank, child: httpUrl2, nodeIntegration: true, nativeWindowOpen: false, openerAccessible: true },
{ parent: httpBlank, child: httpUrl2, nodeIntegration: true, nativeWindowOpen: true, openerAccessible: false }
]; ];
const s = (url: string) => url.startsWith('file') ? 'file://...' : url; const s = (url: string) => url.startsWith('file') ? 'file://...' : url;
@ -1090,11 +1048,11 @@ describe('chromium features', () => {
afterEach(closeAllWindows); afterEach(closeAllWindows);
describe('when opened from main window', () => { describe('when opened from main window', () => {
for (const { parent, child, nodeIntegration, nativeWindowOpen, openerAccessible } of table) { for (const { parent, child, nodeIntegration, openerAccessible } of table) {
for (const sandboxPopup of [false, true]) { for (const sandboxPopup of [false, true]) {
const description = `when parent=${s(parent)} opens child=${s(child)} with nodeIntegration=${nodeIntegration} nativeWindowOpen=${nativeWindowOpen} sandboxPopup=${sandboxPopup}, child should ${openerAccessible ? '' : 'not '}be able to access opener`; const description = `when parent=${s(parent)} opens child=${s(child)} with nodeIntegration=${nodeIntegration} sandboxPopup=${sandboxPopup}, child should ${openerAccessible ? '' : 'not '}be able to access opener`;
it(description, async () => { it(description, async () => {
const w = new BrowserWindow({ show: true, webPreferences: { nodeIntegration: true, nativeWindowOpen, contextIsolation: false } }); const w = new BrowserWindow({ show: true, webPreferences: { nodeIntegration: true, contextIsolation: false } });
w.webContents.setWindowOpenHandler(() => ({ w.webContents.setWindowOpenHandler(() => ({
action: 'allow', action: 'allow',
overrideBrowserWindowOptions: { overrideBrowserWindowOptions: {
@ -1121,11 +1079,9 @@ describe('chromium features', () => {
}); });
describe('when opened from <webview>', () => { describe('when opened from <webview>', () => {
for (const { parent, child, nodeIntegration, nativeWindowOpen, openerAccessible } of table) { for (const { parent, child, nodeIntegration, openerAccessible } of table) {
const description = `when parent=${s(parent)} opens child=${s(child)} with nodeIntegration=${nodeIntegration} nativeWindowOpen=${nativeWindowOpen}, child should ${openerAccessible ? '' : 'not '}be able to access opener`; const description = `when parent=${s(parent)} opens child=${s(child)} with nodeIntegration=${nodeIntegration}, child should ${openerAccessible ? '' : 'not '}be able to access opener`;
// WebView erroneously allows access to the parent window when nativeWindowOpen is false. it(description, async () => {
const skip = !nativeWindowOpen && !openerAccessible;
ifit(!skip)(description, async () => {
// This test involves three contexts: // This test involves three contexts:
// 1. The root BrowserWindow in which the test is run, // 1. The root BrowserWindow in which the test is run,
// 2. A <webview> belonging to the root window, // 2. A <webview> belonging to the root window,
@ -1133,7 +1089,7 @@ describe('chromium features', () => {
// We are testing whether context (3) can access context (2) under various conditions. // We are testing whether context (3) can access context (2) under various conditions.
// This is context (1), the base window for the test. // This is context (1), the base window for the test.
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, webviewTag: true, contextIsolation: false, nativeWindowOpen: false } }); const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, webviewTag: true, contextIsolation: false } });
await w.loadURL('about:blank'); await w.loadURL('about:blank');
const parentCode = `new Promise((resolve) => { const parentCode = `new Promise((resolve) => {
@ -1147,7 +1103,7 @@ describe('chromium features', () => {
// This is context (2), a WebView which will call window.open() // This is context (2), a WebView which will call window.open()
const webview = new WebView() const webview = new WebView()
webview.setAttribute('nodeintegration', '${nodeIntegration ? 'on' : 'off'}') webview.setAttribute('nodeintegration', '${nodeIntegration ? 'on' : 'off'}')
webview.setAttribute('webpreferences', 'nativeWindowOpen=${nativeWindowOpen ? 'yes' : 'no'},contextIsolation=no') webview.setAttribute('webpreferences', 'contextIsolation=no')
webview.setAttribute('allowpopups', 'on') webview.setAttribute('allowpopups', 'on')
webview.src = ${JSON.stringify(parent + '?p=' + encodeURIComponent(child))} webview.src = ${JSON.stringify(parent + '?p=' + encodeURIComponent(child))}
webview.addEventListener('dom-ready', async () => { webview.addEventListener('dom-ready', async () => {

View file

@ -1,7 +1,6 @@
const { ipcRenderer, webFrame } = require('electron'); const { ipcRenderer, webFrame } = require('electron');
ipcRenderer.send('answer', { ipcRenderer.send('answer', {
nativeWindowOpen: webFrame.getWebPreference('nativeWindowOpen'),
argv: process.argv argv: process.argv
}); });
window.close(); window.close();

View file

@ -4,7 +4,6 @@ setImmediate(function () {
if (window.location.toString() === 'bar://page/') { if (window.location.toString() === 'bar://page/') {
const windowOpenerIsNull = window.opener == null; const windowOpenerIsNull = window.opener == null;
ipcRenderer.send('answer', { ipcRenderer.send('answer', {
nativeWindowOpen: webFrame.getWebPreference('nativeWindowOpen'),
nodeIntegration: webFrame.getWebPreference('nodeIntegration'), nodeIntegration: webFrame.getWebPreference('nodeIntegration'),
typeofProcess: typeof global.process, typeofProcess: typeof global.process,
windowOpenerIsNull windowOpenerIsNull

View file

@ -4,8 +4,7 @@ function createWindow () {
const mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false
nativeWindowOpen: true
} }
}); });

View file

@ -18,12 +18,10 @@
"y": 5, "y": 5,
"webPreferences": { "webPreferences": {
"contextIsolation": true, "contextIsolation": true,
"nativeWindowOpen": true,
"nodeIntegration": false, "nodeIntegration": false,
"sandbox": true, "sandbox": true,
"webviewTag": false, "webviewTag": false,
"nodeIntegrationInSubFrames": false, "nodeIntegrationInSubFrames": false
"openerId": null
}, },
"webContents": "[WebContents]" "webContents": "[WebContents]"
}, },
@ -52,12 +50,10 @@
"webPreferences": { "webPreferences": {
"zoomFactor": "2", "zoomFactor": "2",
"contextIsolation": true, "contextIsolation": true,
"nativeWindowOpen": true,
"nodeIntegration": false, "nodeIntegration": false,
"sandbox": true, "sandbox": true,
"webviewTag": false, "webviewTag": false,
"nodeIntegrationInSubFrames": false, "nodeIntegrationInSubFrames": false
"openerId": null
}, },
"webContents": "[WebContents]" "webContents": "[WebContents]"
}, },
@ -83,12 +79,10 @@
"backgroundColor": "gray", "backgroundColor": "gray",
"webPreferences": { "webPreferences": {
"contextIsolation": true, "contextIsolation": true,
"nativeWindowOpen": true,
"nodeIntegration": false, "nodeIntegration": false,
"sandbox": true, "sandbox": true,
"webviewTag": false, "webviewTag": false,
"nodeIntegrationInSubFrames": false, "nodeIntegrationInSubFrames": false
"openerId": null
}, },
"x": 100, "x": 100,
"y": 100, "y": 100,
@ -118,12 +112,10 @@
"title": "sup", "title": "sup",
"webPreferences": { "webPreferences": {
"contextIsolation": true, "contextIsolation": true,
"nativeWindowOpen": true,
"nodeIntegration": false, "nodeIntegration": false,
"sandbox": true, "sandbox": true,
"webviewTag": false, "webviewTag": false,
"nodeIntegrationInSubFrames": false, "nodeIntegrationInSubFrames": false
"openerId": null
}, },
"webContents": "[WebContents]" "webContents": "[WebContents]"
}, },
@ -152,12 +144,10 @@
"y": 1, "y": 1,
"webPreferences": { "webPreferences": {
"contextIsolation": true, "contextIsolation": true,
"nativeWindowOpen": true,
"nodeIntegration": false, "nodeIntegration": false,
"sandbox": true, "sandbox": true,
"webviewTag": false, "webviewTag": false,
"nodeIntegrationInSubFrames": false, "nodeIntegrationInSubFrames": false
"openerId": null
}, },
"webContents": "[WebContents]" "webContents": "[WebContents]"
}, },
@ -168,4 +158,4 @@
}, },
null null
] ]
] ]

View file

@ -22,8 +22,7 @@
"contextIsolation": true, "contextIsolation": true,
"nodeIntegration": false, "nodeIntegration": false,
"webviewTag": false, "webviewTag": false,
"nodeIntegrationInSubFrames": false, "nodeIntegrationInSubFrames": false
"openerId": "placeholder-opener-id"
}, },
"webContents": "[WebContents]" "webContents": "[WebContents]"
}, },
@ -56,8 +55,7 @@
"contextIsolation": true, "contextIsolation": true,
"nodeIntegration": false, "nodeIntegration": false,
"webviewTag": false, "webviewTag": false,
"nodeIntegrationInSubFrames": false, "nodeIntegrationInSubFrames": false
"openerId": "placeholder-opener-id"
}, },
"webContents": "[WebContents]" "webContents": "[WebContents]"
}, },
@ -87,8 +85,7 @@
"contextIsolation": true, "contextIsolation": true,
"nodeIntegration": false, "nodeIntegration": false,
"webviewTag": false, "webviewTag": false,
"nodeIntegrationInSubFrames": false, "nodeIntegrationInSubFrames": false
"openerId": "placeholder-opener-id"
}, },
"x": 100, "x": 100,
"y": 100, "y": 100,
@ -122,8 +119,7 @@
"contextIsolation": true, "contextIsolation": true,
"nodeIntegration": false, "nodeIntegration": false,
"webviewTag": false, "webviewTag": false,
"nodeIntegrationInSubFrames": false, "nodeIntegrationInSubFrames": false
"openerId": "placeholder-opener-id"
}, },
"webContents": "[WebContents]" "webContents": "[WebContents]"
}, },
@ -156,8 +152,7 @@
"contextIsolation": true, "contextIsolation": true,
"nodeIntegration": false, "nodeIntegration": false,
"webviewTag": false, "webviewTag": false,
"nodeIntegrationInSubFrames": false, "nodeIntegrationInSubFrames": false
"openerId": "placeholder-opener-id"
}, },
"webContents": "[WebContents]" "webContents": "[WebContents]"
}, },
@ -168,4 +163,4 @@
}, },
null null
] ]
] ]

View file

@ -15,266 +15,220 @@ function genSnapshot (browserWindow: BrowserWindow, features: string) {
} }
describe('new-window event', () => { describe('new-window event', () => {
const testConfig = { const snapshotFileName = 'native-window-open.snapshot.txt';
native: { const browserWindowOptions = {
snapshotFileName: 'native-window-open.snapshot.txt', show: false,
browserWindowOptions: { width: 200,
show: false, title: 'cool',
width: 200, backgroundColor: 'blue',
title: 'cool', focusable: false,
backgroundColor: 'blue', webPreferences: {
focusable: false, sandbox: true
webPreferences: {
nativeWindowOpen: true,
sandbox: true
}
}
},
proxy: {
snapshotFileName: 'proxy-window-open.snapshot.txt',
browserWindowOptions: {
show: false,
webPreferences: {
nativeWindowOpen: false,
sandbox: false
}
}
} }
}; };
for (const testName of Object.keys(testConfig) as (keyof typeof testConfig)[]) { const snapshotFile = resolve(__dirname, 'fixtures', 'snapshots', snapshotFileName);
const { snapshotFileName, browserWindowOptions } = testConfig[testName]; let browserWindow: BrowserWindow;
let existingSnapshots: any[];
describe(`for ${testName} window opening`, () => { before(() => {
const snapshotFile = resolve(__dirname, 'fixtures', 'snapshots', snapshotFileName); existingSnapshots = parseSnapshots(readFileSync(snapshotFile, { encoding: 'utf8' }));
let browserWindow: BrowserWindow; });
let existingSnapshots: any[];
before(() => { beforeEach((done) => {
existingSnapshots = parseSnapshots(readFileSync(snapshotFile, { encoding: 'utf8' })); browserWindow = new BrowserWindow(browserWindowOptions);
}); browserWindow.loadURL('about:blank');
browserWindow.on('ready-to-show', () => { done(); });
});
beforeEach((done) => { afterEach(closeAllWindows);
browserWindow = new BrowserWindow(browserWindowOptions);
browserWindow.loadURL('about:blank');
browserWindow.on('ready-to-show', () => { done(); });
});
afterEach(closeAllWindows); const newSnapshots: any[] = [];
[
const newSnapshots: any[] = []; 'top=5,left=10,resizable=no',
[ 'zoomFactor=2,resizable=0,x=0,y=10',
'top=5,left=10,resizable=no', 'backgroundColor=gray,webPreferences=0,x=100,y=100',
'zoomFactor=2,resizable=0,x=0,y=10', 'x=50,y=20,title=sup',
'backgroundColor=gray,webPreferences=0,x=100,y=100', 'show=false,top=1,left=1'
'x=50,y=20,title=sup', ].forEach((features, index) => {
'show=false,top=1,left=1' /**
].forEach((features, index) => { * ATTN: If this test is failing, you likely just need to change
/** * `shouldOverwriteSnapshot` to true and then evaluate the snapshot diff
* ATTN: If this test is failing, you likely just need to change * to see if the change is harmless.
* `shouldOverwriteSnapshot` to true and then evaluate the snapshot diff */
* to see if the change is harmless. it(`matches snapshot for ${features}`, async () => {
*/ const newSnapshot = await genSnapshot(browserWindow, features);
it(`matches snapshot for ${features}`, async () => { newSnapshots.push(newSnapshot);
const newSnapshot = await genSnapshot(browserWindow, features); // TODO: The output when these fail could be friendlier.
newSnapshots.push(newSnapshot); expect(stringifySnapshots(newSnapshot)).to.equal(stringifySnapshots(existingSnapshots[index]));
// TODO: The output when these fail could be friendlier.
expect(stringifySnapshots(newSnapshot)).to.equal(stringifySnapshots(existingSnapshots[index]));
});
});
after(() => {
const shouldOverwriteSnapshot = false;
if (shouldOverwriteSnapshot) writeFileSync(snapshotFile, stringifySnapshots(newSnapshots, true));
});
}); });
} });
after(() => {
const shouldOverwriteSnapshot = false;
if (shouldOverwriteSnapshot) writeFileSync(snapshotFile, stringifySnapshots(newSnapshots, true));
});
}); });
describe('webContents.setWindowOpenHandler', () => { describe('webContents.setWindowOpenHandler', () => {
const testConfig = { let browserWindow: BrowserWindow;
native: { beforeEach(async () => {
browserWindowOptions: { browserWindow = new BrowserWindow({ show: false });
show: false, await browserWindow.loadURL('about:blank');
webPreferences: { });
nativeWindowOpen: true
}
}
},
proxy: {
browserWindowOptions: {
show: false,
webPreferences: {
nativeWindowOpen: false
}
}
}
};
for (const testName of Object.keys(testConfig) as (keyof typeof testConfig)[]) { afterEach(closeAllWindows);
let browserWindow: BrowserWindow;
const { browserWindowOptions } = testConfig[testName];
describe(testName, () => { it('does not fire window creation events if an override returns action: deny', async () => {
beforeEach(async () => { const denied = new Promise((resolve) => {
browserWindow = new BrowserWindow(browserWindowOptions); browserWindow.webContents.setWindowOpenHandler(() => {
await browserWindow.loadURL('about:blank'); setTimeout(resolve);
}); return { action: 'deny' };
afterEach(closeAllWindows);
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('new-window', () => {
assert.fail('new-window should not to be called with an overridden window.open');
});
browserWindow.webContents.on('did-create-window', () => {
assert.fail('did-create-window should not to 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('new-window', () => {
assert.fail('new-window should not to be called with an overridden window.open');
});
browserWindow.webContents.on('did-create-window', () => {
assert.fail('did-create-window should not to 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('new-window', () => {
assert.fail('new-window should not to be called with an overridden window.open');
});
browserWindow.webContents.on('did-create-window', () => {
assert.fail('did-create-window should not to 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 Promise.all([
emittedOnce(browserWindow.webContents, 'did-create-window'),
emittedOnce(browserWindow.webContents, 'new-window')
]);
});
it('can change webPreferences of child windows', (done) => {
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { defaultFontSize: 30 } } }));
browserWindow.webContents.on('did-create-window', async (childWindow) => {
await childWindow.webContents.executeJavaScript("document.write('hello')");
const size = await childWindow.webContents.executeJavaScript("getComputedStyle(document.querySelector('body')).fontSize");
expect(size).to.equal('30px');
done();
});
browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
});
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);
}); });
}); });
} browserWindow.webContents.on('new-window', () => {
assert.fail('new-window should not to be called with an overridden window.open');
});
browserWindow.webContents.on('did-create-window', () => {
assert.fail('did-create-window should not to 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('new-window', () => {
assert.fail('new-window should not to be called with an overridden window.open');
});
browserWindow.webContents.on('did-create-window', () => {
assert.fail('did-create-window should not to 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('new-window', () => {
assert.fail('new-window should not to be called with an overridden window.open');
});
browserWindow.webContents.on('did-create-window', () => {
assert.fail('did-create-window should not to 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 Promise.all([
emittedOnce(browserWindow.webContents, 'did-create-window'),
emittedOnce(browserWindow.webContents, 'new-window')
]);
});
it('can change webPreferences of child windows', (done) => {
browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { defaultFontSize: 30 } } }));
browserWindow.webContents.on('did-create-window', async (childWindow) => {
await childWindow.webContents.executeJavaScript("document.write('hello')");
const size = await childWindow.webContents.executeJavaScript("getComputedStyle(document.querySelector('body')).fontSize");
expect(size).to.equal('30px');
done();
});
browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
});
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);
});
}); });
function stringifySnapshots (snapshots: any, pretty = false) { function stringifySnapshots (snapshots: any, pretty = false) {
@ -282,9 +236,6 @@ function stringifySnapshots (snapshots: any, pretty = false) {
if (['sender', 'webContents'].includes(key)) { if (['sender', 'webContents'].includes(key)) {
return '[WebContents]'; return '[WebContents]';
} }
if (key === 'openerId' && typeof value === 'number') {
return 'placeholder-opener-id';
}
if (key === 'processId' && typeof value === 'number') { if (key === 'processId' && typeof value === 'number') {
return 'placeholder-process-id'; return 'placeholder-process-id';
} }
@ -296,8 +247,5 @@ function stringifySnapshots (snapshots: any, pretty = false) {
} }
function parseSnapshots (snapshotsJson: string) { function parseSnapshots (snapshotsJson: string) {
return JSON.parse(snapshotsJson, (key, value) => { return JSON.parse(snapshotsJson);
if (key === 'openerId' && value === 'placeholder-opener-id') return 1;
return value;
});
} }

View file

@ -503,7 +503,7 @@ describe('<webview> tag', function () {
}); });
}); });
describe('nativeWindowOpen option', () => { describe('child windows', () => {
let w: BrowserWindow; let w: BrowserWindow;
beforeEach(async () => { beforeEach(async () => {
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, webviewTag: true, contextIsolation: false } }); w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, webviewTag: true, contextIsolation: false } });
@ -516,7 +516,7 @@ describe('<webview> tag', function () {
loadWebView(w.webContents, { loadWebView(w.webContents, {
allowpopups: 'on', allowpopups: 'on',
nodeintegration: 'on', nodeintegration: 'on',
webpreferences: 'nativeWindowOpen=1,contextIsolation=no', webpreferences: 'contextIsolation=no',
src: `file://${path.join(fixtures, 'api', 'native-window-open-blank.html')}` src: `file://${path.join(fixtures, 'api', 'native-window-open-blank.html')}`
}); });
@ -529,7 +529,7 @@ describe('<webview> tag', function () {
loadWebView(w.webContents, { loadWebView(w.webContents, {
allowpopups: 'on', allowpopups: 'on',
nodeintegration: 'on', nodeintegration: 'on',
webpreferences: 'nativeWindowOpen=1,contextIsolation=no', webpreferences: 'contextIsolation=no',
src: `file://${path.join(fixtures, 'api', 'native-window-open-file.html')}` src: `file://${path.join(fixtures, 'api', 'native-window-open-file.html')}`
}); });
@ -541,7 +541,7 @@ describe('<webview> tag', function () {
// Don't wait for loading to finish. // Don't wait for loading to finish.
loadWebView(w.webContents, { loadWebView(w.webContents, {
nodeintegration: 'on', nodeintegration: 'on',
webpreferences: 'nativeWindowOpen=1,contextIsolation=no', webpreferences: 'contextIsolation=no',
src: `file://${path.join(fixtures, 'api', 'native-window-open-no-allowpopups.html')}` src: `file://${path.join(fixtures, 'api', 'native-window-open-no-allowpopups.html')}`
}); });
@ -554,7 +554,7 @@ describe('<webview> tag', function () {
loadWebView(w.webContents, { loadWebView(w.webContents, {
allowpopups: 'on', allowpopups: 'on',
nodeintegration: 'on', nodeintegration: 'on',
webpreferences: 'nativeWindowOpen=1,contextIsolation=no', webpreferences: 'contextIsolation=no',
src: `file://${path.join(fixtures, 'api', 'native-window-open-cross-origin.html')}` src: `file://${path.join(fixtures, 'api', 'native-window-open-cross-origin.html')}`
}); });
@ -570,7 +570,7 @@ describe('<webview> tag', function () {
const attributes = { const attributes = {
allowpopups: 'on', allowpopups: 'on',
nodeintegration: 'on', nodeintegration: 'on',
webpreferences: 'nativeWindowOpen=1,contextIsolation=no', webpreferences: 'contextIsolation=no',
src: `file://${fixtures}/pages/window-open.html` src: `file://${fixtures}/pages/window-open.html`
}; };
const { url, frameName } = await w.webContents.executeJavaScript(` const { url, frameName } = await w.webContents.executeJavaScript(`
@ -594,7 +594,7 @@ describe('<webview> tag', function () {
// Don't wait for loading to finish. // Don't wait for loading to finish.
loadWebView(w.webContents, { loadWebView(w.webContents, {
allowpopups: 'on', allowpopups: 'on',
webpreferences: 'nativeWindowOpen=1,contextIsolation=no', webpreferences: 'contextIsolation=no',
src: `file://${fixtures}/pages/window-open.html` src: `file://${fixtures}/pages/window-open.html`
}); });
@ -607,7 +607,7 @@ describe('<webview> tag', function () {
loadWebView(w.webContents, { loadWebView(w.webContents, {
allowpopups: 'on', allowpopups: 'on',
webpreferences: 'nativeWindowOpen=1,contextIsolation=no', webpreferences: 'contextIsolation=no',
src: `file://${fixtures}/pages/window-open.html` src: `file://${fixtures}/pages/window-open.html`
}); });
@ -617,7 +617,6 @@ describe('<webview> tag', function () {
it('does not crash when creating window with noopener', async () => { it('does not crash when creating window with noopener', async () => {
loadWebView(w.webContents, { loadWebView(w.webContents, {
allowpopups: 'on', allowpopups: 'on',
webpreferences: 'nativeWindowOpen=1',
src: `file://${path.join(fixtures, 'api', 'native-window-open-noopener.html')}` src: `file://${path.join(fixtures, 'api', 'native-window-open-noopener.html')}`
}); });
await emittedOnce(app, 'browser-window-created'); await emittedOnce(app, 'browser-window-created');

View file

@ -490,7 +490,6 @@ describe('<webview> tag', function () {
generateSpecs('without sandbox'); generateSpecs('without sandbox');
generateSpecs('with sandbox', 'sandbox=yes'); generateSpecs('with sandbox', 'sandbox=yes');
generateSpecs('with nativeWindowOpen', 'nativeWindowOpen=yes');
}); });
describe('webpreferences attribute', () => { describe('webpreferences attribute', () => {

View file

@ -45,7 +45,6 @@ declare namespace NodeJS {
deleteHiddenValue(obj: any, key: string): void; deleteHiddenValue(obj: any, key: string): void;
requestGarbageCollectionForTesting(): void; requestGarbageCollectionForTesting(): void;
runUntilIdle(): void; runUntilIdle(): void;
isSameOrigin(a: string, b: string): boolean;
triggerFatalErrorForTesting(): void; triggerFatalErrorForTesting(): void;
} }
@ -108,9 +107,7 @@ declare namespace NodeJS {
interface InternalWebPreferences { interface InternalWebPreferences {
isWebView: boolean; isWebView: boolean;
hiddenPage: boolean; hiddenPage: boolean;
nativeWindowOpen: boolean;
nodeIntegration: boolean; nodeIntegration: boolean;
openerId: number;
preload: string preload: string
preloadScripts: string[]; preloadScripts: string[];
webviewTag: boolean; webviewTag: boolean;

View file

@ -96,7 +96,6 @@ declare namespace Electron {
} }
interface WebPreferences { interface WebPreferences {
openerId?: number | null;
disablePopups?: boolean; disablePopups?: boolean;
preloadURL?: string; preloadURL?: string;
embedder?: Electron.WebContents; embedder?: Electron.WebContents;