Browse Source

feat: remove nativeWindowOpen option (#29405)

Co-authored-by: Cheng Zhao <[email protected]>
Co-authored-by: Milan Burda <[email protected]>
Jeremy Rose 3 years ago
parent
commit
d44a187d0b
39 changed files with 301 additions and 1149 deletions
  1. 0 1
      docs/README.md
  2. 0 54
      docs/api/browser-window-proxy.md
  3. 0 3
      docs/api/browser-window.md
  4. 0 14
      docs/api/structures/post-body.md
  5. 1 35
      docs/api/window-open.md
  6. 12 0
      docs/breaking-changes.md
  7. 0 2
      filenames.auto.gni
  8. 3 4
      lib/browser/api/web-contents.ts
  9. 0 1
      lib/browser/guest-view-manager.ts
  10. 11 45
      lib/browser/guest-window-manager.ts
  11. 0 213
      lib/browser/guest-window-proxy.ts
  12. 0 1
      lib/browser/init.ts
  13. 0 7
      lib/common/ipc-messages.ts
  14. 1 3
      lib/renderer/common-init.ts
  15. 1 300
      lib/renderer/window-setup.ts
  16. 10 26
      patches/chromium/allow_in-process_windows_to_have_different_web_prefs.patch
  17. 1 2
      shell/browser/api/electron_api_browser_view.cc
  18. 1 2
      shell/browser/api/electron_api_browser_window.cc
  19. 1 1
      shell/browser/electron_browser_client.cc
  20. 0 11
      shell/browser/web_contents_preferences.cc
  21. 0 3
      shell/browser/web_contents_preferences.h
  22. 0 5
      shell/common/api/electron_api_v8_util.cc
  23. 0 5
      shell/common/options_switches.cc
  24. 0 2
      shell/common/options_switches.h
  25. 0 5
      shell/renderer/api/electron_api_web_frame.cc
  26. 20 45
      spec-main/api-browser-window-spec.ts
  27. 1 1
      spec-main/api-ipc-renderer-spec.ts
  28. 3 3
      spec-main/api-web-contents-spec.ts
  29. 31 75
      spec-main/chromium-spec.ts
  30. 0 1
      spec-main/fixtures/api/new-window-preload.js
  31. 0 1
      spec-main/fixtures/api/window-open-preload.js
  32. 1 2
      spec-main/fixtures/crash-cases/setimmediate-window-open-crash/index.js
  33. 6 16
      spec-main/fixtures/snapshots/native-window-open.snapshot.txt
  34. 6 11
      spec-main/fixtures/snapshots/proxy-window-open.snapshot.txt
  35. 183 235
      spec-main/guest-window-manager-spec.ts
  36. 8 9
      spec-main/webview-spec.ts
  37. 0 1
      spec/webview-spec.js
  38. 0 3
      typings/internal-ambient.d.ts
  39. 0 1
      typings/internal-electron.d.ts

+ 0 - 1
docs/README.md

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

+ 0 - 54
docs/api/browser-window-proxy.md

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

+ 0 - 3
docs/api/browser-window.md

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

+ 0 - 14
docs/api/structures/post-body.md

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

+ 1 - 35
docs/api/window-open.md

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

+ 12 - 0
docs/breaking-changes.md

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

+ 0 - 2
filenames.auto.gni

@@ -5,7 +5,6 @@ auto_filenames = {
     "docs/api/app.md",
     "docs/api/auto-updater.md",
     "docs/api/browser-view.md",
-    "docs/api/browser-window-proxy.md",
     "docs/api/browser-window.md",
     "docs/api/client-request.md",
     "docs/api/clipboard.md",
@@ -226,7 +225,6 @@ 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",

+ 3 - 4
lib/browser/api/web-contents.ts

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

+ 0 - 1
lib/browser/guest-view-manager.ts

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

+ 11 - 45
lib/browser/guest-window-manager.ts

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

+ 0 - 213
lib/browser/guest-window-proxy.ts

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

+ 0 - 1
lib/browser/init.ts

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

+ 0 - 7
lib/common/ipc-messages.ts

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

+ 1 - 3
lib/renderer/common-init.ts

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

+ 1 - 300
lib/renderer/window-setup.ts

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

+ 10 - 26
patches/chromium/allow_in-process_windows_to_have_different_web_prefs.patch

@@ -8,21 +8,19 @@ 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 db2d0536ed7a8143a60cebf1c5d7fee1acf4d10d..6cea8d7ce6ff75ae80a4db03c25f913915624342 100644
+index db2d0536ed7a8143a60cebf1c5d7fee1acf4d10d..0cad8646e8b733a3a2d4b1076fdb0276bcd5b7bf 100644
 --- a/third_party/blink/common/web_preferences/web_preferences.cc
 +++ b/third_party/blink/common/web_preferences/web_preferences.cc
-@@ -145,6 +145,22 @@ WebPreferences::WebPreferences()
+@@ -145,6 +145,20 @@ WebPreferences::WebPreferences()
        fake_no_alloc_direct_call_for_testing_enabled(false),
        v8_cache_options(blink::mojom::V8CacheOptions::kDefault),
        record_whole_document(false),
 +      // Begin Electron-specific WebPreferences.
-+      opener_id(0),
 +      context_isolation(false),
 +      is_webview(false),
 +      hidden_page(false),
 +      offscreen(false),
 +      preload(base::FilePath::StringType()),
-+      native_window_open(false),
 +      node_integration(false),
 +      node_integration_in_worker(false),
 +      node_integration_in_sub_frames(false),
@@ -35,7 +33,7 @@ index db2d0536ed7a8143a60cebf1c5d7fee1acf4d10d..6cea8d7ce6ff75ae80a4db03c25f9139
        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 e9f2e215ee1220c66549112982df04201c68fe1a..a8e08adfdeaf3acde4d190766b65ad3fbacfdf58 100644
+index e9f2e215ee1220c66549112982df04201c68fe1a..e9118530b395dbb13180365521dcf03d9ca211e5 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
 @@ -23,6 +23,10 @@ bool StructTraits<blink::mojom::WebPreferencesDataView,
@@ -49,17 +47,15 @@ index e9f2e215ee1220c66549112982df04201c68fe1a..a8e08adfdeaf3acde4d190766b65ad3f
        !data.ReadLazyFrameLoadingDistanceThresholdsPx(
            &out->lazy_frame_loading_distance_thresholds_px) ||
        !data.ReadLazyImageLoadingDistanceThresholdsPx(
-@@ -158,6 +162,21 @@ bool StructTraits<blink::mojom::WebPreferencesDataView,
+@@ -158,6 +162,19 @@ bool StructTraits<blink::mojom::WebPreferencesDataView,
        data.fake_no_alloc_direct_call_for_testing_enabled();
    out->v8_cache_options = data.v8_cache_options();
    out->record_whole_document = data.record_whole_document();
 +  // Begin Electron-specific WebPreferences.
-+  out->opener_id = data.opener_id();
 +  out->context_isolation = data.context_isolation();
 +  out->is_webview = data.is_webview();
 +  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_integration_in_sub_frames = data.node_integration_in_sub_frames();
@@ -72,7 +68,7 @@ index e9f2e215ee1220c66549112982df04201c68fe1a..a8e08adfdeaf3acde4d190766b65ad3f
    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 0d74719b2f8fb91f094772fab96a880cc8787c77..bc447e53a87852aac03fd2487b77aa1009472d36 100644
+index 0d74719b2f8fb91f094772fab96a880cc8787c77..23126786a738c3fe83f7064bf8b597794d871ac5 100644
 --- a/third_party/blink/public/common/web_preferences/web_preferences.h
 +++ b/third_party/blink/public/common/web_preferences/web_preferences.h
 @@ -10,6 +10,7 @@
@@ -83,19 +79,17 @@ index 0d74719b2f8fb91f094772fab96a880cc8787c77..bc447e53a87852aac03fd2487b77aa10
  #include "net/nqe/effective_connection_type.h"
  #include "third_party/blink/public/common/common_export.h"
  #include "third_party/blink/public/mojom/css/preferred_color_scheme.mojom-shared.h"
-@@ -163,6 +164,24 @@ struct BLINK_COMMON_EXPORT WebPreferences {
+@@ -163,6 +164,22 @@ struct BLINK_COMMON_EXPORT WebPreferences {
    blink::mojom::V8CacheOptions v8_cache_options;
    bool record_whole_document;
  
 +  // Begin Electron-specific WebPreferences.
 +  std::vector<base::FilePath> preloads;
-+  int opener_id;
 +  bool context_isolation;
 +  bool is_webview;
 +  bool hidden_page;
 +  bool offscreen;
 +  base::FilePath preload;
-+  bool native_window_open;
 +  bool node_integration;
 +  bool node_integration_in_worker;
 +  bool node_integration_in_sub_frames;
@@ -109,7 +103,7 @@ index 0d74719b2f8fb91f094772fab96a880cc8787c77..bc447e53a87852aac03fd2487b77aa10
    // 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 e0084598921ddcb0cf2aeb33875f05da0b5457e9..90bf73d7a1f2daf921f5a0ae9e4b3c292efaaaa0 100644
+index e0084598921ddcb0cf2aeb33875f05da0b5457e9..bcda6f35ad74b2b8aa9d439155048aab7bd02b21 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 @@
@@ -120,7 +114,7 @@ index e0084598921ddcb0cf2aeb33875f05da0b5457e9..90bf73d7a1f2daf921f5a0ae9e4b3c29
  #include "mojo/public/cpp/bindings/struct_traits.h"
  #include "net/nqe/effective_connection_type.h"
  #include "third_party/blink/public/common/common_export.h"
-@@ -456,6 +457,68 @@ struct BLINK_COMMON_EXPORT StructTraits<blink::mojom::WebPreferencesDataView,
+@@ -456,6 +457,60 @@ struct BLINK_COMMON_EXPORT StructTraits<blink::mojom::WebPreferencesDataView,
      return r.record_whole_document;
    }
  
@@ -129,10 +123,6 @@ index e0084598921ddcb0cf2aeb33875f05da0b5457e9..90bf73d7a1f2daf921f5a0ae9e4b3c29
 +    return r.preloads;
 +  }
 +
-+  static int opener_id(const blink::web_pref::WebPreferences& r) {
-+    return r.opener_id;
-+  }
-+
 +  static bool context_isolation(const blink::web_pref::WebPreferences& r) {
 +    return r.context_isolation;
 +  }
@@ -153,10 +143,6 @@ index e0084598921ddcb0cf2aeb33875f05da0b5457e9..90bf73d7a1f2daf921f5a0ae9e4b3c29
 +    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;
 +  }
@@ -190,7 +176,7 @@ index e0084598921ddcb0cf2aeb33875f05da0b5457e9..90bf73d7a1f2daf921f5a0ae9e4b3c29
      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 af5ba07466b95ab61befdd3994d731ed8f7ef0a6..2623a82a333767f789da2e952adb2bc1997ca4fd 100644
+index af5ba07466b95ab61befdd3994d731ed8f7ef0a6..5ffa7ef2334da800267af3947e68477bf82f3526 100644
 --- a/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
 +++ b/third_party/blink/public/mojom/webpreferences/web_preferences.mojom
 @@ -10,6 +10,7 @@ import "third_party/blink/public/mojom/v8_cache_options.mojom";
@@ -201,19 +187,17 @@ index af5ba07466b95ab61befdd3994d731ed8f7ef0a6..2623a82a333767f789da2e952adb2bc1
  
  enum PointerType {
    kPointerNone                              = 1,             // 1 << 0
-@@ -215,6 +216,24 @@ struct WebPreferences {
+@@ -215,6 +216,22 @@ struct WebPreferences {
    V8CacheOptions v8_cache_options;
    bool record_whole_document;
  
 +  // Begin Electron-specific WebPreferences.
 +  array<mojo_base.mojom.FilePath> preloads;
-+  int32 opener_id;
 +  bool context_isolation;
 +  bool is_webview;
 +  bool hidden_page;
 +  bool offscreen;
 +  mojo_base.mojom.FilePath preload;
-+  bool native_window_open;
 +  bool node_integration;
 +  bool node_integration_in_worker;
 +  bool node_integration_in_sub_frames;

+ 1 - 2
shell/browser/api/electron_api_browser_view.cc

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

+ 1 - 2
shell/browser/api/electron_api_browser_window.cc

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

+ 1 - 1
shell/browser/electron_browser_client.cc

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

+ 0 - 11
shell/browser/web_contents_preferences.cc

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

+ 0 - 3
shell/browser/web_contents_preferences.h

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

+ 0 - 5
shell/common/api/electron_api_v8_util.cc

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

+ 0 - 5
shell/common/options_switches.cc

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

+ 0 - 2
shell/common/options_switches.h

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

+ 0 - 5
shell/renderer/api/electron_api_web_frame.cc

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

+ 20 - 45
spec-main/api-browser-window-spec.ts

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

+ 1 - 1
spec-main/api-ipc-renderer-spec.ts

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

+ 3 - 3
spec-main/api-web-contents-spec.ts

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

+ 31 - 75
spec-main/chromium-spec.ts

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

+ 0 - 1
spec/fixtures/api/new-window-preload.js → spec-main/fixtures/api/new-window-preload.js

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

+ 0 - 1
spec/fixtures/api/window-open-preload.js → spec-main/fixtures/api/window-open-preload.js

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

+ 1 - 2
spec-main/fixtures/crash-cases/setimmediate-window-open-crash/index.js

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

+ 6 - 16
spec-main/fixtures/snapshots/native-window-open.snapshot.txt

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

+ 6 - 11
spec-main/fixtures/snapshots/proxy-window-open.snapshot.txt

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

+ 183 - 235
spec-main/guest-window-manager-spec.ts

@@ -15,266 +15,220 @@ function genSnapshot (browserWindow: BrowserWindow, features: string) {
 }
 
 describe('new-window event', () => {
-  const testConfig = {
-    native: {
-      snapshotFileName: 'native-window-open.snapshot.txt',
-      browserWindowOptions: {
-        show: false,
-        width: 200,
-        title: 'cool',
-        backgroundColor: 'blue',
-        focusable: false,
-        webPreferences: {
-          nativeWindowOpen: true,
-          sandbox: true
-        }
-      }
-    },
-    proxy: {
-      snapshotFileName: 'proxy-window-open.snapshot.txt',
-      browserWindowOptions: {
-        show: false,
-        webPreferences: {
-          nativeWindowOpen: false,
-          sandbox: false
-        }
-      }
+  const snapshotFileName = 'native-window-open.snapshot.txt';
+  const browserWindowOptions = {
+    show: false,
+    width: 200,
+    title: 'cool',
+    backgroundColor: 'blue',
+    focusable: false,
+    webPreferences: {
+      sandbox: true
     }
   };
 
-  for (const testName of Object.keys(testConfig) as (keyof typeof testConfig)[]) {
-    const { snapshotFileName, browserWindowOptions } = testConfig[testName];
+  const snapshotFile = resolve(__dirname, 'fixtures', 'snapshots', snapshotFileName);
+  let browserWindow: BrowserWindow;
+  let existingSnapshots: any[];
 
-    describe(`for ${testName} window opening`, () => {
-      const snapshotFile = resolve(__dirname, 'fixtures', 'snapshots', snapshotFileName);
-      let browserWindow: BrowserWindow;
-      let existingSnapshots: any[];
-
-      before(() => {
-        existingSnapshots = parseSnapshots(readFileSync(snapshotFile, { encoding: 'utf8' }));
-      });
-
-      beforeEach((done) => {
-        browserWindow = new BrowserWindow(browserWindowOptions);
-        browserWindow.loadURL('about:blank');
-        browserWindow.on('ready-to-show', () => { done(); });
-      });
+  before(() => {
+    existingSnapshots = parseSnapshots(readFileSync(snapshotFile, { encoding: 'utf8' }));
+  });
 
-      afterEach(closeAllWindows);
-
-      const newSnapshots: any[] = [];
-      [
-        'top=5,left=10,resizable=no',
-        'zoomFactor=2,resizable=0,x=0,y=10',
-        'backgroundColor=gray,webPreferences=0,x=100,y=100',
-        'x=50,y=20,title=sup',
-        'show=false,top=1,left=1'
-      ].forEach((features, index) => {
-        /**
-         * ATTN: If this test is failing, you likely just need to change
-         * `shouldOverwriteSnapshot` to true and then evaluate the snapshot diff
-         * to see if the change is harmless.
-         */
-        it(`matches snapshot for ${features}`, async () => {
-          const newSnapshot = await genSnapshot(browserWindow, features);
-          newSnapshots.push(newSnapshot);
-          // TODO: The output when these fail could be friendlier.
-          expect(stringifySnapshots(newSnapshot)).to.equal(stringifySnapshots(existingSnapshots[index]));
-        });
-      });
+  beforeEach((done) => {
+    browserWindow = new BrowserWindow(browserWindowOptions);
+    browserWindow.loadURL('about:blank');
+    browserWindow.on('ready-to-show', () => { done(); });
+  });
 
-      after(() => {
-        const shouldOverwriteSnapshot = false;
-        if (shouldOverwriteSnapshot) writeFileSync(snapshotFile, stringifySnapshots(newSnapshots, true));
-      });
+  afterEach(closeAllWindows);
+
+  const newSnapshots: any[] = [];
+  [
+    'top=5,left=10,resizable=no',
+    'zoomFactor=2,resizable=0,x=0,y=10',
+    'backgroundColor=gray,webPreferences=0,x=100,y=100',
+    'x=50,y=20,title=sup',
+    'show=false,top=1,left=1'
+  ].forEach((features, index) => {
+    /**
+     * ATTN: If this test is failing, you likely just need to change
+     * `shouldOverwriteSnapshot` to true and then evaluate the snapshot diff
+     * to see if the change is harmless.
+     */
+    it(`matches snapshot for ${features}`, async () => {
+      const newSnapshot = await genSnapshot(browserWindow, features);
+      newSnapshots.push(newSnapshot);
+      // TODO: The output when these fail could be friendlier.
+      expect(stringifySnapshots(newSnapshot)).to.equal(stringifySnapshots(existingSnapshots[index]));
     });
-  }
+  });
+
+  after(() => {
+    const shouldOverwriteSnapshot = false;
+    if (shouldOverwriteSnapshot) writeFileSync(snapshotFile, stringifySnapshots(newSnapshots, true));
+  });
 });
 
 describe('webContents.setWindowOpenHandler', () => {
-  const testConfig = {
-    native: {
-      browserWindowOptions: {
-        show: false,
-        webPreferences: {
-          nativeWindowOpen: true
-        }
-      }
-    },
-    proxy: {
-      browserWindowOptions: {
-        show: false,
-        webPreferences: {
-          nativeWindowOpen: false
-        }
-      }
-    }
-  };
+  let browserWindow: BrowserWindow;
+  beforeEach(async () => {
+    browserWindow = new BrowserWindow({ show: false });
+    await browserWindow.loadURL('about:blank');
+  });
 
-  for (const testName of Object.keys(testConfig) as (keyof typeof testConfig)[]) {
-    let browserWindow: BrowserWindow;
-    const { browserWindowOptions } = testConfig[testName];
+  afterEach(closeAllWindows);
 
-    describe(testName, () => {
-      beforeEach(async () => {
-        browserWindow = new BrowserWindow(browserWindowOptions);
-        await browserWindow.loadURL('about:blank');
+  it('does not fire window creation events if an override returns action: deny', async () => {
+    const denied = new Promise((resolve) => {
+      browserWindow.webContents.setWindowOpenHandler(() => {
+        setTimeout(resolve);
+        return { action: 'deny' };
       });
+    });
+    browserWindow.webContents.on('new-window', () => {
+      assert.fail('new-window should not to be called with an overridden window.open');
+    });
 
-      afterEach(closeAllWindows);
-
-      it('does not fire window creation events if an override returns action: deny', async () => {
-        const denied = new Promise((resolve) => {
-          browserWindow.webContents.setWindowOpenHandler(() => {
-            setTimeout(resolve);
-            return { action: 'deny' };
-          });
-        });
-        browserWindow.webContents.on('new-window', () => {
-          assert.fail('new-window should not to be called with an overridden window.open');
-        });
+    browserWindow.webContents.on('did-create-window', () => {
+      assert.fail('did-create-window should not to be called with an overridden window.open');
+    });
 
-        browserWindow.webContents.on('did-create-window', () => {
-          assert.fail('did-create-window should not to be called with an overridden window.open');
-        });
+    browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
 
-        browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
+    await denied;
+  });
 
-        await denied;
+  it('is called when clicking on a target=_blank link', async () => {
+    const denied = new Promise((resolve) => {
+      browserWindow.webContents.setWindowOpenHandler(() => {
+        setTimeout(resolve);
+        return { action: 'deny' };
       });
+    });
+    browserWindow.webContents.on('new-window', () => {
+      assert.fail('new-window should not to be called with an overridden window.open');
+    });
 
-      it('is called when clicking on a target=_blank link', async () => {
-        const denied = new Promise((resolve) => {
-          browserWindow.webContents.setWindowOpenHandler(() => {
-            setTimeout(resolve);
-            return { action: 'deny' };
-          });
-        });
-        browserWindow.webContents.on('new-window', () => {
-          assert.fail('new-window should not to be called with an overridden window.open');
-        });
-
-        browserWindow.webContents.on('did-create-window', () => {
-          assert.fail('did-create-window should not to be called with an overridden window.open');
-        });
-
-        await browserWindow.webContents.loadURL('data:text/html,<a target="_blank" href="http://example.com" style="display: block; width: 100%; height: 100%; position: fixed; left: 0; top: 0;">link</a>');
-        browserWindow.webContents.sendInputEvent({ type: 'mouseDown', x: 10, y: 10, button: 'left', clickCount: 1 });
-        browserWindow.webContents.sendInputEvent({ type: 'mouseUp', x: 10, y: 10, button: 'left', clickCount: 1 });
-
-        await denied;
-      });
+    browserWindow.webContents.on('did-create-window', () => {
+      assert.fail('did-create-window should not to be called with an overridden window.open');
+    });
 
-      it('is called when shift-clicking on a link', async () => {
-        const denied = new Promise((resolve) => {
-          browserWindow.webContents.setWindowOpenHandler(() => {
-            setTimeout(resolve);
-            return { action: 'deny' };
-          });
-        });
-        browserWindow.webContents.on('new-window', () => {
-          assert.fail('new-window should not to be called with an overridden window.open');
-        });
-
-        browserWindow.webContents.on('did-create-window', () => {
-          assert.fail('did-create-window should not to be called with an overridden window.open');
-        });
-
-        await browserWindow.webContents.loadURL('data:text/html,<a href="http://example.com" style="display: block; width: 100%; height: 100%; position: fixed; left: 0; top: 0;">link</a>');
-        browserWindow.webContents.sendInputEvent({ type: 'mouseDown', x: 10, y: 10, button: 'left', clickCount: 1, modifiers: ['shift'] });
-        browserWindow.webContents.sendInputEvent({ type: 'mouseUp', x: 10, y: 10, button: 'left', clickCount: 1, modifiers: ['shift'] });
-
-        await denied;
-      });
+    await browserWindow.webContents.loadURL('data:text/html,<a target="_blank" href="http://example.com" style="display: block; width: 100%; height: 100%; position: fixed; left: 0; top: 0;">link</a>');
+    browserWindow.webContents.sendInputEvent({ type: 'mouseDown', x: 10, y: 10, button: 'left', clickCount: 1 });
+    browserWindow.webContents.sendInputEvent({ type: 'mouseUp', x: 10, y: 10, button: 'left', clickCount: 1 });
 
-      it('fires handler with correct params', async () => {
-        const testFrameName = 'test-frame-name';
-        const testFeatures = 'top=10&left=10&something-unknown&show=no';
-        const testUrl = 'app://does-not-exist/';
-        const details = await new Promise<Electron.HandlerDetails>(resolve => {
-          browserWindow.webContents.setWindowOpenHandler((details) => {
-            setTimeout(() => resolve(details));
-            return { action: 'deny' };
-          });
-
-          browserWindow.webContents.executeJavaScript(`window.open('${testUrl}', '${testFrameName}', '${testFeatures}') && true`);
-        });
-        const { url, frameName, features, disposition, referrer } = details;
-        expect(url).to.equal(testUrl);
-        expect(frameName).to.equal(testFrameName);
-        expect(features).to.equal(testFeatures);
-        expect(disposition).to.equal('new-window');
-        expect(referrer).to.deep.equal({
-          policy: 'strict-origin-when-cross-origin',
-          url: ''
-        });
-      });
+    await denied;
+  });
 
-      it('includes post body', async () => {
-        const details = await new Promise<Electron.HandlerDetails>(resolve => {
-          browserWindow.webContents.setWindowOpenHandler((details) => {
-            setTimeout(() => resolve(details));
-            return { action: 'deny' };
-          });
-
-          browserWindow.webContents.loadURL(`data:text/html,${encodeURIComponent(`
-            <form action="http://example.com" target="_blank" method="POST" id="form">
-              <input name="key" value="value"></input>
-            </form>
-            <script>form.submit()</script>
-          `)}`);
-        });
-        const { url, frameName, features, disposition, referrer, postBody } = details;
-        expect(url).to.equal('http://example.com/');
-        expect(frameName).to.equal('');
-        expect(features).to.deep.equal('');
-        expect(disposition).to.equal('foreground-tab');
-        expect(referrer).to.deep.equal({
-          policy: 'strict-origin-when-cross-origin',
-          url: ''
-        });
-        expect(postBody).to.deep.equal({
-          contentType: 'application/x-www-form-urlencoded',
-          data: [{
-            type: 'rawData',
-            bytes: Buffer.from('key=value')
-          }]
-        });
+  it('is called when shift-clicking on a link', async () => {
+    const denied = new Promise((resolve) => {
+      browserWindow.webContents.setWindowOpenHandler(() => {
+        setTimeout(resolve);
+        return { action: 'deny' };
       });
+    });
+    browserWindow.webContents.on('new-window', () => {
+      assert.fail('new-window should not to be called with an overridden window.open');
+    });
 
-      it('does fire window creation events if an override returns action: allow', async () => {
-        browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
+    browserWindow.webContents.on('did-create-window', () => {
+      assert.fail('did-create-window should not to be called with an overridden window.open');
+    });
 
-        setImmediate(() => {
-          browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
-        });
+    await browserWindow.webContents.loadURL('data:text/html,<a href="http://example.com" style="display: block; width: 100%; height: 100%; position: fixed; left: 0; top: 0;">link</a>');
+    browserWindow.webContents.sendInputEvent({ type: 'mouseDown', x: 10, y: 10, button: 'left', clickCount: 1, modifiers: ['shift'] });
+    browserWindow.webContents.sendInputEvent({ type: 'mouseUp', x: 10, y: 10, button: 'left', clickCount: 1, modifiers: ['shift'] });
 
-        await Promise.all([
-          emittedOnce(browserWindow.webContents, 'did-create-window'),
-          emittedOnce(browserWindow.webContents, 'new-window')
-        ]);
-      });
+    await denied;
+  });
 
-      it('can change webPreferences of child windows', (done) => {
-        browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { defaultFontSize: 30 } } }));
+  it('fires handler with correct params', async () => {
+    const testFrameName = 'test-frame-name';
+    const testFeatures = 'top=10&left=10&something-unknown&show=no';
+    const testUrl = 'app://does-not-exist/';
+    const details = await new Promise<Electron.HandlerDetails>(resolve => {
+      browserWindow.webContents.setWindowOpenHandler((details) => {
+        setTimeout(() => resolve(details));
+        return { action: 'deny' };
+      });
 
-        browserWindow.webContents.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('${testUrl}', '${testFrameName}', '${testFeatures}') && true`);
+    });
+    const { url, frameName, features, disposition, referrer } = details;
+    expect(url).to.equal(testUrl);
+    expect(frameName).to.equal(testFrameName);
+    expect(features).to.equal(testFeatures);
+    expect(disposition).to.equal('new-window');
+    expect(referrer).to.deep.equal({
+      policy: 'strict-origin-when-cross-origin',
+      url: ''
+    });
+  });
 
-        browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
+  it('includes post body', async () => {
+    const details = await new Promise<Electron.HandlerDetails>(resolve => {
+      browserWindow.webContents.setWindowOpenHandler((details) => {
+        setTimeout(() => resolve(details));
+        return { action: 'deny' };
       });
 
-      it('does not hang parent window when denying window.open', async () => {
-        browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
-        browserWindow.webContents.executeJavaScript("window.open('https://127.0.0.1')");
-        expect(await browserWindow.webContents.executeJavaScript('42')).to.equal(42);
-      });
+      browserWindow.webContents.loadURL(`data:text/html,${encodeURIComponent(`
+        <form action="http://example.com" target="_blank" method="POST" id="form">
+          <input name="key" value="value"></input>
+        </form>
+        <script>form.submit()</script>
+      `)}`);
+    });
+    const { url, frameName, features, disposition, referrer, postBody } = details;
+    expect(url).to.equal('http://example.com/');
+    expect(frameName).to.equal('');
+    expect(features).to.deep.equal('');
+    expect(disposition).to.equal('foreground-tab');
+    expect(referrer).to.deep.equal({
+      policy: 'strict-origin-when-cross-origin',
+      url: ''
+    });
+    expect(postBody).to.deep.equal({
+      contentType: 'application/x-www-form-urlencoded',
+      data: [{
+        type: 'rawData',
+        bytes: Buffer.from('key=value')
+      }]
     });
-  }
+  });
+
+  it('does fire window creation events if an override returns action: allow', async () => {
+    browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow' }));
+
+    setImmediate(() => {
+      browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
+    });
+
+    await Promise.all([
+      emittedOnce(browserWindow.webContents, 'did-create-window'),
+      emittedOnce(browserWindow.webContents, 'new-window')
+    ]);
+  });
+
+  it('can change webPreferences of child windows', (done) => {
+    browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'allow', overrideBrowserWindowOptions: { webPreferences: { defaultFontSize: 30 } } }));
+
+    browserWindow.webContents.on('did-create-window', async (childWindow) => {
+      await childWindow.webContents.executeJavaScript("document.write('hello')");
+      const size = await childWindow.webContents.executeJavaScript("getComputedStyle(document.querySelector('body')).fontSize");
+      expect(size).to.equal('30px');
+      done();
+    });
+
+    browserWindow.webContents.executeJavaScript("window.open('about:blank', '', 'show=no') && true");
+  });
+
+  it('does not hang parent window when denying window.open', async () => {
+    browserWindow.webContents.setWindowOpenHandler(() => ({ action: 'deny' }));
+    browserWindow.webContents.executeJavaScript("window.open('https://127.0.0.1')");
+    expect(await browserWindow.webContents.executeJavaScript('42')).to.equal(42);
+  });
 });
 
 function stringifySnapshots (snapshots: any, pretty = false) {
@@ -282,9 +236,6 @@ function stringifySnapshots (snapshots: any, pretty = false) {
     if (['sender', 'webContents'].includes(key)) {
       return '[WebContents]';
     }
-    if (key === 'openerId' && typeof value === 'number') {
-      return 'placeholder-opener-id';
-    }
     if (key === 'processId' && typeof value === 'number') {
       return 'placeholder-process-id';
     }
@@ -296,8 +247,5 @@ function stringifySnapshots (snapshots: any, pretty = false) {
 }
 
 function parseSnapshots (snapshotsJson: string) {
-  return JSON.parse(snapshotsJson, (key, value) => {
-    if (key === 'openerId' && value === 'placeholder-opener-id') return 1;
-    return value;
-  });
+  return JSON.parse(snapshotsJson);
 }

+ 8 - 9
spec-main/webview-spec.ts

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

+ 0 - 1
spec/webview-spec.js

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

+ 0 - 3
typings/internal-ambient.d.ts

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

+ 0 - 1
typings/internal-electron.d.ts

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