18

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>
Clarke
  • 295
  • 1
  • 2
  • 10
  • 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 Nov 07 '11 at 21:27
  • 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. http://www.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 Nov 15 '11 at 17:35
  • 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 May 08 '12 at 16:40
  • We have found a workaround that works for us. We are running our entire app in HTTPS which avoids the back forward cache. – Clarke Sep 20 '12 at 21:09
  • 1
    Nothing to add except that this is a perfect example of what a SO question should look like IMHO... +1! – David John Welsh Sep 22 '12 at 11:51
  • for Safari Ipad, what about http://stackoverflow.com/questions/24524248/forcing-mobile-safari-to-re-evaluate-the-cached-page-when-user-presses-back-butt/24524249#24524249 – Bakhshi Jul 03 '14 at 06:16

4 Answers4

26

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>
    
Community
  • 1
  • 1
BrutalDev
  • 5,978
  • 6
  • 55
  • 65
  • In the end, this solution is the same as the rest in that doing a reload does not fix my problem. From below, "This solution works well for dynamic content pages, but breaks down when you navigate back to a page that is expecting a form submission. The location.reload() only loads that URI and does not post the original form values." – Clarke Apr 10 '12 at 19:48
  • 1
    @Clarke: You need to write the POST parameters you want to re-POST to the markup using server-side code (just like the way you are writing out the "test" variable). Inject hidden input elements under the form element for all the variables you want to POST again. Then using the same basic code above, instead of doing a reload, just do a form submit. – BrutalDev Jun 16 '12 at 19:31
  • That seemed a good solution, but in the reality it isn't. That event sometimes fires sometimes not. I can't even explain how to repeat that issue, try to open the page 1 then page 2, then click back, event fires, then repeat a couple of times. – Rantiev Sep 03 '14 at 14:25
1

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:

Elf Sundae
  • 1,517
  • 15
  • 21
  • I tried adding a reload on the page when navigating back to it, but this causes problems on pages that are expecting a form post. The reload simply loads the URI without posting the form data. – Clarke Jan 06 '12 at 21:42
1

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.

  • Thanks for the input, though it will not work for me. Since this works for you, you may want to add ipod to the if statement. I had to do this because an iPod Touch is what I use for testing and had to add it to make it work. This solution works well for dynamic content pages, but breaks down when you navigate back to a page that is expecting a form submission. The location.reload() only loads that URI and does not post the original form values. This is where my example html above is incomplete. – Clarke Jan 06 '12 at 21:26
0

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 (..)

BoltClock
  • 630,065
  • 150
  • 1,295
  • 1,284
StephaneAG
  • 907
  • 1
  • 11
  • 11