18
votes

tl;dr - Safari on iOS 5 is caching so hard, it is breaking my site.

I am struggling with the way the Safari browser in iOS 5 deals with back forward cache, which they call "Page Cache". The way it is described here explains the behavior very well.

Quite simply, the Page Cache makes it so when you leave a page we “pause” it and when you come back we press “play.”

This is causing problems throughout my site. When using the back button, most other browsers will show you the page in the state it was loaded. Not Safari on iOS 5, it shows you the page as you last left it. A simple example would be the disabling of a submit button. If I use Javascript to disable a submit button, then submit a form, when you click back the submit button will still be disabled. This has been an issue in other browsers, including the desktop version of Safari, but it is solved by setting the onload event handler to a blank function. I believe this tells the browser to invalidate the cache because something important could have happened in that function. This hack does not seem to work for Safari on iOS 5.

Below is the issue boiled down to the bare essentials. When you load test.html you will see the text "Original Text". When you click the link, that text will change to "Changed text - forwarding to next page", then in 3 seconds you will be forwarded to test2.html. All is good up to this point in all browsers. In all other browsers, when you click the back button, the text you will see is "Original Text", but on Safari for iOS 5 you will see "Changed text - forwarding to next page".

Any suggestions on how to deal with this?

This is a simple example

test.html

<script>
function changeText() {
    el = document.getElementById("text");
    el.innerHTML = "Changed text - forwarding to next page";
    setTimeout("forward()",3000);       
}
function forward() {
    document.location.href = "test2.html";
}
</script>
<div id="text">Original Text</div>
<a href="Javascript:changeText()">Click Here</a>
<script>
window.onunload = function(){};
</script>

test2.html

<div>Click back button</div>

This is a second example using a form post. This is a simple example of how my app is working. When you navigate back to formtest2.asp, you should see the posted form value and the div text should be original.

formtest.asp

<form method="post" action="formtest2.asp">
    Test: <input type="text" name="test"/>
    <input type="submit" value="Submit"/>
</form>

formtest2.asp

<script>
function changeText() {
    el = document.getElementById("text");
    el.innerHTML = "Changed text - forwarding to next page";
    setTimeout("forward()",3000);       
}
function forward() {
    document.location.href = "test2.html";
}
</script>

<%
Dim test
test = Request("test")
Response.Write("Test value: " & test & "<br />")
%>

<div id="text">Original Text</div>
<a href="Javascript:changeText()">Click Here</a>
<script>
window.onunload = function(){};
</script>

test2.html

<div>Click back button</div>
4
Just a bit more info about this. It seems as though all the browsers that I have tested (Firefox, IE, Safari Desktop) are firing pagehide, and unload, in that order. Safari on iOS 5 only fires pagehide.Clarke
When opening a new tab, the unload event is not fired, just pagehide. When opening a new tab, you would expect to be able to navigate back to an old tab and see the page just as you had left it. The problem is that when we navigate from one page to the next in the same tab/window, the unload event should be fired per w3c but is not. w3.org/TR/DOM-Level-2-Events/events.html. "The unload event occurs when the DOM implementation removes a document from a window or frame. This event is valid for BODY and FRAMESET elements."Clarke
Any solution using location.reload() fails because for this function, Safari on ios 5 will only do a GET, not a POST like all other browsers.Clarke
We have found a workaround that works for us. We are running our entire app in HTTPS which avoids the back forward cache.Clarke
Nothing to add except that this is a perfect example of what a SO question should look like IMHO... +1!David John Welsh

4 Answers

26
votes

I was also looking for an answer to the same issue. Going back in iOS 5 does not execute any javascript and just leaves the page in the previous state it was when you left or were redirected.

Trying the weird "onunload" hack found in "Is there a cross-browser onload event when clicking the back button?" only worked for iOS 4, not iOS 5 which does not call the event as expected. Nickolay pointed out new functions on web-kit called "pageshow" and "pagehide" which are much more reliable than Giuseppe's example.

  1. You need the hack from this article: "Is there a cross-browser onload event when clicking the back button?" for this to work in iOS 4.
  2. Use this script which uses the new event to safely check if the page was loaded from cache and force a reload (avoiding URLs checks and local storage and also include iPods) for iOS 5:

    <body onunload="">
    ...
    <script type="text/javascript">
    if ((/iphone|ipod|ipad.*os 5/gi).test(navigator.appVersion)) {
      window.onpageshow = function(evt) {
        // If persisted then it is in the page cache, force a reload of the page.
        if (evt.persisted) {
          document.body.style.display = "none";
          location.reload();
        }
      };
    }
    </script>
    
