4

I have seen related questions on StackOverflow, but none of the suggestions are working for me. I need browsers to reload a page from the server every time it is displayed, including if the user got there by pressing the Back button. It seems like browsers obey caching rules for everything except the base document. This happens in current versions of Safari and Firefox (as of Dec 2013), verified by packet capture.

The pages in my application are used to edit a database record. At the top of the source files are a couple lines of PHP to store a lock indicating that the record is being edited. Other users cannot edit the same record while the lock exists. The pages have a window unload handler that uses AJAX in non-async mode to release the lock. (There's more to the locking mechanism, but those are the relevant pieces.) When the user returns to the page via Back button, the server-side code is never executed.

I have tried including a header:

Cache-Control: no-cache, must-revalidate

Safari's inspector shows the header was received and processed, but it still does not re-retrieve the page.

I have tried setting a handler to check whether the page's state was maintained:

window.onpageshow = function(event) {
    if (event.persisted) {
        window.location.reload();
    }
};

The if condition never matches: event.persisted is always false.

The annoying part is that this appears to be technically correct. According to the relevant part of the HTML5 spec, since the page registers an unload listener the browser should never try to maintain page state. And it doesn't! When the user presses the back button, the browser is "replaying" the entire page load sequence, including the ready event. It repeats any AJAX calls where the prior results were not cached. The only thing it refuses to actually reload from the server is the main document itself.

How do I get it to reload the main document?

giskard22
  • 743
  • 1
  • 6
  • 15
  • 1
    I'm not sure you're going to be able to solve this in code... seems like an issue that will differ with each browser. Perhaps if you could state what the underlying issue is we could try to find a solution for it? – Jordan Kasper Dec 13 '13 at 23:00
  • The final line of my post is intended to be the underlying issue: how do I get the browser to reload the main document, explicitly making a GET call to the server again, every time the page is displayed? – giskard22 Dec 13 '13 at 23:03

4 Answers4

4

Quick answer:

No you can't... the Back button is even more aggressive and different of a cache than the others. Some insight:

Why is Google Chrome going to the server on pushState?

https://github.com/nickhsharp/prefetchNightmare

That said... a GET request (the browser loading it) shouldn't "do" anything to the server... if anything you should do that lock setting part via an AJAX on the page start... the inverse of how you remove it using AJAX on the done part.

The browsers are pretty clear on their reasons for the crazy BACK/FORWARD caches and you're not going to be able to force their hands on this one.

Community
  • 1
  • 1
Nick Sharp
  • 1,881
  • 12
  • 16
  • I figured this would probably be the answer, if for no other reason than browser behavior changes over time and shouldn't be depended on. Switching to initiating the lock as part of an AJAX call should be pretty easy. Thank you! – giskard22 Dec 13 '13 at 23:38
  • I cannot get the desired behavior. I'm now trying to create the lock as part of the client's request for the record to edit (a JSON retrieval). I read RFC2616 section 9 and I know this violates convention for GET, but it still seems like it should work. Server is sending back `Cache-Control: no-cache, must-revalidate` and `Expires` headers, but the browser doesn't care. I also tried POST, which seems inappropriate but often produces different caching behavior. Still nothing. With POST, Safari's inspector *claims* that the response was not cached even though no packets are exchanged! – giskard22 Dec 16 '13 at 19:20
  • Are you adding those headers to the GET made by AJAX after page load? Or are those in the headers for the actual page. The browser shouldn't be caching those in the same way as it does the uber-aggressive BACK/FORWARD cache. Have you tried just making those GET's via AJAX and making sure to set cache false (if using jQuery) and/or just adding a timestamp to the GET in a query param – Nick Sharp Dec 16 '13 at 20:47
  • Those headers are in the AJAX response. The main page is being sent without any cache-related headers. I hadn't thought of trying to set AJAX options to prevent caching. That's working (thank you!) but I would really like to understand why it was necessary. – giskard22 Dec 16 '13 at 21:21
  • Yeah, I'm really struggling to understand the browser behavior in relation to the pageshow event `persisted` property. The browser insists on caching everything, but it technically is not "maintaining page state" because it's re-running JavaScript as if the page loaded. But if it's doing it based on improperly-cached info, you're in some weird limbo state and you can't detect it. What to do? – giskard22 Dec 16 '13 at 22:19
  • You can read my crazy ass rant that I linked above... quick answer is thankfully that the browsers who do crazy prefetching also mostly support a visibility API, so you kinda wrap your code in one of those as well as your document ready... – Nick Sharp Dec 16 '13 at 23:52
2

Adding this code to my HTML works just fine for me:

<input id="isOld" type="hidden" />
<script>
    onload = function () {
        var el = document.getElementById('isOld');
        if (el.value) {
            el.value = '';
            location.reload();
        }
        el.value = true;
    };
</script>

The code assigns a value to the hidden input that will remain after the back button is clicked in which case the page is force refreshed.

And here's a stripped down version of the above:

<input id="isOld" type="hidden" />
<script>
    setTimeout(function () {
        var el = document.getElementById('alwaysFetch');
        el.value = el.value ? location.reload() : true;
    }, 0);
</script>

This time we no longer rely on the onload event which might conflict with other code elsewhere.

Ziad
  • 1,036
  • 2
  • 21
  • 31
0

There is HTML5 has History API https://developer.mozilla.org/en-US/docs/Web/API/History

Perhaps you may try that.

0

Nick Sharp's answer is correct - you are trying to find a solution to a problem you've created by the way you've built the application. There are some potential solutions: Optimistic locking mostly works - but bind the session id not the user id. Alternatively you could rebuild it modelling it along the lines of a thick client application - where all the interaction is mediated by javascript within a single HTML page load.

symcbean
  • 47,736
  • 6
  • 59
  • 94
  • I'm already locking based on session, not user. This started out as a simpler application and grew, so a single-page design seemed too much at first, then became overwhelming to consider later. Simply initiating the lock as part of the AJAX call to retrieve the database record will be easy to implement. – giskard22 Dec 13 '13 at 23:34