HTML5 Application Cache How To

May 28, 2012 | By

There are a lot of great blog posts, articles, and videos about HTML5 Application Cache, but because it’s fairly new and the spec is still evolving, it’s hard to get your head around all of the intricate details.

What is it?

The Application Cache is a new HTML5 capability that is different from the normal browser cache that we’ve had for years.  The normal browser cache maintains copies of recent files that you have accessed and keeps copies of those files on your computer so that when you need the same file again, it won’t have to re-download it.  Other than some simple browser settings to control the overall cache size and a few meta tags to turn it on and off, you really don’t have any useful control over how it’s used.

The application cache is a web-application-specific cache, the content and behavior of which is controlled by the application itself.  The application cache is persistent and provides full off-line capabilities. It is currently supported in Chrome (v4.0+), Safari (v4.0+), Firefox (v3.5+), and Opera (v10.6+). It’s not currently supported in any shipping version of Internet Explorer, but it is coming in IE10.

Try it now with an offline-enabled website:

Go to http://phraffle.com using Chrome, Firefox, Safari or Opera.  You’ll see a simple one-page site that I created that people use to raffle off giveaways at speaking events.  Nothing special, right?  Now disconnect from the Internet and go to the site again using the same browser.  It still works in your browser as if you were still connected!  Your browser now has a copy of the website cached in the application cache and it will remain cached there until the user removes it.

Looking under the hood from Chrome browser:

If you are using Chrome, you can check out what was downloaded at chrome://appcache-internals/:

You can also see the files being downloaded from Chrome’s JavaScript console:

The console after hitting the same site for a second time:

More information about cache-related events is below.

Try an offline-enabled HTML5 Web app on an iPhone or iPad:

If you have an iPhone, iPad or iPod Touch, go to http://phraffle.com using Safari.  Tap on the share icon and select “Add to Home Screen” (you will see a popup pointing to the share button shortly after the page loads thanks to a cool JavaScript lib at http://cubiq.org/add-to-home-screen).

You’ll now see what looks like a regular app on your home screen with my custom icon.  Now, tap the icon to run the app from your home screen (with or without Internet).  Notice that the app is running full-screen with no Safari browser controls.  This is an iOS HTML5 web app.

I’ll show you how to set this up later in this article.

Setting up application caching:

Here are the exact steps I used to set up the above example:

  • I modified the <html> tag to reference my soon-to-be-created HTML manifest  — <html manifest=”phraffle.appcache”>   This should be added to each HTML file that will be cached so that the user’s browser will be instructed to cache the files regardless of which HTML file was used as an entry point to your site.
  • I then created my cache manifest file called phraffle.appcache in the web root folder.  This file contains a list of files required to run my site offline.  You can view mine here. Notice that it contains all of my HTML, JavaScript, CSS and image assets.  The manifest file name can be named anything, but .appcache  seems to be an established convention.
    CACHE MANIFEST
    
    # Version 1.5
    
    NETWORK:
    *
    
    CACHE:
    index.html
    jquery-1.7.2.min.js
    jquery.backgroundPosition.js
    jquery.spritely.js
    raffle.js
    images/reel_blur_numbers.png
    images/reel_blur.png
    images/reel_dash.png
    images/reel_normal_numbers.png
    images/reel_normal.png
    images/reel_normal.psd
    images/reel_x.png
    
  • My web server has no idea what a .appcache file is so, by default, it will serve it up as a regular text file.  In order for app caching to work properly, the cache manifest file needs the MIME type set to text/cache-manifest.  I’m using Apache web server, so I simply created a .htaccess in my web root folder that has the following line: AddType text/cache-manifest .appcache   If you are using IIS or another web server, you’ll need to do a quick web search to determine how to associate the mime type “text/cache-manifest” to your .appcache file extension.
  • That’s all that is needed to cache the files.  It works offline in any compatible browser, including mobile Safari, Android browser, desktop Chrome, Safari, Firefox, and Opera.