1
votes

I'm meeting the same problem.
On iOS4, when you navigate homepage.html to 2.html, then back to homepage.html, the js will not be called. when you navigate homepage.html to 2.html, from 2.html you continue going to 3.html, and then back from 3 to 2 to homepage, the JS will be called! Yes, It's terrible!
But on iOS5 MobileSafari, It always cannot be called :( Maybe this is a cache bug on iOS5. I tested on iOS 5.0.0 and 5.0.1, got the same result.

But when you implement the onUnload event on body element, it can fix the backforward problem on previous MobileSafari. I simply changed the <body> to <body onUnload=""> in homepage.html, then from homepage to 2.html, when going back to homepage, the javascript on homepage will be called. It works on FF, Desktop Safari, Mobile Safari(iOS4)

So, how can we do on iOS5? Unfortunately I have not found a solution. But my webapp runs in a native client, I fixed this by adding a reload() call from native:

        if (isOS5_)
        {
            [webView stringByEvaluatingJavaScriptFromString:@"location.reload();"];
        }

when webViewDidFinishLoad:

1
votes

on my mobile site, I have fixed the problem with this small JS Code:

if ((/iphone|ipad.*os 5/i).test(navigator.appVersion)) {
    window.onload=function () {
        localStorage.setItem("href",location.href);
    };
    window.onpopstate=function () {
        var a=localStorage.getItem("href"),
            b=location.href;
        if (b!==a&&b.indexOf((a+"#"))===-1) {
            document.body.style.display="none";
            location.reload();
        }
    };
}

Scenario:

Clicking the link “Page2” from “Page1”: Once clicked “Page1” gets closed, “Page2” is loaded and fires the onload and many other events.

[Page1] -> click the link -> [Page2] -> onload -> onpopstate -> ...

At this point if you click on the back button of your browser, “Page2” gets closed and “Page1” is loaded and fires the onload and many other events.

[Page2] -> click on the back button -> [Page1] -> onload -> onpopstate -> ...

Clicking the back button in IOS5 makes closing “Page1” and “Page2” appears but the page is not loaded, the viewport scale gets corrupted and the only event fired is onpopstate.

[Page2] -> click on the back button -> [Page1] -> onpopstate.

The Fix:

The fix works around the events onload, onpopstate and the localStorage object.

onload is the first event managed by the script, and within this it stores the current location.href on localStorage.

If there is not any bug, in onpopstate the current location.href and the href stored must be the same and the script should do nothing.

Using Safari on IOS5 the page is not loaded and onload event is not fired, then location.href and the stored href are different in onpopstate event.

In this case just hide the current page (for another bug in viewport related to this) and reload the page with location object.

0
votes

After finding how to avoid one of the issue(s) we all appear to have on this, I tough I'd like to share :)

After reading few related topics about the subject, mainly on stack overflow "hottest subjects", handling the "Page Cache" ( http://www.webkit.org/blog/427/webkit-page-cache-i-the-basics/ and http://www.webkit.org/blog/516/webkit-page-cache-ii-the-unload-event/ ), checking the "unload" event ( developer.apple.com/library/IOS/ipad/#documentation/AppleApplications/Reference/SafariWebContent/HandlingEvents/HandlingEvents.html ) / "pageshow" does nohing have to deal with the brief "page apparition" in its previous state ( not the previous page INITIAL state, but just the page as it was left ) while user switch back to app / or unlock his device / or return to app after quitting it.

It seems (for me,at least) , that this "visible page glitch" was solved when I did the update for IOS6 ,like 5 mins ago ;D

I was then able to use my webapp in mobile safari as normal, and after adding it to the home screen, the behavior in case of "app switching" or "relaunching" ,with or without cleared cache, is the same as its first startup: you see a BLANK page and then your content (wich may be the page corresponding to the url that were saved to home screen, or any other content loaded using localStorage for example [my case (..) ] ).

For the events I was able to catch, nor on IOS4 IOS5 devices could I manage to get rid of that "visual persistence glitch". I guess that's because of the way "Page Cache" works, but now i'm running IOS6 on iPad, as the glitch is no more there, I guess the Safari team must have improved the way they handle "homescreen" webapps (..)