Monday, September 10, 2012

Expiring the Root Page of Application Cache.

‹prev | My Chain | next›

I need to take a (hopefully) quick break from my regularly scheduled Three.js programming to answer a question that has vexed me for a couple of night now: how to expire the HTML5 application cache? Actually, most of the cache is easy to expire, but there is one part that I cannot suss.

The application cache is a mechanism for creating offline applications. Mr Doob's Amazing Code Editor uses it so that users can still work with the Code Editor even when not connected to the Internet. Since this is an editor, offline capability seems like a good thing.

The application cache is established by the presence of the manifest attribute on the <html> element of a web page:
<!DOCTYPE html>
<html manifest="editor.appcache">
 <head>
  <title>code editor</title>
The appcache manifest file then describes the elements that are cacheable, those that should always be requested over the network, and any fallback. The manifest is stored in the same location as the web page. The manifest for Mr Doob's Code Editor looks like:
CACHE MANIFEST
# 2012-09-10

CACHE:
/favicon.ico
index.html
files/inconsolata.woff
js/editor.js
js/appcache.js
js/rawdeflate.js
js/rawinflate.js
js/codemirror/codemirror.css
js/codemirror/codemirror.js
js/codemirror/mode/css.js
js/codemirror/mode/htmlmixed.js
js/codemirror/mode/javascript.js
js/codemirror/mode/xml.js


NETWORK:
*
An application cache manifest needs to start with the CACHE MANIFEST directive. The CACHE directive introduces a list of files that will be loaded into cache. These files will be available offline and not requested from the server—even during a hard reload. The NETWORK directive introduces the files that will always be loaded over the Internet—even if they are needed by the application. In the case of the Code Editor, the catch-all of '*' is used to indicate that anything not explicitly referenced by CACHE but needed by the application, should be loaded via the internet.

As for my woes, it begins with the commented date immediately following the opening CACHE MANIFEST directive. The comment has no effect on the application cache itself. What it does serve to do is to expired the application cache. If the network is available, when I load up the application at http://gamingJS.com/code-editor no files are loaded except for the application cache manifest.

This presents a problem if js/editor.js was updated. Unless the application cache has changed, the browser will not reload js/editor.js. Even during a hard reload. Even if the user clears her cache and reloads. The js/editor.js file remains in application cache.

Since no new files have been added to the application, I cannot add a file to the manifest, so the convention is to update the timestamp after CACHE MANIFEST to force a reload. And this works just fine:


My problem is that, the code editor is very new. At the very least, I need to make a few changes to it. Mr Doob has made clear his intention to make major changes as well. I need to reference it in Gaming JavaScript at the http://gamingjs.com/code-editor. But that URL is not in the application manifest. The actual page of index.html is included in the manifest, but this is not what is stored in application cache.

Unfortunately, in its early days, much of Code Editor is stored in this web page. When there are changes, I have no way to update them. I (and anyone else with the old cached application) are stuck with the original version of the application. The only solution is to open up chrome://appcache-internals/ to manually remove the old version. This is not exactly ideal.

My ultimate solution to this conundrum is a decidedly pre-HTML5 solution:
    <script>
if (window.location.href.indexOf('.html')) < 0 {
  window.location.href = 'index.html'
}
    </script>
And that seems to do the trick.


Dang it. That was a ton of explanation for a very simple solution. I wish I had thought of that sooner. But, like I said at the outset, this had been bugging me for days. Writing it down turned out to be my rubber ducky.

Update: In the end, I don't think that works as desired. Subsequent reload are not picking up changes. So I am forced to adopt the HTML5 Rocks swapCache() solution:
// Check if a new cache is available on page load.
window.addEventListener('load', function(e) {

  window.applicationCache.addEventListener('updateready', function(e) {
    if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
      // Browser downloaded a new app cache.
      // Swap it in and reload the page to get the new hotness.
      window.applicationCache.swapCache();
      if (confirm('A new version of this site is available. Load it?')) {
        window.location.reload();
      }
    } else {
      // Manifest didn't changed. Nothing new to server.
    }
  }, false);

}, false);
Day #505

3 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. You should update your time stamp on the manifest and make sure the manifest file itself doesn't get cached by the HTTP cache. See https://developers.facebook.com/html5/build/features/offline/ for more info.

    --tobie

    ReplyDelete
    Replies
    1. Yup, I'm doing both of those things. They work and are good advice. My problem is that, at least in Chrome, the `/code-editor/` resource is implicitly added to the appcache by virtue of the manifest attribute in index.html. When the manifest is updated (even if only the timestamp changes), then everything is reloaded except for the main `/code-editor/` resource. The index.html file in appcache contains the most recent stuff, but `/code-editor/`, which is where I access the app, is still old.

      Delete