Breaking down the manifest file:

    • The first line is “CACHE MANIFEST” (without quotes) and is required.
    • Comments start with “#”
    • The CACHE: section contains a list of files to be cached.  Wildcards are not allowed here.  You must list each file that needs to be cached.  In my file, it wasn’t necessary to add the “index.html” line to the list of cached files since every file containing <html manifest=”…”> is automatically cached, but it’s still good practice to include it.
    • The NETWORK: section contains a list of online resources that are used by your app.  This is the most misunderstood part of the manifest file.  Cached apps do not have access to network resources unless those resources are listed in the NETWORK section of the cache manifest.  Wildcards are allowed here (thankfully!).  In my file above, I simply used “*” which basically means “you can access anything you want”.  (You may be wondering what network resources my silly apps needs… I’m using TypeKit and Google Analytics, both of which require Internet access).
      • If I omitted the NETWORK section of my manifest file, my app would still work, but the fancy TypeKit font I used would not be loaded so the boring backup font of Arial would be used, and Google Analytics would not record the page hit.  Even if the user is online, access attempts to any network resource would be ignored as if the app were permanently offline.
      • If a filename is listed in the NETWORK section (e.g. login.php), the current site URL will be prepended.  So listing “login.php” in the NETWORK section is equivalent to listing “http://mysite.com/login.php”.  In this case, the login.php file will not be cached and any accesses to the http://mysite.com/login.php will be from the website itself, not the cache.
      • You can see the specific rules on how the NETWORK section is handled in the actual spec (see bullet 30.)
      • I recommend starting with a simple “*” when you first start playing around with this stuff.  You can always change it to be more specific after you get everything working.
    • The FALLBACK:section specifies fallback pages if an online resource is inaccessible.  I did not need this feature in my app, but below is an example of what a FALLBACK section would look like.  The first URI is the resource, the second is the fallback. Both URIs must be relative and from the same origin as the manifest file. Wildcards may be used.
      FALLBACK:
      *.html /offline.html
      *.jpg /images/missing.jpg
      

iOS – Making the web app run full-screen without Safari controls:

  • After the above changes, I can use mobile Safari on my iPhone and iPad to access the page.  After accessing it once, it’s saved for offline use.  I wanted it to be more app-like, so I added the following meta tag to the  index.html:
    <meta name="apple-mobile-web-app-capable" content="yes"/>
    

    This will make the cached app run in full-screen mode so the user doesn’t see the Safari controls.

