Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add webContents.setWindowOpenHandler API #24517

Merged
merged 54 commits into from Nov 10, 2020
Merged

Conversation

@loc
Copy link
Contributor

@loc loc commented Jul 10, 2020

API changes:

  • Deprecate webContents new-window event in favor of webContents.setWindowOpenHandler(handler) and did-create-window

    new-window is fired after the contents is already created by Chrome. This is a problem when the application wants to prevent that window from being opened, as killing the window during initialization causes many problems and leads to the inevitable crash. Instead, we'll allow cancellation/customization of the new window before it's created. In congress with a change to set WebPreferences via IPC instead of the command line, this allows us to completely* change the child window's BrowserWindowConstructorOptions, even for in-process windows.

    *With the exception of a few outstanding preferences that we need early in initialization, like offscreen and nodeIntegrationInWorker. These could be fixed with some effort.

    Old way:

    mainWindow.webContents.on('new-window', (event, url, frameName, disposition, options, additionalFeatures) => {
        Object.assign(options, {
          modal: true,
        });
        childWindow = new BrowserWindow(options);
        event.newGuest = childWindow;
    });
    
    // or
    
    mainWindow.webContents.on('new-window', (event, url, frameName, disposition, options, additionalFeatures) => {
        event.preventDefault();
    });

    New way:

    mainWindow.webContents.setWindowOpenHandler(({ url, frameName }) => {
      return {
        action: 'allow',
        overrideBrowserWindowOptions: {
          modal: true,  
        }
      }
    });
    mainWindow.webContents.on('did-create-window', (win, { url, frameName, options, disposition, additionalFeatures, referrer, postData }) => {
      childWindow = win;
    });
    
    // or
    
    mainWindow.webContents.setWindowOpenHandler(({ url, frameName }) => {
      return { action: 'deny' };
    });
  • (internal API) webFrame.getWebPreference(prefName)

    Previously, preferences were stuffed into command line switches so that the renderer could look and see what settings it was supposed to be using. However, this wouldn't work for in-process child windows. Instead, we'll stuff Electron preferences into the struct that Chrome already uses for this sort of thing and add an API for the renderer to retreive those preferences. At the moment, only Electron-specific WebPreferences are available for lookup, but it would be easy to add Chromium-defined prefs like defaultFontSize, for example.

    const { webFrame } = require('electron');
    const isContextIsolationEnabled = webFrame.getWebPreference('contextIsolation');

