How to properly invalidate an HTML5 Cache Manifest for online/offline web apps?

Justin Searls picture Justin Searls · Nov 11, 2009 · Viewed 39.3k times · Source

I'm currently using a Cache Manifest (as described here). This effectively makes the necessary resources to run the application available when the user is offline.

Unfortunately, it works a little too well.

After the cache manifest is loaded, Firefox 3.5+ caches all of the resources explicitly referenced in the cache manifest. However, if a file on the server is updated and the user tries force-refreshing the page while online (including the cache-manifest itself), Firefox will absolutely refuse to fetch anything. The application remains completely frozen at the last point it was cached. Questions:

  1. I want Firefox to effectively only rely on the cached resources when the network connection fails. I've tried using the FALLBACK block, but to no avail. Is this even possible?
  2. If #1 is not possible, is it possible for the user to force-refresh a page and bypass this cache (ctrl-F5 doesn't do it and neither does clearing the browser's cache, shockingly) short of clearing their private data? Alternatively, does the cache-manifest mechanism support expiry headers and is its behavior with respect to this documented anywhere?

Answer

Justin Searls picture Justin Searls · Nov 11, 2009

I think I've got this figured out: if there's an error in one's cache-manifest (say, a referenced file does not exist), then Firefox completely will stop processing anything applicationCache related. Meaning, it won't update anything in your cache, including your cached cache-manifest.

To uncover that this was the issue, I borrowed some code from Mozilla and dropped this into a new (non-cached) HTML file in my application. The final message logged stated that there might be a problem in my cache-manifest, and sure enough there was (a missing file).


// Convenience array of status values
var cacheStatusValues = [];
 cacheStatusValues[0] = 'uncached';
 cacheStatusValues[1] = 'idle';
 cacheStatusValues[2] = 'checking';
 cacheStatusValues[3] = 'downloading';
 cacheStatusValues[4] = 'updateready';
 cacheStatusValues[5] = 'obsolete';

 // Listeners for all possible events
 var cache = window.applicationCache;
 cache.addEventListener('cached', logEvent, false);
 cache.addEventListener('checking', logEvent, false);
 cache.addEventListener('downloading', logEvent, false);
 cache.addEventListener('error', logEvent, false);
 cache.addEventListener('noupdate', logEvent, false);
 cache.addEventListener('obsolete', logEvent, false);
 cache.addEventListener('progress', logEvent, false);
 cache.addEventListener('updateready', logEvent, false);

 // Log every event to the console
 function logEvent(e) {
     var online, status, type, message;
     online = (isOnline()) ? 'yes' : 'no';
     status = cacheStatusValues[cache.status];
     type = e.type;
     message = 'online: ' + online;
     message+= ', event: ' + type;
     message+= ', status: ' + status;
     if (type == 'error' && navigator.onLine) {
         message+= ' There was an unknown error, check your Cache Manifest.';
     }
     log('
'+message); } function log(s) { alert(s); } function isOnline() { return navigator.onLine; } if (!$('html').attr('manifest')) { log('No Cache Manifest listed on the tag.') } // Swap in newly download files when update is ready cache.addEventListener('updateready', function(e){ // Don't perform "swap" if this is the first cache if (cacheStatusValues[cache.status] != 'idle') { cache.swapCache(); log('Swapped/updated the Cache Manifest.'); } } , false); // These two functions check for updates to the manifest file function checkForUpdates(){ cache.update(); } function autoCheckForUpdates(){ setInterval(function(){cache.update()}, 10000); } return { isOnline: isOnline, checkForUpdates: checkForUpdates, autoCheckForUpdates: autoCheckForUpdates }

This was certainly helpful, but I should definitely request a feature from Mozilla that prints out malformed cache-manifests at least to the Error Console. It shouldn't require custom code to attach to these events to diagnose an issue as trivial as a renamed file.