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

Modernized version of window.open() API #7485

Open
domenic opened this issue Jan 11, 2022 · 15 comments
Open

Modernized version of window.open() API #7485

domenic opened this issue Jan 11, 2022 · 15 comments
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest

Comments

@domenic
Copy link
Member

domenic commented Jan 11, 2022

window.open() is full of legacy design mistakes. Here is a proposal for what, IMO, it should look like:

window.openWindow(url, { allowOpenerAccess, referrerPolicy });
window.openPopup(url, { left, top, width, height, allowOpenerAccess, referrerPolicy });

In particular such a clean slate would:

  • Not encode left/top/width/height/noopener/noreferrer/popup-ness into strings
  • Not also have a second use where you pass a window name and it navigates that window
  • Allow any referrer policy, not just no-referrer (the latter is possible via today's noreferrer string option)
  • Flip the default so usually you don't get opener access, and you have to opt in to it
  • Not parse the URL relative to the entry settings object (Minimize usage of the entry concept #1431) but instead use the relevant settings object like other modern APIs.
  • Not special case the empty string or about:blank URLs
  • Be extensible to future additional options without adding more terrible string parsing; see e.g. Use a different overload to handle window.open navigations WICG/attribution-reporting-api#130 and "FYI" Review of new window.open() behavior w3ctag/design-reviews#691 (comment)
  • Avoid the issue window.open() has where it's impossible to add a fourth argument dictionary since there is legacy code that does window.open(url, name, features, "replace") and "replace" throws an error when converting to a dictionary.
@domenic domenic added addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest labels Jan 11, 2022
@domenic
Copy link
Member Author

domenic commented Jan 11, 2022

We could also make it throw an exception if the popup is blocked, instead of returning null.

@wanderview
Copy link
Member

FWIW, we had discussed making Clients.openWindow() (and the rest of clients API) available in documents.

https://w3c.github.io/ServiceWorker/#clients-openwindow

It seemed it might have avoided some of the mistakes of window.open(), although its likely missing features. I don't know if leaning into that API would make sense here.

@codecando-x

This comment was marked as resolved.

@domenic

This comment was marked as resolved.

@codecando-x

This comment was marked as resolved.

@annevk

This comment was marked as resolved.

@codecando-x

This comment was marked as resolved.

@jimmywarting

This comment was marked as off-topic.

@dSalieri

This comment was marked as off-topic.

@domenic

This comment was marked as off-topic.

@smaug----
Copy link
Collaborator

smaug---- commented Apr 6, 2023

The current window.open() does indeed have a quite a few odd quirks, so perhaps a new API would be reasonable. But before adding anything it would be good to figure out if similar features should be added also to anchor elements (with target attribute) or form elements (with target attribute). And what other ways there are to open new windows, at least Clients.openWindow() (which is rather weird itself, since it has so limited features).

@past past removed the agenda+ To be discussed at a triage meeting label Apr 6, 2023
@michaelwasserman
Copy link

I shared related explorations in 2020. I wonder if returning a promise would help avoid sync access to pre-initialized window properties (e.g screenX|Y, outerWidth|Height rely on async user agent, operating system, and window manager functionality crbug.com/1434097) It might also help avoid accessing the initial about:blank document (e.g. crbug.com/1434136) These loose ideas may be infeasible or increase friction, I'm just brainstorming.

@domenic
Copy link
Member Author

domenic commented Apr 27, 2023

In the triage calls we've briefly discussed @smaug----'s suggestion of additionally augmenting <a> elements to allow them access to the same feature set. I took the action item to write up what that would look like.

<a> elements already have rel="noopener" and referrerpolicy="". Combined with target="_blank", they have all of the functionality of the OP's proposed openWindow().

What remains is the functionality of the OP's openPopup(): specifically, saying that you want a popup instead of a new window/tab, and saying the left/top/width/height of the popup. If we wanted to add the ability to do that declaratively, I think it'd be something like a popup="" attribute, which has a small microsyntax for specifying the dimensions: e.g. popup="left top width height" or popup="left top widthxheight", with clear rules for parsing that so that if you omit some values you get sensible defaults. (E.g. you should be able to just specify width + height, or maybe just width or just top.)

How this popup="" combines with target="" is a bit tricky. Ideas:

  • It must be used with target="_blank", and is ignored otherwise. (Probably simplest.)
  • It can be used with target="_blank". If the target="" is a preexisting popup window (including the default target of _self), then it moves-and-resizes the window (subject to the same security restrictions as usual on which windows are allowed to move-and-resize other windows).

I think this is fairly separable from the JavaScript API proposal, so I don't think it's a blocker for moving forward with that. But I'm glad we checked to make sure, before forging ahead.


On the question of the return value: we discussed this also a bit in the triage call. There was indeed interest in having it return a promise, but no clarity about when the promise would resolve. Ideas included:

  • As soon as possible, i.e. as soon as the popup's position and size can be determined from the window manager.
  • Something involving the initial navigation away from the "about:blank" document. After the new document's load event is quite late, but maybe after navigation commits, i.e. once location.href has been updated?
    • This feels a bit like a potential cross-origin information leak, but it's not really any more than we have today, since you can just poll w.location for w returned from window.open().

On the connection to Service Worker's openWindow(): that one takes a URL, with no options, and returns a promise for a WindowClient, or null if the resulting window is cross-storage-partition. I personally think that existing API fits well with the newly-proposed ones, especially if we make them promise-returning. They're different, but not in confusing ways:

  • window.openWindow() takes extra options. clients.openWindow() doesn't. Maybe the latter could be expanded to take a referrerPolicy option. (But the allowOpenerAccess option doesn't make sense there.)
  • window.openWindow() returns a promise for a Window. clients.openWindow() returns a promise for a WindowClient. These seem like appropriate in-Window vs. in-ServiceWorkerGlobalScope representations of a window.

So in conclusion, I think this proposal could move forward.

@LeaVerou
Copy link

This came up in our TAG review of Document Picture-in-Picture, as the reasons for this whole new API (instead of just an alwaysOnTop option for window.open()) were these issues with window.open():

  • Options cannot be feature detected
  • Windows can outlive their opener, which is often not desirable

It may be good to take these into account when designing a window.open() replacement, as these are more broadly useful, and not specific to PiP use cases.

It had come up a while before, when a popup option to distinguish popups vs tabs was proposed.
It would be unfortunate if ad hoc features keep getting proposed to work around the issues in window.open(), so I hope we can get stakeholder interest to fix the root problem.

I think we're largely aligned on the goals:

  • Improved ergonomics
  • Feature detection
  • Improved coverage of use cases
  • Improved security by default

Now, on to the specifics:

@domenic

  • Not encode left/top/width/height/noopener/noreferrer/popup-ness into strings

Yes! Dictionary instead of weird encoded strings is a low hanging fruit here.

  • Not also have a second use where you pass a window name and it navigates that window

Makes sense, it's a bit weird, and there are other ways to address these use cases.

  • Allow any referrer policy, not just no-referrer (the latter is possible via today's noreferrer string option)

Agreed.

  • Flip the default so usually you don't get opener access, and you have to opt in to it

I suspect the vast majority of use cases do need opener access, but making it explicit does improve security.
I wonder if we want to generalize it into an allow option, so that permissons can be more fine grained in the future (akin to iframe sandbox)

From a quick read that seems reasonable, but could you elaborate on what this would mean for this API?

  • Not special case the empty string or about:blank URLs

Would it instead make sense to make the url optional, part of the settings dictionary?
It’s a very common use case to open a blank window and generate content for it via JS.

Yes!

  • Avoid the issue window.open() has where it's impossible to add a fourth argument dictionary since there is legacy code that does window.open(url, name, features, "replace") and "replace" throws an error when converting to a dictionary.

Also low-hanging fruit.

window.openWindow(url, { allowOpenerAccess, referrerPolicy });
window.openPopup(url, { left, top, width, height, allowOpenerAccess, referrerPolicy });

It does seem reasonable to have separate methods for opening a new tab or creating a popup.
I wonder if making Window constructible would make sense? Though that would afford less flexibility than a factory method (e.g. can’t return a promise).

We could also make it throw an exception if the popup is blocked, instead of returning null.

Makes sense.


I don't see a plan for supporting feature detection. Simply throwing when these methods are called is not sufficient, as authors need to be able to know what options are supported before opening any windows.
Making Window constructible and having a separate method for the actual navigation (either popup or tab) could address this, as it would be the constructor that would throw, and the constructor by itself would not produce any navigation. If the method returns the window object produced (or a promise that resolves to it), it can still be a one liner.

@jimmywarting

This comment was marked as off-topic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
addition/proposal New features or enhancements needs implementer interest Moving the issue forward requires implementers to express interest
Development

No branches or pull requests

10 participants