window.caches Sample

Available in Chrome 43+ | View on GitHub | Browse Samples

Background

This sample demonstrates basic service worker registration, in conjunction with pre-fetching of specific resource URLs during the installation phase. Additionally, it illustrates how window.caches can be used to make calls against the Cache Storage API from the context of a normal document. (This was previously only exposed to service workers.)

Live Output

The resources currently in the cache are listed below. Some initial files have been added via the service worker's install handler. You can add additional files to the cache or remove files from the context of the current page, without having to pass messages back and forth to the service worker.

    
    

    JavaScript Snippet

    var CACHE_NAME = 'window-cache-v1';
    var cacheEntriesUl = document.querySelector('#cache-entries');
    
    function initializeUI() {
      document.querySelector('#files').style.display = 'block';
    
      document.querySelector('#add').addEventListener('click', function() {
        var url = document.querySelector('#url').value;
        if (url) {
          addUrlToCache(url);
        }
      });
    
      showList();
    }
    
    function showList() {
      // Clear out any previous entries, in case this is being called after adding a
      // new entry to the cache.
      while (cacheEntriesUl.firstChild) {
        cacheEntriesUl.removeChild(cacheEntriesUl.firstChild);
      }
    
      // All the Cache Storage API methods return Promises. If you're not familiar
      // with them, see http://www.html5rocks.com/en/tutorials/es6/promises/
      // Here, we're iterating over all the available caches, and for each cache,
      // iterating over all the entries, adding each to the list.
      window.caches.keys().then(function(cacheNames) {
        cacheNames.forEach(function(cacheName) {
          window.caches.open(cacheName).then(function(cache) {
            return cache.keys();
          }).then(function(requests) {
            requests.forEach(function(request) {
              addRequestToList(cacheName, request);
            });
          });
        });
      });
    }
    
    // This uses window.fetch() (https://developers.google.com/web/updates/2015/03/introduction-to-fetch)
    // to retrieve a Response from the network, and store it in the named cache.
    // In some cases, cache.add() can be used instead of the fetch()/cache.put(),
    // but only if we know that the resource we're fetching supports CORS.
    // cache.add() will fail when the response status isn't 200, and when CORS isn't
    // supported, the response status is always 0.
    // (See https://github.com/w3c/ServiceWorker/issues/823).
    function addUrlToCache(url) {
      window.fetch(url, {mode: 'no-cors'}).then(function(response) {
        caches.open(CACHE_NAME).then(function(cache) {
          cache.put(url, response).then(showList);
        });
      }).catch(function(error) {
        ChromeSamples.setStatus(error);
      });
    }
    
    // Helper method to add a cached Request to the list of the cache contents.
    function addRequestToList(cacheName, request) {
      var url = request.url;
    
      var spanElement = document.createElement('span');
      spanElement.textContent = url;
    
      var buttonElement = document.createElement('button');
      buttonElement.textContent = 'Remove';
      buttonElement.dataset.url = url;
      buttonElement.dataset.cacheName = cacheName;
      buttonElement.addEventListener('click', function() {
        remove(this.dataset.cacheName, this.dataset.url).then(function() {
          var parent = this.parentNode;
          var grandParent = parent.parentNode;
          grandParent.removeChild(parent);
        }.bind(this));
      });
    
      var liElement = document.createElement('li');
      liElement.appendChild(spanElement);
      liElement.appendChild(buttonElement);
    
      cacheEntriesUl.appendChild(liElement);
    }
    
    // Given a cache name and URL, removes the cached entry.
    function remove(cacheName, url) {
      return window.caches.open(cacheName).then(function(cache) {
        return cache.delete(url);
      });
    }
    
    if ('caches' in window) {
      if ('serviceWorker' in navigator) {
        navigator.serviceWorker.register('service-worker.js');
        // As soon as the service worker has been installed, active the UI elements.
        navigator.serviceWorker.ready.then(initializeUI);
      }
    } else {
      ChromeSamples.setStatus('window.caches is not supported in your browser.');
    }

    Service Worker's JavaScript

    /*
     Copyright 2015 Google Inc. All Rights Reserved.
     Licensed under the Apache License, Version 2.0 (the "License");
     you may not use this file except in compliance with the License.
     You may obtain a copy of the License at
     http://www.apache.org/licenses/LICENSE-2.0
     Unless required by applicable law or agreed to in writing, software
     distributed under the License is distributed on an "AS IS" BASIS,
     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     See the License for the specific language governing permissions and
     limitations under the License.
    */
    
    // While overkill for this specific sample in which there is only one cache,
    // this is one best practice that can be followed in general to keep track of
    // multiple caches used by a given service worker, and keep them all versioned.
    // It maps a shorthand identifier for a cache to a specific, versioned cache name.
    
    // Note that since global state is discarded in between service worker restarts, these
    // variables will be reinitialized each time the service worker handles an event, and you
    // should not attempt to change their values inside an event handler. (Treat them as constants.)
    
    // If at any point you want to force pages that use this service worker to start using a fresh
    // cache, then increment the CACHE_VERSION value. It will kick off the service worker update
    // flow and the old cache(s) will be purged as part of the activate event handler when the
    // updated service worker is activated.
    var CACHE_VERSION = 1;
    var CURRENT_CACHES = {
      prefetch: 'window-cache-v' + CACHE_VERSION
    };
    
    self.addEventListener('install', function(event) {
      var urlsToPrefetch = [
        './static/pre_fetched.txt',
        './static/pre_fetched.html'
      ];
    
      event.waitUntil(
        caches.open(CURRENT_CACHES.prefetch).then(function(cache) {
          return cache.addAll(urlsToPrefetch).then(function() {
            console.log('All resources have been fetched and cached.');
            // skipWaiting() allows this service worker to become active
            // immediately, bypassing the waiting state, even if there's a previous
            // version of the service worker already installed.
            self.skipWaiting();
          });
        }).catch(function(error) {
          // This catch() will handle any exceptions from the caches.open()/cache.addAll() steps.
          console.error('Pre-fetching failed:', error);
        })
      );
    });
    
    self.addEventListener('activate', function(event) {
      // clients.claim() tells the active service worker to take immediate
      // control of all of the clients under its scope.
      self.clients.claim();
    
      // Delete all caches that aren't named in CURRENT_CACHES.
      // While there is only one cache in this example, the same logic will handle the case where
      // there are multiple versioned caches.
      var expectedCacheNames = Object.keys(CURRENT_CACHES).map(function(key) {
        return CURRENT_CACHES[key];
      });
    
      event.waitUntil(
        caches.keys().then(function(cacheNames) {
          return Promise.all(
            cacheNames.map(function(cacheName) {
              if (expectedCacheNames.indexOf(cacheName) === -1) {
                // If this cache name isn't present in the array of "expected" cache names,
                // then delete it.
                console.log('Deleting out of date cache:', cacheName);
                return caches.delete(cacheName);
              }
            })
          );
        })
      );
    });