Browse Source

feat: add webContents.setWindowOpenHandler API (#24517)

Co-authored-by: Jeremy Rose <[email protected]>
loc 4 years ago
parent
commit
0b85fdf26c
56 changed files with 2074 additions and 872 deletions
  1. 1 1
      .eslintrc.json
  2. 52 1
      docs/api/web-contents.md
  3. 90 54
      docs/api/window-open.md
  4. 16 9
      docs/tutorial/security.md
  5. 1 0
      filenames.auto.gni
  6. 90 29
      lib/browser/api/web-contents.ts
  7. 248 308
      lib/browser/guest-window-manager.ts
  8. 211 0
      lib/browser/guest-window-proxy.ts
  9. 1 1
      lib/browser/init.ts
  10. 2 0
      lib/browser/navigation-controller.ts
  11. 2 2
      lib/common/parse-features-string.ts
  12. 2 2
      lib/renderer/api/context-bridge.ts
  13. 17 15
      lib/renderer/init.ts
  14. 4 3
      lib/sandboxed_renderer/init.ts
  15. 2 2
      package.json
  16. 2 0
      patches/chromium/.patches
  17. 279 0
      patches/chromium/allow_in_process_windows_to_have_different_web_prefs.patch
  18. 21 6
      patches/chromium/can_create_window.patch
  19. 433 0
      patches/chromium/chore_provide_iswebcontentscreationoverridden_with_full_params.patch
  20. 1 1
      patches/chromium/fix_route_mouse_event_navigations_through_the_web_contents_delegate.patch
  21. 2 2
      patches/chromium/refactor_expose_cursor_changes_to_the_webcontentsobserver.patch
  22. 1 1
      patches/chromium/web_contents.patch
  23. 2 3
      shell/browser/api/electron_api_session.cc
  24. 2 2
      shell/browser/api/electron_api_session.h
  25. 42 10
      shell/browser/api/electron_api_web_contents.cc
  26. 6 3
      shell/browser/api/electron_api_web_contents.h
  27. 10 18
      shell/browser/electron_browser_client.cc
  28. 1 0
      shell/browser/event_emitter_mixin.h
  29. 3 3
      shell/browser/session_preferences.cc
  30. 4 6
      shell/browser/session_preferences.h
  31. 95 106
      shell/browser/web_contents_preferences.cc
  32. 1 1
      shell/browser/web_contents_preferences.h
  33. 4 0
      shell/browser/web_view_guest_delegate.cc
  34. 9 33
      shell/common/options_switches.cc
  35. 6 24
      shell/common/options_switches.h
  36. 68 6
      shell/renderer/api/electron_api_web_frame.cc
  37. 5 2
      shell/renderer/content_settings_observer.cc
  38. 10 13
      shell/renderer/electron_render_frame_observer.cc
  39. 14 12
      shell/renderer/electron_renderer_client.cc
  40. 4 3
      shell/renderer/electron_sandboxed_renderer_client.cc
  41. 13 14
      shell/renderer/renderer_client_base.cc
  42. 0 2
      shell/renderer/renderer_client_base.h
  43. 7 7
      spec-main/api-browser-view-spec.ts
  44. 4 4
      spec-main/api-browser-window-affinity-spec.ts
  45. 73 45
      spec-main/api-browser-window-spec.ts
  46. 24 44
      spec-main/chromium-spec.ts
  47. 41 41
      spec-main/fixtures/snapshots/native-window-open.snapshot.txt
  48. 24 19
      spec-main/fixtures/snapshots/proxy-window-open.snapshot.txt
  49. 98 3
      spec-main/guest-window-manager-spec.ts
  50. 5 2
      spec/fixtures/api/new-window-preload.js
  51. 8 2
      spec/fixtures/api/window-open-preload.js
  52. 3 1
      spec/fixtures/pages/visibilitychange.html
  53. BIN
      spec/fixtures/test.asar/deleteme/a.asar
  54. 3 2
      typings/internal-ambient.d.ts
  55. 3 0
      typings/internal-electron.d.ts
  56. 4 4
      yarn.lock

+ 1 - 1
.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"

+ 52 - 1
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 `<a target='_blank'>`.
 
@@ -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

+ 90 - 54
docs/api/window-open.md

@@ -1,18 +1,34 @@
-# `window.open` Function
-
-> Open a new window and load a URL.
-
-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.
-
-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.
-
-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.
+# Opening windows from the renderer
+
+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:
+
+- clicking on links or submitting forms adorned with `target=_blank`
+- JavaScript calling `window.open()`
+
+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`.
+
+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.
-
-### `window.opener.postMessage(message, targetOrigin)`
+  `features` will be passed to any registered `webContents`'s
+  `did-create-window` event handler in the `additionalFeatures` argument.
 
-* `message` String
-* `targetOrigin` String
+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.
 
-Sends a message to the parent window with the specified origin or `*` for no
-origin preference.
+### `BrowserWindowProxy` example
 
-### Using Chrome's `window.open()` implementation
+```javascript
 
-If you want to use Chrome's built-in `window.open()` implementation, set
-`nativeWindowOpen` to `true` in the `webPreferences` options object.
+// main.js
+const mainWindow = new BrowserWindow()
 
-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.
+mainWindow.webContents.setWindowOpenHandler(({ url }) => {
+  if (url.startsWith('https://github.com/')) {
+    return true
+  }
+  return false
+})
 
-This option can also be set on `<webview>` tags as well:
+mainWindow.webContents.on('did-create-window', (childWindow) => {
+  // For example...
+  childWindow.webContents('will-navigate', (e) => {
+    e.preventDefault()
+  })
+})
+```
 
-```html
-<webview webpreferences="nativeWindowOpen=yes"></webview>
+```javascript
+// renderer.js
+const windowProxy = window.open('https://github.com/', null, 'minimizable=false')
+windowProxy.postMessage('hi', '*')
 ```
 
-The creation of the `BrowserWindow` is customizable via `WebContents`'s
-`new-window` event.
+### Native `Window` example
 
 ```javascript
-// main process
+// 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('<h1>Hello</h1>')
+const childWindow = window.open('', 'modal')
+childWindow.document.write('<h1>Hello</h1>')
 ```

+ 16 - 9
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

+ 1 - 0
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",

+ 90 - 29
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() || {};

+ 248 - 308
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<string, Electron.BrowserWindow>();
-
-// 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<string, any>, parent: Record<string, any>, visited?: Set<Record<string, any>>) {
-  // 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<string, any>) {
-  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()
-      };
-    }
+type PostData = LoadURLOptions['postData']
+export type WindowOpenArgs = {
+  url: string,
+  frameName: string,
+  features: string,
+}
 
