You Shouldn’t Call window.open() Asynchronously
The Window.open()
function lets one webpage open another page in a new window or tab. This ability was, of course, immediately abused to serve popup ads, so browsers now have popup-blockers that try to detect and block that kind of behavior. If you’re using this API, you need to design around the heuristics of the popup-blocker.
The particulars of exactly how the popup-blocker works is a browser-specific implementation detail, but in general Window.open()
should only be called with user input event (e.g. click
) in the call stack. However, when you start throwing asynchronous operations into the mix, like fetching a URL from the server before opening it, the browser has a tendency to get confused and block the new window anyways.
Here’s a naive implementation that can exhibit this behavior:
async function handleButtonClicked() {
const targetUrl = await getUrlFromServer();
window.open(targetUrl);
}
This results in a suboptimal user experience where the user needs to explicitly approve the new window, turning one click into two.
I’ve found that taking a slightly different approach avoids this problem.
Instead of trying to open the window asynchronously, you can open a blank window synchronously in the event handler, then asynchronously alter its contents. This is possible because Window.open()
returns a WindowProxy
instance (if the new window opens successfully), which can be used to manipulate the newly-opened window. In this case, we just need to alter its location
to navigate it to a different URL.
function handleButtonClicked() {
const newWindow = window.open();
if (newWindow) {
getUrlFromServer().then((targetUrl) => {
newWindow.location.href = targetUrl;
}).catch(() => {
newWindow.close();
});
}
}
By default, the new window will open with about:blank
. If the async operation is expected to take more than a fraction of a second, then I’d recommend initially populating the new window with some loading indicator.
An important caveat to this approach is that you cannot set the noopener
or noreferrer
window features, since providing either of those causes Window.open()
to return null
instead of a WindowProxy
instance as usual. If you’re opening a page with potentially untrusted content, then one workaround for this is to manually set newWindow.opener
to null
.