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
Copy link
Copy Markdown
Contributor

@k35o k35o commented May 18, 2026

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
Copy link
Copy Markdown
Owner

@uhyo uhyo left a comment

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
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