-    // 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());
+const frameNamesToWindow = new Map<string, BrowserWindow>();
+const registerFrameNameToGuestWindow = (name: string, win: BrowserWindow) => frameNamesToWindow.set(name, win);
+const unregisterFrameName = (name: string) => frameNamesToWindow.delete(name);
+const getGuestWindowByFrameName = (name: string) => frameNamesToWindow.get(name);
+
+/**
+ * `openGuestWindow` is called for both implementations of window.open
+ * (BrowserWindowProxy and nativeWindowOpen) to create and setup event handling
+ * for the new window.
+ *
+ * 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;
   }
 
-  // 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;
-    }
+  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)
+      })
+    });
   }
 
-  if (!webPreferences.nativeWindowOpen) {
-    // Sets correct openerId here to give correct options to 'new-window' event handler
-    options.webPreferences.openerId = embedder.id;
-  }
+  handleWindowLifecycleEvents({ embedder, frameName, guest: window });
 
-  return options;
-};
+  embedder.emit('did-create-window', window, { url, frameName, options: browserWindowOptions, disposition, additionalFeatures, referrer, postData });
 
-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;
+  return window;
 }
 
-// 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
-  };
-};
-
-// 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;
+/**
+ * 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<string, any>, 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<string, any>, 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<Event> (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}`);
-    }
-
-    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));
-};
-
-const handleMessageSync = function (channel: string, handler: (event: ElectronInternal.IpcMainInternalEvent, guestContents: Electron.webContents, ...args: any[]) => any) {
-  ipcMainUtils.handleSync(channel, makeSafeHandler(handler));
-};
-
-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}`);
-  }
+// 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
 };
 
-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}`);
-  }
-
-  return (getGuestWindow(guestContents) as any)[method](...args);
-});
+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);
 
-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);
+  const deprecatedInheritedOptions = getDeprecatedInheritedOptions(embedder);
 
-  if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) {
-    const sourceId = event.sender.id;
-    guestContents._sendInternal(IPC_MESSAGES.GUEST_WINDOW_POSTMESSAGE, sourceId, message, sourceOrigin);
-  }
-});
+  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 })
+    }
+  };
+}
 
-const webContentsMethodsAsync = new Set([
-  'loadURL',
-  'executeJavaScript',
-  'print'
-]);
+export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {}, insecureParsedWebPreferences: parsedWebPreferences = {}, useDeprecatedBehaviorForOptionInheritance = true }: {
+  embedder: WebContents,
+  insecureParsedWebPreferences?: ReturnType<typeof parseFeatures>['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;
 
-handleMessage(IPC_MESSAGES.GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD, (event, guestContents, method: string, ...args: any[]) => {
-  securityCheck(event.sender, guestContents, canAccessWindow);
+  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
+  };
+}
 
-  if (!webContentsMethodsAsync.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 (guestContents as any)[method](...args);
-});
+  const { type, show, ...inheritableOptions } = (embedder as any).browserWindowOptions;
+  return inheritableOptions;
+}
 
-const webContentsMethodsSync = new Set([
-  'getURL'
-]);
+function formatPostDataHeaders (postData: any) {
+  if (!postData) return;
 
-handleMessageSync(IPC_MESSAGES.GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD, (event, guestContents, method: string, ...args: any[]) => {
-  securityCheck(event.sender, guestContents, canAccessWindow);
+  let extraHeaders = 'content-type: application/x-www-form-urlencoded';
 
-  if (!webContentsMethodsSync.has(method)) {
-    console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`);
-    throw new Error(`Invalid method: ${method}`);
+  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
+      )}`;
+    }
   }
 
-  return (guestContents as any)[method](...args);
-});
+  return extraHeaders;
+}

+ 211 - 0
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<T, Event> = (event: Event, guestContents: Electron.WebContents, ...args: any[]) => T;
+const makeSafeHandler = function<T, Event> (handler: IpcHandler<T, Event>) {
+  return (event: Event, guestId: number, ...args: any[]) => {
+    // Access webContents via electron to prevent circular require.
+    const guestContents = webContents.fromId(guestId);
+    if (!guestContents) {
+      throw new Error(`Invalid guestId: ${guestId}`);
+    }
+
+    return handler(event, guestContents as Electron.WebContents, ...args);
+  };
+};
+
+const handleMessage = function (channel: string, handler: IpcHandler<any, Electron.IpcMainInvokeEvent>) {
+  ipcMainInternal.handle(channel, makeSafeHandler(handler));
+};
+
+const handleMessageSync = function (channel: string, handler: IpcHandler<any, ElectronInternal.IpcMainInternalEvent>) {
+  ipcMainUtils.handleSync(channel, makeSafeHandler(handler));
+};
+
+type ContentsCheck = (contents: WebContents, guestContents: WebContents) => boolean;
+const securityCheck = function (contents: WebContents, guestContents: WebContents, check: ContentsCheck) {
+  if (!check(contents, guestContents)) {
+    console.error(
+      `Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`
+    );
+    throw new Error(`Access denied to guestId: ${guestContents.id}`);
+  }
+};
+
+const windowMethods = new Set(['destroy', 'focus', 'blur']);
+
+handleMessage(
+  IPC_MESSAGES.GUEST_WINDOW_MANAGER_WINDOW_METHOD,
+  (event, guestContents, method, ...args) => {
+    securityCheck(event.sender, guestContents, canAccessWindow);
+
+    if (!windowMethods.has(method)) {
+      console.error(
+        `Blocked ${event.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);
+  }
+);

+ 1 - 1
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;

+ 2 - 0
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(() => {});

+ 2 - 2
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<BrowserWindowConstructorOptions, 'webPreferences'> & { [key: string]: CoercedValue },
+    options: parsed as Omit<BrowserWindowConstructorOptions, 'webPreferences'>,
     webPreferences,
     additionalFeatures: bareKeys
   };

+ 2 - 2
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');

+ 17 - 15
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<T> (
-  name: string, defaultValue: T, converter?: (value: string) => T
+const parseOption = function<TDefault> (
+  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) {

+ 4 - 3
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) {

+ 2 - 2
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"
   }
-}
+}

+ 2 - 0
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

+ 279 - 0
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 <[email protected]>
+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 StructTraits<blink::mojom::WebPreferencesDataView,
+       !data.ReadCursiveFontFamilyMap(&out->cursive_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 StructTraits<blink::mojom::WebPreferencesDataView,
+   out->navigate_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 <string>
+ #include <vector>
+ 
++#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<base::FilePath> 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<blink::mojom::WebPreferencesDataView,
+     return r.record_whole_document;
+   }
+ 
++  // Begin Electron-specific WebPreferences.
++  static const std::vector<base::FilePath>& 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<mojo_base.mojom.FilePath> 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
++};

+ 21 - 6
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<Referrer>(),
 diff --git a/content/common/frame.mojom b/content/common/frame.mojom
 index 703c4611691b72380423576eebdadcd23e6ae913..2be0f93b7ea3791bb776158795a44aa7422e19ac 100644
 --- a/content/common/frame.mojom

+ 433 - 0
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 <[email protected]>
+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<jobject> obj = GetJavaDelegate(env);
+   if (obj.is_null())
+     return false;
+   ScopedJavaLocalRef<jstring> 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<content::WebContents> 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<WebContentsImpl*>(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,

+ 1 - 1
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) {

+ 2 - 2
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();
  }
  

+ 1 - 1
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) {

+ 2 - 3
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<base::FilePath::StringType>& preloads) {
+void Session::SetPreloads(const std::vector<base::FilePath>& preloads) {
   auto* prefs = SessionPreferences::FromBrowserContext(browser_context());
   DCHECK(prefs);
   prefs->set_preloads(preloads);
 }
 
-std::vector<base::FilePath::StringType> Session::GetPreloads() const {
+std::vector<base::FilePath> Session::GetPreloads() const {
   auto* prefs = SessionPreferences::FromBrowserContext(browser_context());
   DCHECK(prefs);
   return prefs->preloads();

+ 2 - 2
shell/browser/api/electron_api_session.h

@@ -115,8 +115,8 @@ class Session : public gin::Wrappable<Session>,
                                      const std::string& uuid);
   void DownloadURL(const GURL& url);
   void CreateInterruptedDownload(const gin_helper::Dictionary& options);
-  void SetPreloads(const std::vector<base::FilePath::StringType>& preloads);
-  std::vector<base::FilePath::StringType> GetPreloads() const;
+  void SetPreloads(const std::vector<base::FilePath>& preloads);
+  std::vector<base::FilePath> GetPreloads() const;
   v8::Local<v8::Value> Cookies(v8::Isolate* isolate);
   v8::Local<v8::Value> Protocol(v8::Isolate* isolate);
   v8::Local<v8::Value> ServiceWorkerContext(v8::Isolate* isolate);

+ 42 - 10
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<content::Referrer>();
   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<base::FilePath::StringType> WebContents::GetPreloadPaths() const {
+std::vector<base::FilePath> 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<v8::ObjectTemplate> 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",

+ 6 - 3
shell/browser/api/electron_api_web_contents.h

@@ -252,6 +252,8 @@ class WebContents : public gin::Wrappable<WebContents>,
   v8::Local<v8::Promise> 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<WebContents>,
                       const scoped_refptr<network::ResourceRequestBody>& body);
 
   // Returns the preload script path of current WebContents.
-  std::vector<base::FilePath::StringType> GetPreloadPaths() const;
+  std::vector<base::FilePath> GetPreloadPaths() const;
 
   // Returns the web preferences of current WebContents.
   v8::Local<v8::Value> GetWebPreferences(v8::Isolate* isolate) const;
@@ -456,8 +458,7 @@ class WebContents : public gin::Wrappable<WebContents>,
       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<WebContents>,
   // Observers of this WebContents.
   base::ObserverList<ExtendedWebContentsObserver> observers_;
 
+  v8::Global<v8::Value> pending_child_web_preferences_;
+
   bool initially_shown_ = true;
 
   service_manager::BinderRegistryWithArgs<content::RenderFrameHost*> registry_;

+ 10 - 18
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);
-      }
     }
   }
 }

+ 1 - 0
shell/browser/event_emitter_mixin.h

@@ -21,6 +21,7 @@ template <typename T>
 class EventEmitterMixin {
  public:
   // this.emit(name, new Event(), args...);
+  // Returns true if event.preventDefault() was called during processing.
   template <typename... Args>
   bool Emit(base::StringPiece name, Args&&... args) {
     v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();

+ 3 - 3
shell/browser/session_preferences.cc

@@ -25,13 +25,13 @@ SessionPreferences* SessionPreferences::FromBrowserContext(
 }
 
 // static
-std::vector<base::FilePath::StringType> SessionPreferences::GetValidPreloads(
+std::vector<base::FilePath> SessionPreferences::GetValidPreloads(
     content::BrowserContext* context) {
-  std::vector<base::FilePath::StringType> result;
+  std::vector<base::FilePath> 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;

+ 4 - 6
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<base::FilePath::StringType> GetValidPreloads(
+  static std::vector<base::FilePath> GetValidPreloads(
       content::BrowserContext* context);
 
   explicit SessionPreferences(content::BrowserContext* context);
   ~SessionPreferences() override;
 
-  void set_preloads(const std::vector<base::FilePath::StringType>& preloads) {
+  void set_preloads(const std::vector<base::FilePath>& preloads) {
     preloads_ = preloads;
   }
-  const std::vector<base::FilePath::StringType>& preloads() const {
-    return preloads_;
-  }
+  const std::vector<base::FilePath>& preloads() const { return preloads_; }
 
  private:
   // The user data key.
   static int kLocatorKey;
 
-  std::vector<base::FilePath::StringType> preloads_;
+  std::vector<base::FilePath> preloads_;
 };
 
 }  // namespace electron

+ 95 - 106
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") {

+ 1 - 1
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_; }

+ 4 - 0
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.

+ 9 - 33
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

+ 6 - 24
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

+ 68 - 6
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<v8::Value> GetWebPreference(v8::Isolate* isolate,
+                                      v8::Local<v8::Value> 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<v8::Value> window,
                               double min_level,
@@ -574,13 +635,13 @@ v8::Local<v8::Promise> 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<v8::Promise> 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<v8::Promise> 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<v8::Object> 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);

+ 5 - 2
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;
   }
 

+ 10 - 13
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

+ 14 - 12
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<v8::Context> 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<v8::Context> 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<v8::Context> 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

+ 4 - 3
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<v8::Context> 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

+ 13 - 14
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<SpellCheck>(this);
+  spellcheck_ = std::make_unique<SpellCheck>(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<v8::Context> 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();

+ 0 - 2
shell/renderer/renderer_client_base.h

@@ -79,7 +79,6 @@ class RendererClientBase : public content::ContentRendererClient
 
   std::unique_ptr<blink::WebPrescientNetworking> 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<v8::Context> 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;

+ 7 - 7
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');
     });
   });
 });

+ 4 - 4
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,

+ 73 - 45
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');
         });
       });

+ 24 - 44
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) {

+ 41 - 41
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]"
     },
     [],
     {

+ 24 - 19
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]"
     },
     [],
     {

+ 98 - 3
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)) {

+ 5 - 2
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();

+ 8 - 2
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();
   }
 });

+ 3 - 1
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);
   })
 </script>
 </body>

BIN
spec/fixtures/test.asar/deleteme/a.asar


+ 3 - 2
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;

+ 3 - 0
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<Electron.BrowserWindowConstructorOptions['webPreferences']> & Pick<Electron.BrowserWindowConstructorOptions, 'backgroundColor'>): 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;

+ 4 - 4
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"