diff --git a/deploy.sh b/deploy.sh index ec7eebfdf..d1643691b 100644 --- a/deploy.sh +++ b/deploy.sh @@ -75,6 +75,8 @@ else > $WEB_ROOT/index.intermediate.html node_modules/.bin/emu-algify --throwing-indicators < $WEB_ROOT/index.intermediate.html > $WEB_ROOT/index.html rm $WEB_ROOT/index.intermediate.html + + cp service-worker.js $WEB_ROOT/service-worker.js echo "Living standard output to $WEB_ROOT" fi diff --git a/index.bs b/index.bs index 56a2629c0..facb32f25 100644 --- a/index.bs +++ b/index.bs @@ -4308,3 +4308,10 @@ This standard is written by Domenic Denicola tyoshino@chromium.org). Per CC0, to the extent possible under law, the editor has waived all copyright and related or neighboring rights to this work. + + diff --git a/service-worker.js b/service-worker.js new file mode 100644 index 000000000..6196fa2fb --- /dev/null +++ b/service-worker.js @@ -0,0 +1,113 @@ +"use strict"; + +const cacheKey = "v2"; +const toCache = [ + "/", + "https://resources.whatwg.org/standard.css", + "https://resources.whatwg.org/bikeshed.css", + "https://resources.whatwg.org/file-issue.js", + "https://resources.whatwg.org/commit-snapshot-shortcut-key.js", + "https://resources.whatwg.org/logo-streams.svg" +]; + +self.oninstall = e => { + e.waitUntil(caches.open(cacheKey).then(cache => cache.addAll(toCache))); +}; + +self.onfetch = e => { + if (e.request.method !== "GET") { + return; + } + + if (needsToBeFresh(e.request)) { + // Since this is a Living Standard, it is imperative that you see the freshest content, so we use a + // network-then-cache strategy for the main content. + e.respondWith( + fetch(e.request).then(res => { + e.waitUntil(refreshCacheFromNetworkResponse(e.request, res)); + return res; + }) + .catch(() => { + return caches.match(e.request); + }) + ); + } else { + // For auxiliary resources, we can use a cache-then-network strategy; it is OK to not get the freshest. + e.respondWith( + caches.match(e.request).then(cachedResponse => { + const networkFetchPromise = fetch(e.request); + + // Ignore network fetch or caching errors; they just mean we won't be able to refresh the cache. + e.waitUntil( + networkFetchPromise + .then(res => refreshCacheFromNetworkResponse(e.request, res)) + .catch(() => {}) + ); + + return cachedResponse || networkFetchPromise; + }) + ); + } +}; + +self.onactivate = e => { + e.waitUntil(caches.keys().then(keys => { + return Promise.all(keys.filter(key => key !== cacheKey).map(key => caches.delete(key))); + })); +}; + +function refreshCacheFromNetworkResponse(req, res) { + if (!res.ok) { + throw new Error(`${res.url} is responding with ${res.status}`); + } + + const resForCache = res.clone(); + + return caches.open(cacheKey).then(cache => cache.put(req, resForCache)); +} + +function needsToBeFresh(req) { + const requestURL = new URL(req.url); + return requestURL.origin === location.origin && requestURL.pathname === "/"; +} + +// From https://github.com/jakearchibald/async-waituntil-polyfill +// Apache 2 License: https://github.com/jakearchibald/async-waituntil-polyfill/blob/master/LICENSE +{ + const waitUntil = ExtendableEvent.prototype.waitUntil; + const respondWith = FetchEvent.prototype.respondWith; + const promisesMap = new WeakMap(); + + ExtendableEvent.prototype.waitUntil = function(promise) { + const extendableEvent = this; + let promises = promisesMap.get(extendableEvent); + + if (promises) { + promises.push(Promise.resolve(promise)); + return; + } + + promises = [Promise.resolve(promise)]; + promisesMap.set(extendableEvent, promises); + + // call original method + return waitUntil.call(extendableEvent, Promise.resolve().then(function processPromises() { + const len = promises.length; + + // wait for all to settle + return Promise.all(promises.map(p => p.catch(()=>{}))).then(() => { + // have new items been added? If so, wait again + if (promises.length != len) return processPromises(); + // we're done! + promisesMap.delete(extendableEvent); + // reject if one of the promises rejected + return Promise.all(promises); + }); + })); + }; + + FetchEvent.prototype.respondWith = function(promise) { + this.waitUntil(promise); + return respondWith.call(this, promise); + }; +}