Implementation details:

  • Allowing for in-process WebPreference changes

    Prior to this change, WebPreferences were set via the command line, which meant that in-process child windows were forced to inherit WebPreferences from their parent. Now, we overload the Chrome WebPreference struct with our own properties so they are accessible in the renderer (via a new webFrame.getWebPreference).

    window-open

  • Some behavior changes that make window opening less, um, surprising.

    • Non-security related BrowserWindowConstructorOptions are no longer inherited from the parent. This was confusing when you had to "undo" preferences that may have been set in a different part of your app.
    • window.open features keys without values are now interpreted as true, to w3c spec.
    • We no longer check for cycles in BrowserWindowOptions. (#8340)

Checklist

Release Notes

Notes: Add setWindowOpenHandler API for renderer-created child windows, and deprecate new-window event.

loc

This comment has been hidden.

Copy link
Member

@MarshallOfSound MarshallOfSound left a comment

Awesome stuff, love the diagram 😄

docs/api/web-contents.md Outdated Show resolved Hide resolved
docs/api/web-contents.md Outdated Show resolved Hide resolved
docs/api/window-open.md Outdated Show resolved Hide resolved
lib/browser/api/web-contents.ts Outdated Show resolved Hide resolved
lib/browser/api/web-contents.ts Outdated Show resolved Hide resolved
lib/browser/api/web-contents.ts Outdated Show resolved Hide resolved
lib/browser/guest-window-manager.ts Outdated Show resolved Hide resolved
shell/browser/web_contents_preferences.cc Show resolved Hide resolved
shell/browser/electron_browser_client.cc Outdated Show resolved Hide resolved
@loc loc force-pushed the in-process-web-prefs branch from f14d641 to a2f17c6 Jul 21, 2020
@loc loc force-pushed the in-process-web-prefs branch from a2f17c6 to ab954be Jul 21, 2020
@loc loc marked this pull request as ready for review Jul 21, 2020
@loc loc requested a review from as a code owner Jul 21, 2020
@loc loc requested a review from nornagon Jul 21, 2020
@loc
Copy link
Contributor Author

@loc loc commented Jul 21, 2020

A couple tests are still failing, but I'm going to get the review ball rolling while I address them.

@jkleinsc
Copy link
Contributor

@jkleinsc jkleinsc commented Oct 28, 2020

I'm in favor of approach (4). Mutating the event feels clunky even if there is precedence to do so. I also like the declarative nature of the return object on (4).

@jkleinsc
Copy link
Contributor

@jkleinsc jkleinsc commented Nov 10, 2020

The API WG approved this PR at our Nov 9, 2020 meeting

@nornagon nornagon merged commit 0b85fdf into master Nov 10, 2020
13 of 15 checks passed
@release-clerk
Copy link

@release-clerk release-clerk bot commented Nov 10, 2020

Release Notes Persisted

Add setWindowOpenHandler API for renderer-created child windows, and deprecate new-window event.

@nornagon nornagon deleted the in-process-web-prefs branch Nov 10, 2020
@sentialx
Copy link
Contributor

@sentialx sentialx commented Nov 13, 2020

Is it now possible to set e.newGuest to a BrowserView?

@stewartlord
Copy link
Contributor

@stewartlord stewartlord commented Nov 19, 2020

@nornagon Is there a way to influence how the new window is created? Specifically, we are looking at how to open into a BrowserView

@stewartlord
Copy link
Contributor

@stewartlord stewartlord commented Nov 26, 2020

@loc @nornagon Just wanted to share an idea that @sentialx and I discussed to add support for browser views. We tried to think of a way to do this without changing too much about the new setWindowOpenHandler() API. I'm not sure it's ideal, but it looks like the design is being driven by a requirement to leave creation of the new guest up to electron internally? With that in mind:

mainWindow.webContents.setWindowOpenHandler(({ url, frameName }) => {
  return {
    action: 'allow',
    guestType: 'browserView', // default: 'window'
    overrideBrowserViewOptions: {
      ...
    }
  }
});
mainWindow.webContents.on('did-create-browser-view', (view, { url, frameName, options, disposition, additionalFeatures, referrer, postData }) => {
  browserWindow.addBrowserView(view)
  ...
});

I believe there might be an outstanding issue re: 'close'? Eryk was going to think about this a bit more and refine the idea, but I wanted to get some initial feedback on the above.

@nornagon
Copy link
Member

@nornagon nornagon commented Nov 30, 2020

@stewartlord hm, that proposal raises a few concerns for me, specifically it seems difficult to correctly correlate the window open handler invocation with the did-create-browser-view event.

However, I like the direction this is going in and to collect discussion I'd encourage you to open a proposal over at https://github.com/electron/governance/tree/master/wg-api/spec-documents (see https://github.com/electron/governance/blob/master/wg-api/spec-documents/api-spec-template.md for a template)

@stewartlord
Copy link
Contributor

@stewartlord stewartlord commented Dec 2, 2020

@nornagon Thanks for the feedback. Eryk had a different idea and opened a PR that should solve the issue with less effort:
#26802

@pushkin-
Copy link

@pushkin- pushkin- commented Dec 16, 2020

Is there a way with this new paradigm to pend window creation? I only want to create the window if the URL is in my allowlist (whitelist). Currently, I'm preventing the new-window event, checking my allowlist asynchronously, and then creating the window.

Is the way to do this using the new paradigm to create the window in a hidden state, then after it's been created, check with the allowlist and close the window if the URL doesn't pass the check; otherwise, show the window? I presume I can do that bit in mainWindow.webContents.on('did-create-window' since I expect that can be asynchronous.

@nornagon
Copy link
Member

@nornagon nornagon commented Dec 16, 2020

There is not. Chromium code forces that a decision about whether to create the window or not be made synchronously.

SpacingBat3 added a commit to SpacingBat3/WebCord that referenced this issue May 7, 2021
Switch from event 'new-window' to function 'setWindowOpenHandler()'.
For more information, see:
electron/electron#24517
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet

8 participants