Skip to content

Close tabs after in-page redirect in Safari#19

Open
Sissing wants to merge 1 commit into
sethcottle:mainfrom
Sissing:fix/safari-spa-redirect-not-closing
Open

Close tabs after in-page redirect in Safari#19
Sissing wants to merge 1 commit into
sethcottle:mainfrom
Sissing:fix/safari-spa-redirect-not-closing

Conversation

@Sissing
Copy link
Copy Markdown

@Sissing Sissing commented May 29, 2026

What this fixes

In Safari, tabs that reach their final closable URL through an in-page navigation never get closed.

The clearest example is Linear. When you click a Linear link, it does its desktop-app handoff and the tab ends up on ...?noRedirect=1. That final URL is reached via a client-side route change, not a full page load. The predefined Linear pattern already matches ...?noRedirect=1 correctly, so the regex isn't the problem. The problem is that the background script never sees that URL in Safari.

Two reasons for that:

  • Safari doesn't reliably fire tabs.onUpdated for in-page History API navigations (pushState/replaceState).
  • Safari doesn't support webNavigation.onHistoryStateUpdated, so the usual SPA workaround isn't available either.

So in Safari the only onUpdated event is the initial load, and by the time the URL settles on ...?noRedirect=1 nothing fires. The tab stays open.

The change

A small content script (content-spa-nav.js) runs on the hosts TabCloser already targets. It watches location.href (poll + popstate/hashchange) and reports URL changes to the background script. The background script feeds those into the existing scheduleClose(), so all the matching, interval, and cancel-on-navigate logic is unchanged.

Notes:

  • No pattern changes. This only gives the existing patterns a chance to run in Safari.
  • Content scripts run in an isolated world, so patching the page's history.pushState from the content script isn't reliable (it only patches the content script's own copy). Polling location.href works regardless.
  • The match list mirrors the current predefinedUrlPatterns hosts, so the script does nothing on unrelated pages.

Testing

Tested in Safari (unsigned/temporary extension):

  • Linear: bare issue link now closes after the redirect to ?noRedirect=1. This is the case I reproduced and verified end to end.
  • Figma and Zoom: also close as expected.

I haven't verified every service individually. The ones I didn't test use the same code path, so I'd expect them to behave the same, but it'd be good to confirm. Some of them may already have worked if their redirect is a full load rather than an in-page one. In that case this just doesn't change anything for them, since scheduleClose() already de-dupes per tab.

Possible follow-up

The content script polls every 500ms for the tab's lifetime. It only sends a message when the URL actually changes, so it's not chatty, but stopping the interval after a match or after some idle time would be a reasonable tightening if you'd prefer. Happy to add that if you want it in this PR.

Safari does not reliably fire tabs.onUpdated for in-page History API
navigations and does not support webNavigation.onHistoryStateUpdated, so
services that reach their closable URL via a client-side route change
(e.g. Linear landing on ...?noRedirect=1) are never seen by the
background script and the leftover tab is never closed.

Add a content script on the predefined service hosts that reports
location.href changes to the background script, which then runs its
existing scheduleClose() matching logic. No pattern changes.
@Sissing Sissing marked this pull request as ready for review May 29, 2026 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant