Search code examples
javascriptbrowserhashback-buttonbrowser-history

How distinguish between browser back and user manually changing location hash


Question

Given navigation from /page.html#A to /page.html#B, is there a way to distinguish between a user:

  1. Clicking the browsers's 'back' button, and
  2. Manually changing the url back to /page.html#A ?

Background / Context

I'm building a web app, where a single page transitions between multiple slides of content, each identified by a specific location hash, eg '#A', '#B'.

For example, when the user is on slide 'A', and selects option 'B', the location changes from /page.html#A to /page.html#B.

After the transition, location.hash==#B, and back() (either via JS or browser button) would return the user to location.hash==#A.

However, there is nothing to prevent a user from manually changing the hash in the URL bar. In this case the browser would consider this a navigation forward, inserting /page.html#B in the back history. That is, navigation history would be #A > #B > #A and clicking back would now take a user to #B.

I need to distinguish between these two cases so that when I know the user has manually updated the url hash, I can trigger go(N) to synchronise the browser back/next state.

Attempts so far

1) HTML5 popstate event:
I had hoped that the html5 popstate event ( https://developer.mozilla.org/en-US/docs/Web/Events/popstate ) would only be fired for case#1, but I can confirm it fires in both cases.

2) Browser .onhashchange event
I can confirm that if present, the event is fired in both cases

3) jQuery mobile hashChange() I can confirm is fired in both cases

4) Read browser navigation history
My next thought would be to maintain a JS array of hash history, and compare whether the new hash and browser history match the JS array, but JS can't read the browser location history for security reasons.

Thoughts

I know that if I call window.history.forward(), and no page exists, nothing happens. I'm thinking a JS array of hash history, calling forward(), checking the new location.hash (as security now allows it), comparing to JS array, then calling go(N) to synchronise the browser back/next state. But it's a bit messy.


Solution

  • Yes, you can distinguish between:

    1. Clicking the back()/forward() browser button, and
    2. Manually editing the location.hash in the browser URL bar

    It is also possible to use both alongside in-page HTML element navigation.

    Issues:

    1. Browser back(), forward() and go() calls do not immediately update location.hash. It is required to wait ~10ms via setTimeout() to let browser finish the navigation and update location.

    Solution (pseudo-code):

    • Maintain an array of backward_history_hashes (note 'backwards' means logically, not temporally)
    • Maintain a value of current_location.hash
    • Maintain an array of forward_history_hashes
    • Maintain a boolean flag for in-page navigation, default to FALSE
    • Maintain a boolean flag whether to ignore_hash_change
    • Create a setTimeout() monitor to check for location.hash changes

    In each case, the history arrays are simple string arrays of location.hashes

    on_in_page_navigation()

    • set in_page_flag = true
    • trigger browser navigation via back(), forward() or go(N)
    • set in_page_flag = false

    on_location_hash_change()

    • set ignore_hash_change = true
    • if( ! in_page_flag) rewrite_browser_history()
    • display content corresponding to new location.hash
    • set ignore_hash_change = false

    rewrite_browser_history()

    • just assume that it was a manual URL edit, and use JS history arrays to trigger back() and forward() calls to generate the desired browser-history
    • execute go(N) to desired location.hash to synchronize browser-history with JS history arrays