inertiajs / inertia

Inertia.js lets you quickly build modern single-page React, Vue and Svelte apps using classic server-side routing and controllers.
https://inertiajs.com
MIT License
6.24k stars 420 forks source link

Browser Back/Forward buttons issue on iOS Chrome #1954

Open jakemake opened 2 weeks ago

jakemake commented 2 weeks ago

Version:

Describe the problem:

Browser history not working properly on iOS Chrome v 128.0.6613.98 When i navigate through my web app and then click browsers "Back" button or using gesture "swipe from left" Chrome does not follow correct path (not navigating to previous page) I use component from @inertiajs/vue2 package When i check on iOS Safari or Brave browser everything works fine

Browser back and forward and swipe actions should work properly based on history

Steps to reproduce:

  1. Open https://demo.inertiajs.com/login or https://inertiajs.com/ or another web app which uses inertiajs on Chrome iOS
  2. Try to navigate through web app (open some links)
  3. hit the back button or swipe from left gesture
RobertBoes commented 2 weeks ago

I've also replied on Discord (https://discord.com/channels/592327939920494592/592327939920494594/1279105338045632636), but I'll share my findings here as well.

By the looks of it this is intended behavior in Chrome/WebKit, as it's a security feature. A window.history.pushState() call can basically only be triggered through user-interaction. If it's done without user-interaction then the history item receives a special flag, then when the user navigates back it'll skip all those entries with a special flag. See https://bugs.webkit.org/show_bug.cgi?id=248303#c5

I think this is caused by the way Inertia navigates. When a user clicks on a link an XHR request is made, then when the response comes back a call is made to window.history.pushState(), so the browser would not see that as user-interaction and that history entry receives the special flag. The issue isn't with using the History API, sites like nuxtjs work as expected, it's that the pushState call is made later, outside of user-interaction.

I would think using promises all the way would let the browser figure out the origin of the call is in fact a user-interaction, but not sure if that's the case here.

reinink commented 2 weeks ago

Yikes, this isn't good. Thanks for bringing this to our attention. Weird that it only happens in iOS Chrome. Going to have to do some digging into this one...

fjahn commented 1 day ago

Just stumbled upon this as well. The way I worked around this issue for now in Vue is by replacing all references to the Link component with a custom component that falls back to regular a tags if the user is on Chrome iOS:

<template>
    <Link
        v-if="supportsPwaLinks"
        v-bind="$attrs"
    >
        <slot/>
    </Link>
    <a
        v-else
        v-bind="$attrs"
    >
        <slot/>
    </a>
</template>

<script setup>
import {Link} from '@inertiajs/vue3'

const supportsPwaLinks = !isChromeIos()

function isChromeIos() {
    return (
        !import.meta.env.SSR
        && /CriOS/i.test(navigator.userAgent)
        && /iphone|ipod|ipad/i.test(navigator.userAgent)
    )
}
</script>

The line !import.meta.env.SSR is only necessary to make SSR work.

This is horrific, but it's a good enough workaround for our purposes.