Skip to content

fix: handle stale Navigation API entries in Safari Private Browsing#185

Merged
uhyo merged 2 commits into
uhyo:masterfrom
k35o:fix/safari-private-routing
May 19, 2026
Merged

fix: handle stale Navigation API entries in Safari Private Browsing#185
uhyo merged 2 commits into
uhyo:masterfrom
k35o:fix/safari-private-routing

Conversation

@k35o

@k35o k35o commented May 18, 2026

Copy link
Copy Markdown
Contributor

Closes #184

Summary

Works around a WebKit Navigation API bug in Safari Private Browsing where an intercepted navigation commits, but navigation.currentEntry.url / .id remain stale and currententrychange does not fire.

Upstream bug: https://bugs.webkit.org/show_bug.cgi?id=314976

User-visible symptom: after clicking a link in Safari Private Browsing, the URL bar updates but the rendered route stays on the previous page until reload.

Approach

This keeps the workaround contained inside the Navigation API adapter:

  • Capture event.destination.url during intercepted navigations.
  • Use that committed destination when resolving the current snapshot, but only when it matches the current entry id.
  • Subscribe to navigatesuccess as a fallback signal for the buggy Safari Private Browsing path.
  • Use composite loader cache keys of entry.id + url so same-URL navigations still run loaders correctly.

The adapter still avoids reading location.href.

Limitations

This fixes URL-based routing in Safari Private Browsing.

State that depends on navigation.currentEntry.getState() can still be stale in this browser mode because WebKit leaves currentEntry itself stale. In practice, this means useRouteState remains limited until the upstream browser bug is fixed.

There is also a small Safari Private Browsing cache cleanup limitation: dispose-time cleanup may miss composite loader cache keys because entry.url is stale. The cache can grow by unique URL during the tab session and is released when the tab closes. Correctness is unaffected.

Tests

Added regression coverage in NavigationAPIAdapter.privateBrowsing.test.ts for:

  • resolving the committed URL when currentEntry is stale
  • subsequent intercepted navigations
  • navigatesuccess fallback notifications
  • preventing committed destination leakage across unrelated navigations
  • composite loader cache cleanup behavior

Manually verified in macOS Safari Private Browsing using the example app.

before.mov
after.mov

k35o added 2 commits May 18, 2026 13:18
Adds __simulateInterceptedNavigation(url, { privateBrowsing }) that
drives the full navigate -> commit -> handler -> navigatesuccess
lifecycle in unit tests, with an opt-in flag to simulate the WebKit
Private Browsing bug where currentEntry stays stale and
currententrychange does not fire.
…owsing

Works around a WebKit bug where intercepted navigations commit but
navigation.currentEntry.url/.id stay stale and currententrychange does
not fire. Captures event.destination.url scoped to entry.id, prefers it
in getSnapshot, and listens to navigatesuccess as a fallback notifier.
Composite loader cache keys (entry.id + url) keep loader behavior
consistent across both modes.

Upstream bug: https://bugs.webkit.org/show_bug.cgi?id=314976

@uhyo uhyo left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! Look good as a workaround. Good job also filling an upstream bug and including the link in the comments 😃

@uhyo uhyo merged commit d7fb519 into uhyo:master May 19, 2026
1 check passed
@uhyo

uhyo commented May 20, 2026

Copy link
Copy Markdown
Owner

Released as 1.1.1. Thank you for this contribution 🙂

@k35o

k35o commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

@uhyo
Thanks for the quick review and release! 🙌

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.

Safari Private Browsing breaks routing due to stale navigation.currentEntry

2 participants