Application Cache, Cache Manifest and Offline Application are all terms used to describe the process of caching a web application for offline use. However we can also leverage this to improve loading times on HTML mobile applications by using this technique as a kind of super cache. For a high-performance HTML application, tricks like this make an HTML application have load times similar to a native application. Trouble is, there are some pretty significant issues that arise from using the technology in this way. I’ve long experimented with using offline caching and the Google Maps API v3 to power the mobile view of our campus map. Along the way, I’ve met more than a few challenges that you just wouldn’t expect.
Application Cache Challenges
-
Inconsistent Protocols
While the spec states that loading resources from an HTTP address when an HTTPS address was used for the cache-manifest file is disallowed, the other direction should be allowed. Specifically, if I load the page via HTTP, I should be able to use images, stylesheets, scripts, etc which are loaded via HTTPS. But in practice all of the major browsers have bugs which disallow or fail in this situation. Workaround: use consistent protocols.
-
Handling Errors
Pop quiz: Which of the following will break a page using a cache manifest file?
- Cache manifest refers to a file that does not exist
- Cache manifest refers to a file that is not used
- Requesting a resource not covered in any section of the cache manifest
- Cache manifest file itself does not exist
Answer: Both a and c. When using an external api such as Google Maps, either can easily happen and be completely outside of your control if the API provider servers resources from a new domain. It’s easy to cause both to happen on your own as well. Workaround: Vigilance and specifically handling ALL of the application cache events.
-
Changing User Agents
Having a page which outputs different content depending on user agents turns out to problematic when combined with a cache-manifest file. The reason: if a user selects the “Request Desktop Site” option in Google Chrome for Android, Chrome still sends the mobile user agent string when checking if the cache manifest file has changed. This means a user is permanently stuck to the mobile view even though they are specifically requesting the desktop view. Since the navigator.userAgent string in the DOM always reports the mobile user agent, there is no known workaround to this one.
-
Cache Out-Of-Date
Once an application cache is created, the next page load immediately loads the content from the cache and then in the background checks to see if the cache manifest file has changed. If so, the application cache is updated, but the user doesn’t see the new version until the page is loaded yet again. In order to make sure your user always sees the newest version, an easy solution is to simply not initialize the page until the application cache is either validated as current, or the new application cache is constructed at which time you reload the page. The reality is that over slow connections, like a mobile device with marginal signal, the user is then stuck waiting – possibly without feedback. As a hybrid approach, you can set a timeout and if the application cache events haven’t fired by that time, go ahead and initialize the current application cache.
-
User is Offline
Allowing offline use was the original intent of an application cache, so why is this an issue? Because many external APIs (such as Google Maps API) requires an active connection. In our use case we are using the application cache as caching on steroids. Thankfully this is the easiest one of all to handle. Using fallback entries in the cache manifest file or testing navigator.onLine in script both make this task a breeze – as long as you don’t forget about it.
Sample Code
Here’s sample code for loading a web application which utilizes the application cache for offline storage:
var AppCacheTimeout = -1;
function AppCacheReady() {
clearTimeout(AppCacheTimeout);
window.applicationCache.removeEventListener('noupdate',
arguments.callee, false);
window.applicationCache.removeEventListener('cached',
arguments.callee, false);
//Load the external api (dynamic script insertion),
// initalize the page, etc....
}
if (window.applicationCache &&
window.applicationCache.status != window.applicationCache.UNCACHED) {
AppCacheTimeout = setTimeout(AppCacheReady, 2000);
window.applicationCache.addEventListener('updateready', function () {
window.applicationCache.swapCache();
location.reload();
}, false);
window.applicationCache.addEventListener('obsolete',function () {
window.location.reload(true);
}, false);
window.applicationCache.addEventListener('noupdate', AppCacheReady,
false);
window.applicationCache.addEventListener('cached', AppCacheReady,
false);
window.applicationCache.addEventListener('error', function() {
// provide user feedback - your page is probably broken.
}, false);
} else {
AppCacheReady();
}
Discover more from Web Strategy and Development News
Subscribe to get the latest posts sent to your email.