Developer

How Google and Mozilla are aiming to make web apps shine offline

A look at how building offline functionality into web apps could soon become a lot easier using Service Workers.

Building web apps that work well offline can still be a difficult and frustrating process for developers.

However, as part of efforts to close the gap with native apps, browser makers are looking to make it simpler to create web apps that work when the network is down.

The Application Cache in the HTML5 spec was supposed to provide a relatively easy way for web apps to function offline, but fell short in a number of important ways, such as not automatically loading fresh content once data had been cached. As a result, the Application Cache has drawn some harsh criticism.

Google and Mozilla are looking at ways to make web apps 'offline-first' - where everything the app needs to function, at least in its most basic form, is stored on the device and it only needs to connect to the network to pull new data.

For Mozilla, improving these offline capabilities is important for Firefox OS, its open-source mobile operating system designed to run apps built from web technologies such as HTML, CSS and JavaScript (JS). The first phones to run the OS launched in regions such as Eastern Europe and South America, areas whose network infrastructure is such that apps can't assume a network connection.

To help developers create web apps that function smoothly offline, Google and Mozilla are implementing support for the W3C Service Workers specification.

Service Workers are event-driven JavaScript scripts that run independently of web pages and control how they receive data.

What will make Service Worker scripts so handy when building offline apps is their ability to intercept network requests and serve content from their own scriptable cache.

The forthcoming Chrome 40 and as yet unspecified future versions of Firefox and Firefox OS will include support for what will probably be compatible implementations of Service Workers. Support for Service Workers can be enabled in Chrome Canary, Firefox Nightly and Opera browsers by tweaking settings.

To begin using a Service Worker, a developer must first register the worker's script within a JavaScript file used by their app or page. This is done by calling the serviceWorker.register() method and passing the path to the service worker script file as an argument.

if ('serviceWorker' in navigator) {

navigator.serviceWorker.register('/sw-test/sw.js', {

scope: '/sw-test'

}).then(function(sw) {

// registration worked

console.log('Registration succeeded.');

}).catch(function(error) {

// registration failed

console.log('Registration failed with ' + error);

});

}

Once registered, the script will be able to intercept all requests made by pages subsequently loaded that are sitting within its scope, such as sw-test/index.html or sw-test/mouse/trap.html in the example above. Once the script is registered, the method returns a promise.

By default the scope of Service Workers in Chrome 40 will be limited by the location of the Service Worker script, so a script at /answers/sw.js would only be able to control navigation requests from documents whose URL started with /answers.

Setting up the cache

Once a Service Worker is registered, it will then be installed and activated by the browser.

Once installation is complete, it is a good time to populate the Service Worker cache with the resources you want to be available locally.

When installation is complete, an installation event is fired, which can be detected by registering an event listener inside the Service Worker script. This listener can then be passed a function that fills the cache.

this.addEventListener('install', function(event) {

event.waitUntil(cachesPolyfill.open('demo-cache').then(function(cache) {

return cache.addAll(

'/sw-test/',

'/sw-test/index.html',

'/sw-test/style.css',

'/sw-test/app.js',

'/sw-test/image-list.js',

'/sw-test/star-wars-logo.jpg',

'/sw-test/gallery/',

'/sw-test/gallery/image1.jpg',

'/sw-test/gallery/image2.jpg'

);

})

);

});

Here the cachesPolyfill.open() method creates a new cache called demo-cache, which will be the site resources cache. This returns a promise for a created cache. Upon the promise being fulfilled, a function calls the addAll() method on the cache, which for its parameters takes a list of origin-relative URLs to all the resources you want to cache.

The Service Worker cache API is planned to work in a similar fashion to the browser's standard cache but is specific to a web domain.

The detail of how to achieve this could change because the cache API is not finalised, with the functionality currently provided by a JavaScript polyfill that needs to be imported by the Service Worker script.

However, Google is intending to implement support for a subset of the Service Worker Cache API in Chrome 40.

Making sure your app works offline

Directing the browser to pull data and resources from the cache, rather than over the network, is achieved by listening out for the fetch event. The event listener needs to be registered within the Service Worker script.

The fetch event is fired every time a resource - such as images, CSS and JS files - sitting in the scope of the Service Worker is fetched. It is also fired when a document controlled by a Service Worker makes a request for a resource, even if that resource is from a different origin.

self.addEventListener('fetch', function(event) {

console.log(event.request);

});

As pointed out by Google developer advocate Jake Archibald in his explainer for Service Workers, when combined with event's respondWith() method, this provides the ability to hijack a request and inject your own response.

self.addEventListener('fetch', function(event) {

event.respondWith(new Response("Hello world!"));

});

Or in this example capturing all requests for jpg images and replacing them with a Google doodle.

self.addEventListener('fetch', function(event) {

if (/\.jpg$/.test(event.request.url)) {

event.respondWith( fetch('//www.google.co.uk/logos/...3-hp.gif', {

mode: 'no-cors'

}) );

}

});

Directing the page to pull resources from the cache rather than over the network works similarly to above, as shown in this example, where the match() method checks each resource requested from the network against the cache and returns the relevant resource if available.

self.addEventListener('fetch', function(event) {

event.respondWith( cachesPolyfill.match(event.request).then(function(response) {

return response || fetch(event.request);

}) );

});

Service Workers can hijack connections, respond differently, and filter responses - capabilities that could be used in a man-in-the-middle attack. To prevent this, it is only possible to register Service Workers on pages served over HTTPS.

Service Workers can also be started by browsers or other user agents without an attached document and may be killed by the user agent at nearly any time. Their ability to respond to events when a page isn't open allows them to be used to control background processes such as Push messaging and background sync.

It's still early days for Service Workers and details of the implementations in major browsers are subject to change but to find out more, read this explainer by Google's Archibald or this summary from the Mozilla Developer Network, from where many of these examples were sourced.

About Nick Heath

Nick Heath is chief reporter for TechRepublic. He writes about the technology that IT decision makers need to know about, and the latest happenings in the European tech scene.

Editor's Picks

Free Newsletters, In your Inbox