html
html copied to clipboard
Modernized version of window.open() API
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'snoreferrer
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 (https://github.com/whatwg/html/issues/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. https://github.com/WICG/conversion-measurement-api/issues/130 and https://github.com/w3ctag/design-reviews/issues/691#issuecomment-993155427
- Avoid the issue
window.open()
has where it's impossible to add a fourth argument dictionary since there is legacy code that doeswindow.open(url, name, features, "replace")
and"replace"
throws an error when converting to a dictionary.
We could also make it throw an exception if the popup is blocked, instead of returning null.
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.
Could you please add the ability to pass in header values to the GET request that occurs when a new tab is open in the browser using window.open?
I ran across this restricted behavior when attempting to pass in an "Accept" header to tell the server to return CSV instead of JSON on that first GET request which happens when windows.open(url, '_blank') is called.
It seems that the only way I can get this to happen is by altering the url or the query string to define what mime type I am expecting in the response, which the Accept header is really for. Or by making a POST request to generate the CSV file and then making another GET request to download it in the new tab. Which is 2 requests when it could be done in one if passing in headers to that first new tab GET request was possible.
If the window.open would allow headers to be passed in that would make it so I could do all this in one call to window.open like so:
options.url = '/reports/q12w3e4tynbvcx4567'; options.headers = {'Accept':'text/csv'}; options.mode = '_blank'; window.open(options);
There are many other folks which have a similar interest. https://stackoverflow.com/questions/4325968/window-open-with-headers
Thanks
No, allowing arbitrary headers on navigations is not something the web platform will support, for security reasons.
Thanks @domenic
Could you explain what specific security reasons you are concerned about?
"Arbitrary" is an all encompassing word here, since not all headers are created equal. The request is for a HTTP standard header to be accepted not just any custom header.
The "Accept' header https://datatracker.ietf.org/doc/html/rfc7231#page-38
On top of that, web-servers hopefully validate the values that are passed into the Accept header most likely using a list like this: https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
I am not familiar with a specific browser's codebases but they probably also validate that the value that is passed into the Accept header is on a list of supported mime types.
The Accept header is passed in already by the browser. The request is to be able to choose this value from an already defined list which the browsers and webserver use.
Just trying to understand the security concern here.
While we could probably allow some headers to be set for navigations (essentially in line with what "no-cors" allows), it's not clear that the complexity it warrants in terms of validation and ensuring correctness of implementation is worth the effort. It's probably best to open a separate issue for that specific request if you want to make a case for it.
tip for controlling referrerPolicy, headers, method, body cache and all the rest of the things you could be able to do with a Request
object
const request = new Request(url, { method: 'POST', body: new FormData() })
window.openWindow(request, ...rest)
window.openPopup(request, { left, top, width, height, allowOpenerAccess })
@domenic Are there words speaking that window.openWindow
and window.openTab
are Transient activation-consuming APIs? Because in the current spec I found nothing that express that window.open
is Transient activation-consuming API (its behaviour directly says that it is).
@dSalieri that is not related to the new feature proposal in this issue, and is off-topic. If you'd like help reading the spec, I suggest https://stackoverflow.com/ or https://whatwg.org/chat.
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).
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.
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 thetarget=""
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'sload
event is quite late, but maybe after navigation commits, i.e. oncelocation.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
forw
returned fromwindow.open()
.
- 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
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 areferrerPolicy
option. (But theallowOpenerAccess
option doesn't make sense there.) -
window.openWindow()
returns a promise for aWindow
.clients.openWindow()
returns a promise for aWindowClient
. These seem like appropriate in-Window
vs. in-ServiceWorkerGlobalScope
representations of a window.
So in conclusion, I think this proposal could move forward.
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'snoreferrer
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)
- 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.
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.
- 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)
Yes!
- Avoid the issue
window.open()
has where it's impossible to add a fourth argument dictionary since there is legacy code that doeswindow.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.
just tough it could be nice if you could apply the same sandboxing possibilities that you can do to iframes to control weather or not it should allow javascript, download files allowing same origin (guess this is allowOpenerAccess
?)