iOS – Adding custom icons and a startup image:

  • By default, iOS will use a screenshot as the app icon.  I wanted something a little nicer.
  • I created the 3 required icon sizes (57×57, 72×72 and 114×114) and added the following 3 lines to my index.html’s HEAD section:
    <link rel="apple-touch-icon" href="icon57.png" />
    <link rel="apple-touch-icon" sizes="72x72" href="icon72.png" />
    <link rel="apple-touch-icon" sizes="114x114" href="icon144.png" />
    
  • You’ll notice that the images I used are square with no rounded corners (e.g. http://phraffle.com/icon144.png).  The rounded corners, drop shadow, and reflection are added by the device automatically.  You can avoid these “enhancements” by adding “-precomposed” to the filename (e.g. icon72-precomposed.png).
  • It’s also possible to add a startup image for a web app. I didn’t do this on mine, but if you want to try it, here’s all you need in your html:
    <link rel="apple-touch-startup-image" href="startup.png">
    

Last year, the Financial Times rolled out their new iPad webapp using this same technique to avoid the Apple app-store rules (specifically, Apple’s requirement to take 30% of any in-app purchase).  Obviously this isn’t a perfect solution for many apps, but it’s a good option to have in some cases.

By the way, if you would like to build more sophisticated apps using HTML/CSS/JS, and take advantage of native APIs and true app-packaging, check out PhoneGap.

IMPORTANT — Updating your app (updating the user’s cached files):

    • If you make any changes to your HTML, CSS, or other cached assets,  you will need to modify the cache manifest file so that users with previously cached versions of the files receive fresh ones.   The contents of the manifest file must change for it to be re-processed on the client side (simply updating the timestamp of the file isn’t enough).  You’ll notice that in my phraffle.appcache file, I have a simple comment with an arbitrary version number.  I simply increment the number each time I modify any files in the cache list to force previously cached files to be refreshed.
    • It’s not currently possible to update a single file in the cache list.  It’s all or nothing.  When the browser sees a newly modified cache manifest file, it will re-download all files in the CACHE section again.  This is obviously a bit of a shortcoming.  Hopefully we’ll see this addressed in the future.
    • If any of the files listed in the manifest are not accessible, the caching will abort.  The JavaScript console in Chrome will show the file caching activity, so you will quickly see any 404 or similar errors.
    • I highly recommend validating your cache manifest using the following site — http://manifest-validator.com/
    • Double-reload needed — when you access phraffle.com, your browser always checks the manifest file to see if it’s been updated (you will see this happen in the JavaScript console).  If the manifest was updated, the browser will download all of the files in the cached file list, BUT, the original content will have already loaded at this point (remember that it’s already cached, so it loads super fast!).  This means that you will not see the new content until you hit refresh again.  This is a bit of a pain at times!  There is a workaround that you can implement using the following JavaScript code:
      if (window.applicationCache) {
          applicationCache.addEventListener('updateready', function() {
              if (confirm('An update is available. Reload now?')) {
                  window.location.reload();
              }
          });
      }
      

JavaScript Application Cache APIs:

There is an AppCache API that provides several hooks into the caching.  Everything is defined in the applicationCache object.  The two main methods are updateCache() and swapCache().  In addition, there are several cache events that you can listen for:

  • checking – Fired once when the app reads the manifest file to see if it has changed.
  • noupdate - The manifest file has not changed.
  • downloading - Files are being downloaded
  • progress - Fired once per each file downloaded. If you have 15 files, you’ll see 15 progress events. Unfortunately, the event itself doesn’t contain information about which file just completed, so it’s fairly limited in its current form.
  • cached – Files have finished downloading.
  • updateready - A new copy of the cache is ready to be swapped in.
  • obsolete - The manifest file is code 404 or code 410; the application cache for the site has been deleted.
  • error - An error occurred when loading the manifest, its parent page, or a listed resource, or the manifest file changed while the update was running. The cache update has been aborted.

If you want to see these fire, you can use the following code:

function logEvent(event) {
  console.log(event.type);
}

window.applicationCache.addEventListener('checking',logEvent,false);
window.applicationCache.addEventListener('noupdate',logEvent,false);
window.applicationCache.addEventListener('downloading',logEvent,false);
window.applicationCache.addEventListener('progress',logEvent,false);
window.applicationCache.addEventListener('cached',logEvent,false);
window.applicationCache.addEventListener('updateready',logEvent,false);
window.applicationCache.addEventListener('obsolete',logEvent,false);
window.applicationCache.addEventListener('error',logEvent,false);

Although it’s not part of the applicationCache object, you might want to know how to programmatically check if the user is online or offline.  This is done by simply checking navigator.onLine (boolean).

Using Google Analytics, Typekit and other services with Application Caching

When I first offline-enabled phraffle.com, I didn’t have the NETWORK section in my cache manifest. All of the files were still cached, but to my surprise, my Typekit “bello-pro” font wasn’t displaying and I was suddenly not recording hits on Google Analytics, even when I was online.

Typekit, Google Analytics and many other services obviously require internet access, but my lack of a NETWORK section in my cache manifest meant that all external URLs were inaccessible. Once I added the NETWORK section, everything worked normally again (when I was online of course).

Other fun facts, quirks, and ramblings:

  • I was unable to find any definitive information about the size limitations of the app cache.   I did some testing with Chrome (v19), and it seems to be capped at around 50MB.  When I tried a file over 50MB, it gave a simple error in the JavaScript console with no meaningful description. I’ve read that Firefox allows over 500MB, but I haven’t confirmed this.
  • Tobie Langel, one of the software engineers at Facebook, recently wrote a blog post where he lists several enhancements that Facebook would like to see as the spec moves forward.  I think he covers it quite well!
  • Do NOT add the actual cache manifest file to the list of files to cache in the manifest.  Weird, endless-loop, recursive stuff will occur.
  • From my extensive browsing on stackoverflow, I can tell you one of the most common mistakes to getting this working correctly is forgetting to change the MIME type for the cache manifest file itself or forgetting to put the colon on “CACHE:”.
  • Weird iOS full-screen bugs — There are some weird cache issues with full-screen, run-from-home-screen iOS web apps.  At first I thought I was going nuts, because I would clear Safari’s cache, yet I would still see old content until I re-ran the app a few times.  I found many reports of others seeing the same thing.  My advice is to add something to your page so you can confirm that the content did indeed refresh.  It seems to be fine now that I have my config finalized, but it can really make development a challenge when you suddenly can’t clear the cache.
  • From iOS devices, you can see a list of websites that have saved data using app cache by going to SETTINGS, GENERAL, SAFARI, ADVANCED, WEBSITE DATA.  This screen allows you to selectively delete the data on a site-by-site basis.  You can also clear the entire Safari cache on the main advanced screen.   Clearing Safari’s cache also clears all app cache files for all sites.
  • Android — Application caching is supported on Android, but I couldn’t find a way to run it full-screen without any browser controls.  Let me know if you know how to do this.  I suspect that you’ll need to make a full app out of it (using PhoneGap of course!).

Resources I used:

Here are the resources I found while researching this today:

Filed in: HTML5, iOS/iPhone/iPad, JavaScript | Tags: , , , , , , , , , , , , , , ,

About the Author (Author Profile)

Greg is a developer advocate for the Google Cloud Platform based in San Francisco, California

Comments (3)

  1. Hi Greg,
    I thought you might be interested in knowing that I built an extension for Brackets which generates a cache manifest for you, and lets you validate it against http://manifest-validator.com/, straight from your editor ;)

    Here it is:
    https://github.com/davidderaedt/appcache-gen

    Of course, feedback is very welcome ;)

  2. Don Kerr

    Thanks Greg! Very helpful. :)
    Don Kerr