Sometimes, Web Developers are surprised to find that the window.close()
API doesn’t always close the browser window.
When looking at the Developer Tools console, they’ll see a message like
Scripts may close only the windows that were opened by them.
Before we dive into what variables govern what happens when close()
is called, it’s important to understand why there’s a restriction at all.
While sometimes explained away as nebulous “Security reasons!” the real reason is more aligned with simple user-experience— users could lose important state in their browser window or “back/forward” stack (in IE, we called this the TravelLog) if a tab or window went away unexpectedly. I might’ve been using a tab to explore a set of search results, and if one of those results could blow away both itself and the backstack with the search results page, this would be pretty annoying.
There’s a minor anti-abuse/security argument as well– if a browser tab could blow itself away freely, this might be useful as a part of scareware or other user-experience abuses.
Here’s what the dom-window-close section of the HTML Standard has to say:
This seems simple enough, although the parts I’ve bolded hide a lot of complexity and nuance. (Most obviously, “what should we do if the script was run in response to an action of the user?“)
Unfortunately for us, each browser has a different set of behaviors (explore with this test page), partly because most were implemented before the standard was written.
In Internet Explorer, a tab/window will be closed silently if it was created as a result of a JavaScript window.open()
call. There’s no attempt to check whether the back/forward stack contains only one document: a tab with a large travellog will still close silently if it was opened by script. (IE also allows HTA documents to close themselves without restriction.)
In all other circumstances, the tab/window will not silently close: instead, the user is presented with a one of two modal dialogs, depending on whether the page represents the only tab in the browser window:
As of Chromium 88, window.close()
succeeds if the new window/tab has an opener
or if the back/forward stack contains fewer than two items.
As you can see, there are subtle differences here between what the spec requires and what the browser implements.
First, notice that I said “has an opener” rather than “was created by a script.”
opener
property allows a popup window to refer to the tab that created it. opener
set. window.open()
or hyperlink with a named target (not _blank
), then by default, the opener
property is set.rel=opener
or rel=noopener
to specify whether the new tab has an opener
set.window.open()
JavaScript call can specify noopener
in its windowFeatures string to set the new tab’s opener
to null
.As you can see from the above list, normal link clicks and JavaScript open() invocations can result in tabs with or without the opener
set. This can be very confusing: shift+click
on a link might result in a tab that cannot self-close, while left-click
on that same link results in a tab that’s can always close itself.
Secondly, notice I said “items” rather than “Documents.” In most cases, these are equivalent, but they’re not exactly the same. Consider the case where the new tab navigates to a HTML document with a Table of Contents at the top. The user clicks on a ToC link to #Section3
and the browser dutifully scrolls down to that section. The back/forward stack now contains two items, but one document. Chromium will now block window.close()
. crbug.com/1170131 tracks fixing Chromium’s longstanding shortcoming by counting the number of Documents in the back/forward stack, but it will be tricky to fix because presently the renderer process running JavaScript has access only to the count of items in the back/forward stack, not their URLs.
When Chrome blocks close()
, it emits a notice to the console:
Scripts may close only the windows that were opened by them.
…but there’s no notice to the user, who may be left confused if they clicked on a “Close” button or link inside the page. Recently filed crbug.com/1170034 suggests introducing a dialog box like Internet Explorer’s. (As an aside, it also sets a new standard for bug filing by including comic-book style art showing unhappy users converting to happy users if the proposed feature is landed. :)
Chromium: Exotic Bug Trivia
This is a super-obscure corner case. However, I’ve seen it independently reported against both Chrome and Edge in the span of five years, and it makes me laugh.
If you set Chromium’s On Startup option to “Continue where you left off” and you navigate to a page that tries to close itself, the browser will kill itself on every startup.
It’s hard to get into this state, but it’s still possible in Chrome/Edge 90.
Repro steps: Visit https://webdbg.com/test/opener/. Click the ‘Page that tries to close itself‘ link. Hit Ctrl+Shift+Delete
and Clear Browsing History (to clear the back/forward stack). Close the browser with the ‘X’ icon. Now, try to launch the browser from your start menu. Giggle wildly as your browser launches and then blows itself away.
WebKit’s code is similar to Chromium’s (unsurprising, considering their shared lineage) except it does not equate navigations triggered with noopener
with those created by browser UI. Thus, in Safari, the user may click between many different same-origin pages and close()
is still permitted.
If close()
is blocked, Safari’s (well-hidden) JavaScript console shows:
Can't close the window since it was not opened by JavaScript
Unlike Chromium, Firefox correctly implements the “only one Document” part of the spec. Firefox calls nsGlobalWindowOuter::IsOnlyTopLevelDocumentInSHistory which calls csh->LegacySHistory()->IsEmptyOrHasEntriesForSingleTopLevelPage() which enumerates the history list to check, if there is more than one entry, whether all of the entries are for the same Document. If they are all for the same document, the close()
succeeds.
Firefox offers an about:config
setting named dom.allow_scripts_to_close_windows
to remove restrictions.
When Firefox blocks close()
, it emits a notice to the console:
Scripts may not close windows that were not opened by script.
There’s an 18 year-old feature request for Firefox to show a confirmation dialog instead of failing silently.
umm…. browsers are wicked complicated?
-Eric