From 0b85fdf26c396112afac4bbb26d89a9f3033854b Mon Sep 17 00:00:00 2001 From: loc Date: Tue, 10 Nov 2020 09:06:03 -0800 Subject: [PATCH] feat: add webContents.setWindowOpenHandler API (#24517) Co-authored-by: Jeremy Rose --- .eslintrc.json | 2 +- docs/api/web-contents.md | 53 +- docs/api/window-open.md | 150 +++-- docs/tutorial/security.md | 25 +- filenames.auto.gni | 1 + lib/browser/api/web-contents.ts | 119 +++- lib/browser/guest-window-manager.ts | 576 ++++++++---------- lib/browser/guest-window-proxy.ts | 211 +++++++ lib/browser/init.ts | 2 +- lib/browser/navigation-controller.ts | 2 + lib/common/parse-features-string.ts | 4 +- lib/renderer/api/context-bridge.ts | 4 +- lib/renderer/init.ts | 32 +- lib/sandboxed_renderer/init.ts | 7 +- package.json | 4 +- patches/chromium/.patches | 2 + ..._windows_to_have_different_web_prefs.patch | 279 +++++++++ patches/chromium/can_create_window.patch | 27 +- ...screationoverridden_with_full_params.patch | 433 +++++++++++++ ...ns_through_the_web_contents_delegate.patch | 2 +- ...r_changes_to_the_webcontentsobserver.patch | 4 +- patches/chromium/web_contents.patch | 2 +- shell/browser/api/electron_api_session.cc | 5 +- shell/browser/api/electron_api_session.h | 4 +- .../browser/api/electron_api_web_contents.cc | 52 +- shell/browser/api/electron_api_web_contents.h | 9 +- shell/browser/electron_browser_client.cc | 28 +- shell/browser/event_emitter_mixin.h | 1 + shell/browser/session_preferences.cc | 6 +- shell/browser/session_preferences.h | 10 +- shell/browser/web_contents_preferences.cc | 201 +++--- shell/browser/web_contents_preferences.h | 2 +- shell/browser/web_view_guest_delegate.cc | 4 + shell/common/options_switches.cc | 42 +- shell/common/options_switches.h | 30 +- shell/renderer/api/electron_api_web_frame.cc | 74 ++- shell/renderer/content_settings_observer.cc | 7 +- .../electron_render_frame_observer.cc | 23 +- shell/renderer/electron_renderer_client.cc | 26 +- .../electron_sandboxed_renderer_client.cc | 7 +- shell/renderer/renderer_client_base.cc | 27 +- shell/renderer/renderer_client_base.h | 2 - spec-main/api-browser-view-spec.ts | 14 +- spec-main/api-browser-window-affinity-spec.ts | 8 +- spec-main/api-browser-window-spec.ts | 118 ++-- spec-main/chromium-spec.ts | 68 +-- .../snapshots/native-window-open.snapshot.txt | 82 +-- .../snapshots/proxy-window-open.snapshot.txt | 43 +- spec-main/guest-window-manager-spec.ts | 101 ++- spec/fixtures/api/new-window-preload.js | 7 +- spec/fixtures/api/window-open-preload.js | 10 +- spec/fixtures/pages/visibilitychange.html | 4 +- spec/fixtures/test.asar/deleteme/a.asar | Bin 778 -> 0 bytes typings/internal-ambient.d.ts | 5 +- typings/internal-electron.d.ts | 3 + yarn.lock | 8 +- 56 files changed, 2087 insertions(+), 885 deletions(-) create mode 100644 lib/browser/guest-window-proxy.ts create mode 100644 patches/chromium/allow_in_process_windows_to_have_different_web_prefs.patch create mode 100644 patches/chromium/chore_provide_iswebcontentscreationoverridden_with_full_params.patch delete mode 100644 spec/fixtures/test.asar/deleteme/a.asar diff --git a/.eslintrc.json b/.eslintrc.json index 637325a8016..55d0f171213 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -14,7 +14,7 @@ "@typescript-eslint/no-unused-vars": ["error", { "vars": "all", "args": "after-used", - "ignoreRestSiblings": false + "ignoreRestSiblings": true }], "prefer-const": ["error", { "destructuring": "all" diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 89f3555673c..3055f9be97f 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -134,7 +134,7 @@ Returns: Emitted when page receives favicon urls. -#### Event: 'new-window' +#### Event: 'new-window' _Deprecated_ Returns: @@ -155,6 +155,8 @@ Returns: be set. If no post data is to be sent, the value will be `null`. Only defined when the window is being created by a form that set `target=_blank`. +Deprecated in favor of [`webContents.setWindowOpenHandler`](web-contents.md#contentssetwindowopenhandler-handler). + Emitted when the page requests to open a new window for a `url`. It could be requested by `window.open` or an external link like ``. @@ -189,6 +191,39 @@ myBrowserWindow.webContents.on('new-window', (event, url, frameName, disposition }) ``` +#### Event: 'did-create-window' + +Returns: +* `window` BrowserWindow +* `details` Object + * `url` String - URL for the created window. + * `frameName` String - Name given to the created window in the + `window.open()` call. + * `options` BrowserWindowConstructorOptions - The options used to create the + BrowserWindow. They are merged in increasing precedence: options inherited + from the parent, parsed options from the `features` string from + `window.open()`, and options given by + [`webContents.setWindowOpenHandler`](web-contents.md#contentssetwindowopenhandler-handler). + Unrecognized options are not filtered out. + * `additionalFeatures` String[] - The non-standard features (features not + handled Chromium or Electron) _Deprecated_ + * `referrer` [Referrer](structures/referrer.md) - The referrer that will be + passed to the new window. May or may not result in the `Referer` header + being sent, depending on the referrer policy. + * `postBody` [PostBody](structures/post-body.md) (optional) - The post data + that will be sent to the new window, along with the appropriate headers + that will be set. If no post data is to be sent, the value will be `null`. + Only defined when the window is being created by a form that set + `target=_blank`. + * `disposition` String - Can be `default`, `foreground-tab`, + `background-tab`, `new-window`, `save-to-disk` and `other`. + +Emitted _after_ successful creation of a window via `window.open` in the renderer. +Not emitted if the creation of the window is canceled from +[`webContents.setWindowOpenHandler`](web-contents.md#contentssetwindowopenhandler-handler). + +See [`window.open()`](window-open.md) for more details and how to use this in conjunction with `webContents.setWindowOpenHandler`. + #### Event: 'will-navigate' Returns: @@ -1123,6 +1158,22 @@ Works like `executeJavaScript` but evaluates `scripts` in an isolated context. Ignore application menu shortcuts while this web contents is focused. +#### `contents.setWindowOpenHandler(handler)` + +* `handler` Function<{action: 'deny'} | {action: 'allow', overrideBrowserWindowOptions?: BrowserWindowConstructorOptions}> + * `details` Object + * `url` String - The _resolved_ version of the URL passed to `window.open()`. e.g. opening a window with `window.open('foo')` will yield something like `https://the-origin/the/current/path/foo`. + * `frameName` String - Name of the window provided in `window.open()` + * `features` String - Comma separated list of window features provided to `window.open()`. + Returns `{action: 'deny'} | {action: 'allow', overrideBrowserWindowOptions?: BrowserWindowConstructorOptions}` - `deny` cancels the creation of the new + window. `allow` will allow the new window to be created. Specifying `overrideBrowserWindowOptions` allows customization of the created window. + Returning an unrecognized value such as a null, undefined, or an object + without a recognized 'action' value will result in a console error and have + the same effect as returning `{action: 'deny'}`. + +Called before creating a window when `window.open()` is called from the +renderer. See [`window.open()`](window-open.md) for more details and how to use this in conjunction with `did-create-window`. + #### `contents.setAudioMuted(muted)` * `muted` Boolean diff --git a/docs/api/window-open.md b/docs/api/window-open.md index 35bd0cffd2d..a5c63591190 100644 --- a/docs/api/window-open.md +++ b/docs/api/window-open.md @@ -1,18 +1,34 @@ -# `window.open` Function +# Opening windows from the renderer -> Open a new window and load a URL. +There are several ways to control how windows are created from trusted or +untrusted content within a renderer. Windows can be created from the renderer in two ways: -When `window.open` is called to create a new window in a web page, a new instance -of [`BrowserWindow`](browser-window.md) will be created for the `url` and a proxy will be returned -to `window.open` to let the page have limited control over it. +- clicking on links or submitting forms adorned with `target=_blank` +- JavaScript calling `window.open()` -The proxy has limited standard functionality implemented to be -compatible with traditional web pages. For full control of the new window -you should create a `BrowserWindow` directly. +In non-sandboxed renderers, or when `nativeWindowOpen` is false (the default), this results in the creation of a +[`BrowserWindowProxy`](browser-window-proxy.md), a light wrapper around +`BrowserWindow`. -The newly created `BrowserWindow` will inherit the parent window's options by -default. To override inherited options you can set them in the `features` -string. +However, when the `sandbox` (or directly, `nativeWindowOpen`) option is set, a +`Window` instance is created, as you'd expect in the browser. For same-origin +content, the new window is created within the same process, enabling the parent +to access the child window directly. This can be very 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. + +Electron pairs this native Chrome `Window` with a BrowserWindow under the hood. +You can take advantage of all the customization available when creating a +BrowserWindow in the main process by using `webContents.setWindowOpenHandler()` +for renderer-created windows. + +BrowserWindow constructor options are set by, in increasing precedence +order: options inherited from the parent, parsed options +from the `features` string from `window.open()`, security-related webPreferences +inherited from the parent, and options given by +[`webContents.setWindowOpenHandler`](web-contents.md#contentssetwindowopenhandler-handler). +Note that `webContents.setWindowOpenHandler` has final say and full privilege +because it is invoked in the main process. ### `window.open(url[, frameName][, features])` @@ -20,16 +36,22 @@ string. * `frameName` String (optional) * `features` String (optional) -Returns [`BrowserWindowProxy`](browser-window-proxy.md) - Creates a new window -and returns an instance of `BrowserWindowProxy` class. +Returns [`BrowserWindowProxy`](browser-window-proxy.md) | [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window) -The `features` string follows the format of standard browser, but each feature -has to be a field of `BrowserWindow`'s options. These are the features you can set via `features` string: `zoomFactor`, `nodeIntegration`, `preload`, `javascript`, `contextIsolation`, `webviewTag`. +`features` is a comma-separated key-value list, following the standard format of +the browser. Electron will parse `BrowserWindowConstructorOptions` out of this +list where possible, for convenience. For full control and better ergonomics, +consider using `webContents.setWindowOpenHandler` to customize the +BrowserWindow creation. + +A subset of `WebPreferences` can be set directly, +unnested, from the features string: `zoomFactor`, `nodeIntegration`, `preload`, +`javascript`, `contextIsolation`, and `webviewTag`. For example: ```js -window.open('https://github.com', '_blank', 'nodeIntegration=no') +window.open('https://github.com', '_blank', 'top=500,left=200,frame=false,nodeIntegration=no') ``` **Notes:** @@ -41,60 +63,74 @@ window.open('https://github.com', '_blank', 'nodeIntegration=no') * JavaScript will always be disabled in the opened `window` if it is disabled on the parent window. * Non-standard features (that are not handled by Chromium or Electron) given in - `features` will be passed to any registered `webContent`'s `new-window` event - handler in the `additionalFeatures` argument. + `features` will be passed to any registered `webContents`'s + `did-create-window` event handler in the `additionalFeatures` argument. -### `window.opener.postMessage(message, targetOrigin)` +To customize or cancel the creation of the window, you can optionally set an +override handler with `webContents.setWindowOpenHandler()` from the main +process. Returning `false` cancels the window, while returning an object sets +the `BrowserWindowConstructorOptions` used when creating the window. Note that +this is more powerful than passing options through the feature string, as the +renderer has more limited privileges in deciding security preferences than the +main process. -* `message` String -* `targetOrigin` String - -Sends a message to the parent window with the specified origin or `*` for no -origin preference. - -### Using Chrome's `window.open()` implementation - -If you want to use Chrome's built-in `window.open()` implementation, set -`nativeWindowOpen` to `true` in the `webPreferences` options object. - -Native `window.open()` allows synchronous access to opened windows so it is -convenient choice if you need to open a dialog or a preferences window. - -This option can also be set on `` tags as well: - -```html - -``` - -The creation of the `BrowserWindow` is customizable via `WebContents`'s -`new-window` event. +### `BrowserWindowProxy` example ```javascript -// main process + +// main.js +const mainWindow = new BrowserWindow() + +mainWindow.webContents.setWindowOpenHandler(({ url }) => { + if (url.startsWith('https://github.com/')) { + return true + } + return false +}) + +mainWindow.webContents.on('did-create-window', (childWindow) => { + // For example... + childWindow.webContents('will-navigate', (e) => { + e.preventDefault() + }) +}) +``` + +```javascript +// renderer.js +const windowProxy = window.open('https://github.com/', null, 'minimizable=false') +windowProxy.postMessage('hi', '*') +``` + +### Native `Window` example + +```javascript +// main.js const mainWindow = new BrowserWindow({ - width: 800, - height: 600, webPreferences: { nativeWindowOpen: true } }) -mainWindow.webContents.on('new-window', (event, url, frameName, disposition, options, additionalFeatures) => { - if (frameName === 'modal') { - // open window as modal - event.preventDefault() - Object.assign(options, { - modal: true, - parent: mainWindow, - width: 100, - height: 100 - }) - event.newGuest = new BrowserWindow(options) + +// In this example, only windows with the `about:blank` url will be created. +// All other urls will be blocked. +mainWindow.webContents.setWindowOpenHandler(({ url }) => { + if (url === 'about:blank') { + return { + frame: false, + fullscreenable: false, + backgroundColor: 'black', + webPreferences: { + preload: 'my-child-window-preload-script.js' + } + } } + return false }) ``` ```javascript // renderer process (mainWindow) -const modal = window.open('', 'modal') -modal.document.write('

Hello

') +const childWindow = window.open('', 'modal') +childWindow.document.write('

Hello

') ``` diff --git a/docs/tutorial/security.md b/docs/tutorial/security.md index b67ce2e266f..c3c95d640d8 100644 --- a/docs/tutorial/security.md +++ b/docs/tutorial/security.md @@ -611,22 +611,29 @@ windows at runtime. ### How? -[`webContents`][web-contents] will emit the [`new-window`][new-window] event -before creating new windows. That event will be passed, amongst other -parameters, the `url` the window was requested to open and the options used to -create it. We recommend that you use the event to scrutinize the creation of -windows, limiting it to only what you need. +[`webContents`][web-contents] will delegate to its [window open +handler][window-open-handler] before creating new windows. The handler will +receive, amongst other parameters, the `url` the window was requested to open +and the options used to create it. We recommend that you register a handler to +monitor the creation of windows, and deny any unexpected window creation. ```js const { shell } = require('electron') app.on('web-contents-created', (event, contents) => { - contents.on('new-window', async (event, navigationUrl) => { + contents.setWindowOpenHandler(({ url }) => { // In this example, we'll ask the operating system // to open this event's url in the default browser. - event.preventDefault() + // + // See the following item for considerations regarding what + // URLs should be allowed through to shell.openExternal. + if (isSafeForExternalOpen(url)) { + setImmediate(() => { + shell.openExternal(url) + }) + } - await shell.openExternal(navigationUrl) + return { action: 'deny' } }) }) ``` @@ -811,7 +818,7 @@ which potential security issues are not as widely known. [browser-view]: ../api/browser-view.md [webview-tag]: ../api/webview-tag.md [web-contents]: ../api/web-contents.md -[new-window]: ../api/web-contents.md#event-new-window +[window-open-handler]: ../api/web-contents.md#contentssetwindowopenhandler-handler [will-navigate]: ../api/web-contents.md#event-will-navigate [open-external]: ../api/shell.md#shellopenexternalurl-options [sandbox]: ../api/sandbox-option.md diff --git a/filenames.auto.gni b/filenames.auto.gni index b8789b10055..68825e65ef5 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -237,6 +237,7 @@ auto_filenames = { "lib/browser/devtools.ts", "lib/browser/guest-view-manager.ts", "lib/browser/guest-window-manager.ts", + "lib/browser/guest-window-proxy.ts", "lib/browser/init.ts", "lib/browser/ipc-main-impl.ts", "lib/browser/ipc-main-internal-utils.ts", diff --git a/lib/browser/api/web-contents.ts b/lib/browser/api/web-contents.ts index 4b4f8b16097..0779c06ef64 100644 --- a/lib/browser/api/web-contents.ts +++ b/lib/browser/api/web-contents.ts @@ -1,13 +1,12 @@ -import { app, ipcMain, session, deprecate } from 'electron/main'; -import type { MenuItem, MenuItemConstructorOptions } from 'electron/main'; +import { app, ipcMain, session, deprecate, BrowserWindowConstructorOptions } from 'electron/main'; +import type { MenuItem, MenuItemConstructorOptions, LoadURLOptions } from 'electron/main'; import * as url from 'url'; import * as path from 'path'; -import { internalWindowOpen } from '@electron/internal/browser/guest-window-manager'; +import { openGuestWindow, makeWebPreferences } from '@electron/internal/browser/guest-window-manager'; import { NavigationController } from '@electron/internal/browser/navigation-controller'; import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'; import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils'; -import { parseFeatures } from '@electron/internal/common/parse-features-string'; import { MessagePortMain } from '@electron/internal/browser/message-port-main'; import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages'; @@ -21,6 +20,8 @@ const getNextId = function () { return ++nextId; }; +type PostData = LoadURLOptions['postData'] + /* eslint-disable camelcase */ type MediaSize = { name: string, @@ -439,6 +440,40 @@ WebContents.prototype.loadFile = function (filePath, options = {}) { })); }; +WebContents.prototype.setWindowOpenHandler = function (handler: (details: Electron.HandlerDetails) => ({action: 'allow'} | {action: 'deny', overrideBrowserWindowOptions?: BrowserWindowConstructorOptions})) { + this._windowOpenHandler = handler; +}; + +WebContents.prototype._callWindowOpenHandler = function (event: any, url: string, frameName: string, rawFeatures: string): BrowserWindowConstructorOptions | null { + if (!this._windowOpenHandler) { + return null; + } + const response = this._windowOpenHandler({ url, frameName, features: rawFeatures }); + + if (typeof response !== 'object') { + event.preventDefault(); + console.error(`The window open handler response must be an object, but was instead of type '${typeof response}'.`); + return null; + } + + if (response === null) { + event.preventDefault(); + console.error('The window open handler response must be an object, but was instead null.'); + return null; + } + + if (response.action === 'deny') { + event.preventDefault(); + return null; + } else if (response.action === 'allow') { + if (typeof response.overrideBrowserWindowOptions === 'object' && response.overrideBrowserWindowOptions !== null) { return response.overrideBrowserWindowOptions; } else { return {}; } + } else { + event.preventDefault(); + console.error('The window open handler response must be an object with an \'action\' property of \'allow\' or \'deny\'.'); + return null; + } +}; + const addReplyToEvent = (event: any) => { event.reply = (...args: any[]) => { event.sender.sendToFrame(event.frameId, ...args); @@ -490,6 +525,8 @@ WebContents.prototype._init = function () { this.getActiveIndex = navigationController.getActiveIndex.bind(navigationController); this.length = navigationController.length.bind(navigationController); + this._windowOpenHandler = null; + // Every remote callback from renderer process would add a listener to the // render-view-deleted event, so ignore the listeners warning. this.setMaxListeners(0); @@ -570,43 +607,67 @@ WebContents.prototype._init = function () { if (this.getType() !== 'remote') { // Make new windows requested by links behave like "window.open". this.on('-new-window' as any, (event: any, url: string, frameName: string, disposition: string, - rawFeatures: string, referrer: string, postData: Electron.UploadRawData[]) => { - const { options, webPreferences, additionalFeatures } = parseFeatures(rawFeatures); - const mergedOptions = { - show: true, - width: 800, - height: 600, - title: frameName, - webPreferences, - ...options - }; + rawFeatures: string, referrer: any, postData: PostData) => { + openGuestWindow({ + event, + embedder: event.sender, + disposition, + referrer, + postData, + overrideBrowserWindowOptions: {}, + windowOpenArgs: { + url, + frameName, + features: rawFeatures + } + }); + }); - internalWindowOpen(event, url, referrer, frameName, disposition, mergedOptions, additionalFeatures, postData); + let windowOpenOverriddenOptions: BrowserWindowConstructorOptions | null = null; + this.on('-will-add-new-contents' as any, (event: any, url: string, frameName: string, rawFeatures: string) => { + windowOpenOverriddenOptions = this._callWindowOpenHandler(event, url, frameName, rawFeatures); + if (!event.defaultPrevented) { + const secureOverrideWebPreferences = windowOpenOverriddenOptions ? { + // Allow setting of backgroundColor as a webPreference even though + // it's technically a BrowserWindowConstructorOptions option because + // we need to access it in the renderer at init time. + backgroundColor: windowOpenOverriddenOptions.backgroundColor, + ...windowOpenOverriddenOptions.webPreferences + } : undefined; + this._setNextChildWebPreferences( + makeWebPreferences({ embedder: event.sender, secureOverrideWebPreferences }) + ); + } }); // Create a new browser window for the native implementation of // "window.open", used in sandbox and nativeWindowOpen mode. this.on('-add-new-contents' as any, (event: any, webContents: Electron.WebContents, disposition: string, - userGesture: boolean, left: number, top: number, width: number, height: number, url: string, frameName: string, - referrer: string, rawFeatures: string, postData: Electron.UploadRawData[]) => { + _userGesture: boolean, _left: number, _top: number, _width: number, _height: number, url: string, frameName: string, + referrer: Electron.Referrer, rawFeatures: string, postData: PostData) => { + const overriddenOptions = windowOpenOverriddenOptions || undefined; + windowOpenOverriddenOptions = null; + if ((disposition !== 'foreground-tab' && disposition !== 'new-window' && disposition !== 'background-tab')) { event.preventDefault(); return; } - const { options, webPreferences, additionalFeatures } = parseFeatures(rawFeatures); - const mergedOptions = { - show: true, - width: 800, - height: 600, - webContents, - title: frameName, - webPreferences, - ...options - }; - - internalWindowOpen(event, url, referrer, frameName, disposition, mergedOptions, additionalFeatures, postData); + openGuestWindow({ + event, + embedder: event.sender, + guest: webContents, + overrideBrowserWindowOptions: overriddenOptions, + disposition, + referrer, + postData, + windowOpenArgs: { + url, + frameName, + features: rawFeatures + } + }); }); const prefs = this.getWebPreferences() || {}; diff --git a/lib/browser/guest-window-manager.ts b/lib/browser/guest-window-manager.ts index e8da636eda3..544da996298 100644 --- a/lib/browser/guest-window-manager.ts +++ b/lib/browser/guest-window-manager.ts @@ -1,360 +1,300 @@ -import * as electron from 'electron/main'; -import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'; -import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils'; +/** + * Create and minimally track guest windows at the direction of the renderer + * (via window.open). Here, "guest" roughly means "child" — it's not necessarily + * emblematic of its process status; both in-process (same-origin + * nativeWindowOpen) and out-of-process (cross-origin nativeWindowOpen and + * BrowserWindowProxy) are created here. "Embedder" roughly means "parent." + */ +import { BrowserWindow } from 'electron/main'; +import type { BrowserWindowConstructorOptions, Referrer, WebContents, LoadURLOptions } from 'electron/main'; import { parseFeatures } from '@electron/internal/common/parse-features-string'; import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages'; -const { isSameOrigin } = process._linkedBinding('electron_common_v8_util'); - -const { BrowserWindow } = electron; -const hasProp = {}.hasOwnProperty; -const frameToGuest = new Map(); - -// Security options that child windows will always inherit from parent windows -const inheritedWebPreferences = new Map([ - ['contextIsolation', true], - ['javascript', false], - ['nativeWindowOpen', true], - ['nodeIntegration', false], - ['enableRemoteModule', false], - ['sandbox', true], - ['webviewTag', false], - ['nodeIntegrationInSubFrames', false], - ['enableWebSQL', false] -]); - -// Copy attribute of |parent| to |child| if it is not defined in |child|. -const mergeOptions = function (child: Record, parent: Record, visited?: Set>) { - // Check for circular reference. - if (visited == null) visited = new Set(); - if (visited.has(parent)) return; - - visited.add(parent); - for (const key in parent) { - if (key === 'type') continue; - if (!hasProp.call(parent, key)) continue; - if (key in child && key !== 'webPreferences') continue; - - const value = parent[key]; - if (typeof value === 'object' && !Array.isArray(value)) { - child[key] = mergeOptions(child[key] || {}, value, visited); - } else { - child[key] = value; - } - } - visited.delete(parent); - - return child; -}; - -// Merge |options| with the |embedder|'s window's options. -const mergeBrowserWindowOptions = function (embedder: Electron.WebContents, options: Record) { - if (options.webPreferences == null) { - options.webPreferences = {}; - } - if (embedder.browserWindowOptions != null) { - let parentOptions = embedder.browserWindowOptions; - - // if parent's visibility is available, that overrides 'show' flag (#12125) - const win = BrowserWindow.fromWebContents(embedder); - if (win != null) { - parentOptions = { - ...win.getBounds(), - ...embedder.browserWindowOptions, - show: win.isVisible() - }; - } - - // Inherit the original options if it is a BrowserWindow. - mergeOptions(options, parentOptions); - } else { - // Or only inherit webPreferences if it is a webview. - mergeOptions(options.webPreferences, embedder.getLastWebPreferences()); - } - - // Inherit certain option values from parent window - const webPreferences = embedder.getLastWebPreferences(); - for (const [name, value] of inheritedWebPreferences) { - if ((webPreferences as any)[name] === value) { - options.webPreferences[name] = value; - } - } - - if (!webPreferences.nativeWindowOpen) { - // Sets correct openerId here to give correct options to 'new-window' event handler - options.webPreferences.openerId = embedder.id; - } - - return options; -}; - -const MULTIPART_CONTENT_TYPE = 'multipart/form-data'; -const URL_ENCODED_CONTENT_TYPE = 'application/x-www-form-urlencoded'; -function makeContentTypeHeader ({ contentType, boundary }: { contentType: string, boundary?: string }) { - const header = `content-type: ${contentType};`; - if (contentType === MULTIPART_CONTENT_TYPE) { - return `${header} boundary=${boundary}`; - } - return header; +type PostData = LoadURLOptions['postData'] +export type WindowOpenArgs = { + url: string, + frameName: string, + features: string, } -// Figure out appropriate headers for post data. -const parseContentTypeFormat = function (postData: Electron.UploadRawData[]) { - if (postData.length) { - // For multipart forms, the first element will start with the boundary - // notice, which looks something like `------WebKitFormBoundary12345678` - // Note, this regex would fail when submitting a urlencoded form with an - // input attribute of name="--theKey", but, uhh, don't do that? - const postDataFront = postData[0].bytes.toString(); - const boundary = /^--.*[^-\r\n]/.exec(postDataFront); - if (boundary) { - return { - boundary: boundary[0].substr(2), - contentType: MULTIPART_CONTENT_TYPE - }; - } - } - // Either the form submission didn't contain any inputs (the postData array - // was empty), or we couldn't find the boundary and thus we can assume this is - // a key=value style form. - return { - contentType: URL_ENCODED_CONTENT_TYPE - }; -}; +const frameNamesToWindow = new Map(); +const registerFrameNameToGuestWindow = (name: string, win: BrowserWindow) => frameNamesToWindow.set(name, win); +const unregisterFrameName = (name: string) => frameNamesToWindow.delete(name); +const getGuestWindowByFrameName = (name: string) => frameNamesToWindow.get(name); -// Setup a new guest with |embedder| -const setupGuest = function (embedder: Electron.WebContents, frameName: string, guest: Electron.BrowserWindow) { - // When |embedder| is destroyed we should also destroy attached guest, and if - // guest is closed by user then we should prevent |embedder| from double - // closing guest. - const guestId = guest.webContents.id; +/** + * `openGuestWindow` is called for both implementations of window.open + * (BrowserWindowProxy and nativeWindowOpen) to create and setup event handling + * for the new window. + * + * Until its removal in 12.0.0, the `new-window` event is fired, allowing the + * user to preventDefault() on the passed event (which ends up calling + * DestroyWebContents in the nativeWindowOpen code path). + */ +export function openGuestWindow ({ event, embedder, guest, referrer, disposition, postData, overrideBrowserWindowOptions, windowOpenArgs }: { + event: { sender: WebContents, defaultPrevented: boolean }, + embedder: WebContents, + guest?: WebContents, + referrer: Referrer, + disposition: string, + postData?: PostData, + overrideBrowserWindowOptions?: BrowserWindowConstructorOptions, + windowOpenArgs: WindowOpenArgs, +}): BrowserWindow | undefined { + const { url, frameName, features } = windowOpenArgs; + const isNativeWindowOpen = !!guest; + const { options: browserWindowOptions, additionalFeatures } = makeBrowserWindowOptions({ + embedder, + features, + frameName, + overrideOptions: overrideBrowserWindowOptions + }); + + const didCancelEvent = emitDeprecatedNewWindowEvent({ + event, + embedder, + guest, + browserWindowOptions, + windowOpenArgs, + additionalFeatures, + disposition, + referrer + }); + if (didCancelEvent) return; + + // To spec, subsequent window.open calls with the same frame name (`target` in + // spec parlance) will reuse the previous window. + // https://html.spec.whatwg.org/multipage/window-object.html#apis-for-creating-and-navigating-browsing-contexts-by-name + const existingWindow = getGuestWindowByFrameName(frameName); + if (existingWindow) { + existingWindow.loadURL(url); + return existingWindow; + } + + const window = new BrowserWindow({ + webContents: guest, + ...browserWindowOptions + }); + if (!isNativeWindowOpen) { + // 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). + window.loadURL(url, { + httpReferrer: referrer, + ...(postData && { + postData, + extraHeaders: formatPostDataHeaders(postData) + }) + }); + } + + handleWindowLifecycleEvents({ embedder, frameName, guest: window }); + + embedder.emit('did-create-window', window, { url, frameName, options: browserWindowOptions, disposition, additionalFeatures, referrer, postData }); + + return window; +} + +/** + * Manage the relationship between embedder window and guest window. When the + * guest is destroyed, notify the embedder. When the embedder is destroyed, so + * too is the guest destroyed; this is Electron convention and isn't based in + * browser behavior. + */ +const handleWindowLifecycleEvents = function ({ embedder, guest, frameName }: { + embedder: WebContents, + guest: BrowserWindow, + frameName: string +}) { const closedByEmbedder = function () { guest.removeListener('closed', closedByUser); guest.destroy(); }; + + const cachedGuestId = guest.webContents.id; const closedByUser = function () { - embedder._sendInternal(`${IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_CLOSED}_${guestId}`); + embedder._sendInternal(`${IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_CLOSED}_${cachedGuestId}`); embedder.removeListener('current-render-view-deleted' as any, closedByEmbedder); }; embedder.once('current-render-view-deleted' as any, closedByEmbedder); guest.once('closed', closedByUser); + if (frameName) { - frameToGuest.set(frameName, guest); - guest.frameName = frameName; + registerFrameNameToGuestWindow(frameName, guest); guest.once('closed', function () { - frameToGuest.delete(frameName); + unregisterFrameName(frameName); }); } - return guestId; }; -// Create a new guest created by |embedder| with |options|. -const createGuest = function (embedder: Electron.webContents, url: string, referrer: string | Electron.Referrer, - frameName: string, options: Record, postData?: Electron.UploadRawData[]) { - let guest = frameToGuest.get(frameName); - if (frameName && (guest != null)) { - guest.loadURL(url); - return guest.webContents.id; - } - - // Remember the embedder window's id. - if (options.webPreferences == null) { - options.webPreferences = {}; - } - - guest = new BrowserWindow(options); - if (!options.webContents) { - // We should not call `loadURL` if the window was constructed from an - // existing webContents (window.open in a sandboxed renderer). - // - // Navigating to the url when creating the window from an existing - // webContents is not necessary (it will navigate there anyway). - const loadOptions: Electron.LoadURLOptions = { - httpReferrer: referrer - }; - if (postData != null) { - loadOptions.postData = postData; - loadOptions.extraHeaders = makeContentTypeHeader(parseContentTypeFormat(postData)); - } - guest.loadURL(url, loadOptions); - } - - return setupGuest(embedder, frameName, guest); -}; - -const getGuestWindow = function (guestContents: Electron.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: Electron.WebContents, target: Electron.WebContents) { - return target.getLastWebPreferences().openerId === sender.id; -}; - -const isRelatedWindow = function (sender: Electron.WebContents, target: Electron.WebContents) { - return isChildWindow(sender, target) || isChildWindow(target, sender); -}; - -const isScriptableWindow = function (sender: Electron.WebContents, target: Electron.WebContents) { - return isRelatedWindow(sender, target) && isSameOrigin(sender.getURL(), target.getURL()); -}; - -const isNodeIntegrationEnabled = function (sender: Electron.WebContents) { - return sender.getLastWebPreferences().nodeIntegration === true; -}; - -// Checks whether |sender| can access the |target|: -const canAccessWindow = function (sender: Electron.WebContents, target: Electron.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(`${IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_OPEN} denied: expected native window.open`); - } - if (url == null || url === '') url = 'about:blank'; - if (frameName == null) frameName = ''; - if (features == null) features = ''; - - const disposition = 'new-window'; - const { options, webPreferences, additionalFeatures } = parseFeatures(features); - if (!options.title) options.title = frameName; - (options as Electron.BrowserWindowConstructorOptions).webPreferences = webPreferences; - - const referrer: Electron.Referrer = { url: '', policy: 'default' }; - internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures); -}); - -// Routed window.open messages with fully parsed options -export function internalWindowOpen (event: ElectronInternal.IpcMainInternalEvent, url: string, referrer: string | Electron.Referrer, - frameName: string, disposition: string, options: Record, additionalFeatures: string[], postData?: Electron.UploadRawData[]) { - options = mergeBrowserWindowOptions(event.sender, options); +/** + * Deprecated in favor of `webContents.setWindowOpenHandler` and + * `did-create-window` in 11.0.0. Will be removed in 12.0.0. + */ +function emitDeprecatedNewWindowEvent ({ event, embedder, guest, windowOpenArgs, browserWindowOptions, additionalFeatures, disposition, referrer, postData }: { + event: { sender: WebContents, defaultPrevented: boolean }, + embedder: WebContents, + guest?: WebContents, + windowOpenArgs: WindowOpenArgs, + browserWindowOptions: BrowserWindowConstructorOptions, + additionalFeatures: string[] + disposition: string, + referrer: Referrer, + postData?: PostData, +}): boolean { + const { url, frameName } = windowOpenArgs; + const isWebViewWithPopupsDisabled = embedder.getType() === 'webview' && (embedder as any).getLastWebPreferences().disablePopups; const postBody = postData ? { data: postData, - ...parseContentTypeFormat(postData) + headers: formatPostDataHeaders(postData) } : null; - event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer, postBody); - const { newGuest } = event as unknown as { newGuest: Electron.BrowserWindow }; - if ((event.sender.getType() === 'webview' && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) { - if (newGuest != null) { - if (options.webContents === newGuest.webContents) { - // the webContents is not changed, so set defaultPrevented to false to + embedder.emit( + 'new-window', + event, + url, + frameName, + disposition, + { + ...browserWindowOptions, + webContents: guest + }, + additionalFeatures, + referrer, + postBody + ); + + const { newGuest } = event as any; + if (isWebViewWithPopupsDisabled) return true; + if (event.defaultPrevented) { + if (newGuest) { + if (guest === newGuest.webContents) { + // The webContents is not changed, so set defaultPrevented to false to // stop the callers of this event from destroying the webContents. (event as any).defaultPrevented = false; } - event.returnValue = setupGuest(event.sender, frameName, newGuest); - } else { - event.returnValue = null; + + handleWindowLifecycleEvents({ + embedder: event.sender, + guest: newGuest, + frameName + }); } - } else { - event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData); + return true; } + return false; } -const makeSafeHandler = function (handler: (event: Event, guestContents: Electron.webContents, ...args: any[]) => any) { - return (event: Event, guestId: number, ...args: any[]) => { - // Access webContents via electron to prevent circular require. - const guestContents = electron.webContents.fromId(guestId); - if (!guestContents) { - throw new Error(`Invalid guestId: ${guestId}`); +// Security options that child windows will always inherit from parent windows +const securityWebPreferences: { [key: string]: boolean } = { + contextIsolation: true, + javascript: false, + nativeWindowOpen: true, + nodeIntegration: false, + enableRemoteModule: false, + sandbox: true, + webviewTag: false, + nodeIntegrationInSubFrames: false, + enableWebSQL: false +}; + +function makeBrowserWindowOptions ({ embedder, features, frameName, overrideOptions, useDeprecatedBehaviorForBareValues = true, useDeprecatedBehaviorForOptionInheritance = true }: { + embedder: WebContents, + features: string, + frameName: string, + overrideOptions?: BrowserWindowConstructorOptions, + useDeprecatedBehaviorForBareValues?: boolean + useDeprecatedBehaviorForOptionInheritance?: boolean +}) { + const { options: parsedOptions, webPreferences: parsedWebPreferences, additionalFeatures } = parseFeatures(features, useDeprecatedBehaviorForBareValues); + + const deprecatedInheritedOptions = getDeprecatedInheritedOptions(embedder); + + return { + additionalFeatures, + options: { + ...(useDeprecatedBehaviorForOptionInheritance && deprecatedInheritedOptions), + show: true, + title: frameName, + width: 800, + height: 600, + ...parsedOptions, + ...overrideOptions, + webPreferences: makeWebPreferences({ embedder, insecureParsedWebPreferences: parsedWebPreferences, secureOverrideWebPreferences: overrideOptions && overrideOptions.webPreferences, useDeprecatedBehaviorForOptionInheritance: true }) } - - return handler(event, guestContents, ...args); }; -}; +} -const handleMessage = function (channel: string, handler: (event: Electron.IpcMainInvokeEvent, guestContents: Electron.webContents, ...args: any[]) => any) { - ipcMainInternal.handle(channel, makeSafeHandler(handler)); -}; +export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {}, insecureParsedWebPreferences: parsedWebPreferences = {}, useDeprecatedBehaviorForOptionInheritance = true }: { + embedder: WebContents, + insecureParsedWebPreferences?: ReturnType['webPreferences'], + // Note that override preferences are considered elevated, and should only be + // sourced from the main process, as they override security defaults. If you + // have unvetted prefs, use parsedWebPreferences. + secureOverrideWebPreferences?: BrowserWindowConstructorOptions['webPreferences'], + useDeprecatedBehaviorForBareValues?: boolean + useDeprecatedBehaviorForOptionInheritance?: boolean +}) { + const deprecatedInheritedOptions = getDeprecatedInheritedOptions(embedder); + const parentWebPreferences = (embedder as any).getLastWebPreferences(); + const securityWebPreferencesFromParent = Object.keys(securityWebPreferences).reduce((map, key) => { + if (securityWebPreferences[key] === parentWebPreferences[key]) { + map[key] = parentWebPreferences[key]; + } + return map; + }, {} as any); + const openerId = parentWebPreferences.nativeWindowOpen ? null : embedder.id; -const handleMessageSync = function (channel: string, handler: (event: ElectronInternal.IpcMainInternalEvent, guestContents: Electron.webContents, ...args: any[]) => any) { - ipcMainUtils.handleSync(channel, makeSafeHandler(handler)); -}; + return { + ...(useDeprecatedBehaviorForOptionInheritance && deprecatedInheritedOptions ? deprecatedInheritedOptions.webPreferences : null), + ...parsedWebPreferences, + // Note that order is key here, we want to disallow the renderer's + // ability to change important security options but allow main (via + // setWindowOpenHandler) to change them. + ...securityWebPreferencesFromParent, + ...secureOverrideWebPreferences, + // Sets correct openerId here to give correct options to 'new-window' event handler + // TODO: Figure out another way to pass this? + openerId + }; +} -const securityCheck = function (contents: Electron.WebContents, guestContents: Electron.WebContents, check: (sender: Electron.WebContents, target: Electron.WebContents) => boolean) { - 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: string, ...args: any[]) => { - securityCheck(event.sender, guestContents, canAccessWindow); - - if (!windowMethods.has(method)) { - console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`); - throw new Error(`Invalid method: ${method}`); +/** + * Current Electron behavior is to inherit all options from the parent window. + * In practical use, this is kind of annoying because consumers have to know + * about the parent window's preferences in order to unset them and makes child + * windows even more of an anomaly. In 11.0.0 we will remove this behavior and + * only critical security preferences will be inherited by default. + */ +function getDeprecatedInheritedOptions (embedder: WebContents) { + if (!(embedder as any).browserWindowOptions) { + // If it's a webview, return just the webPreferences. + return { + webPreferences: (embedder as any).getLastWebPreferences() + }; } - return (getGuestWindow(guestContents) as any)[method](...args); -}); + const { type, show, ...inheritableOptions } = (embedder as any).browserWindowOptions; + return inheritableOptions; +} -handleMessage(IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE, (event, guestContents, message, targetOrigin, sourceOrigin) => { - if (targetOrigin == null) { - targetOrigin = '*'; +function formatPostDataHeaders (postData: any) { + if (!postData) return; + + let extraHeaders = 'content-type: application/x-www-form-urlencoded'; + + if (postData.length > 0) { + const postDataFront = postData[0].bytes.toString(); + const boundary = /^--.*[^-\r\n]/.exec( + postDataFront + ); + if (boundary != null) { + extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr( + 2 + )}`; + } } - // 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: string, ...args: any[]) => { - 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: string, ...args: any[]) => { - 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); -}); + return extraHeaders; +} diff --git a/lib/browser/guest-window-proxy.ts b/lib/browser/guest-window-proxy.ts new file mode 100644 index 00000000000..c8f91bfc43e --- /dev/null +++ b/lib/browser/guest-window-proxy.ts @@ -0,0 +1,211 @@ +/** + * 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 as any).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 as any).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 as any).getLastWebPreferences(); + if (lastWebPreferences.nativeWindowOpen || lastWebPreferences.sandbox) { + (event as any).returnValue = null; + throw new Error( + 'GUEST_WINDOW_MANAGER_WINDOW_OPEN denied: expected native window.open' + ); + } + + const browserWindowOptions = (event.sender as any)._callWindowOpenHandler(event, url, frameName, features); + if (event.defaultPrevented) { + return; + } + const guest = openGuestWindow({ + event, + embedder: event.sender, + referrer: { url: '', policy: 'default' }, + disposition: 'new-window', + overrideBrowserWindowOptions: browserWindowOptions, + windowOpenArgs: { + url: url || 'about:blank', + frameName: frameName || '', + features: features || '' + } + }); + + (event as any).returnValue = guest ? guest.webContents.id : null; + } +); + +type IpcHandler = (event: Event, guestContents: Electron.WebContents, ...args: any[]) => T; +const makeSafeHandler = function (handler: IpcHandler) { + 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) { + ipcMainInternal.handle(channel, makeSafeHandler(handler)); +}; + +const handleMessageSync = function (channel: string, handler: IpcHandler) { + 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.sender.getURL()} 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); + } +); diff --git a/lib/browser/init.ts b/lib/browser/init.ts index 7f1f4e44358..8360dc65e67 100644 --- a/lib/browser/init.ts +++ b/lib/browser/init.ts @@ -76,7 +76,7 @@ require('@electron/internal/browser/rpc-server'); // Load the guest view manager. require('@electron/internal/browser/guest-view-manager'); -require('@electron/internal/browser/guest-window-manager'); +require('@electron/internal/browser/guest-window-proxy'); // Now we try to load app's package.json. let packagePath = null; diff --git a/lib/browser/navigation-controller.ts b/lib/browser/navigation-controller.ts index 79c8b771fe9..5367839be1f 100644 --- a/lib/browser/navigation-controller.ts +++ b/lib/browser/navigation-controller.ts @@ -123,11 +123,13 @@ export class NavigationController extends EventEmitter { this.webContents.removeListener('did-fail-load', failListener); this.webContents.removeListener('did-start-navigation', navigationListener); this.webContents.removeListener('did-stop-loading', stopLoadingListener); + this.webContents.removeListener('destroyed', stopLoadingListener); }; this.webContents.on('did-finish-load', finishListener); this.webContents.on('did-fail-load', failListener); this.webContents.on('did-start-navigation', navigationListener); this.webContents.on('did-stop-loading', stopLoadingListener); + this.webContents.on('destroyed', stopLoadingListener); }); // Add a no-op rejection handler to silence the unhandled rejection error. p.catch(() => {}); diff --git a/lib/common/parse-features-string.ts b/lib/common/parse-features-string.ts index 698b178dc67..57c481a775f 100644 --- a/lib/common/parse-features-string.ts +++ b/lib/common/parse-features-string.ts @@ -81,7 +81,7 @@ type AllowedWebPreference = (typeof allowedWebPreferences)[number]; /** * Parses a feature string that has the format used in window.open(). * - * `useSoonToBeDeprecatedBehaviorForBareKeys` — In the html spec, windowFeatures keys + * `useSoonToBeDeprecatedBehaviorForBareKeys` - In the html spec, windowFeatures keys * without values are interpreted as `true`. Previous versions of Electron did * not respect this. In order to not break any applications, this will be * flipped in the next major version. @@ -103,7 +103,7 @@ export function parseFeatures ( if (parsed.top !== undefined) parsed.y = parsed.top; return { - options: parsed as Omit & { [key: string]: CoercedValue }, + options: parsed as Omit, webPreferences, additionalFeatures: bareKeys }; diff --git a/lib/renderer/api/context-bridge.ts b/lib/renderer/api/context-bridge.ts index 46d68543faa..ef4c8046e23 100644 --- a/lib/renderer/api/context-bridge.ts +++ b/lib/renderer/api/context-bridge.ts @@ -1,7 +1,7 @@ -const { hasSwitch } = process._linkedBinding('electron_common_command_line'); +const { getWebPreference } = process._linkedBinding('electron_renderer_web_frame'); const binding = process._linkedBinding('electron_renderer_context_bridge'); -const contextIsolationEnabled = hasSwitch('context-isolation'); +const contextIsolationEnabled = getWebPreference(window, 'contextIsolation'); const checkContextIsolationEnabled = () => { if (!contextIsolationEnabled) throw new Error('contextBridge API can only be used when contextIsolation is enabled'); diff --git a/lib/renderer/init.ts b/lib/renderer/init.ts index 86e52b2b7dc..d1ab84dd030 100644 --- a/lib/renderer/init.ts +++ b/lib/renderer/init.ts @@ -55,31 +55,33 @@ webFrameInit(); // Process command line arguments. const { hasSwitch, getSwitchValue } = process._linkedBinding('electron_common_command_line'); +const { getWebPreference } = process._linkedBinding('electron_renderer_web_frame'); -const parseOption = function ( - name: string, defaultValue: T, converter?: (value: string) => T +const parseOption = function ( + name: string, defaultValue: TDefault, converter?: (value: string) => any ) { - return hasSwitch(name) + const value = getWebPreference(window, name); + return value ? ( converter - ? converter(getSwitchValue(name)) - : getSwitchValue(name) + ? converter(value) + : value ) : defaultValue; }; -const contextIsolation = hasSwitch('context-isolation'); -const nodeIntegration = hasSwitch('node-integration'); -const webviewTag = hasSwitch('webview-tag'); -const isHiddenPage = hasSwitch('hidden-page'); -const usesNativeWindowOpen = hasSwitch('native-window-open'); -const rendererProcessReuseEnabled = hasSwitch('disable-electron-site-instance-overrides'); +const contextIsolation = getWebPreference(window, 'contextIsolation'); +const nodeIntegration = getWebPreference(window, 'nodeIntegration'); +const webviewTag = getWebPreference(window, 'webviewTag'); +const isHiddenPage = getWebPreference(window, 'hiddenPage'); +const usesNativeWindowOpen = getWebPreference(window, 'nativeWindowOpen'); +const rendererProcessReuseEnabled = getWebPreference(window, 'disableElectronSiteInstanceOverrides'); const preloadScript = parseOption('preload', null); -const preloadScripts = parseOption('preload-scripts', [], value => value.split(path.delimiter)) as string[]; -const appPath = parseOption('app-path', null); -const guestInstanceId = parseOption('guest-instance-id', null, value => parseInt(value)); -const openerId = parseOption('opener-id', null, value => parseInt(value)); +const preloadScripts = parseOption('preloadScripts', []); +const guestInstanceId = parseOption('guestInstanceId', null, value => parseInt(value)); +const openerId = parseOption('openerId', null, value => parseInt(value)); +const appPath = hasSwitch('app-path') ? getSwitchValue('app-path') : null; // The webContents preload script is loaded after the session preload scripts. if (preloadScript) { diff --git a/lib/sandboxed_renderer/init.ts b/lib/sandboxed_renderer/init.ts index 8bbcc4a4569..ec10f88219f 100644 --- a/lib/sandboxed_renderer/init.ts +++ b/lib/sandboxed_renderer/init.ts @@ -114,6 +114,7 @@ function preloadRequire (module: string) { // Process command line arguments. const { hasSwitch } = process._linkedBinding('electron_common_command_line'); +const { getWebPreference } = process._linkedBinding('electron_renderer_web_frame'); // Similar to nodes --expose-internals flag, this exposes _linkedBinding so // that tests can call it to get access to some test only bindings @@ -121,9 +122,9 @@ if (hasSwitch('unsafely-expose-electron-internals-for-testing')) { preloadProcess._linkedBinding = process._linkedBinding; } -const contextIsolation = hasSwitch('context-isolation'); -const isHiddenPage = hasSwitch('hidden-page'); -const rendererProcessReuseEnabled = hasSwitch('disable-electron-site-instance-overrides'); +const contextIsolation = getWebPreference(window, 'contextIsolation'); +const isHiddenPage = getWebPreference(window, 'hiddenPage'); +const rendererProcessReuseEnabled = getWebPreference(window, 'disableElectronSiteInstanceOverrides'); const usesNativeWindowOpen = true; switch (window.location.protocol) { diff --git a/package.json b/package.json index bc923a385e8..491e4c9f0de 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "https://github.com/electron/electron", "description": "Build cross platform desktop apps with JavaScript, HTML, and CSS", "devDependencies": { - "@electron/docs-parser": "^0.10.0", + "@electron/docs-parser": "^0.10.1", "@electron/typescript-definitions": "^8.8.0", "@octokit/auth-app": "^2.10.0", "@octokit/rest": "^18.0.3", @@ -148,4 +148,4 @@ "@types/temp": "^0.8.34", "aws-sdk": "^2.727.1" } -} \ No newline at end of file +} diff --git a/patches/chromium/.patches b/patches/chromium/.patches index 3bc63b5e7c9..71dc75c0bfa 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -83,6 +83,7 @@ feat_allow_embedders_to_add_observers_on_created_hunspell.patch feat_add_onclose_to_messageport.patch web_contents.patch ui_gtk_public_header.patch +allow_in_process_windows_to_have_different_web_prefs.patch refactor_expose_cursor_changes_to_the_webcontentsobserver.patch crash_allow_setting_more_options.patch breakpad_treat_node_processes_as_browser_processes.patch @@ -97,6 +98,7 @@ remove_some_deps_that_do_not_work_on_arm64.patch fix_check_issecureeventinputenabled_in_constructor_before_setting.patch skip_atk_toolchain_check.patch worker_feat_add_hook_to_notify_script_ready.patch +chore_provide_iswebcontentscreationoverridden_with_full_params.patch fix_properly_honor_printing_page_ranges.patch fix_use_electron_generated_resources.patch chore_expose_v8_initialization_isolate_callbacks.patch diff --git a/patches/chromium/allow_in_process_windows_to_have_different_web_prefs.patch b/patches/chromium/allow_in_process_windows_to_have_different_web_prefs.patch new file mode 100644 index 00000000000..0cf7f111d10 --- /dev/null +++ b/patches/chromium/allow_in_process_windows_to_have_different_web_prefs.patch @@ -0,0 +1,279 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andy Locascio +Date: Wed, 6 May 2020 16:37:54 -0700 +Subject: allow in-process windows to have different web prefs + +Allow earlier access to newly created WebContents so that we can change +WebPreferences of in-process child windows, rather than relying on +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 +index 1e51bb68df930c44e165b32c8c03b99986792209..9535330e3e7a3c4621e96eadbf90f4da2bf2daf0 100644 +--- a/third_party/blink/common/web_preferences/web_preferences.cc ++++ b/third_party/blink/common/web_preferences/web_preferences.cc +@@ -143,6 +143,29 @@ WebPreferences::WebPreferences() + navigate_on_drag_drop(true), + v8_cache_options(blink::mojom::V8CacheOptions::kDefault), + record_whole_document(false), ++ ++ // Begin Electron-specific WebPreferences. ++ disable_electron_site_instance_overrides(), ++ background_color(base::EmptyString()), ++ opener_id(0), ++ context_isolation(false), ++ enable_remote_module(false), ++ world_safe_execute_javascript(false), ++ guest_instance_id(0), ++ hidden_page(false), ++ offscreen(false), ++ preload(base::FilePath::StringType()), ++ native_window_open(false), ++ node_integration(false), ++ node_integration_in_worker(false), ++ node_leakage_in_renderers(false), ++ node_integration_in_sub_frames(false), ++ enable_spellcheck(false), ++ enable_plugins(false), ++ enable_websql(false), ++ webview_tag(false), ++ // End Electron-specific WebPreferences. ++ + cookie_enabled(true), + accelerated_video_decode_enabled(false), + 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 +index 912c1bf5919bc657a4903d3fbca60c1013aa9d98..b3759ab22c071ad94062f6c3f7c24920ba3cde47 100644 +--- a/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc ++++ b/third_party/blink/common/web_preferences/web_preferences_mojom_traits.cc +@@ -144,6 +144,11 @@ bool StructTraitscursive_font_family_map) || + !data.ReadFantasyFontFamilyMap(&out->fantasy_font_family_map) || + !data.ReadPictographFontFamilyMap(&out->pictograph_font_family_map) || ++ // Begin Electron-specific WebPreferences. ++ !data.ReadPreloads(&out->preloads) || ++ !data.ReadBackgroundColor(&out->background_color) || ++ !data.ReadPreload(&out->preload) || ++ // End Electron-specific WebPreferences. + !data.ReadLazyFrameLoadingDistanceThresholdsPx( + &out->lazy_frame_loading_distance_thresholds_px) || + !data.ReadLazyImageLoadingDistanceThresholdsPx( +@@ -267,6 +272,27 @@ bool StructTraitsnavigate_on_drag_drop = data.navigate_on_drag_drop(); + out->v8_cache_options = data.v8_cache_options(); + out->record_whole_document = data.record_whole_document(); ++ ++ // Begin Electron-specific WebPreferences. ++ out->disable_electron_site_instance_overrides = data.disable_electron_site_instance_overrides(); ++ out->opener_id = data.opener_id(); ++ out->context_isolation = data.context_isolation(); ++ out->enable_remote_module = data.enable_remote_module(); ++ out->world_safe_execute_javascript = data.world_safe_execute_javascript(); ++ out->guest_instance_id = data.guest_instance_id(); ++ out->hidden_page = data.hidden_page(); ++ out->offscreen = data.offscreen(); ++ out->native_window_open = data.native_window_open(); ++ out->node_integration = data.node_integration(); ++ out->node_integration_in_worker = data.node_integration_in_worker(); ++ out->node_leakage_in_renderers = data.node_leakage_in_renderers(); ++ out->node_integration_in_sub_frames = data.node_integration_in_sub_frames(); ++ out->enable_spellcheck = data.enable_spellcheck(); ++ out->enable_plugins = data.enable_plugins(); ++ out->enable_websql = data.enable_websql(); ++ out->webview_tag = data.webview_tag(); ++ // End Electron-specific WebPreferences. ++ + out->cookie_enabled = data.cookie_enabled(); + out->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 +index 81e4729f540ff272a43c77045187fe3d4bde3ea0..3a2c60f7616f724d3a0399f7fe8becae60d6ae88 100644 +--- a/third_party/blink/public/common/web_preferences/web_preferences.h ++++ b/third_party/blink/public/common/web_preferences/web_preferences.h +@@ -9,6 +9,7 @@ + #include + #include + ++#include "base/files/file_path.h" + #include "base/strings/string16.h" + #include "base/time/time.h" + #include "build/build_config.h" +@@ -154,6 +155,29 @@ struct BLINK_COMMON_EXPORT WebPreferences { + blink::mojom::V8CacheOptions v8_cache_options; + bool record_whole_document; + ++ // Begin Electron-specific WebPreferences. ++ std::vector preloads; ++ bool disable_electron_site_instance_overrides; ++ std::string background_color; ++ int opener_id; ++ bool context_isolation; ++ bool enable_remote_module; ++ bool world_safe_execute_javascript; ++ int guest_instance_id; ++ bool hidden_page; ++ bool offscreen; ++ base::FilePath preload; ++ bool native_window_open; ++ bool node_integration; ++ bool node_integration_in_worker; ++ bool node_leakage_in_renderers; ++ bool node_integration_in_sub_frames; ++ bool enable_spellcheck; ++ bool enable_plugins; ++ bool enable_websql; ++ bool webview_tag; ++ // End Electron-specific WebPreferences. ++ + // This flags corresponds to a Page's Settings' setCookieEnabled state. It + // 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 +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 74b22fbaad5c8ae911dca4b26bbfffd8e9079856..decef06a6d0cda2da8aa3c88d589dbff75ff369e 100644 +--- 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 +@@ -6,6 +6,7 @@ + #define THIRD_PARTY_BLINK_PUBLIC_COMMON_WEB_PREFERENCES_WEB_PREFERENCES_MOJOM_TRAITS_H_ + + #include "build/build_config.h" ++#include "mojo/public/cpp/base/file_path_mojom_traits.h" + #include "mojo/public/cpp/bindings/struct_traits.h" + #include "net/nqe/effective_connection_type.h" + #include "third_party/blink/public/common/common_export.h" +@@ -452,6 +453,88 @@ struct BLINK_COMMON_EXPORT StructTraits& preloads(const blink::web_pref::WebPreferences& r) { ++ return r.preloads; ++ } ++ ++ static bool disable_electron_site_instance_overrides(const blink::web_pref::WebPreferences& r) { ++ return r.disable_electron_site_instance_overrides; ++ } ++ ++ static const std::string& background_color(const blink::web_pref::WebPreferences& r) { ++ return r.background_color; ++ } ++ ++ static int opener_id(const blink::web_pref::WebPreferences& r) { ++ return r.opener_id; ++ } ++ ++ static bool context_isolation(const blink::web_pref::WebPreferences& r) { ++ return r.context_isolation; ++ } ++ ++ static bool enable_remote_module(const blink::web_pref::WebPreferences& r) { ++ return r.enable_remote_module; ++ } ++ ++ static bool world_safe_execute_javascript(const blink::web_pref::WebPreferences& r) { ++ return r.world_safe_execute_javascript; ++ } ++ ++ static int guest_instance_id(const blink::web_pref::WebPreferences& r) { ++ return r.guest_instance_id; ++ } ++ ++ static bool hidden_page(const blink::web_pref::WebPreferences& r) { ++ return r.hidden_page; ++ } ++ ++ static bool offscreen(const blink::web_pref::WebPreferences& r) { ++ return r.offscreen; ++ } ++ ++ static const base::FilePath& preload(const blink::web_pref::WebPreferences& r) { ++ 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) { ++ return r.node_integration; ++ } ++ ++ static bool node_integration_in_worker(const blink::web_pref::WebPreferences& r) { ++ return r.node_integration_in_worker; ++ } ++ ++ static bool node_leakage_in_renderers(const blink::web_pref::WebPreferences& r) { ++ return r.node_leakage_in_renderers; ++ } ++ ++ static bool node_integration_in_sub_frames(const blink::web_pref::WebPreferences& r) { ++ return r.node_integration_in_sub_frames; ++ } ++ ++ static bool enable_spellcheck(const blink::web_pref::WebPreferences& r) { ++ return r.enable_spellcheck; ++ } ++ ++ static bool enable_plugins(const blink::web_pref::WebPreferences& r) { ++ return r.enable_plugins; ++ } ++ ++ static bool enable_websql(const blink::web_pref::WebPreferences& r) { ++ return r.enable_websql; ++ } ++ ++ static bool webview_tag(const blink::web_pref::WebPreferences& r) { ++ return r.webview_tag; ++ } ++ // End Electron-specific WebPreferences. ++ + static bool cookie_enabled(const blink::web_pref::WebPreferences& r) { + 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 +index ac1099c8fb923184a014bf9cbaa35c496cf85cd9..688ea93b1d067555b7b48165ba7a33625f9d597f 100644 +--- a/third_party/blink/public/mojom/webpreferences/web_preferences.mojom ++++ b/third_party/blink/public/mojom/webpreferences/web_preferences.mojom +@@ -8,6 +8,7 @@ import "third_party/blink/public/mojom/css/preferred_color_scheme.mojom"; + import "third_party/blink/public/mojom/v8_cache_options.mojom"; + import "url/mojom/url.mojom"; + import "mojo/public/mojom/base/string16.mojom"; ++import "mojo/public/mojom/base/file_path.mojom"; + + enum PointerType { + kPointerFirstType = 1, // 1 << 0 +@@ -202,6 +203,29 @@ struct WebPreferences { + V8CacheOptions v8_cache_options; + bool record_whole_document; + ++ // Begin Electron-specific WebPreferences. ++ array preloads; ++ bool disable_electron_site_instance_overrides; ++ string background_color; ++ int32 opener_id; ++ bool context_isolation; ++ bool enable_remote_module; ++ bool world_safe_execute_javascript; ++ int32 guest_instance_id; ++ bool hidden_page; ++ bool offscreen; ++ mojo_base.mojom.FilePath preload; ++ bool native_window_open; ++ bool node_integration; ++ bool node_integration_in_worker; ++ bool node_leakage_in_renderers; ++ bool node_integration_in_sub_frames; ++ bool enable_spellcheck; ++ bool enable_plugins; ++ bool enable_websql; ++ bool webview_tag; ++ // End Electron-specific WebPreferences. ++ + // This flags corresponds to a Page's Settings' setCookieEnabled state. It + // 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 +@@ -428,4 +452,4 @@ struct WebPreferences { + // Whether touch input can trigger HTML drag-and-drop operations. The + // default value depends on the platform. + bool touch_drag_drop_enabled; +-}; +\ No newline at end of file ++}; diff --git a/patches/chromium/can_create_window.patch b/patches/chromium/can_create_window.patch index ff62b6e8422..587fe2eff45 100644 --- a/patches/chromium/can_create_window.patch +++ b/patches/chromium/can_create_window.patch @@ -21,22 +21,37 @@ index 365d0d8cf45c664160048a7a9606907cb5414292..dd52048e922904826c5b31d13f17dfc9 &no_javascript_access); diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc -index 962cd0fb5a428530cd9e2f8e689f2d09bc95ce1b..e099a6cc44e73da8b24603fd86b72139f0735fb2 100644 +index 962cd0fb5a428530cd9e2f8e689f2d09bc95ce1b..ea458219bd182bd9ff21b203c282e7d0738049dd 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc -@@ -3624,9 +3624,9 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow( +@@ -3582,6 +3582,14 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow( } + auto* new_contents_impl = new_contents.get(); - if (delegate_) { -- delegate_->WebContentsCreated(this, render_process_id, -- opener->GetRoutingID(), params.frame_name, -- params.target_url, new_contents_impl); ++ // Call this earlier than Chrome to associate the web preferences with the ++ // WebContents before the view gets created. ++ if (delegate_) { + delegate_->WebContentsCreatedWithFullParams(this, render_process_id, + opener->GetRoutingID(), + params, new_contents_impl); ++ } ++ + new_contents_impl->GetController().SetSessionStorageNamespace( + partition_id, session_storage_namespace); + +@@ -3623,12 +3631,6 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow( + AddDestructionObserver(new_contents_impl); } +- if (delegate_) { +- delegate_->WebContentsCreated(this, render_process_id, +- opener->GetRoutingID(), params.frame_name, +- params.target_url, new_contents_impl); +- } +- observers_.ForEachObserver([&](WebContentsObserver* observer) { + observer->DidOpenRequestedURL(new_contents_impl, opener, params.target_url, + params.referrer.To(), diff --git a/content/common/frame.mojom b/content/common/frame.mojom index 703c4611691b72380423576eebdadcd23e6ae913..2be0f93b7ea3791bb776158795a44aa7422e19ac 100644 --- a/content/common/frame.mojom diff --git a/patches/chromium/chore_provide_iswebcontentscreationoverridden_with_full_params.patch b/patches/chromium/chore_provide_iswebcontentscreationoverridden_with_full_params.patch new file mode 100644 index 00000000000..8b09f1fdb94 --- /dev/null +++ b/patches/chromium/chore_provide_iswebcontentscreationoverridden_with_full_params.patch @@ -0,0 +1,433 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andy Locascio +Date: Wed, 9 Sep 2020 16:56:06 -0700 +Subject: chore: provide IsWebContentsCreationOverridden with full params + +Pending upstream patch, this gives us fuller access to the window.open params +so that we will be able to decide whether to cancel it or not. + +diff --git a/chrome/browser/android/document/document_web_contents_delegate.cc b/chrome/browser/android/document/document_web_contents_delegate.cc +index 0e90487923c57c0570e73ef0f0e8c5acc2576932..fcdc88233b2277f3b37a2a2b0bdee7d71b721bf8 100644 +--- a/chrome/browser/android/document/document_web_contents_delegate.cc ++++ b/chrome/browser/android/document/document_web_contents_delegate.cc +@@ -46,8 +46,7 @@ bool DocumentWebContentsDelegate::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const mojom::CreateNewWindowParams& params) { + NOTREACHED(); + return true; + } +diff --git a/chrome/browser/android/document/document_web_contents_delegate.h b/chrome/browser/android/document/document_web_contents_delegate.h +index 5b4d70991e19edcdfee731c56251932bf43e535f..fe1977c5e6ce0f5b30e8be529b9efa51785db57f 100644 +--- a/chrome/browser/android/document/document_web_contents_delegate.h ++++ b/chrome/browser/android/document/document_web_contents_delegate.h +@@ -41,8 +41,7 @@ class DocumentWebContentsDelegate + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override; ++ const mojom::CreateNewWindowParams& params) override; + }; + + #endif // CHROME_BROWSER_ANDROID_DOCUMENT_DOCUMENT_WEB_CONTENTS_DELEGATE_H_ +diff --git a/chrome/browser/chromeos/first_run/drive_first_run_controller.cc b/chrome/browser/chromeos/first_run/drive_first_run_controller.cc +index a52c550f1aeadcdb36948078ed5fec24838c3da8..4aaddf84c0b3574218df5bdea77a72967271d5a8 100644 +--- a/chrome/browser/chromeos/first_run/drive_first_run_controller.cc ++++ b/chrome/browser/chromeos/first_run/drive_first_run_controller.cc +@@ -123,8 +123,7 @@ class DriveWebContentsManager : public content::WebContentsObserver, + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override; ++ const mojom::CreateNewWindowParams& params) override; + content::WebContents* CreateCustomWebContents( + content::RenderFrameHost* opener, + content::SiteInstance* source_site_instance, +@@ -234,15 +233,14 @@ bool DriveWebContentsManager::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const mojom::CreateNewWindowParams& params) { + if (window_container_type == content::mojom::WindowContainerType::NORMAL) + return false; + + // Check that the target URL is for the Drive app. + const extensions::Extension* extension = + extensions::ExtensionRegistry::Get(profile_) +- ->enabled_extensions().GetAppByURL(target_url); ++ ->enabled_extensions().GetAppByURL(params.target_url); + + return extension && extension->id() == app_id_; + } +diff --git a/chrome/browser/media/offscreen_tab.cc b/chrome/browser/media/offscreen_tab.cc +index aba8e2273c98d2dc9a01a698f8de187056b3ce01..5141b4d1a711416f4dfc030838b9cf22e7613f8b 100644 +--- a/chrome/browser/media/offscreen_tab.cc ++++ b/chrome/browser/media/offscreen_tab.cc +@@ -282,8 +282,7 @@ bool OffscreenTab::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const mojom::CreateNewWindowParams& params) { + // Disallow creating separate WebContentses. The WebContents implementation + // uses this to spawn new windows/tabs, which is also not allowed for + // offscreen tabs. +diff --git a/chrome/browser/media/offscreen_tab.h b/chrome/browser/media/offscreen_tab.h +index fb09bf2c5d22e3838575403b53867d0021e13b67..36e7982bbcc7c8b50bb2942ada39862bad4bbc22 100644 +--- a/chrome/browser/media/offscreen_tab.h ++++ b/chrome/browser/media/offscreen_tab.h +@@ -106,8 +106,7 @@ class OffscreenTab final : public ProfileObserver, + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) final; ++ const mojom::CreateNewWindowParams& params) override; + void EnterFullscreenModeForTab( + content::RenderFrameHost* requesting_frame, + const blink::mojom::FullscreenOptions& options) final; +diff --git a/chrome/browser/ui/ash/assistant/assistant_web_view_impl.cc b/chrome/browser/ui/ash/assistant/assistant_web_view_impl.cc +index 672d5d41e33178ed6c6f62156e69b1adaf099fe8..4c0f8db8a9c40c34df8abfa89d88b0ceaef76392 100644 +--- a/chrome/browser/ui/ash/assistant/assistant_web_view_impl.cc ++++ b/chrome/browser/ui/ash/assistant/assistant_web_view_impl.cc +@@ -77,10 +77,9 @@ bool AssistantWebViewImpl::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const mojom::CreateNewWindowParams& params) { + if (params_.suppress_navigation) { +- NotifyDidSuppressNavigation(target_url, ++ NotifyDidSuppressNavigation(params.target_url, + WindowOpenDisposition::NEW_FOREGROUND_TAB, + /*from_user_gesture=*/true); + return true; +diff --git a/chrome/browser/ui/ash/assistant/assistant_web_view_impl.h b/chrome/browser/ui/ash/assistant/assistant_web_view_impl.h +index 07014765f33bdddebcc5bc32a2713d6523faf394..f866f69f9c810d89f1a0e9e4952293f66804602a 100644 +--- a/chrome/browser/ui/ash/assistant/assistant_web_view_impl.h ++++ b/chrome/browser/ui/ash/assistant/assistant_web_view_impl.h +@@ -48,8 +48,7 @@ class AssistantWebViewImpl : public ash::AssistantWebView, + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override; ++ const mojom::CreateNewWindowParams& params) override; + content::WebContents* OpenURLFromTab( + content::WebContents* source, + const content::OpenURLParams& params) override; +diff --git a/chrome/browser/ui/ash/keyboard/chrome_keyboard_web_contents.cc b/chrome/browser/ui/ash/keyboard/chrome_keyboard_web_contents.cc +index 3d9b343765bdca5723a94e00db1426308a641a50..57afd89cb06ff255f126c7f7784f10e7689cefe3 100644 +--- a/chrome/browser/ui/ash/keyboard/chrome_keyboard_web_contents.cc ++++ b/chrome/browser/ui/ash/keyboard/chrome_keyboard_web_contents.cc +@@ -64,8 +64,7 @@ class ChromeKeyboardContentsDelegate : public content::WebContentsDelegate, + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override { ++ const mojom::CreateNewWindowParams& params) override { + return true; + } + +diff --git a/chrome/browser/ui/browser.cc b/chrome/browser/ui/browser.cc +index 30a8f5eeaedb9bc186faabca645917bc53e7f041..8e7316a1255f9de8f3374b7ed99a8c8f42701b43 100644 +--- a/chrome/browser/ui/browser.cc ++++ b/chrome/browser/ui/browser.cc +@@ -1818,12 +1818,11 @@ bool Browser::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const mojom::CreateNewWindowParams& params) { + return window_container_type == + content::mojom::WindowContainerType::BACKGROUND && + ShouldCreateBackgroundContents(source_site_instance, opener_url, +- frame_name); ++ params->frame_name); + } + + WebContents* Browser::CreateCustomWebContents( +diff --git a/chrome/browser/ui/browser.h b/chrome/browser/ui/browser.h +index 79cde7e1b913e91a15405a0ae1cd7e97fa11971e..b5f00b336b0e15e675f8ce03997343f24b5b0e51 100644 +--- a/chrome/browser/ui/browser.h ++++ b/chrome/browser/ui/browser.h +@@ -769,8 +769,7 @@ class Browser : public TabStripModelObserver, + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override; ++ const mojom::CreateNewWindowParams& params) override; + content::WebContents* CreateCustomWebContents( + content::RenderFrameHost* opener, + content::SiteInstance* source_site_instance, +diff --git a/chrome/browser/ui/media_router/presentation_receiver_window_controller.cc b/chrome/browser/ui/media_router/presentation_receiver_window_controller.cc +index 41177655d0608837ff7af7b944f22b1e657c68f6..6068228781eb1dabe1890f9121a3f86df4f6ac52 100644 +--- a/chrome/browser/ui/media_router/presentation_receiver_window_controller.cc ++++ b/chrome/browser/ui/media_router/presentation_receiver_window_controller.cc +@@ -201,8 +201,7 @@ bool PresentationReceiverWindowController::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const mojom::CreateNewWindowParams& params) { + // Disallow creating separate WebContentses. The WebContents implementation + // uses this to spawn new windows/tabs, which is also not allowed for + // local presentations. +diff --git a/chrome/browser/ui/media_router/presentation_receiver_window_controller.h b/chrome/browser/ui/media_router/presentation_receiver_window_controller.h +index 058ec72442d59989c4d6df4a7c791ecfeff0ef99..f7c8c2139382cb2e290c561624291afe647383cf 100644 +--- a/chrome/browser/ui/media_router/presentation_receiver_window_controller.h ++++ b/chrome/browser/ui/media_router/presentation_receiver_window_controller.h +@@ -99,8 +99,7 @@ class PresentationReceiverWindowController final + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override; ++ const mojom::CreateNewWindowParams& params) override; + + // The profile used for the presentation. + Profile* otr_profile_; +diff --git a/components/embedder_support/android/delegate/web_contents_delegate_android.cc b/components/embedder_support/android/delegate/web_contents_delegate_android.cc +index 420803e98b1dde758dc72ba5a481f0b7cbde836b..633353c180aa748c2d80eb07412bfd0ffcf07ae3 100644 +--- a/components/embedder_support/android/delegate/web_contents_delegate_android.cc ++++ b/components/embedder_support/android/delegate/web_contents_delegate_android.cc +@@ -167,14 +167,13 @@ bool WebContentsDelegateAndroid::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const mojom::CreateNewWindowParams& params) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef obj = GetJavaDelegate(env); + if (obj.is_null()) + return false; + ScopedJavaLocalRef java_url = +- ConvertUTF8ToJavaString(env, target_url.spec()); ++ ConvertUTF8ToJavaString(env, params.target_url.spec()); + return !Java_WebContentsDelegateAndroid_shouldCreateWebContents(env, obj, + java_url); + } +diff --git a/components/embedder_support/android/delegate/web_contents_delegate_android.h b/components/embedder_support/android/delegate/web_contents_delegate_android.h +index fdd6866f595974f5a38e288a48b1e386a33c54d1..653981ffa4f58e727b2d7a2631f6213e3790c39f 100644 +--- a/components/embedder_support/android/delegate/web_contents_delegate_android.h ++++ b/components/embedder_support/android/delegate/web_contents_delegate_android.h +@@ -78,8 +78,7 @@ class WebContentsDelegateAndroid : public content::WebContentsDelegate { + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override; ++ const mojom::CreateNewWindowParams& params) override; + void CloseContents(content::WebContents* source) override; + void SetContentsBounds(content::WebContents* source, + const gfx::Rect& bounds) override; +diff --git a/components/offline_pages/content/background_loader/background_loader_contents.cc b/components/offline_pages/content/background_loader/background_loader_contents.cc +index 53fad64f87a952fd0d7398958288ecde259b57bf..0b8359b6179bf16e58978e5f3e51a911ad3d0a3f 100644 +--- a/components/offline_pages/content/background_loader/background_loader_contents.cc ++++ b/components/offline_pages/content/background_loader/background_loader_contents.cc +@@ -80,8 +80,7 @@ bool BackgroundLoaderContents::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const mojom::CreateNewWindowParams& params) { + // Background pages should not create other webcontents/tabs. + return true; + } +diff --git a/components/offline_pages/content/background_loader/background_loader_contents.h b/components/offline_pages/content/background_loader/background_loader_contents.h +index c5c5a7b63b5b3b62a9517cbef3ae23ce57a3c89c..4f1b7e88d6d2ae89a60311c8aeb1fceea87f2b02 100644 +--- a/components/offline_pages/content/background_loader/background_loader_contents.h ++++ b/components/offline_pages/content/background_loader/background_loader_contents.h +@@ -60,8 +60,7 @@ class BackgroundLoaderContents : public content::WebContentsDelegate { + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override; ++ const mojom::CreateNewWindowParams& params) override; + + void AddNewContents(content::WebContents* source, + std::unique_ptr new_contents, +diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc +index 2a80aaa5d4421bf2b3a46cc1674256944b33a1fa..409c300c3831bb3fd3f0a019fb6e85bd866a705a 100644 +--- a/content/browser/web_contents/web_contents_impl.cc ++++ b/content/browser/web_contents/web_contents_impl.cc +@@ -3543,8 +3543,7 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow( + + if (delegate_ && delegate_->IsWebContentsCreationOverridden( + source_site_instance, params.window_container_type, +- opener->GetLastCommittedURL(), params.frame_name, +- params.target_url)) { ++ opener->GetLastCommittedURL(), params)) { + return static_cast(delegate_->CreateCustomWebContents( + opener, source_site_instance, is_new_browsing_instance, + opener->GetLastCommittedURL(), params.frame_name, params.target_url, +diff --git a/content/public/browser/web_contents_delegate.cc b/content/public/browser/web_contents_delegate.cc +index b0513d20bd48fe8ffe8398b5387e13545749c508..7ce75c95771cc0053db01ec5318a68624bae9f4d 100644 +--- a/content/public/browser/web_contents_delegate.cc ++++ b/content/public/browser/web_contents_delegate.cc +@@ -134,8 +134,7 @@ bool WebContentsDelegate::IsWebContentsCreationOverridden( + SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const mojom::CreateNewWindowParams& params) { + return false; + } + +diff --git a/content/public/browser/web_contents_delegate.h b/content/public/browser/web_contents_delegate.h +index 94f576a6d52731f92c65adb958be5ca0a3391d4c..33359363fa18364d254aab5360f4c952f358bd17 100644 +--- a/content/public/browser/web_contents_delegate.h ++++ b/content/public/browser/web_contents_delegate.h +@@ -317,8 +317,7 @@ class CONTENT_EXPORT WebContentsDelegate { + SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url); ++ const mojom::CreateNewWindowParams& params); + + // Allow delegate to creates a custom WebContents when + // WebContents::CreateNewWindow() is called. This function is only called +diff --git a/extensions/browser/guest_view/extension_options/extension_options_guest.cc b/extensions/browser/guest_view/extension_options/extension_options_guest.cc +index bda659c7a464a58413e7eb33a897065268ab3fc6..29950878aca7f1518265561dea79b78d82230982 100644 +--- a/extensions/browser/guest_view/extension_options/extension_options_guest.cc ++++ b/extensions/browser/guest_view/extension_options/extension_options_guest.cc +@@ -212,8 +212,7 @@ bool ExtensionOptionsGuest::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const content::mojom::CreateNewWindowParams& params) { + // This method handles opening links from within the guest. Since this guest + // view is used for displaying embedded extension options, we want any + // external links to be opened in a new tab, not in a new guest view so we +diff --git a/extensions/browser/guest_view/extension_options/extension_options_guest.h b/extensions/browser/guest_view/extension_options/extension_options_guest.h +index 97273c32a05acf325fd0de22c4f79c1746aa23bc..4b357acd069387963347d35d82371b955147893f 100644 +--- a/extensions/browser/guest_view/extension_options/extension_options_guest.h ++++ b/extensions/browser/guest_view/extension_options/extension_options_guest.h +@@ -55,8 +55,7 @@ class ExtensionOptionsGuest + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) final; ++ const content::mojom::CreateNewWindowParams& params) final; + content::WebContents* CreateCustomWebContents( + content::RenderFrameHost* opener, + content::SiteInstance* source_site_instance, +diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc +index 998d6d0c481fd49ff3a8a852e05347179f80b276..6ce9f2e9e8031d9abf03da7e656f221ce0912790 100644 +--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc ++++ b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.cc +@@ -380,8 +380,7 @@ bool MimeHandlerViewGuest::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const content::mojom::CreateNewWindowParams& params) { + return true; + } + +diff --git a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h +index a7f0b19a8ab9bac6f1315ebd715d8e1b134edfe1..cbe2912d4ab2d9015396bbddf7836e106d0bff8b 100644 +--- a/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h ++++ b/extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h +@@ -155,8 +155,7 @@ class MimeHandlerViewGuest + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override; ++ const content::mojom::CreateNewWindowParams& params) override; + content::WebContents* CreateCustomWebContents( + content::RenderFrameHost* opener, + content::SiteInstance* source_site_instance, +diff --git a/fuchsia/engine/browser/frame_impl.cc b/fuchsia/engine/browser/frame_impl.cc +index 32a8e39b23bdf962b88eba3a40346febfbac298a..bc120621e1df1921651f3323650a8644ff688933 100644 +--- a/fuchsia/engine/browser/frame_impl.cc ++++ b/fuchsia/engine/browser/frame_impl.cc +@@ -346,8 +346,7 @@ bool FrameImpl::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const mojom::CreateNewWindowParams& params) { + // Specify a generous upper bound for unacknowledged popup windows, so that we + // can catch bad client behavior while not interfering with normal operation. + constexpr size_t kMaxPendingWebContentsCount = 10; +diff --git a/fuchsia/engine/browser/frame_impl.h b/fuchsia/engine/browser/frame_impl.h +index 7027e5ee119d52ce9a43a1901beb18a81161512c..f20f7b169b10f68b8e0a3115a058378c6058e229 100644 +--- a/fuchsia/engine/browser/frame_impl.h ++++ b/fuchsia/engine/browser/frame_impl.h +@@ -198,8 +198,7 @@ class FrameImpl : public fuchsia::web::Frame, + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override; ++ const mojom::CreateNewWindowParams& params) override; + void WebContentsCreated(content::WebContents* source_contents, + int opener_render_process_id, + int opener_render_frame_id, +diff --git a/headless/lib/browser/headless_web_contents_impl.cc b/headless/lib/browser/headless_web_contents_impl.cc +index ebe323930ac3298b182c08e6b78c635e785ea4fa..5c1846d948cd6394252a8c94790aa3094f8c9c33 100644 +--- a/headless/lib/browser/headless_web_contents_impl.cc ++++ b/headless/lib/browser/headless_web_contents_impl.cc +@@ -164,8 +164,7 @@ class HeadlessWebContentsImpl::Delegate : public content::WebContentsDelegate { + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override { ++ const mojom::CreateNewWindowParams& params) override { + return headless_web_contents_->browser_context() + ->options() + ->block_new_web_contents(); +diff --git a/ui/views/controls/webview/web_dialog_view.cc b/ui/views/controls/webview/web_dialog_view.cc +index 39a58363e717a0b9d71b7e3ca26578f81f8fb453..4372cf1e383ef3884048fb2a06c72888abcac9e3 100644 +--- a/ui/views/controls/webview/web_dialog_view.cc ++++ b/ui/views/controls/webview/web_dialog_view.cc +@@ -427,8 +427,7 @@ bool WebDialogView::IsWebContentsCreationOverridden( + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) { ++ const content::mojom::CreateNewWindowParams& params) { + if (delegate_) + return delegate_->HandleShouldOverrideWebContentsCreation(); + return false; +diff --git a/ui/views/controls/webview/web_dialog_view.h b/ui/views/controls/webview/web_dialog_view.h +index 97142bc886e1bbf05871fb6603342ed0cd15dcf8..94206bb4674e696093a5cfc027281254c3bf37ff 100644 +--- a/ui/views/controls/webview/web_dialog_view.h ++++ b/ui/views/controls/webview/web_dialog_view.h +@@ -153,8 +153,7 @@ class WEBVIEW_EXPORT WebDialogView : public ClientView, + content::SiteInstance* source_site_instance, + content::mojom::WindowContainerType window_container_type, + const GURL& opener_url, +- const std::string& frame_name, +- const GURL& target_url) override; ++ const content::mojom::CreateNewWindowParams& params) override; + void RequestMediaAccessPermission( + content::WebContents* web_contents, + const content::MediaStreamRequest& request, diff --git a/patches/chromium/fix_route_mouse_event_navigations_through_the_web_contents_delegate.patch b/patches/chromium/fix_route_mouse_event_navigations_through_the_web_contents_delegate.patch index 39b351002c8..3a425464539 100644 --- a/patches/chromium/fix_route_mouse_event_navigations_through_the_web_contents_delegate.patch +++ b/patches/chromium/fix_route_mouse_event_navigations_through_the_web_contents_delegate.patch @@ -13,7 +13,7 @@ This patch can be removed once app.allowRendererProcessReuse is forced to true as then Chromiums assumptions around processes become correct. diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc -index e099a6cc44e73da8b24603fd86b72139f0735fb2..2f207fda8afb44901d2027cb2ec1da1de826521f 100644 +index ea458219bd182bd9ff21b203c282e7d0738049dd..1455dfcae9e1ded39d351f6569919f0c313e3de5 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -3012,11 +3012,13 @@ bool WebContentsImpl::HandleMouseEvent(const blink::WebMouseEvent& event) { diff --git a/patches/chromium/refactor_expose_cursor_changes_to_the_webcontentsobserver.patch b/patches/chromium/refactor_expose_cursor_changes_to_the_webcontentsobserver.patch index 90bd9f78d2d..8495e30f2d7 100644 --- a/patches/chromium/refactor_expose_cursor_changes_to_the_webcontentsobserver.patch +++ b/patches/chromium/refactor_expose_cursor_changes_to_the_webcontentsobserver.patch @@ -43,10 +43,10 @@ index ffe62ea1114943d1535a806fa515122c47072372..f5f851e4852b045555d5832b7ec72be9 void RenderWidgetHostImpl::OnCursorVisibilityStateChanged(bool is_visible) { diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc -index 22cba415650311acd16750e7ad0956ca71aa75e8..eff997916d5dd833d66d3fa9738dee1fe55075f8 100644 +index 37d02e3c950bdb0a1e0f9ef20be71c24fdbde10d..2a80aaa5d4421bf2b3a46cc1674256944b33a1fa 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc -@@ -4123,6 +4123,12 @@ bool WebContentsImpl::OnUpdateDragCursor() { +@@ -4125,6 +4125,12 @@ bool WebContentsImpl::OnUpdateDragCursor() { browser_plugin_embedder_->OnUpdateDragCursor(); } diff --git a/patches/chromium/web_contents.patch b/patches/chromium/web_contents.patch index 7215f50a9ae..1e965cf0b3e 100644 --- a/patches/chromium/web_contents.patch +++ b/patches/chromium/web_contents.patch @@ -9,7 +9,7 @@ is needed for OSR. Originally landed in https://github.com/electron/libchromiumcontent/pull/226. diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc -index 2f207fda8afb44901d2027cb2ec1da1de826521f..22cba415650311acd16750e7ad0956ca71aa75e8 100644 +index 1455dfcae9e1ded39d351f6569919f0c313e3de5..37d02e3c950bdb0a1e0f9ef20be71c24fdbde10d 100644 --- a/content/browser/web_contents/web_contents_impl.cc +++ b/content/browser/web_contents/web_contents_impl.cc @@ -2744,6 +2744,12 @@ void WebContentsImpl::Init(const WebContents::CreateParams& params) { diff --git a/shell/browser/api/electron_api_session.cc b/shell/browser/api/electron_api_session.cc index 29f4d76f9cf..eb981f37961 100644 --- a/shell/browser/api/electron_api_session.cc +++ b/shell/browser/api/electron_api_session.cc @@ -784,14 +784,13 @@ void Session::CreateInterruptedDownload(const gin_helper::Dictionary& options) { length, last_modified, etag, base::Time::FromDoubleT(start_time))); } -void Session::SetPreloads( - const std::vector& preloads) { +void Session::SetPreloads(const std::vector& preloads) { auto* prefs = SessionPreferences::FromBrowserContext(browser_context()); DCHECK(prefs); prefs->set_preloads(preloads); } -std::vector Session::GetPreloads() const { +std::vector Session::GetPreloads() const { auto* prefs = SessionPreferences::FromBrowserContext(browser_context()); DCHECK(prefs); return prefs->preloads(); diff --git a/shell/browser/api/electron_api_session.h b/shell/browser/api/electron_api_session.h index 5c7aba99fd1..64acdca2950 100644 --- a/shell/browser/api/electron_api_session.h +++ b/shell/browser/api/electron_api_session.h @@ -115,8 +115,8 @@ class Session : public gin::Wrappable, const std::string& uuid); void DownloadURL(const GURL& url); void CreateInterruptedDownload(const gin_helper::Dictionary& options); - void SetPreloads(const std::vector& preloads); - std::vector GetPreloads() const; + void SetPreloads(const std::vector& preloads); + std::vector GetPreloads() const; v8::Local Cookies(v8::Isolate* isolate); v8::Local Protocol(v8::Isolate* isolate); v8::Local ServiceWorkerContext(v8::Isolate* isolate); diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index f201ec4fba5..2e041fc5ead 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -639,7 +639,11 @@ void WebContents::InitWithSessionAndOptions( prefs->caret_blink_interval = *interval; // Save the preferences in C++. - new WebContentsPreferences(web_contents(), options); + // If there's already a WebContentsPreferences object, we created it as part + // of the webContents.setWindowOpenHandler path, so don't overwrite it. + if (!WebContentsPreferences::From(web_contents())) { + new WebContentsPreferences(web_contents(), options); + } // Trigger re-calculation of webkit prefs. web_contents()->NotifyPreferencesChanged(); @@ -778,18 +782,44 @@ void WebContents::WebContentsCreatedWithFullParams( tracker->referrer = params.referrer.To(); tracker->raw_features = params.raw_features; tracker->body = params.body; + + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + + gin_helper::Dictionary dict; + gin::ConvertFromV8(isolate, pending_child_web_preferences_.Get(isolate), + &dict); + pending_child_web_preferences_.Reset(); + + // Associate the preferences passed in via `setWindowOpenHandler` with the + // content::WebContents that was just created for the child window. These + // preferences will be picked up by the RenderWidgetHost via its call to the + // delegate's OverrideWebkitPrefs. + new WebContentsPreferences(new_contents, dict); } bool WebContents::IsWebContentsCreationOverridden( content::SiteInstance* source_site_instance, content::mojom::WindowContainerType window_container_type, const GURL& opener_url, - const std::string& frame_name, - const GURL& target_url) { - if (Emit("-will-add-new-contents", target_url, frame_name)) { - return true; - } - return false; + const content::mojom::CreateNewWindowParams& params) { + bool default_prevented = Emit("-will-add-new-contents", params.target_url, + params.frame_name, params.raw_features); + // If the app prevented the default, redirect to CreateCustomWebContents, + // which always returns nullptr, which will result in the window open being + // prevented (window.open() will return null in the renderer). + return default_prevented; +} + +void WebContents::SetNextChildWebPreferences( + const gin_helper::Dictionary preferences) { + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + v8::Locker locker(isolate); + v8::HandleScope handle_scope(isolate); + // Store these prefs for when Chrome calls WebContentsCreatedWithFullParams + // with the new child contents. + pending_child_web_preferences_.Reset(isolate, preferences.GetHandle()); } content::WebContents* WebContents::CreateCustomWebContents( @@ -1076,7 +1106,7 @@ void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) { if (web_contents()->GetRenderViewHost() == render_view_host) { // When the RVH that has been deleted is the current RVH it means that the // the web contents are being closed. This is communicated by this event. - // Currently tracked by guest-window-manager.js to destroy the + // Currently tracked by guest-window-manager.ts to destroy the // BrowserWindow. Emit("current-render-view-deleted", render_view_host->GetProcess()->GetID()); @@ -2738,11 +2768,11 @@ void WebContents::DoGetZoomLevel(DoGetZoomLevelCallback callback) { std::move(callback).Run(GetZoomLevel()); } -std::vector WebContents::GetPreloadPaths() const { +std::vector WebContents::GetPreloadPaths() const { auto result = SessionPreferences::GetValidPreloads(GetBrowserContext()); if (auto* web_preferences = WebContentsPreferences::From(web_contents())) { - base::FilePath::StringType preload; + base::FilePath preload; if (web_preferences->GetPreloadPath(&preload)) { result.emplace_back(preload); } @@ -3017,6 +3047,8 @@ v8::Local WebContents::FillObjectTemplate( .SetMethod("_getPrinters", &WebContents::GetPrinterList) .SetMethod("_printToPDF", &WebContents::PrintToPDF) #endif + .SetMethod("_setNextChildWebPreferences", + &WebContents::SetNextChildWebPreferences) .SetMethod("addWorkSpace", &WebContents::AddWorkSpace) .SetMethod("removeWorkSpace", &WebContents::RemoveWorkSpace) .SetMethod("showDefinitionForSelection", diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h index d61bbfc2a82..b2caa90f2e0 100644 --- a/shell/browser/api/electron_api_web_contents.h +++ b/shell/browser/api/electron_api_web_contents.h @@ -252,6 +252,8 @@ class WebContents : public gin::Wrappable, v8::Local PrintToPDF(base::DictionaryValue settings); #endif + void SetNextChildWebPreferences(const gin_helper::Dictionary); + // DevTools workspace api. void AddWorkSpace(gin::Arguments* args, const base::FilePath& path); void RemoveWorkSpace(gin::Arguments* args, const base::FilePath& path); @@ -354,7 +356,7 @@ class WebContents : public gin::Wrappable, const scoped_refptr& body); // Returns the preload script path of current WebContents. - std::vector GetPreloadPaths() const; + std::vector GetPreloadPaths() const; // Returns the web preferences of current WebContents. v8::Local GetWebPreferences(v8::Isolate* isolate) const; @@ -456,8 +458,7 @@ class WebContents : public gin::Wrappable, content::SiteInstance* source_site_instance, content::mojom::WindowContainerType window_container_type, const GURL& opener_url, - const std::string& frame_name, - const GURL& target_url) override; + const content::mojom::CreateNewWindowParams& params) override; content::WebContents* CreateCustomWebContents( content::RenderFrameHost* opener, content::SiteInstance* source_site_instance, @@ -679,6 +680,8 @@ class WebContents : public gin::Wrappable, // Observers of this WebContents. base::ObserverList observers_; + v8::Global pending_child_web_preferences_; + bool initially_shown_ = true; service_manager::BinderRegistryWithArgs registry_; diff --git a/shell/browser/electron_browser_client.cc b/shell/browser/electron_browser_client.cc index 2d9701e38e5..891035e02cc 100644 --- a/shell/browser/electron_browser_client.cc +++ b/shell/browser/electron_browser_client.cc @@ -225,12 +225,6 @@ void BindNetworkHintsHandler( NetworkHintsHandlerImpl::Create(frame_host, std::move(receiver)); } -#if defined(OS_WIN) -const base::FilePath::StringPieceType kPathDelimiter = FILE_PATH_LITERAL(";"); -#else -const base::FilePath::StringPieceType kPathDelimiter = FILE_PATH_LITERAL(":"); -#endif - #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) // Used by the GetPrivilegeRequiredByUrl() and GetProcessPrivilege() functions // below. Extension, and isolated apps require different privileges to be @@ -610,13 +604,21 @@ void ElectronBrowserClient::OverrideWebkitPrefs( ? blink::mojom::PreferredColorScheme::kDark : blink::mojom::PreferredColorScheme::kLight; + auto* web_contents = content::WebContents::FromRenderViewHost(host); + auto preloads = + SessionPreferences::GetValidPreloads(web_contents->GetBrowserContext()); + if (!preloads.empty()) + prefs->preloads = preloads; + if (CanUseCustomSiteInstance()) + prefs->disable_electron_site_instance_overrides = true; + SetFontDefaults(prefs); // Custom preferences of guest page. - auto* web_contents = content::WebContents::FromRenderViewHost(host); auto* web_preferences = WebContentsPreferences::From(web_contents); - if (web_preferences) + if (web_preferences) { web_preferences->OverrideWebkitPrefs(prefs); + } } void ElectronBrowserClient::SetCanUseCustomSiteInstance(bool should_disable) { @@ -803,16 +805,6 @@ void ElectronBrowserClient::AppendExtraCommandLineSwitches( if (web_preferences) web_preferences->AppendCommandLineSwitches( command_line, IsRendererSubFrame(process_id)); - auto preloads = SessionPreferences::GetValidPreloads( - web_contents->GetBrowserContext()); - if (!preloads.empty()) - command_line->AppendSwitchNative( - switches::kPreloadScripts, - base::JoinString(preloads, kPathDelimiter)); - if (CanUseCustomSiteInstance()) { - command_line->AppendSwitch( - switches::kDisableElectronSiteInstanceOverrides); - } } } } diff --git a/shell/browser/event_emitter_mixin.h b/shell/browser/event_emitter_mixin.h index 2270dfdc128..7221899e959 100644 --- a/shell/browser/event_emitter_mixin.h +++ b/shell/browser/event_emitter_mixin.h @@ -21,6 +21,7 @@ template class EventEmitterMixin { public: // this.emit(name, new Event(), args...); + // Returns true if event.preventDefault() was called during processing. template bool Emit(base::StringPiece name, Args&&... args) { v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); diff --git a/shell/browser/session_preferences.cc b/shell/browser/session_preferences.cc index 0d05526909b..2cdce00d2fa 100644 --- a/shell/browser/session_preferences.cc +++ b/shell/browser/session_preferences.cc @@ -25,13 +25,13 @@ SessionPreferences* SessionPreferences::FromBrowserContext( } // static -std::vector SessionPreferences::GetValidPreloads( +std::vector SessionPreferences::GetValidPreloads( content::BrowserContext* context) { - std::vector result; + std::vector result; if (auto* self = FromBrowserContext(context)) { for (const auto& preload : self->preloads()) { - if (base::FilePath(preload).IsAbsolute()) { + if (preload.IsAbsolute()) { result.emplace_back(preload); } else { LOG(ERROR) << "preload script must have absolute path: " << preload; diff --git a/shell/browser/session_preferences.h b/shell/browser/session_preferences.h index 914f9d7b440..958f4789d30 100644 --- a/shell/browser/session_preferences.h +++ b/shell/browser/session_preferences.h @@ -17,24 +17,22 @@ class SessionPreferences : public base::SupportsUserData::Data { public: static SessionPreferences* FromBrowserContext( content::BrowserContext* context); - static std::vector GetValidPreloads( + static std::vector GetValidPreloads( content::BrowserContext* context); explicit SessionPreferences(content::BrowserContext* context); ~SessionPreferences() override; - void set_preloads(const std::vector& preloads) { + void set_preloads(const std::vector& preloads) { preloads_ = preloads; } - const std::vector& preloads() const { - return preloads_; - } + const std::vector& preloads() const { return preloads_; } private: // The user data key. static int kLocatorKey; - std::vector preloads_; + std::vector preloads_; }; } // namespace electron diff --git a/shell/browser/web_contents_preferences.cc b/shell/browser/web_contents_preferences.cc index ba0a27cdbb5..3561997a7a5 100644 --- a/shell/browser/web_contents_preferences.cc +++ b/shell/browser/web_contents_preferences.cc @@ -241,22 +241,22 @@ bool WebContentsPreferences::GetPreference(base::StringPiece name, return GetAsString(&preference_, name, value); } -bool WebContentsPreferences::GetPreloadPath( - base::FilePath::StringType* path) const { +bool WebContentsPreferences::GetPreloadPath(base::FilePath* path) const { DCHECK(path); - base::FilePath::StringType preload; - if (GetAsString(&preference_, options::kPreloadScript, &preload)) { - if (base::FilePath(preload).IsAbsolute()) { + base::FilePath::StringType preload_path; + if (GetAsString(&preference_, options::kPreloadScript, &preload_path)) { + base::FilePath preload(preload_path); + if (preload.IsAbsolute()) { *path = std::move(preload); return true; } else { LOG(ERROR) << "preload script must have absolute path."; } - } else if (GetAsString(&preference_, options::kPreloadURL, &preload)) { + } else if (GetAsString(&preference_, options::kPreloadURL, &preload_path)) { // Translate to file path if there is "preload-url" option. - base::FilePath preload_path; - if (net::FileURLToFilePath(GURL(preload), &preload_path)) { - *path = std::move(preload_path.value()); + base::FilePath preload; + if (net::FileURLToFilePath(GURL(preload_path), &preload)) { + *path = std::move(preload); return true; } else { LOG(ERROR) << "preload url must be file:// protocol."; @@ -287,36 +287,16 @@ WebContentsPreferences* WebContentsPreferences::From( void WebContentsPreferences::AppendCommandLineSwitches( base::CommandLine* command_line, bool is_subframe) { - // Check if plugins are enabled. - if (IsEnabled(options::kPlugins)) - command_line->AppendSwitch(switches::kEnablePlugins); - // Experimental flags. if (IsEnabled(options::kExperimentalFeatures)) command_line->AppendSwitch( ::switches::kEnableExperimentalWebPlatformFeatures); - // Check if we have node integration specified. - if (IsEnabled(options::kNodeIntegration)) - command_line->AppendSwitch(switches::kNodeIntegration); - - // Whether to enable node integration in Worker. - if (IsEnabled(options::kNodeIntegrationInWorker)) - command_line->AppendSwitch(switches::kNodeIntegrationInWorker); - - // Check if webview tag creation is enabled, default to nodeIntegration value. - if (IsEnabled(options::kWebviewTag)) - command_line->AppendSwitch(switches::kWebviewTag); - // Sandbox can be enabled for renderer processes hosting cross-origin frames // unless nodeIntegrationInSubFrames is enabled bool can_sandbox_frame = is_subframe && !IsEnabled(options::kNodeIntegrationInSubFrames); - // If the `sandbox` option was passed to the BrowserWindow's webPreferences, - // pass `--enable-sandbox` to the renderer so it won't have any node.js - // integration. Otherwise disable Chromium sandbox, unless app.enableSandbox() - // was called. if (IsEnabled(options::kSandbox) || can_sandbox_frame) { command_line->AppendSwitch(switches::kEnableSandbox); } else if (!command_line->HasSwitch(switches::kEnableSandbox)) { @@ -324,15 +304,6 @@ void WebContentsPreferences::AppendCommandLineSwitches( command_line->AppendSwitch(::switches::kNoZygote); } - // Check if nativeWindowOpen is enabled. - if (IsEnabled(options::kNativeWindowOpen)) - command_line->AppendSwitch(switches::kNativeWindowOpen); - - // The preload script. - base::FilePath::StringType preload; - if (GetPreloadPath(&preload)) - command_line->AppendSwitchNative(switches::kPreloadScript, preload); - // Custom args for renderer process auto* customArgs = preference_.FindKeyOfType(options::kCustomArgs, base::Value::Type::LIST); @@ -343,46 +314,13 @@ void WebContentsPreferences::AppendCommandLineSwitches( } } -#if BUILDFLAG(ENABLE_REMOTE_MODULE) - // Whether to enable the remote module - if (IsEnabled(options::kEnableRemoteModule, false)) - command_line->AppendSwitch(switches::kEnableRemoteModule); -#endif - - // Run Electron APIs and preload script in isolated world - if (IsEnabled(options::kContextIsolation)) - command_line->AppendSwitch(switches::kContextIsolation); - - if (IsEnabled(options::kWorldSafeExecuteJavaScript)) - command_line->AppendSwitch(switches::kWorldSafeExecuteJavaScript); - - // --background-color. - std::string s; - if (GetAsString(&preference_, options::kBackgroundColor, &s)) { - command_line->AppendSwitchASCII(switches::kBackgroundColor, s); - } else if (!IsEnabled(options::kOffscreen)) { - // For non-OSR WebContents, we expect to have white background, see - // https://github.com/electron/electron/issues/13764 for more. - command_line->AppendSwitchASCII(switches::kBackgroundColor, "#fff"); - } - // --offscreen + // TODO(loc): Offscreen is duplicated in WebPreferences because it's needed + // earlier than we can get WebPreferences at the moment. if (IsEnabled(options::kOffscreen)) { command_line->AppendSwitch(options::kOffscreen); } - // --guest-instance-id, which is used to identify guest WebContents. - int guest_instance_id = 0; - if (GetAsInteger(&preference_, options::kGuestInstanceID, &guest_instance_id)) - command_line->AppendSwitchASCII(switches::kGuestInstanceID, - base::NumberToString(guest_instance_id)); - - // Pass the opener's window id. - int opener_id; - if (GetAsInteger(&preference_, options::kOpenerID, &opener_id)) - command_line->AppendSwitchASCII(switches::kOpenerID, - base::NumberToString(opener_id)); - #if defined(OS_MAC) // Enable scroll bounce. if (IsEnabled(options::kScrollBounce)) @@ -402,6 +340,7 @@ void WebContentsPreferences::AppendCommandLineSwitches( } } + std::string s; // Enable blink features. if (GetAsString(&preference_, options::kEnableBlinkFeatures, &s)) command_line->AppendSwitchASCII(::switches::kEnableBlinkFeatures, s); @@ -410,39 +349,8 @@ void WebContentsPreferences::AppendCommandLineSwitches( if (GetAsString(&preference_, options::kDisableBlinkFeatures, &s)) command_line->AppendSwitchASCII(::switches::kDisableBlinkFeatures, s); - if (guest_instance_id) { - // Webview `document.visibilityState` tracks window visibility so we need - // to let it know if the window happens to be hidden right now. - auto* manager = WebViewManager::GetWebViewManager(web_contents_); - if (manager) { - auto* embedder = manager->GetEmbedder(guest_instance_id); - if (embedder) { - auto* relay = NativeWindowRelay::FromWebContents(embedder); - if (relay) { - auto* window = relay->GetNativeWindow(); - if (window) { - const bool visible = window->IsVisible() && !window->IsMinimized(); - if (!visible) { - command_line->AppendSwitch(switches::kHiddenPage); - } - } - } - } - } - } - - if (IsEnabled(options::kNodeIntegrationInSubFrames)) - command_line->AppendSwitch(switches::kNodeIntegrationInSubFrames); - -#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) - if (IsEnabled(options::kSpellcheck)) { - command_line->AppendSwitch(switches::kEnableSpellcheck); - } -#endif - - // Whether to allow the WebSQL api - if (IsEnabled(options::kEnableWebSQL)) - command_line->AppendSwitch(switches::kEnableWebSQL); + if (IsEnabled(options::kNodeIntegrationInWorker)) + command_line->AppendSwitch(switches::kNodeIntegrationInWorker); // We are appending args to a webContents so let's save the current state // of our preferences object so that during the lifetime of the WebContents @@ -507,6 +415,87 @@ void WebContentsPreferences::OverrideWebkitPrefs( if (GetAsString(&preference_, "defaultEncoding", &encoding)) prefs->default_encoding = encoding; + // --background-color. + std::string color; + if (GetAsString(&preference_, options::kBackgroundColor, &color)) { + prefs->background_color = color; + } else if (!IsEnabled(options::kOffscreen)) { + prefs->background_color = "#fff"; + } + + // Pass the opener's window id. + int opener_id; + if (GetAsInteger(&preference_, options::kOpenerID, &opener_id)) + prefs->opener_id = opener_id; + + // Run Electron APIs and preload script in isolated world + prefs->context_isolation = IsEnabled(options::kContextIsolation); + +#if BUILDFLAG(ENABLE_REMOTE_MODULE) + // Whether to enable the remote module + prefs->enable_remote_module = IsEnabled(options::kEnableRemoteModule, false); +#endif + + prefs->world_safe_execute_javascript = + IsEnabled(options::kWorldSafeExecuteJavaScript); + + int guest_instance_id = 0; + if (GetAsInteger(&preference_, options::kGuestInstanceID, &guest_instance_id)) + prefs->guest_instance_id = guest_instance_id; + + prefs->hidden_page = false; + if (guest_instance_id) { + // Webview `document.visibilityState` tracks window visibility so we need + // to let it know if the window happens to be hidden right now. + auto* manager = WebViewManager::GetWebViewManager(web_contents_); + if (manager) { + auto* embedder = manager->GetEmbedder(guest_instance_id); + if (embedder) { + auto* relay = NativeWindowRelay::FromWebContents(embedder); + if (relay) { + auto* window = relay->GetNativeWindow(); + if (window) { + const bool visible = window->IsVisible() && !window->IsMinimized(); + if (!visible) { + prefs->hidden_page = true; + } + } + } + } + } + } + + prefs->offscreen = IsEnabled(options::kOffscreen); + + // The preload script. + GetPreloadPath(&prefs->preload); + + // Check if nativeWindowOpen is enabled. + prefs->native_window_open = IsEnabled(options::kNativeWindowOpen); + + // Check if we have node integration specified. + prefs->node_integration = IsEnabled(options::kNodeIntegration); + + // Whether to enable node integration in Worker. + prefs->node_integration_in_worker = + IsEnabled(options::kNodeIntegrationInWorker); + + prefs->node_integration_in_sub_frames = + IsEnabled(options::kNodeIntegrationInSubFrames); + +#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) + prefs->enable_spellcheck = IsEnabled(options::kSpellcheck); +#endif + + // Check if plugins are enabled. + prefs->enable_plugins = IsEnabled(options::kPlugins); + + // Check if webview tag creation is enabled, default to nodeIntegration value. + prefs->webview_tag = IsEnabled(options::kWebviewTag); + + // Whether to allow the WebSQL api + prefs->enable_websql = IsEnabled(options::kEnableWebSQL); + std::string v8_cache_options; if (GetAsString(&preference_, "v8CacheOptions", &v8_cache_options)) { if (v8_cache_options == "none") { diff --git a/shell/browser/web_contents_preferences.h b/shell/browser/web_contents_preferences.h index bb1f5b5036f..15eda7396a2 100644 --- a/shell/browser/web_contents_preferences.h +++ b/shell/browser/web_contents_preferences.h @@ -59,7 +59,7 @@ class WebContentsPreferences bool GetPreference(base::StringPiece name, std::string* value) const; // Returns the preload script path. - bool GetPreloadPath(base::FilePath::StringType* path) const; + bool GetPreloadPath(base::FilePath* path) const; // Returns the web preferences. base::Value* preference() { return &preference_; } diff --git a/shell/browser/web_view_guest_delegate.cc b/shell/browser/web_view_guest_delegate.cc index c92337114ea..45a6fcaeccf 100644 --- a/shell/browser/web_view_guest_delegate.cc +++ b/shell/browser/web_view_guest_delegate.cc @@ -40,6 +40,10 @@ void WebViewGuestDelegate::AttachToIframe( content::WebContents* guest_web_contents = api_web_contents_->web_contents(); + // Force a refresh of the webPreferences so that OverrideWebkitPrefs runs on + // the new web contents before the renderer process initializes. + // guest_web_contents->NotifyPreferencesChanged(); + // Attach this inner WebContents |guest_web_contents| to the outer // WebContents |embedder_web_contents|. The outer WebContents's // frame |embedder_frame| hosts the inner WebContents. diff --git a/shell/common/options_switches.cc b/shell/common/options_switches.cc index 510896db52a..40d5e7db5ca 100644 --- a/shell/common/options_switches.cc +++ b/shell/common/options_switches.cc @@ -109,6 +109,8 @@ const char kZoomFactor[] = "zoomFactor"; // Script that will be loaded by guest WebContents before other scripts. const char kPreloadScript[] = "preload"; +const char kPreloadScripts[] = "preloadScripts"; + // Like --preload, but the passed argument is an URL. const char kPreloadURL[] = "preloadURL"; @@ -181,6 +183,11 @@ const char kWebGL[] = "webgl"; // navigation. const char kNavigateOnDragDrop[] = "navigateOnDragDrop"; +const char kDisableElectronSiteInstanceOverrides[] = + "disableElectronSiteInstanceOverrides"; +const char kEnableNodeLeakageInRenderers[] = "enableNodeLeakageInRenderers"; +const char kHiddenPage[] = "hiddenPage"; + #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) const char kSpellcheck[] = "spellcheck"; #endif @@ -200,9 +207,6 @@ namespace switches { // Enable chromium sandbox. const char kEnableSandbox[] = "enable-sandbox"; -// Enable plugins. -const char kEnablePlugins[] = "enable-plugins"; - // Ppapi Flash path. const char kPpapiFlashPath[] = "ppapi-flash-path"; @@ -242,33 +246,11 @@ const char kAppPath[] = "app-path"; const char kEnableApiFilteringLogging[] = "enable-api-filtering-logging"; // The command line switch versions of the options. -const char kBackgroundColor[] = "background-color"; -const char kPreloadScript[] = "preload"; -const char kPreloadScripts[] = "preload-scripts"; -const char kNodeIntegration[] = "node-integration"; -const char kContextIsolation[] = "context-isolation"; -const char kWorldSafeExecuteJavaScript[] = "world-safe-execute-javascript"; -const char kGuestInstanceID[] = "guest-instance-id"; -const char kOpenerID[] = "opener-id"; const char kScrollBounce[] = "scroll-bounce"; -const char kHiddenPage[] = "hidden-page"; -const char kNativeWindowOpen[] = "native-window-open"; -const char kWebviewTag[] = "webview-tag"; -const char kDisableElectronSiteInstanceOverrides[] = - "disable-electron-site-instance-overrides"; -const char kEnableNodeLeakageInRenderers[] = "enable-node-leakage-in-renderers"; // Command switch passed to renderer process to control nodeIntegration. const char kNodeIntegrationInWorker[] = "node-integration-in-worker"; -// Command switch passed to renderer process to control whether node -// environments will be created in sub-frames. -const char kNodeIntegrationInSubFrames[] = "node-integration-in-subframes"; - -// Command switch passed to render process to control whether WebSQL api -// is allowed. -const char kEnableWebSQL[] = "enable-websql"; - // Widevine options // Path to Widevine CDM binaries. const char kWidevineCdmPath[] = "widevine-cdm-path"; @@ -294,16 +276,10 @@ const char kEnableAuthNegotiatePort[] = "enable-auth-negotiate-port"; // If set, NTLM v2 is disabled for POSIX platforms. const char kDisableNTLMv2[] = "disable-ntlm-v2"; -#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) -const char kEnableSpellcheck[] = "enable-spellcheck"; -#endif - -#if BUILDFLAG(ENABLE_REMOTE_MODULE) -const char kEnableRemoteModule[] = "enable-remote-module"; -#endif - const char kGlobalCrashKeys[] = "global-crash-keys"; +const char kEnableWebSQL[] = "enable-websql"; + } // namespace switches } // namespace electron diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h index 0dd8fc8351e..6cfbc2c44b6 100644 --- a/shell/common/options_switches.h +++ b/shell/common/options_switches.h @@ -60,6 +60,7 @@ extern const char kTrafficLightPosition[]; // WebPreferences. extern const char kZoomFactor[]; extern const char kPreloadScript[]; +extern const char kPreloadScripts[]; extern const char kPreloadURL[]; extern const char kNodeIntegration[]; extern const char kContextIsolation[]; @@ -89,6 +90,10 @@ extern const char kNavigateOnDragDrop[]; extern const char kEnableWebSQL[]; extern const char kEnablePreferredSizeMode[]; +extern const char kDisableElectronSiteInstanceOverrides[]; +extern const char kEnableNodeLeakageInRenderers[]; +extern const char kHiddenPage[]; + #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) extern const char kSpellcheck[]; #endif @@ -104,7 +109,6 @@ extern const char kEnableRemoteModule[]; namespace switches { extern const char kEnableSandbox[]; -extern const char kEnablePlugins[]; extern const char kPpapiFlashPath[]; extern const char kPpapiFlashVersion[]; extern const char kDisableHttpCache[]; @@ -119,23 +123,8 @@ extern const char kAppUserModelId[]; extern const char kAppPath[]; extern const char kEnableApiFilteringLogging[]; -extern const char kBackgroundColor[]; -extern const char kPreloadScript[]; -extern const char kPreloadScripts[]; -extern const char kNodeIntegration[]; -extern const char kContextIsolation[]; -extern const char kWorldSafeExecuteJavaScript[]; -extern const char kGuestInstanceID[]; -extern const char kOpenerID[]; extern const char kScrollBounce[]; -extern const char kHiddenPage[]; -extern const char kNativeWindowOpen[]; extern const char kNodeIntegrationInWorker[]; -extern const char kWebviewTag[]; -extern const char kNodeIntegrationInSubFrames[]; -extern const char kDisableElectronSiteInstanceOverrides[]; -extern const char kEnableNodeLeakageInRenderers[]; -extern const char kEnableWebSQL[]; extern const char kWidevineCdmPath[]; extern const char kWidevineCdmVersion[]; @@ -147,16 +136,9 @@ extern const char kAuthNegotiateDelegateWhitelist[]; extern const char kEnableAuthNegotiatePort[]; extern const char kDisableNTLMv2[]; -#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) -extern const char kEnableSpellcheck[]; -#endif - -#if BUILDFLAG(ENABLE_REMOTE_MODULE) -extern const char kEnableRemoteModule[]; -#endif - extern const char kGlobalCrashKeys[]; +extern const char kEnableWebSQL[]; } // namespace switches } // namespace electron diff --git a/shell/renderer/api/electron_api_web_frame.cc b/shell/renderer/api/electron_api_web_frame.cc index 8db19a16c7c..20134ede9ef 100644 --- a/shell/renderer/api/electron_api_web_frame.cc +++ b/shell/renderer/api/electron_api_web_frame.cc @@ -20,6 +20,7 @@ #include "shell/common/api/api.mojom.h" #include "shell/common/gin_converters/blink_converter.h" #include "shell/common/gin_converters/callback_converter.h" +#include "shell/common/gin_converters/file_path_converter.h" #include "shell/common/gin_helper/dictionary.h" #include "shell/common/gin_helper/error_thrower.h" #include "shell/common/gin_helper/promise.h" @@ -29,6 +30,7 @@ #include "shell/renderer/electron_renderer_client.h" #include "third_party/blink/public/common/page/page_zoom.h" #include "third_party/blink/public/common/web_cache/web_cache_resource_type_stats.h" +#include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "third_party/blink/public/platform/web_cache.h" #include "third_party/blink/public/platform/web_isolated_world_info.h" #include "third_party/blink/public/web/web_custom_element.h" @@ -395,6 +397,65 @@ double GetZoomFactor(gin_helper::ErrorThrower thrower, return blink::PageZoomLevelToZoomFactor(zoom_level); } +v8::Local GetWebPreference(v8::Isolate* isolate, + v8::Local window, + std::string pref_name) { + content::RenderFrame* render_frame = GetRenderFrame(window); + const auto& prefs = render_frame->GetBlinkPreferences(); + + if (pref_name == options::kPreloadScripts) { + return gin::ConvertToV8(isolate, prefs.preloads); + } else if (pref_name == options::kDisableElectronSiteInstanceOverrides) { + return gin::ConvertToV8(isolate, + prefs.disable_electron_site_instance_overrides); + } else if (pref_name == options::kBackgroundColor) { + return gin::ConvertToV8(isolate, prefs.background_color); + } else if (pref_name == options::kOpenerID) { + // NOTE: openerId is internal-only. + return gin::ConvertToV8(isolate, prefs.opener_id); + } else if (pref_name == options::kContextIsolation) { + return gin::ConvertToV8(isolate, prefs.context_isolation); +#if BUILDFLAG(ENABLE_REMOTE_MODULE) + } else if (pref_name == options::kEnableRemoteModule) { + return gin::ConvertToV8(isolate, prefs.enable_remote_module); +#endif + } else if (pref_name == options::kWorldSafeExecuteJavaScript) { + return gin::ConvertToV8(isolate, prefs.world_safe_execute_javascript); + } else if (pref_name == options::kGuestInstanceID) { + // NOTE: guestInstanceId is internal-only. + return gin::ConvertToV8(isolate, prefs.guest_instance_id); + } else if (pref_name == options::kHiddenPage) { + // NOTE: hiddenPage is internal-only. + return gin::ConvertToV8(isolate, prefs.hidden_page); + } else if (pref_name == options::kOffscreen) { + return gin::ConvertToV8(isolate, prefs.offscreen); + } else if (pref_name == options::kPreloadScript) { + 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) { + return gin::ConvertToV8(isolate, prefs.node_integration); + } else if (pref_name == options::kNodeIntegrationInWorker) { + return gin::ConvertToV8(isolate, prefs.node_integration_in_worker); + } else if (pref_name == options::kEnableNodeLeakageInRenderers) { + // NOTE: enableNodeLeakageInRenderers is internal-only. + return gin::ConvertToV8(isolate, prefs.node_leakage_in_renderers); + } else if (pref_name == options::kNodeIntegrationInSubFrames) { + return gin::ConvertToV8(isolate, true); +#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) + } else if (pref_name == options::kSpellcheck) { + return gin::ConvertToV8(isolate, prefs.enable_spellcheck); +#endif + } else if (pref_name == options::kPlugins) { + return gin::ConvertToV8(isolate, prefs.enable_plugins); + } else if (pref_name == options::kEnableWebSQL) { + return gin::ConvertToV8(isolate, prefs.enable_websql); + } else if (pref_name == options::kWebviewTag) { + return gin::ConvertToV8(isolate, prefs.webview_tag); + } + return v8::Null(isolate); +} + void SetVisualZoomLevelLimits(gin_helper::ErrorThrower thrower, v8::Local window, double min_level, @@ -574,13 +635,13 @@ v8::Local ExecuteJavaScript(gin_helper::Arguments* args, ScriptExecutionCallback::CompletionCallback completion_callback; args->GetNext(&completion_callback); - bool world_safe_exec_js = base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kWorldSafeExecuteJavaScript); + auto& prefs = render_frame->GetBlinkPreferences(); render_frame->GetWebFrame()->RequestExecuteScriptAndReturnValue( blink::WebScriptSource(blink::WebString::FromUTF16(code)), has_user_gesture, - new ScriptExecutionCallback(std::move(promise), world_safe_exec_js, + new ScriptExecutionCallback(std::move(promise), + prefs.world_safe_execute_javascript, std::move(completion_callback))); return handle; @@ -640,8 +701,7 @@ v8::Local ExecuteJavaScriptInIsolatedWorld( blink::WebURL(GURL(url)), start_line)); } - bool world_safe_exec_js = base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kWorldSafeExecuteJavaScript); + auto& prefs = render_frame->GetBlinkPreferences(); // Debugging tip: if you see a crash stack trace beginning from this call, // then it is very likely that some exception happened when executing the @@ -649,7 +709,8 @@ v8::Local ExecuteJavaScriptInIsolatedWorld( render_frame->GetWebFrame()->RequestExecuteScriptInIsolatedWorld( world_id, &sources.front(), sources.size(), has_user_gesture, scriptExecutionType, - new ScriptExecutionCallback(std::move(promise), world_safe_exec_js, + new ScriptExecutionCallback(std::move(promise), + prefs.world_safe_execute_javascript, std::move(completion_callback))); return handle; @@ -852,6 +913,7 @@ void Initialize(v8::Local exports, dict.SetMethod("allowGuestViewElementDefinition", &AllowGuestViewElementDefinition); dict.SetMethod("getWebFrameId", &GetWebFrameId); + dict.SetMethod("getWebPreference", &GetWebPreference); dict.SetMethod("setSpellCheckProvider", &SetSpellCheckProvider); dict.SetMethod("insertText", &InsertText); dict.SetMethod("insertCSS", &InsertCSS); diff --git a/shell/renderer/content_settings_observer.cc b/shell/renderer/content_settings_observer.cc index bb3c8423171..04360134213 100644 --- a/shell/renderer/content_settings_observer.cc +++ b/shell/renderer/content_settings_observer.cc @@ -7,6 +7,7 @@ #include "base/command_line.h" #include "content/public/renderer/render_frame.h" #include "shell/common/options_switches.h" +#include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "third_party/blink/public/platform/url_conversion.h" #include "third_party/blink/public/platform/web_security_origin.h" #include "third_party/blink/public/web/web_local_frame.h" @@ -23,8 +24,10 @@ ContentSettingsObserver::~ContentSettingsObserver() = default; bool ContentSettingsObserver::AllowStorageAccessSync(StorageType storage_type) { if (storage_type == StorageType::kDatabase && - !base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kEnableWebSQL)) { + // Command line support is still relevant for extensions. + !(base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableWebSQL) || + render_frame()->GetBlinkPreferences().enable_websql)) { return false; } diff --git a/shell/renderer/electron_render_frame_observer.cc b/shell/renderer/electron_render_frame_observer.cc index 738c5b6ed91..e3a18424c55 100644 --- a/shell/renderer/electron_render_frame_observer.cc +++ b/shell/renderer/electron_render_frame_observer.cc @@ -21,6 +21,7 @@ #include "services/service_manager/public/cpp/interface_provider.h" #include "shell/common/options_switches.h" #include "shell/common/world_ids.h" +#include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "third_party/blink/public/platform/web_isolated_world_info.h" #include "third_party/blink/public/web/blink.h" #include "third_party/blink/public/web/web_document.h" @@ -64,21 +65,18 @@ void ElectronRenderFrameObserver::DidInstallConditionalFeatures( if (ShouldNotifyClient(world_id)) renderer_client_->DidCreateScriptContext(context, render_frame_); - auto* command_line = base::CommandLine::ForCurrentProcess(); - - bool use_context_isolation = renderer_client_->isolated_world(); + auto prefs = render_frame_->GetBlinkPreferences(); + bool use_context_isolation = prefs.context_isolation; // This logic matches the EXPLAINED logic in electron_renderer_client.cc // to avoid explaining it twice go check that implementation in // DidCreateScriptContext(); bool is_main_world = IsMainWorld(world_id); bool is_main_frame = render_frame_->IsMainFrame(); bool reuse_renderer_processes_enabled = - command_line->HasSwitch(switches::kDisableElectronSiteInstanceOverrides); - bool is_not_opened = - !render_frame_->GetWebFrame()->Opener() || - command_line->HasSwitch(switches::kEnableNodeLeakageInRenderers); - bool allow_node_in_sub_frames = - command_line->HasSwitch(switches::kNodeIntegrationInSubFrames); + prefs.disable_electron_site_instance_overrides; + bool is_not_opened = !render_frame_->GetWebFrame()->Opener() || + prefs.node_leakage_in_renderers; + bool allow_node_in_sub_frames = prefs.node_integration_in_sub_frames; bool should_create_isolated_context = use_context_isolation && is_main_world && (is_main_frame || allow_node_in_sub_frames) && @@ -163,10 +161,9 @@ bool ElectronRenderFrameObserver::IsIsolatedWorld(int world_id) { } bool ElectronRenderFrameObserver::ShouldNotifyClient(int world_id) { - bool allow_node_in_sub_frames = - base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kNodeIntegrationInSubFrames); - if (renderer_client_->isolated_world() && + auto prefs = render_frame_->GetBlinkPreferences(); + bool allow_node_in_sub_frames = prefs.node_integration_in_sub_frames; + if (prefs.context_isolation && (render_frame_->IsMainFrame() || allow_node_in_sub_frames)) return IsIsolatedWorld(world_id); else diff --git a/shell/renderer/electron_renderer_client.cc b/shell/renderer/electron_renderer_client.cc index a6fbf9ef414..fe9891c2469 100644 --- a/shell/renderer/electron_renderer_client.cc +++ b/shell/renderer/electron_renderer_client.cc @@ -20,6 +20,7 @@ #include "shell/common/options_switches.h" #include "shell/renderer/electron_render_frame_observer.h" #include "shell/renderer/web_worker_observer.h" +#include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "third_party/blink/public/web/web_document.h" #include "third_party/blink/public/web/web_local_frame.h" @@ -91,17 +92,15 @@ void ElectronRendererClient::DidCreateScriptContext( // TODO(zcbenz): Do not create Node environment if node integration is not // enabled. - auto* command_line = base::CommandLine::ForCurrentProcess(); - // Only load node if we are a main frame or a devtools extension // unless node support has been explicitly enabled for sub frames + auto prefs = render_frame->GetBlinkPreferences(); bool reuse_renderer_processes_enabled = - command_line->HasSwitch(switches::kDisableElectronSiteInstanceOverrides); + prefs.disable_electron_site_instance_overrides; // Consider the window not "opened" if it does not have an Opener, or if a // user has manually opted in to leaking node in the renderer bool is_not_opened = - !render_frame->GetWebFrame()->Opener() || - command_line->HasSwitch(switches::kEnableNodeLeakageInRenderers); + !render_frame->GetWebFrame()->Opener() || prefs.node_leakage_in_renderers; // Consider this the main frame if it is both a Main Frame and it wasn't // opened. We allow an opened main frame to have node if renderer process // reuse is enabled as that will correctly free node environments prevent a @@ -109,8 +108,7 @@ void ElectronRendererClient::DidCreateScriptContext( bool is_main_frame = render_frame->IsMainFrame() && (is_not_opened || reuse_renderer_processes_enabled); bool is_devtools = IsDevToolsExtension(render_frame); - bool allow_node_in_subframes = - command_line->HasSwitch(switches::kNodeIntegrationInSubFrames); + bool allow_node_in_subframes = prefs.node_integration_in_sub_frames; bool should_load_node = (is_main_frame || is_devtools || allow_node_in_subframes) && !IsWebViewFrame(renderer_context, render_frame); @@ -186,10 +184,9 @@ void ElectronRendererClient::WillReleaseScriptContext( // for existing users. // We also do this if we have disable electron site instance overrides to // avoid memory leaks - auto* command_line = base::CommandLine::ForCurrentProcess(); - if (command_line->HasSwitch(switches::kNodeIntegrationInSubFrames) || - command_line->HasSwitch( - switches::kDisableElectronSiteInstanceOverrides)) { + auto prefs = render_frame->GetBlinkPreferences(); + if (prefs.node_integration_in_sub_frames || + prefs.disable_electron_site_instance_overrides) { node::FreeEnvironment(env); if (env == node_bindings_->uv_env()) node::FreeIsolateData(node_bindings_->isolate_data()); @@ -213,6 +210,8 @@ bool ElectronRendererClient::ShouldFork(blink::WebLocalFrame* frame, void ElectronRendererClient::WorkerScriptReadyForEvaluationOnWorkerThread( v8::Local context) { + // TODO(loc): Note that this will not be correct for in-process child windows + // with webPreferences that have a different value for nodeIntegrationInWorker if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kNodeIntegrationInWorker)) { WebWorkerObserver::GetCurrent()->WorkerScriptReadyForEvaluation(context); @@ -221,6 +220,8 @@ void ElectronRendererClient::WorkerScriptReadyForEvaluationOnWorkerThread( void ElectronRendererClient::WillDestroyWorkerContextOnWorkerThread( v8::Local context) { + // TODO(loc): Note that this will not be correct for in-process child windows + // with webPreferences that have a different value for nodeIntegrationInWorker if (base::CommandLine::ForCurrentProcess()->HasSwitch( switches::kNodeIntegrationInWorker)) { WebWorkerObserver::GetCurrent()->ContextWillDestroy(context); @@ -230,8 +231,9 @@ void ElectronRendererClient::WillDestroyWorkerContextOnWorkerThread( void ElectronRendererClient::SetupMainWorldOverrides( v8::Handle context, content::RenderFrame* render_frame) { + auto prefs = render_frame->GetBlinkPreferences(); // We only need to run the isolated bundle if webview is enabled - if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kWebviewTag)) + if (!prefs.webview_tag) return; // Setup window overrides in the main world context // Wrap the bundle into a function that receives the isolatedWorld as diff --git a/shell/renderer/electron_sandboxed_renderer_client.cc b/shell/renderer/electron_sandboxed_renderer_client.cc index 963e6e173e3..bba81eb28f5 100644 --- a/shell/renderer/electron_sandboxed_renderer_client.cc +++ b/shell/renderer/electron_sandboxed_renderer_client.cc @@ -19,6 +19,7 @@ #include "shell/common/node_util.h" #include "shell/common/options_switches.h" #include "shell/renderer/electron_render_frame_observer.h" +#include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "third_party/blink/public/web/blink.h" #include "third_party/blink/public/web/web_document.h" #include "third_party/electron_node/src/node_binding.h" @@ -198,8 +199,7 @@ void ElectronSandboxedRendererClient::DidCreateScriptContext( bool is_devtools = IsDevTools(render_frame) || IsDevToolsExtension(render_frame); bool allow_node_in_sub_frames = - base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kNodeIntegrationInSubFrames); + render_frame->GetBlinkPreferences().node_integration_in_sub_frames; bool should_load_preload = (is_main_frame || is_devtools || allow_node_in_sub_frames) && !IsWebViewFrame(context, render_frame); @@ -232,8 +232,9 @@ void ElectronSandboxedRendererClient::DidCreateScriptContext( void ElectronSandboxedRendererClient::SetupMainWorldOverrides( v8::Handle context, content::RenderFrame* render_frame) { + auto prefs = render_frame->GetBlinkPreferences(); // We only need to run the isolated bundle if webview is enabled - if (!base::CommandLine::ForCurrentProcess()->HasSwitch(switches::kWebviewTag)) + if (!prefs.webview_tag) return; // Setup window overrides in the main world context diff --git a/shell/renderer/renderer_client_base.cc b/shell/renderer/renderer_client_base.cc index abb8d8778e7..46d0941b189 100644 --- a/shell/renderer/renderer_client_base.cc +++ b/shell/renderer/renderer_client_base.cc @@ -31,6 +31,7 @@ #include "shell/renderer/electron_api_service_impl.h" #include "shell/renderer/electron_autofill_agent.h" #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h" +#include "third_party/blink/public/common/web_preferences/web_preferences.h" #include "third_party/blink/public/web/blink.h" #include "third_party/blink/public/web/web_custom_element.h" // NOLINT(build/include_alpha) #include "third_party/blink/public/web/web_frame_widget.h" @@ -112,8 +113,6 @@ RendererClientBase::RendererClientBase() { ParseSchemesCLISwitch(command_line, switches::kStreamingSchemes); for (const std::string& scheme : streaming_schemes_list) media::AddStreamingScheme(scheme.c_str()); - isolated_world_ = base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kContextIsolation); // We rely on the unique process host id which is notified to the // renderer process via command line switch from the content layer, // if this switch is removed from the content layer for some reason, @@ -135,9 +134,8 @@ void RendererClientBase::DidCreateScriptContext( global.SetHidden("contextId", context_id); #if BUILDFLAG(ENABLE_REMOTE_MODULE) - auto* command_line = base::CommandLine::ForCurrentProcess(); bool enableRemoteModule = - command_line->HasSwitch(switches::kEnableRemoteModule); + render_frame->GetBlinkPreferences().enable_remote_module; global.SetHidden("enableRemoteModule", enableRemoteModule); #endif } @@ -153,6 +151,8 @@ void RendererClientBase::RenderThreadStarted() { // On macOS, popup menus are rendered by the main process by default. // This causes problems in OSR, since when the popup is rendered separately, // it won't be captured in the rendered image. + // TODO(loc): This will be wrong for in-process child windows, as this + // function won't run again for them. if (command_line->HasSwitch(options::kOffscreen)) { blink::WebView::SetUseExternalPopupMenus(false); } @@ -177,8 +177,7 @@ void RendererClientBase::RenderThreadStarted() { #endif #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) - if (command_line->HasSwitch(switches::kEnableSpellcheck)) - spellcheck_ = std::make_unique(this); + spellcheck_ = std::make_unique(this); #endif blink::WebCustomElement::AddEmbedderCustomElementName("webview"); @@ -274,11 +273,11 @@ void RendererClientBase::RenderFrameCreated( if (render_frame->IsMainFrame() && render_view) { blink::WebView* webview = render_view->GetWebView(); if (webview) { - base::CommandLine* cmd = base::CommandLine::ForCurrentProcess(); - if (cmd->HasSwitch(switches::kGuestInstanceID)) { // webview. + auto prefs = render_frame->GetBlinkPreferences(); + if (prefs.guest_instance_id) { // webview. webview->SetBaseBackgroundColor(SK_ColorTRANSPARENT); } else { // normal window. - std::string name = cmd->GetSwitchValueASCII(switches::kBackgroundColor); + std::string name = prefs.background_color; SkColor color = name.empty() ? SK_ColorTRANSPARENT : ParseHexColor(name); webview->SetBaseBackgroundColor(color); @@ -300,8 +299,7 @@ void RendererClientBase::RenderFrameCreated( #endif #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) - auto* command_line = base::CommandLine::ForCurrentProcess(); - if (command_line->HasSwitch(switches::kEnableSpellcheck)) + if (render_frame->GetBlinkPreferences().enable_spellcheck) new SpellCheckProvider(render_frame, spellcheck_.get(), this); #endif } @@ -330,12 +328,11 @@ bool RendererClientBase::OverrideCreatePlugin( content::RenderFrame* render_frame, const blink::WebPluginParams& params, blink::WebPlugin** plugin) { - base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); if (params.mime_type.Utf8() == content::kBrowserPluginMimeType || #if BUILDFLAG(ENABLE_PDF_VIEWER) params.mime_type.Utf8() == kPdfPluginMimeType || #endif // BUILDFLAG(ENABLE_PDF_VIEWER) - command_line->HasSwitch(switches::kEnablePlugins)) + render_frame->GetBlinkPreferences().enable_plugins) return false; *plugin = nullptr; @@ -444,7 +441,9 @@ void RendererClientBase::RunScriptsAtDocumentEnd( v8::Local RendererClientBase::GetContext( blink::WebLocalFrame* frame, v8::Isolate* isolate) const { - if (isolated_world()) + auto* render_frame = content::RenderFrame::FromWebFrame(frame); + DCHECK(render_frame); + if (render_frame && render_frame->GetBlinkPreferences().context_isolation) return frame->WorldScriptContext(isolate, WorldIDs::ISOLATED_WORLD_ID); else return frame->MainWorldScriptContext(); diff --git a/shell/renderer/renderer_client_base.h b/shell/renderer/renderer_client_base.h index 433626dfa10..ff751ee36f2 100644 --- a/shell/renderer/renderer_client_base.h +++ b/shell/renderer/renderer_client_base.h @@ -79,7 +79,6 @@ class RendererClientBase : public content::ContentRendererClient std::unique_ptr CreatePrescientNetworking( content::RenderFrame* render_frame) override; - bool isolated_world() const { return isolated_world_; } // Get the context that the Electron API is running in. v8::Local GetContext(blink::WebLocalFrame* frame, @@ -143,7 +142,6 @@ class RendererClientBase : public content::ContentRendererClient #if defined(WIDEVINE_CDM_AVAILABLE) ChromeKeySystemsProvider key_systems_provider_; #endif - bool isolated_world_; std::string renderer_client_id_; // An increasing ID used for identifying an V8 context in this process. int64_t next_context_id_ = 0; diff --git a/spec-main/api-browser-view-spec.ts b/spec-main/api-browser-view-spec.ts index 15709cb031e..e6813e31cef 100644 --- a/spec-main/api-browser-view-spec.ts +++ b/spec-main/api-browser-view-spec.ts @@ -225,16 +225,16 @@ describe('BrowserView module', () => { }); describe('window.open()', () => { - it('works in BrowserView', async () => { + it('works in BrowserView', (done) => { view = new BrowserView(); w.setBrowserView(view); - const newWindow = emittedOnce(view.webContents, 'new-window'); - view.webContents.once('new-window', event => event.preventDefault()); + view.webContents.setWindowOpenHandler(({ url, frameName }) => { + expect(url).to.equal('http://host/'); + expect(frameName).to.equal('host'); + done(); + return { action: 'deny' }; + }); view.webContents.loadFile(path.join(fixtures, 'pages', 'window-open.html')); - const [, url, frameName,,, additionalFeatures] = await newWindow; - expect(url).to.equal('http://host/'); - expect(frameName).to.equal('host'); - expect(additionalFeatures[0]).to.equal('this-is-not-a-standard-feature'); }); }); }); diff --git a/spec-main/api-browser-window-affinity-spec.ts b/spec-main/api-browser-window-affinity-spec.ts index e8f4b5dc924..23d2af93b5e 100644 --- a/spec-main/api-browser-window-affinity-spec.ts +++ b/spec-main/api-browser-window-affinity-spec.ts @@ -114,7 +114,7 @@ describe('BrowserWindow with affinity module', () => { ]); await closeWindow(w, { assertNotWindows: false }); }); - it('disables node integration when first window is false', async () => { + it('allows nodeIntegration to enable in second window with the same affinity', async () => { const [, w1] = await Promise.all([ testNodeIntegration(false), createWindowWithWebPrefs({ @@ -124,7 +124,7 @@ describe('BrowserWindow with affinity module', () => { }) ]); const [, w2] = await Promise.all([ - testNodeIntegration(false), + testNodeIntegration(true), createWindowWithWebPrefs({ affinity: affinityWithNodeTrue, preload, @@ -149,7 +149,7 @@ describe('BrowserWindow with affinity module', () => { await closeWindow(w, { assertNotWindows: false }); }); - it('enables node integration when first window is true', async () => { + it('allows nodeIntegration to disable in second window with the same affinity', async () => { const [, w1] = await Promise.all([ testNodeIntegration(true), createWindowWithWebPrefs({ @@ -159,7 +159,7 @@ describe('BrowserWindow with affinity module', () => { }) ]); const [, w2] = await Promise.all([ - testNodeIntegration(true), + testNodeIntegration(false), createWindowWithWebPrefs({ affinity: affinityWithNodeFalse, preload, diff --git a/spec-main/api-browser-window-spec.ts b/spec-main/api-browser-window-spec.ts index 741b51a6a4f..5c7eb8ebf03 100644 --- a/spec-main/api-browser-window-spec.ts +++ b/spec-main/api-browser-window-spec.ts @@ -2175,20 +2175,27 @@ describe('BrowserWindow module', () => { it('should open windows in same domain with cross-scripting enabled', async () => { const w = new BrowserWindow({ - show: false, + show: true, webPreferences: { sandbox: true, preload } }); - w.webContents.once('new-window', (event, url, frameName, disposition, options) => { - options.webPreferences!.preload = preload; - }); + + w.webContents.setWindowOpenHandler(() => ({ + action: 'allow', + overrideBrowserWindowOptions: { + webPreferences: { + preload + } + } + })); + const htmlPath = path.join(__dirname, 'fixtures', 'api', 'sandbox.html?window-open'); const pageUrl = 'file://' + htmlPath; const answer = emittedOnce(ipcMain, 'answer'); w.loadURL(pageUrl); - const [, url, frameName, , options] = await emittedOnce(w.webContents, 'new-window'); + const [, { url, frameName, options }] = await emittedOnce(w.webContents, 'did-create-window'); const expectedUrl = process.platform === 'win32' ? 'file:///' + htmlPath.replace(/\\/g, '/') : pageUrl; @@ -2202,16 +2209,22 @@ describe('BrowserWindow module', () => { it('should open windows in another domain with cross-scripting disabled', async () => { const w = new BrowserWindow({ - show: false, + show: true, webPreferences: { sandbox: true, preload } }); - w.webContents.once('new-window', (event, url, frameName, disposition, options) => { - options.webPreferences!.preload = preload; - }); + w.webContents.setWindowOpenHandler(() => ({ + action: 'allow', + overrideBrowserWindowOptions: { + webPreferences: { + preload + } + } + })); + w.loadFile( path.join(__dirname, 'fixtures', 'api', 'sandbox.html'), { search: 'window-open-external' } @@ -2267,12 +2280,10 @@ describe('BrowserWindow module', () => { }); const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js'); - w.webContents.once('new-window', (event, url, frameName, disposition, options) => { - options.webPreferences!.preload = preloadPath; - }); + w.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { preload: preloadPath } } })); w.loadFile(path.join(fixtures, 'api', 'new-window.html')); - const [, args] = await emittedOnce(ipcMain, 'answer'); - expect(args).to.include('--enable-sandbox'); + const [, { argv }] = await emittedOnce(ipcMain, 'answer'); + expect(argv).to.include('--enable-sandbox'); }); it('should open windows with the options configured via new-window event listeners', async () => { @@ -2284,11 +2295,7 @@ describe('BrowserWindow module', () => { }); const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js'); - w.webContents.once('new-window', (event, url, frameName, disposition, options) => { - options.webPreferences!.preload = preloadPath; - const prefs = options.webPreferences as any; - prefs.foo = 'bar'; - }); + w.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { preload: preloadPath, foo: 'bar' } } })); w.loadFile(path.join(fixtures, 'api', 'new-window.html')); const [[, childWebContents]] = await Promise.all([ emittedOnce(app, 'web-contents-created'), @@ -2307,11 +2314,13 @@ describe('BrowserWindow module', () => { } }); let childWc: WebContents | null = null; - w.webContents.on('new-window', (event, url, frameName, disposition, options) => { - options.webPreferences!.preload = preload; - childWc = (options as any).webContents; + w.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { preload } } })); + + w.webContents.on('did-create-window', (win) => { + childWc = win.webContents; expect(w.webContents).to.not.equal(childWc); }); + ipcMain.once('parent-ready', function (event) { expect(event.sender).to.equal(w.webContents, 'sender should be the parent'); event.sender.send('verified'); @@ -2438,9 +2447,7 @@ describe('BrowserWindow module', () => { enableRemoteModule: true } }); - w.webContents.once('new-window', (event, url, frameName, disposition, options) => { - options.webPreferences!.preload = preload; - }); + w.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { preload } } })); w.loadFile(path.join(__dirname, 'fixtures', 'api', 'sandbox.html'), { search: 'reload-remote-child' }); @@ -2602,20 +2609,30 @@ describe('BrowserWindow module', () => { }); it('should inherit the nativeWindowOpen setting in opened windows', async () => { const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js'); - w.webContents.once('new-window', (event, url, frameName, disposition, options) => { - options.webPreferences!.preload = preloadPath; - }); + + w.webContents.setWindowOpenHandler(() => ({ + action: 'allow', + overrideBrowserWindowOptions: { + webPreferences: { + preload: preloadPath + } + } + })); w.loadFile(path.join(fixtures, 'api', 'new-window.html')); - const [, args] = await emittedOnce(ipcMain, 'answer'); - expect(args).to.include('--native-window-open'); + 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 () => { const preloadPath = path.join(fixtures, 'api', 'new-window-preload.js'); - w.webContents.once('new-window', (event, url, frameName, disposition, options) => { - options.webPreferences!.preload = preloadPath; - const prefs = options.webPreferences! as any; - prefs.foo = 'bar'; - }); + w.webContents.setWindowOpenHandler(() => ({ + action: 'allow', + overrideBrowserWindowOptions: { + webPreferences: { + preload: preloadPath, + foo: 'bar' + } + } + })); w.loadFile(path.join(fixtures, 'api', 'new-window.html')); const [[, childWebContents]] = await Promise.all([ emittedOnce(app, 'web-contents-created'), @@ -2655,13 +2672,19 @@ describe('BrowserWindow module', () => { } }); - w.webContents.once('new-window', (event, url, frameName, disposition, options) => { - options.webPreferences!.preload = path.join(fixtures, 'api', 'window-open-preload.js'); - }); + w.webContents.setWindowOpenHandler(() => ({ + action: 'allow', + overrideBrowserWindowOptions: { + webPreferences: { + preload: path.join(fixtures, 'api', 'window-open-preload.js') + } + } + })); + w.loadFile(path.join(fixtures, 'api', 'window-open-location-open.html')); - const [, args, typeofProcess] = await emittedOnce(ipcMain, 'answer'); - expect(args).not.to.include('--node-integration'); - expect(args).to.include('--native-window-open'); + const [, { nodeIntegration, nativeWindowOpen, typeofProcess }] = await emittedOnce(ipcMain, 'answer'); + expect(nodeIntegration).to.be.false(); + expect(nativeWindowOpen).to.be.true(); expect(typeofProcess).to.eql('undefined'); }); @@ -2675,11 +2698,16 @@ describe('BrowserWindow module', () => { } }); - w.webContents.once('new-window', (event, url, frameName, disposition, options) => { - options.webPreferences!.preload = path.join(fixtures, 'api', 'window-open-preload.js'); - }); + w.webContents.setWindowOpenHandler(() => ({ + action: 'allow', + overrideBrowserWindowOptions: { + webPreferences: { + preload: path.join(fixtures, 'api', 'window-open-preload.js') + } + } + })); w.loadFile(path.join(fixtures, 'api', 'window-open-location-open.html')); - const [, , , windowOpenerIsNull] = await emittedOnce(ipcMain, 'answer'); + const [, { windowOpenerIsNull }] = await emittedOnce(ipcMain, 'answer'); expect(windowOpenerIsNull).to.be.false('window.opener is null'); }); }); diff --git a/spec-main/chromium-spec.ts b/spec-main/chromium-spec.ts index f534fca6717..586f1a9101a 100644 --- a/spec-main/chromium-spec.ts +++ b/spec-main/chromium-spec.ts @@ -82,15 +82,19 @@ describe('window.postMessage', () => { await closeAllWindows(); }); - it('sets the source and origin correctly', async () => { - const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } }); - w.loadURL(`file://${fixturesPath}/pages/window-open-postMessage-driver.html`); - const [, message] = await emittedOnce(ipcMain, 'complete'); - expect(message.data).to.equal('testing'); - expect(message.origin).to.equal('file://'); - expect(message.sourceEqualsOpener).to.equal(true); - expect(message.eventOrigin).to.equal('file://'); - }); + for (const nativeWindowOpen of [true, false]) { + describe(`when nativeWindowOpen: ${nativeWindowOpen}`, () => { + it('sets the source and origin correctly', async () => { + const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nativeWindowOpen } }); + w.loadURL(`file://${fixturesPath}/pages/window-open-postMessage-driver.html`); + const [, message] = await emittedOnce(ipcMain, 'complete'); + expect(message.data).to.equal('testing'); + expect(message.origin).to.equal('file://'); + expect(message.sourceEqualsOpener).to.equal(true); + expect(message.eventOrigin).to.equal('file://'); + }); + }); + } }); describe('focus handling', () => { @@ -620,7 +624,7 @@ describe('chromium features', () => { describe('window.open', () => { for (const show of [true, false]) { - it(`inherits parent visibility over parent {show=${show}} option`, async () => { + it(`shows the child regardless of parent visibility when parent {show=${show}}`, async () => { const w = new BrowserWindow({ show }); // toggle visibility @@ -635,7 +639,7 @@ describe('chromium features', () => { const newWindow = emittedOnce(w.webContents, 'new-window'); w.loadFile(path.join(fixturesPath, 'pages', 'window-open.html')); const [,,,, options] = await newWindow; - expect(options.show).to.equal(w.isVisible()); + expect(options.show).to.equal(true); }); } @@ -671,35 +675,6 @@ describe('chromium features', () => { expect(preferences.javascript).to.be.false(); }); - it('handles cycles when merging the parent options into the child options', async () => { - const foo = {} as any; - foo.bar = foo; - foo.baz = { - hello: { - world: true - } - }; - foo.baz2 = foo.baz; - const w = new BrowserWindow({ show: false, foo: foo } as any); - - w.loadFile(path.join(fixturesPath, 'pages', 'window-open.html')); - const [,,,, options] = await emittedOnce(w.webContents, 'new-window'); - expect(options.show).to.be.false(); - expect((options as any).foo).to.deep.equal({ - bar: undefined, - baz: { - hello: { - world: true - } - }, - baz2: { - hello: { - world: true - } - } - }); - }); - it('defines a window.location getter', async () => { let targetURL: string; if (process.platform === 'win32') { @@ -925,10 +900,15 @@ describe('chromium features', () => { 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`; it(description, async () => { - const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, nativeWindowOpen } }); - w.webContents.once('new-window', (e, url, frameName, disposition, options) => { - options!.webPreferences!.sandbox = sandboxPopup; - }); + const w = new BrowserWindow({ show: true, webPreferences: { nodeIntegration: true, nativeWindowOpen } }); + w.webContents.setWindowOpenHandler(() => ({ + action: 'allow', + overrideBrowserWindowOptions: { + webPreferences: { + sandbox: sandboxPopup + } + } + })); await w.loadURL(parent); const childOpenerLocation = await w.webContents.executeJavaScript(`new Promise(resolve => { window.addEventListener('message', function f(e) { diff --git a/spec-main/fixtures/snapshots/native-window-open.snapshot.txt b/spec-main/fixtures/snapshots/native-window-open.snapshot.txt index 95e94c60001..944073f4f1a 100644 --- a/spec-main/fixtures/snapshots/native-window-open.snapshot.txt +++ b/spec-main/fixtures/snapshots/native-window-open.snapshot.txt @@ -2,33 +2,33 @@ [ "top=5,left=10,resizable=no", { - "sender": "[WebContents]", - "returnValue": "placeholder-guest-contents-id" + "sender": "[WebContents]" }, "about:blank", "frame name", "new-window", { - "show": true, "width": 800, - "height": 600, - "webContents": "[WebContents]", "title": "frame name", + "backgroundColor": "blue", + "focusable": false, "webPreferences": { "nativeWindowOpen": true, "sandbox": true, "backgroundColor": "blue", "nodeIntegration": false, "webviewTag": false, - "nodeIntegrationInSubFrames": false + "nodeIntegrationInSubFrames": false, + "openerId": null }, + "show": true, + "height": 600, "top": 5, "left": 10, "resizable": false, "x": 10, "y": 5, - "backgroundColor": "blue", - "focusable": false + "webContents": "[WebContents]" }, [], { @@ -40,32 +40,32 @@ [ "zoomFactor=2,resizable=0,x=0,y=10", { - "sender": "[WebContents]", - "returnValue": "placeholder-guest-contents-id" + "sender": "[WebContents]" }, "about:blank", "frame name", "new-window", { - "show": true, "width": 800, - "height": 600, - "webContents": "[WebContents]", "title": "frame name", + "backgroundColor": "blue", + "focusable": false, "webPreferences": { - "zoomFactor": "2", "nativeWindowOpen": true, "sandbox": true, "backgroundColor": "blue", + "zoomFactor": "2", "nodeIntegration": false, "webviewTag": false, - "nodeIntegrationInSubFrames": false + "nodeIntegrationInSubFrames": false, + "openerId": null }, + "show": true, + "height": 600, "resizable": false, "x": 0, "y": 10, - "backgroundColor": "blue", - "focusable": false + "webContents": "[WebContents]" }, [], { @@ -77,30 +77,30 @@ [ "backgroundColor=gray,webPreferences=0,x=100,y=100", { - "sender": "[WebContents]", - "returnValue": "placeholder-guest-contents-id" + "sender": "[WebContents]" }, "about:blank", "frame name", "new-window", { - "show": true, "width": 800, - "height": 600, - "webContents": "[WebContents]", "title": "frame name", + "backgroundColor": "gray", + "focusable": false, "webPreferences": { "nativeWindowOpen": true, "sandbox": true, "backgroundColor": "gray", "nodeIntegration": false, "webviewTag": false, - "nodeIntegrationInSubFrames": false + "nodeIntegrationInSubFrames": false, + "openerId": null }, - "backgroundColor": "gray", + "show": true, + "height": 600, "x": 100, "y": 100, - "focusable": false + "webContents": "[WebContents]" }, [], { @@ -112,30 +112,30 @@ [ "x=50,y=20,title=sup", { - "sender": "[WebContents]", - "returnValue": "placeholder-guest-contents-id" + "sender": "[WebContents]" }, "about:blank", "frame name", "new-window", { - "show": true, "width": 800, - "height": 600, - "webContents": "[WebContents]", "title": "sup", + "backgroundColor": "blue", + "focusable": false, "webPreferences": { "nativeWindowOpen": true, "sandbox": true, "backgroundColor": "blue", "nodeIntegration": false, "webviewTag": false, - "nodeIntegrationInSubFrames": false + "nodeIntegrationInSubFrames": false, + "openerId": null }, + "show": true, + "height": 600, "x": 50, "y": 20, - "backgroundColor": "blue", - "focusable": false + "webContents": "[WebContents]" }, [], { @@ -147,32 +147,32 @@ [ "show=false,top=1,left=1", { - "sender": "[WebContents]", - "returnValue": "placeholder-guest-contents-id" + "sender": "[WebContents]" }, "about:blank", "frame name", "new-window", { - "show": false, "width": 800, - "height": 600, - "webContents": "[WebContents]", "title": "frame name", + "backgroundColor": "blue", + "focusable": false, "webPreferences": { "nativeWindowOpen": true, "sandbox": true, "backgroundColor": "blue", "nodeIntegration": false, "webviewTag": false, - "nodeIntegrationInSubFrames": false + "nodeIntegrationInSubFrames": false, + "openerId": null }, + "show": false, + "height": 600, "top": 1, "left": 1, "x": 1, "y": 1, - "backgroundColor": "blue", - "focusable": false + "webContents": "[WebContents]" }, [], { diff --git a/spec-main/fixtures/snapshots/proxy-window-open.snapshot.txt b/spec-main/fixtures/snapshots/proxy-window-open.snapshot.txt index 66020217d7f..84a69d21643 100644 --- a/spec-main/fixtures/snapshots/proxy-window-open.snapshot.txt +++ b/spec-main/fixtures/snapshots/proxy-window-open.snapshot.txt @@ -9,21 +9,22 @@ "frame name", "new-window", { + "show": true, + "title": "frame name", + "width": 800, + "height": 600, "top": 5, "left": 10, "resizable": false, "x": 10, "y": 5, - "title": "frame name", "webPreferences": { "nodeIntegration": false, "webviewTag": false, "nodeIntegrationInSubFrames": false, "openerId": "placeholder-opener-id" }, - "width": 800, - "height": 600, - "show": false + "webContents": "[WebContents]" }, [], { @@ -42,10 +43,13 @@ "frame name", "new-window", { + "show": true, + "title": "frame name", + "width": 800, + "height": 600, "resizable": false, "x": 0, "y": 10, - "title": "frame name", "webPreferences": { "zoomFactor": "2", "nodeIntegration": false, @@ -53,9 +57,7 @@ "nodeIntegrationInSubFrames": false, "openerId": "placeholder-opener-id" }, - "width": 800, - "height": 600, - "show": false + "webContents": "[WebContents]" }, [], { @@ -74,6 +76,10 @@ "frame name", "new-window", { + "show": true, + "title": "frame name", + "width": 800, + "height": 600, "backgroundColor": "gray", "webPreferences": { "nodeIntegration": false, @@ -84,10 +90,7 @@ }, "x": 100, "y": 100, - "title": "frame name", - "width": 800, - "height": 600, - "show": false + "webContents": "[WebContents]" }, [], { @@ -106,18 +109,19 @@ "frame name", "new-window", { + "show": true, + "title": "sup", + "width": 800, + "height": 600, "x": 50, "y": 20, - "title": "sup", "webPreferences": { "nodeIntegration": false, "webviewTag": false, "nodeIntegrationInSubFrames": false, "openerId": "placeholder-opener-id" }, - "width": 800, - "height": 600, - "show": false + "webContents": "[WebContents]" }, [], { @@ -137,19 +141,20 @@ "new-window", { "show": false, + "title": "frame name", + "width": 800, + "height": 600, "top": 1, "left": 1, "x": 1, "y": 1, - "title": "frame name", "webPreferences": { "nodeIntegration": false, "webviewTag": false, "nodeIntegrationInSubFrames": false, "openerId": "placeholder-opener-id" }, - "width": 800, - "height": 600 + "webContents": "[WebContents]" }, [], { diff --git a/spec-main/guest-window-manager-spec.ts b/spec-main/guest-window-manager-spec.ts index c8b9f216f50..41e91d84b34 100644 --- a/spec-main/guest-window-manager-spec.ts +++ b/spec-main/guest-window-manager-spec.ts @@ -1,15 +1,16 @@ import { BrowserWindow } from 'electron'; import { writeFileSync, readFileSync } from 'fs'; import { resolve } from 'path'; -import { expect } from 'chai'; +import { expect, assert } from 'chai'; import { closeAllWindows } from './window-helpers'; +const { emittedOnce } = require('./events-helpers'); function genSnapshot (browserWindow: BrowserWindow, features: string) { return new Promise((resolve) => { browserWindow.webContents.on('new-window', (...args: any[]) => { resolve([features, ...args]); }); - browserWindow.webContents.executeJavaScript(`window.open('about:blank', 'frame name', '${features}')`); + browserWindow.webContents.executeJavaScript(`window.open('about:blank', 'frame name', '${features}') && true`); }); } @@ -52,7 +53,7 @@ describe('new-window event', () => { beforeEach((done) => { browserWindow = new BrowserWindow(browserWindowOptions); browserWindow.loadURL('about:blank'); - browserWindow.on('ready-to-show', () => done()); + browserWindow.on('ready-to-show', () => { done(); }); }); afterEach(closeAllWindows); @@ -86,6 +87,100 @@ describe('new-window event', () => { } }); +describe('webContents.setWindowOpenHandler', () => { + const testConfig = { + native: { + browserWindowOptions: { + show: false, + webPreferences: { + nativeWindowOpen: true + } + } + }, + proxy: { + browserWindowOptions: { + show: false, + webPreferences: { + nativeWindowOpen: false + } + } + } + }; + + for (const testName of Object.keys(testConfig) as (keyof typeof testConfig)[]) { + let browserWindow: BrowserWindow; + const { browserWindowOptions } = testConfig[testName]; + + describe(testName, () => { + beforeEach((done) => { + browserWindow = new BrowserWindow(browserWindowOptions); + browserWindow.loadURL('about:blank'); + browserWindow.on('ready-to-show', () => { browserWindow.show(); done(); }); + }); + + afterEach(closeAllWindows); + + it('does not fire window creation events if an override returns action: deny', (done) => { + browserWindow.webContents.setWindowOpenHandler(() => ({ 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') && true"); + + setTimeout(() => { + done(); + }, 500); + }); + + it('fires handler with correct params', (done) => { + const testFrameName = 'test-frame-name'; + const testFeatures = 'top=10&left=10&something-unknown'; + const testUrl = 'app://does-not-exist/'; + browserWindow.webContents.setWindowOpenHandler(({ url, frameName, features }) => { + expect(url).to.equal(testUrl); + expect(frameName).to.equal(testFrameName); + expect(features).to.equal(testFeatures); + done(); + return { action: 'deny' }; + }); + + browserWindow.webContents.executeJavaScript(`window.open('${testUrl}', '${testFrameName}', '${testFeatures}') && true`); + }); + + 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') && 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') && true"); + }); + }); + } +}); + function stringifySnapshots (snapshots: any, pretty = false) { return JSON.stringify(snapshots, (key, value) => { if (['sender', 'webContents'].includes(key)) { diff --git a/spec/fixtures/api/new-window-preload.js b/spec/fixtures/api/new-window-preload.js index 46b6cce25bd..b0f17aa20d6 100644 --- a/spec/fixtures/api/new-window-preload.js +++ b/spec/fixtures/api/new-window-preload.js @@ -1,4 +1,7 @@ -const { ipcRenderer } = require('electron'); +const { ipcRenderer, webFrame } = require('electron'); -ipcRenderer.send('answer', process.argv); +ipcRenderer.send('answer', { + nativeWindowOpen: webFrame.getWebPreference('nativeWindowOpen'), + argv: process.argv +}); window.close(); diff --git a/spec/fixtures/api/window-open-preload.js b/spec/fixtures/api/window-open-preload.js index 035a0bcfcc0..28fc272eb59 100644 --- a/spec/fixtures/api/window-open-preload.js +++ b/spec/fixtures/api/window-open-preload.js @@ -1,9 +1,15 @@ -const { ipcRenderer } = require('electron'); +const { ipcRenderer, webFrame } = require('electron'); setImmediate(function () { if (window.location.toString() === 'bar://page/') { const windowOpenerIsNull = window.opener == null; - ipcRenderer.send('answer', process.argv, typeof global.process, windowOpenerIsNull); + ipcRenderer.send('answer', { + nativeWindowOpen: webFrame.getWebPreference('nativeWindowOpen'), + nodeIntegration: webFrame.getWebPreference('nodeIntegration'), + sandbox: webFrame.getWebPreference('sandbox'), + typeofProcess: typeof global.process, + windowOpenerIsNull + }); window.close(); } }); diff --git a/spec/fixtures/pages/visibilitychange.html b/spec/fixtures/pages/visibilitychange.html index 9f49f520de1..0d4f07c46f9 100644 --- a/spec/fixtures/pages/visibilitychange.html +++ b/spec/fixtures/pages/visibilitychange.html @@ -4,7 +4,9 @@ const {ipcRenderer} = require('electron') ipcRenderer.send('pong', document.visibilityState, document.hidden) document.addEventListener('visibilitychange', function () { - ipcRenderer.send('pong', document.visibilityState, document.hidden) + setTimeout(() => { + ipcRenderer.send('pong', document.visibilityState, document.hidden) + }, 500); }) diff --git a/spec/fixtures/test.asar/deleteme/a.asar b/spec/fixtures/test.asar/deleteme/a.asar deleted file mode 100644 index 0b74f5639cd8a5eee06fb7d8dcb9e58b6f5fa7a5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 778 zcmb7C+YW*-3|)**dd-`|_|Q=frRVf?W5(ES z#8`&0#%LjzN1-8YA(J&X&mJ&IWDqT{0@6hQWu9x%KnhV9LKjde{qw0}VJl)skBCFp zVHYGOnnA?T(zbOG^TbxTh+`6el`j=dS0sO t7@Vw diff --git a/typings/internal-ambient.d.ts b/typings/internal-ambient.d.ts index 7ab163479b1..9cb2ea179da 100644 --- a/typings/internal-ambient.d.ts +++ b/typings/internal-ambient.d.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-var */ declare var internalBinding: any; declare var nodeProcess: any; declare var isolatedWorld: any; @@ -45,8 +46,8 @@ declare namespace NodeJS { clearWeaklyTrackedValues(): void; getWeaklyTrackedValues(): any[]; addRemoteObjectRef(contextId: string, id: number): void; + isSameOrigin(a: string, b: string): boolean; triggerFatalErrorForTesting(): void; - isSameOrigin(left: string, right: string): boolean; } interface EnvironmentBinding { @@ -118,7 +119,7 @@ declare namespace NodeJS { session?: Electron.Session; partition?: string; referrer?: string; - } + }; type ResponseHead = { statusCode: number; statusMessage: string; diff --git a/typings/internal-electron.d.ts b/typings/internal-electron.d.ts index 83598f879f4..d52bab2c666 100644 --- a/typings/internal-electron.d.ts +++ b/typings/internal-electron.d.ts @@ -64,6 +64,9 @@ declare namespace Electron { equal(other: WebContents): boolean; _initiallyShown: boolean; browserWindowOptions: BrowserWindowConstructorOptions; + _windowOpenHandler: ((opts: {url: string, frameName: string, features: string}) => any) | null; + _callWindowOpenHandler(event: any, url: string, frameName: string, rawFeatures: string): Electron.BrowserWindowConstructorOptions | null; + _setNextChildWebPreferences(prefs: Partial & Pick): void; _send(internal: boolean, sendToAll: boolean, channel: string, args: any): boolean; _sendToFrame(internal: boolean, sendToAll: boolean, frameId: number, channel: string, args: any): boolean; _sendToFrameInternal(frameId: number, channel: string, ...args: any[]): boolean; diff --git a/yarn.lock b/yarn.lock index e620d48d795..13cd504f806 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,10 +18,10 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@electron/docs-parser@^0.10.0": - version "0.10.0" - resolved "https://registry.yarnpkg.com/@electron/docs-parser/-/docs-parser-0.10.0.tgz#cc399f3847c37a38af8c13c711dc57eb07e21994" - integrity sha512-dNUNsW3tC5VWyWDD6awxDsA0Wc91PVaG4KmSBnqQmXE7bNjnEFE1hf5TaH0KOmJq1KMyQ2rbp7rrrvqRoLKP1Q== +"@electron/docs-parser@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@electron/docs-parser/-/docs-parser-0.10.1.tgz#aa5911c4ef2ec237d7a126111019ec45058088db" + integrity sha512-gDKGfc4ilPsKGCCyCCU20iJnHRV3QPYthOocgfAnzm5lOANssxLjl4KeN/DO8nTmKX/BmFsf+XGNa4Penq0L8A== dependencies: "@types/markdown-it" "^0.0.9" chai "^4.2.0"