Prevent replacing history state when scroll regions are unchanged to fix popstate behavior in WebKit #2629
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Fixes #2122.
Fixes #2402.
Here's a summary of the deep dive I did to debug this issue.
We rely on the
PopStateEventto detect when the user clicks the back button in the browser to retrieve the previous page from history and swap the component/page accordingly. However, at least on iOS, in browsers other than Safari (which all use WebKit/WKWebView), this event isn't always fired. As a result, the user can get thrown out of the Inertia app and back to the page they were on before entering it.According to the MDN Docs, the event is only triggered by a browser action, which effectively means a user interaction like clicking the back button.
Here's the problem: when you visit a subsequent page, Inertia mutates the browser history twice.
Since these two operations happen almost instantly, the browser doesn't treat them as a user interaction but rather as a programmatic update. This behavior exists to protect users from malicious sites that could fill up your browser history with junk, making the back button practically useless. So instead of firing the
PopStateEvent, the browser just takes you right out of the app.I'm not entirely sure why we store the scroll regions on leaving the page, since that already happens when scrolling on the current page. When I removed it, all tests still passed, but I wasn't fully comfortable removing it.
So instead, we're now only replacing the state when the scroll data has actually changed. Since scroll positions are continuously saved as users scroll (via event listeners in
scroll.ts), and since scroll positions rarely change at the exact moment of navigation, this effectively prevents the unnecessaryreplaceState()call that was happening right beforepushState()during page navigation.