Using JavaScript History.Back() Fails in Safari .. How to Make It Cross-Browser

Using javascript history.back() fails in Safari .. how do I make it cross-browser?

it should be history.go(-1); return false;
or
history.go(-1); event.preventDefault();

Is there a cross-browser onload event when clicking the back button?

Guys, I found that JQuery has only one effect: the page is reloaded when the back button is pressed. This has nothing to do with "ready".

How does this work? Well, JQuery adds an onunload event listener.

// http://code.jquery.com/jquery-latest.js
jQuery(window).bind("unload", function() { // ...

By default, it does nothing. But somehow this seems to trigger a reload in Safari, Opera and Mozilla -- no matter what the event handler contains.

[edit(Nickolay): here's why it works that way: webkit.org, developer.mozilla.org. Please read those articles (or my summary in a separate answer below) and consider whether you really need to do this and make your page load slower for your users.]

Can't believe it? Try this:

<body onunload=""><!-- This does the trick -->
<script type="text/javascript">
alert('first load / reload');
window.onload = function(){alert('onload')};
</script>
<a href="http://stackoverflow.com">click me, then press the back button</a>
</body>

You will see similar results when using JQuery.

You may want to compare to this one without onunload

<body><!-- Will not reload on back button -->
<script type="text/javascript">
alert('first load / reload');
window.onload = function(){alert('onload')};
</script>
<a href="http://stackoverflow.com">click me, then press the back button</a>
</body>

How to Detect Browser Back Button event - Cross Browser

(Note: As per Sharky's feedback, I've included code to detect backspaces)

So, I've seen these questions frequently on SO, and have recently run into the issue of controlling back button functionality myself. After a few days of searching for the best solution for my application (Single-Page with Hash Navigation), I've come up with a simple, cross-browser, library-less system for detecting the back button.

Most people recommend using:

window.onhashchange = function() {
//blah blah blah
}

However, this function will also be called when a user uses on in-page element that changes the location hash. Not the best user experience when your user clicks and the page goes backwards or forwards.

To give you a general outline of my system, I'm filling up an array with previous hashes as my user moves through the interface. It looks something like this:

function updateHistory(curr) {
window.location.lasthash.push(window.location.hash);
window.location.hash = curr;
}

Pretty straight forward. I do this to ensure cross-browser support, as well as support for older browsers. Simply pass the new hash to the function, and it'll store it for you and then change the hash (which is then put into the browser's history).

I also utilise an in-page back button that moves the user between pages using the lasthash array. It looks like this:

function goBack() {
window.location.hash = window.location.lasthash[window.location.lasthash.length-1];
//blah blah blah
window.location.lasthash.pop();
}

So this will move the user back to the last hash, and remove that last hash from the array (I have no forward button right now).

So. How do I detect whether or not a user has used my in-page back button, or the browser button?

At first I looked at window.onbeforeunload, but to no avail - that is only called if the user is going to change pages. This does not happen in a single-page-application using hash navigation.

So, after some more digging, I saw recommendations for trying to set a flag variable. The issue with this in my case, is that I would try to set it, but as everything is asynchronous, it wouldn't always be set in time for the if statement in the hash change. .onMouseDown wasn't always called in click, and adding it to an onclick wouldn't ever trigger it fast enough.

This is when I started to look at the difference between document, and window. My final solution was to set the flag using document.onmouseover, and disable it using document.onmouseleave.

What happens is that while the user's mouse is inside the document area (read: the rendered page, but excluding the browser frame), my boolean is set to true. As soon as the mouse leaves the document area, the boolean flips to false.

This way, I can change my window.onhashchange to:

window.onhashchange = function() {
if (window.innerDocClick) {
window.innerDocClick = false;
} else {
if (window.location.hash != '#undefined') {
goBack();
} else {
history.pushState("", document.title, window.location.pathname);
location.reload();
}
}
}

You'll note the check for #undefined. This is because if there is no history available in my array, it returns undefined. I use this to ask the user if they want to leave using a window.onbeforeunload event.

So, in short, and for people that aren't necessarily using an in-page back button or an array to store the history:

document.onmouseover = function() {
//User's mouse is inside the page.
window.innerDocClick = true;
}

document.onmouseleave = function() {
//User's mouse has left the page.
window.innerDocClick = false;
}

window.onhashchange = function() {
if (window.innerDocClick) {
//Your own in-page mechanism triggered the hash change
} else {
//Browser back button was clicked
}
}

And there you have it. a simple, three-part way to detect back button usage vs in-page elements with regards to hash navigation.

EDIT:

To ensure that the user doesn't use backspace to trigger the back event, you can also include the following (Thanks to @thetoolman on this Question):

$(function(){
/*
* this swallows backspace keys on any non-input element.
* stops backspace -> back
*/
var rx = /INPUT|SELECT|TEXTAREA/i;

$(document).bind("keydown keypress", function(e){
if( e.which == 8 ){ // 8 == backspace
if(!rx.test(e.target.tagName) || e.target.disabled || e.target.readOnly ){
e.preventDefault();
}
}
});
});

history.go(-1) behavior in different browsers

No, Browsers can act differently to histroy.go. How you interact with the browser before history.go can have different effects when it is called. To make cross-browser javascript is fairly tricky, but correcting the history issue should be fairly simple. I answered your only question, "Is this true?". It is likely you want to know how to fix the issue and that is specific to your code.

Setting cross-domain cookies in Safari

From the Safari Developer FAQ:

Safari ships with a conservative cookie policy which limits cookie writes to only the pages chosen ("navigated to") by the user. This default conservative policy may confuse frame based sites that attempt to write cookies and fail.

I have found no way to get around this.

If it's worth anything, Chrome doesn't set the cookies either if you use the <script> appending method, but if you have a hidden <img> with the same source, Chrome works in addition to the rest of the browsers (except, again, Safari)

Using Javascript: How to create a 'Go Back' link that takes the user to a link if there's no history for the tab or window?

You cannot check window.history.length as it contains the amount of pages in you visited in total in a given session:

window.history.length (Integer)

Read-only. Returns the number of elements in the session history, including the currently loaded page. For example, for a page loaded in a new tab this property returns 1. Cite 1

Lets say a user visits your page, clicks on some links and goes back:


www.mysite.com/index.html <-- first page and now current page <----+
www.mysite.com/about.html |
www.mysite.com/about.html#privacy |
www.mysite.com/terms.html <-- user uses backbutton or your provided solution to go back

Now window.history.length is 4. You cannot traverse through the history items due to security reasons. Otherwise on could could read the user's history and get his online banking session id or other sensitive information.

You can set a timeout, that will enable you to act if the previous page isn't loaded in a given time. However, if the user has a slow Internet connection and the timeout is to short, this method will redirect him to your default location all the time:

window.goBack = function (e){
var defaultLocation = "http://www.mysite.com";
var oldHash = window.location.hash;

history.back(); // Try to go back

var newHash = window.location.hash;

/* If the previous page hasn't been loaded in a given time (in this case
* 1000ms) the user is redirected to the default location given above.
* This enables you to redirect the user to another page.
*
* However, you should check whether there was a referrer to the current
* site. This is a good indicator for a previous entry in the history
* session.
*
* Also you should check whether the old location differs only in the hash,
* e.g. /index.html#top --> /index.html# shouldn't redirect to the default
* location.
*/

if(
newHash === oldHash &&
(typeof(document.referrer) !== "string" || document.referrer === "")
){
window.setTimeout(function(){
// redirect to default location
window.location.href = defaultLocation;
},1000); // set timeout in ms
}
if(e){
if(e.preventDefault)
e.preventDefault();
if(e.preventPropagation)
e.preventPropagation();
}
return false; // stop event propagation and browser default event
}
<span class="goback" onclick="goBack();">Go back!</span>

Note that typeof(document.referrer) !== "string" is important, as browser vendors can disable the referrer due to security reasons (session hashes, custom GET URLs). But if we detect a referrer and it's empty, it's probaly save to say that there's no previous page (see note below). Still there could be some strange browser quirk going on, so it's safer to use the timeout than to use a simple redirection.

EDIT: Don't use <a href='#'>...</a>, as this will add another entry to the session history. It's better to use a <span> or some other element. Note that typeof document.referrer is always "string" and not empty if your page is inside of a (i)frame.

See also:

  • W3C: HTML5: 5.4.2 The History interface


Related Topics



Leave a reply



Submit