From 846149008aaa90adc1cbc861a9f30b406c353176 Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Thu, 14 Feb 2019 17:37:47 -0800 Subject: [PATCH 1/6] Add initial jsdoc comments for lifecycle events --- .eslintrc.js | 11 +++- packages/workbox-window/Workbox.mjs | 99 +++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 55e57da56..c2fb8080d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -21,8 +21,15 @@ module.exports = { WorkboxSW: false, }, rules: { - "jsdoc/check-types": 2, - "jsdoc/newline-after-description": 2, + 'jsdoc/check-types': 2, + 'jsdoc/newline-after-description': 2, + 'max-len': [2, { + code: 80, + tabWidth: 2, + ignoreComments: true, + ignoreUrls: true, + ignorePattern: '^\\s*import', + }], }, plugins: [ 'jsdoc', diff --git a/packages/workbox-window/Workbox.mjs b/packages/workbox-window/Workbox.mjs index f27578661..d33a1655a 100644 --- a/packages/workbox-window/Workbox.mjs +++ b/packages/workbox-window/Workbox.mjs @@ -28,6 +28,11 @@ const REGISTRATION_TIMEOUT_DURATION = 60000; * A class to aid in handling service worker registration, updates, and * reacting to service worker lifecycle events. * + * @fires {@link module:workbox-window.Workbox#installed|installed} + * @fires {@link module:workbox-window.Workbox#waiting|waiting} + * @fires {@link module:workbox-window.Workbox#controlling|controlling} + * @fires {@link module:workbox-window.Workbox#activated|activated} + * * @memberof module:workbox-window */ class Workbox extends EventTargetShim { @@ -448,4 +453,98 @@ class Workbox extends EventTargetShim { } } +// The jsdoc comments below outline the events this instance may dispatch: +// ----------------------------------------------------------------------- + +/** + * The `installed` event is dispatched on a [`Workbox`]{@link module:workbox-window.Workbox} + * instance after it calls [`register()`]{@link module:workbox-window.Workbox#register} + * if all of the following conditions are met: + * - An [updatefound]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/onupdatefound} + * event is dispatched on the current [registration]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration}, + * and a new service worker starts installing. + * - The installing service worker's [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} + * matches the `Workbox` instance's `scriptURL`. + * - The installing service worker's [state]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/state} + * changes to `installed`. + * + * @event module:workbox-window.Workbox#installed + * @type {Event} + * @property {ServiceWorker} sw The service worker instance. + * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange} + * event. + * @property {boolean} isUpdate True if a service worker was already + * controlling when this `Workbox` instance called `register()`. + */ + +/** + * The `waiting` event is dispatched on a [`Workbox`]{@link module:workbox-window.Workbox} + * instance after it calls [`register()`]{@link module:workbox-window.Workbox#register} + * if all of the following conditions are met: + * - An [updatefound]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/onupdatefound} + * event is dispatched on the current [registration]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration}, + * and a new service worker starts installing. + * - The installing service worker's [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} + * matches the `Workbox` instance's `scriptURL`. + * - The installing service worker's [state]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/state} + * is `installed`. + * - The installing service worker's state does not immediately change to + * `activating`. + * + * @event module:workbox-window.Workbox#waiting + * @type {Event} + * @property {ServiceWorker} sw The service worker instance. + * @property {Event} originalEvent The native `controllerchange` event + * @property {boolean} isUpdate True if a service worker was already + * controlling when this `Workbox` instance called `register()`. + * @property {boolean} wasWaitingBeforeRegister True if a service worker with + * a matching `scriptURL` was already waiting when this `Workbox` + * instance called `register()`. + */ + +/** + * The `controlling` event is dispatched on a [`Workbox`]{@link module:workbox-window.Workbox} + * instance after it calls [`register()`]{@link module:workbox-window.Workbox#register} + * if all of the following conditions are met: + * - An [updatefound]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/onupdatefound} + * event is dispatched on the current [registration]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration}, + * and a new service worker starts installing. + * - The installing service worker's [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} + * matches the `Workbox` instance's `scriptURL`. + * - A [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange} + * event is dispatched on the service worker [container]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer} + * - The [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} + * of the new [controller]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/controller} + * matches the `Workbox` instance's `scriptURL`. + * + * @event module:workbox-window.Workbox#controlling + * @type {WorkboxEvent} + * @property {ServiceWorker} sw The service worker instance. + * @property {Event} originalEvent The original [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange} + * event. + * @property {boolean} isUpdate True if a service worker was already + * controlling when this service worker was registered. + */ + +/** + * The `activated` event is dispatched on a [`Workbox`]{@link module:workbox-window.Workbox} + * instance after it calls [`register()`]{@link module:workbox-window.Workbox#register} + * if all of the following conditions are met: + * - An [updatefound]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/onupdatefound} + * event is dispatched on the current [registration]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration}, + * and a new service worker starts installing. + * - The installing service worker's [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} + * matches the `Workbox` instance's `scriptURL`. + * - The installing service worker's [state]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/state} + * is `activated`. + * + * @event module:workbox-window.Workbox#activated + * @type {Event} + * @property {ServiceWorker} sw The service worker instance. + * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange} + * event. + * @property {boolean} isUpdate True if a service worker was already + * controlling when this `Workbox` instance called `register()`. + */ + export {Workbox}; From 02ac9a4ea128c4d6f41d52f7bc47e746efcb1fd3 Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Fri, 15 Feb 2019 11:54:17 -0800 Subject: [PATCH 2/6] Finish jsdoc event comments and update code --- packages/workbox-window/Workbox.mjs | 223 +++++++++++++++------- test/workbox-window/integration/test.js | 48 +++++ test/workbox-window/unit/test-Workbox.mjs | 38 +++- 3 files changed, 238 insertions(+), 71 deletions(-) diff --git a/packages/workbox-window/Workbox.mjs b/packages/workbox-window/Workbox.mjs index d33a1655a..83b721ff4 100644 --- a/packages/workbox-window/Workbox.mjs +++ b/packages/workbox-window/Workbox.mjs @@ -28,10 +28,15 @@ const REGISTRATION_TIMEOUT_DURATION = 60000; * A class to aid in handling service worker registration, updates, and * reacting to service worker lifecycle events. * - * @fires {@link module:workbox-window.Workbox#installed|installed} - * @fires {@link module:workbox-window.Workbox#waiting|waiting} - * @fires {@link module:workbox-window.Workbox#controlling|controlling} - * @fires {@link module:workbox-window.Workbox#activated|activated} + * @fires [message]{@link module:workbox-window.Workbox#message} + * @fires [installed]{@link module:workbox-window.Workbox#installed} + * @fires [waiting]{@link module:workbox-window.Workbox#waiting} + * @fires [controlling]{@link module:workbox-window.Workbox#controlling} + * @fires [activated]{@link module:workbox-window.Workbox#activated} + * @fires [redundant]{@link module:workbox-window.Workbox#redundant} + * @fires [externalinstalled]{@link module:workbox-window.Workbox#externalinstalled} + * @fires [externalwaiting]{@link module:workbox-window.Workbox#externalwaiting} + * @fires [externalactivated]{@link module:workbox-window.Workbox#externalactivated} * * @memberof module:workbox-window */ @@ -89,6 +94,10 @@ class Workbox extends EventTargetShim { await new Promise((res) => addEventListener('load', res)); } + // Set this flag to true if any service worker was controlling the page + // at registration time. + this._isUpdate = Boolean(navigator.serviceWorker.controller); + // Before registering, attempt to determine if a SW is already controlling // the page, and if that SW script (and version, if specified) matches this // instance's script. @@ -122,12 +131,23 @@ class Workbox extends EventTargetShim { } } - // If there's an active and waiting service worker before the - // `updatefound` event fires, it means there was a waiting service worker - // in the queue before this one was registered. - if (this._registration.waiting && this._registration.active) { - logger.warn('A service worker was already waiting to activate ' + - 'before this script was registered...'); + // If there's a waiting service worker with a matching URL before the + // `updatefound` event fires, it likely means the this site is open + // in another tab, or the user refreshed the page without unloading it + // first. + // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting + if (this._registration.waiting && + urlsMatch(this._registration.waiting.scriptURL, this._scriptURL)) { + // Run this in the next microtask, so any code that adds an event + // listener after awaiting `register()` will get this event. + Promise.resolve().then(() => { + this.dispatchEvent(new WorkboxEvent('waiting', { + sw: this._registration.waiting, + wasWaitingBeforeRegister: true, + })); + logger.warn('A service worker was already waiting to activate ' + + 'before this script was registered...'); + }); } const currentPageIsOutOfScope = () => { @@ -361,8 +381,13 @@ class Workbox extends EventTargetShim { const isExternal = sw === this._externalSW; const eventPrefix = isExternal ? 'external' : ''; + const eventProps = {sw, originalEvent}; + if (!isExternal && this._isUpdate) { + eventProps.isUpdate = true; + } + this.dispatchEvent(new WorkboxEvent( - eventPrefix + state, {sw, originalEvent})); + eventPrefix + state, eventProps)); if (state === 'installed') { // This timeout is used to ignore cases where the service worker calls @@ -377,7 +402,7 @@ class Workbox extends EventTargetShim { // Ensure the SW is still waiting (it may now be redundant). if (state === 'installed' && this._registration.waiting === sw) { this.dispatchEvent(new WorkboxEvent( - eventPrefix + 'waiting', {sw, originalEvent})); + eventPrefix + 'waiting', eventProps)); if (process.env.NODE_ENV !== 'production') { if (isExternal) { @@ -435,10 +460,10 @@ class Workbox extends EventTargetShim { _onControllerChange(originalEvent) { const sw = this._sw; if (sw === navigator.serviceWorker.controller) { + this.dispatchEvent(new WorkboxEvent('controlling', {sw, originalEvent})); if (process.env.NODE_ENV !== 'production') { logger.log('Registered service worker now controlling this page.'); } - this.dispatchEvent(new WorkboxEvent('controlling', {sw, originalEvent})); this._controllingDeferred.resolve(sw); } } @@ -457,94 +482,156 @@ class Workbox extends EventTargetShim { // ----------------------------------------------------------------------- /** - * The `installed` event is dispatched on a [`Workbox`]{@link module:workbox-window.Workbox} - * instance after it calls [`register()`]{@link module:workbox-window.Workbox#register} - * if all of the following conditions are met: - * - An [updatefound]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/onupdatefound} - * event is dispatched on the current [registration]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration}, - * and a new service worker starts installing. - * - The installing service worker's [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} - * matches the `Workbox` instance's `scriptURL`. - * - The installing service worker's [state]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/state} - * changes to `installed`. + * The `message` event is dispatched any time a `postMessage` (or a + * `BroadcastChannel` message with the `workbox` channel name) is received. + * + * @event module:workbox-window.Workbox#message + * @type {WorkboxEvent} + * @property {*} data The `data` property from the original `message` event. + * @property {Event} originalEvent The original [`message`]{@link https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent} + * event. + * @property {string} type `message`. + * @property {Workbox} target The `Workbox` instance. + */ + +/** + * The `installed` event is dispatched if the state of a + * [`Workbox`]{@link module:workbox-window.Workbox} instance's + * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw} + * changes to `installed`. + * + * Then can happen either the very first time a service worker is installed, + * or after an update to the current service worker is found. In the case + * of an update being found, the event's `isUpdate` property will be `true`. * * @event module:workbox-window.Workbox#installed - * @type {Event} + * @type {WorkboxEvent} * @property {ServiceWorker} sw The service worker instance. * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange} * event. - * @property {boolean} isUpdate True if a service worker was already - * controlling when this `Workbox` instance called `register()`. + * @property {boolean|undefined} isUpdate True if a service worker was already + * controlling when this `Workbox` instance called `register()`. + * @property {string} type `installed`. + * @property {Workbox} target The `Workbox` instance. */ /** - * The `waiting` event is dispatched on a [`Workbox`]{@link module:workbox-window.Workbox} - * instance after it calls [`register()`]{@link module:workbox-window.Workbox#register} - * if all of the following conditions are met: - * - An [updatefound]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/onupdatefound} - * event is dispatched on the current [registration]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration}, - * and a new service worker starts installing. - * - The installing service worker's [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} - * matches the `Workbox` instance's `scriptURL`. - * - The installing service worker's [state]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/state} - * is `installed`. - * - The installing service worker's state does not immediately change to - * `activating`. + * The `waiting` event is dispatched if the state of a + * [`Workbox`]{@link module:workbox-window.Workbox} instance's + * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw} + * changes to `installed` and then doesn't immediately change to `activating`. + * It may also be dispatched if a service worker with the same + * [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} + * was already waiting when the [`register()`]{@link module:workbox-window.Workbox#register} + * method was called. * * @event module:workbox-window.Workbox#waiting - * @type {Event} + * @type {WorkboxEvent} * @property {ServiceWorker} sw The service worker instance. * @property {Event} originalEvent The native `controllerchange` event - * @property {boolean} isUpdate True if a service worker was already + * @property {boolean|undefined} isUpdate True if a service worker was already * controlling when this `Workbox` instance called `register()`. - * @property {boolean} wasWaitingBeforeRegister True if a service worker with + * @property {boolean|undefined} wasWaitingBeforeRegister True if a service worker with * a matching `scriptURL` was already waiting when this `Workbox` * instance called `register()`. + * @property {string} type `waiting`. + * @property {Workbox} target The `Workbox` instance. */ /** - * The `controlling` event is dispatched on a [`Workbox`]{@link module:workbox-window.Workbox} - * instance after it calls [`register()`]{@link module:workbox-window.Workbox#register} - * if all of the following conditions are met: - * - An [updatefound]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/onupdatefound} - * event is dispatched on the current [registration]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration}, - * and a new service worker starts installing. - * - The installing service worker's [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} - * matches the `Workbox` instance's `scriptURL`. - * - A [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange} - * event is dispatched on the service worker [container]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer} - * - The [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} - * of the new [controller]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/controller} - * matches the `Workbox` instance's `scriptURL`. + * The `controlling` event is dispatched if a + * [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange} + * fires on the service worker [container]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer} + * and the [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} + * of the new [controller]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/controller} + * matches the `scriptURL` of the `Workbox` instance's + * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw}. * * @event module:workbox-window.Workbox#controlling * @type {WorkboxEvent} * @property {ServiceWorker} sw The service worker instance. * @property {Event} originalEvent The original [`controllerchange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/oncontrollerchange} * event. - * @property {boolean} isUpdate True if a service worker was already + * @property {boolean|undefined} isUpdate True if a service worker was already * controlling when this service worker was registered. + * @property {string} type `controlling`. + * @property {Workbox} target The `Workbox` instance. */ /** - * The `activated` event is dispatched on a [`Workbox`]{@link module:workbox-window.Workbox} - * instance after it calls [`register()`]{@link module:workbox-window.Workbox#register} - * if all of the following conditions are met: - * - An [updatefound]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/onupdatefound} - * event is dispatched on the current [registration]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration}, - * and a new service worker starts installing. - * - The installing service worker's [`scriptURL`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/scriptURL} - * matches the `Workbox` instance's `scriptURL`. - * - The installing service worker's [state]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/state} - * is `activated`. + * The `activated` event is dispatched if the state of a + * [`Workbox`]{@link module:workbox-window.Workbox} instance's + * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw} + * changes to `activated`. * * @event module:workbox-window.Workbox#activated - * @type {Event} + * @type {WorkboxEvent} + * @property {ServiceWorker} sw The service worker instance. + * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange} + * event. + * @property {boolean|undefined} isUpdate True if a service worker was already + * controlling when this `Workbox` instance called `register()`. + * @property {string} type `activated`. + * @property {Workbox} target The `Workbox` instance. + */ + +/** + * The `redundant` event is dispatched if the state of a + * [`Workbox`]{@link module:workbox-window.Workbox} instance's + * [registered service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-registered-sw} + * changes to `redundant`. + * + * @event module:workbox-window.Workbox#redundant + * @type {WorkboxEvent} + * @property {ServiceWorker} sw The service worker instance. + * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange} + * event. + * @property {boolean|undefined} isUpdate True if a service worker was already + * controlling when this `Workbox` instance called `register()`. + * @property {string} type `redundant`. + * @property {Workbox} target The `Workbox` instance. + */ + +/** + * The `externalinstalled` event is dispatched if the state of an + * [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-external-sw} + * changes to `installed`. + * + * @event module:workbox-window.Workbox#externalinstalled + * @type {WorkboxEvent} + * @property {ServiceWorker} sw The service worker instance. + * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange} + * event. + * @property {string} type `externalinstalled`. + * @property {Workbox} target The `Workbox` instance. + */ + +/** + * The `externalwaiting` event is dispatched if the state of an + * [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-external-sw} + * changes to `waiting`. + * + * @event module:workbox-window.Workbox#externalwaiting + * @type {WorkboxEvent} + * @property {ServiceWorker} sw The service worker instance. + * @property {Event|undefined} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange} + * event. + * @property {string} type `externalwaiting`. + * @property {Workbox} target The `Workbox` instance. + */ + +/** + * The `externalactivated` event is dispatched if the state of an + * [external service worker]{@link https://developers.google.com/web/tools/workbox/modules/workbox-precaching#def-external-sw} + * changes to `activated`. + * + * @event module:workbox-window.Workbox#externalactivated + * @type {WorkboxEvent} * @property {ServiceWorker} sw The service worker instance. * @property {Event} originalEvent The original [`statechange`]{@link https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorker/onstatechange} * event. - * @property {boolean} isUpdate True if a service worker was already - * controlling when this `Workbox` instance called `register()`. + * @property {string} type `externalactivated`. + * @property {Workbox} target The `Workbox` instance. */ export {Workbox}; diff --git a/test/workbox-window/integration/test.js b/test/workbox-window/integration/test.js index 0918c0e17..aec39c835 100644 --- a/test/workbox-window/integration/test.js +++ b/test/workbox-window/integration/test.js @@ -109,6 +109,7 @@ describe(`[workbox-window] Workbox`, function() { wb.addEventListener('activated', () => { cb({ + isUpdate: installedSpy.args[0][0].isUpdate, installedSpyCallCount: installedSpy.callCount, waitingSpyCallCount: waitingSpy.callCount, controllingSpyCallCount: controllingSpy.callCount, @@ -120,6 +121,7 @@ describe(`[workbox-window] Workbox`, function() { } }); + expect(result.isUpdate).to.equal(undefined); expect(result.installedSpyCallCount).to.equal(1); expect(result.activatedSpyCallCount).to.equal(1); expect(result.controllingSpyCallCount).to.equal(1); @@ -128,6 +130,52 @@ describe(`[workbox-window] Workbox`, function() { expect(result.waitingSpyCallCount).to.equal(0); }); + it(`reports all events for an updated SW registration`, async function() { + const result = await executeAsyncAndCatch(async (cb) => { + try { + const wb1 = new Workbox('sw-clients-claim.tmp.js?v=1'); + const redundantSpy = sinon.spy(); + wb1.addEventListener('redundant', redundantSpy); + + await wb1.register(); + await wb1.controlling; + + const wb2 = new Workbox('sw-clients-claim.tmp.js?v=2'); + await wb2.register(); + + const installedSpy = sinon.spy(); + const waitingSpy = sinon.spy(); + const activatedSpy = sinon.spy(); + const controllingSpy = sinon.spy(); + + wb2.addEventListener('installed', installedSpy); + wb2.addEventListener('waiting', waitingSpy); + wb2.addEventListener('controlling', controllingSpy); + wb2.addEventListener('activated', activatedSpy); + + wb2.addEventListener('activated', () => { + cb({ + wb1IsUpdate: redundantSpy.args[0][0].isUpdate, + wb2IsUpdate: installedSpy.args[0][0].isUpdate, + installedSpyCallCount: installedSpy.callCount, + waitingSpyCallCount: waitingSpy.callCount, + controllingSpyCallCount: controllingSpy.callCount, + activatedSpyCallCount: activatedSpy.callCount, + }); + }); + } catch (error) { + cb({error: error.stack}); + } + }); + + expect(result.wb1IsUpdate).to.equal(undefined); + expect(result.wb2IsUpdate).to.equal(true); + expect(result.installedSpyCallCount).to.equal(1); + expect(result.waitingSpyCallCount).to.equal(0); + expect(result.activatedSpyCallCount).to.equal(1); + expect(result.controllingSpyCallCount).to.equal(1); + }); + it(`reports all events for an external SW registration`, async function() { const firstTab = await getLastWindowHandle(); diff --git a/test/workbox-window/unit/test-Workbox.mjs b/test/workbox-window/unit/test-Workbox.mjs index 07d99a9f8..5ba6aad10 100644 --- a/test/workbox-window/unit/test-Workbox.mjs +++ b/test/workbox-window/unit/test-Workbox.mjs @@ -81,7 +81,7 @@ const updateVersion = async (version, scriptURL) => { const assertMatchesWorkboxEvent = (event, props) => { for (const [key, value] of Object.entries(props)) { - if (key === 'originalEvent') { + if (key === 'originalEvent' && typeof value !== 'undefined') { expect(event.originalEvent.type).to.equal(value.type); } else { expect(event[key]).to.equal(value); @@ -486,6 +486,7 @@ describe(`[workbox-window] Workbox`, function() { const wb1 = new Workbox(scriptURL); const installed1Spy = sandbox.spy(); wb1.addEventListener('installed', installed1Spy); + await wb1.register(); await nextEvent(wb1, 'installed'); @@ -496,8 +497,8 @@ describe(`[workbox-window] Workbox`, function() { wb2.addEventListener('installed', installed2Spy); await wb2.register(); - // Create a third instace for a different stip to assert the callback - // runs again, but only for own instances. + // Create a third instance for a different script to assert the + // callback runs again, but only for own instances. const wb3 = new Workbox(uniq('sw-clients-claim.tmp.js')); const installed3Spy = sandbox.spy(); wb3.addEventListener('installed', installed3Spy); @@ -510,6 +511,7 @@ describe(`[workbox-window] Workbox`, function() { target: wb1, sw: await wb1.getSW(), originalEvent: {type: 'statechange'}, + isUpdate: true, }); expect(installed2Spy.callCount).to.equal(0); @@ -520,6 +522,7 @@ describe(`[workbox-window] Workbox`, function() { target: wb3, sw: await wb3.getSW(), originalEvent: {type: 'statechange'}, + isUpdate: true, }); }); }); @@ -550,8 +553,10 @@ describe(`[workbox-window] Workbox`, function() { target: wb1, sw: await wb1.getSW(), originalEvent: {type: 'statechange'}, + wasWaitingBeforeRegister: undefined, }); + console.log(waiting2Spy.args); expect(waiting2Spy.callCount).to.equal(0); expect(waiting3Spy.callCount).to.equal(1); @@ -560,6 +565,33 @@ describe(`[workbox-window] Workbox`, function() { target: wb3, sw: await wb3.getSW(), originalEvent: {type: 'statechange'}, + wasWaitingBeforeRegister: undefined, + }); + }); + + it(`runs if a service worker was already waiting at registration time`, async function() { + const scriptURL = uniq('sw-no-skip-waiting.tmp.js'); + + const wb1 = new Workbox(scriptURL); + await wb1.register(); + await nextEvent(wb1, 'waiting'); + + // Register a new instance for the already waiting script. + const wb2 = new Workbox(scriptURL); + const waiting2Spy = sandbox.spy(); + wb2.addEventListener('waiting', waiting2Spy); + await wb2.register(); + + console.log(waiting2Spy.args); + + expect(waiting2Spy.callCount).to.equal(1); + assertMatchesWorkboxEvent(waiting2Spy.args[0][0], { + type: 'waiting', + target: wb2, + sw: await wb1.getSW(), + originalEvent: undefined, + isUpdate: undefined, + wasWaitingBeforeRegister: true, }); }); }); From d2288137913a6533d8f4ee384d025f5350e90dbb Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Fri, 15 Feb 2019 12:25:40 -0800 Subject: [PATCH 3/6] Fix failing test --- packages/workbox-window/Workbox.mjs | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/workbox-window/Workbox.mjs b/packages/workbox-window/Workbox.mjs index 83b721ff4..20ba9ea56 100644 --- a/packages/workbox-window/Workbox.mjs +++ b/packages/workbox-window/Workbox.mjs @@ -117,6 +117,25 @@ class Workbox extends EventTargetShim { 'statechange', this._onStateChange, {once: true}); } + // If there's a waiting service worker with a matching URL before the + // `updatefound` event fires, it likely means the this site is open + // in another tab, or the user refreshed the page without unloading it + // first. + // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting + if (this._registration.waiting && + urlsMatch(this._registration.waiting.scriptURL, this._scriptURL)) { + // Run this in the next microtask, so any code that adds an event + // listener after awaiting `register()` will get this event. + Promise.resolve().then(() => { + this.dispatchEvent(new WorkboxEvent('waiting', { + sw: this._registration.waiting, + wasWaitingBeforeRegister: true, + })); + logger.warn('A service worker was already waiting to activate ' + + 'before this script was registered...'); + }); + } + if (process.env.NODE_ENV !== 'production') { logger.log('Successfully registered service worker.', this._scriptURL); @@ -131,25 +150,6 @@ class Workbox extends EventTargetShim { } } - // If there's a waiting service worker with a matching URL before the - // `updatefound` event fires, it likely means the this site is open - // in another tab, or the user refreshed the page without unloading it - // first. - // https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle#waiting - if (this._registration.waiting && - urlsMatch(this._registration.waiting.scriptURL, this._scriptURL)) { - // Run this in the next microtask, so any code that adds an event - // listener after awaiting `register()` will get this event. - Promise.resolve().then(() => { - this.dispatchEvent(new WorkboxEvent('waiting', { - sw: this._registration.waiting, - wasWaitingBeforeRegister: true, - })); - logger.warn('A service worker was already waiting to activate ' + - 'before this script was registered...'); - }); - } - const currentPageIsOutOfScope = () => { const scopeURL = new URL( this._registerOptions.scope || this._scriptURL, document.baseURI); From 695992b69319abdea9886f9f0dddc074a4f2ed96 Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Fri, 15 Feb 2019 12:28:10 -0800 Subject: [PATCH 4/6] Move log message back to a dev-only block --- packages/workbox-window/Workbox.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/workbox-window/Workbox.mjs b/packages/workbox-window/Workbox.mjs index 20ba9ea56..4d9ff7be1 100644 --- a/packages/workbox-window/Workbox.mjs +++ b/packages/workbox-window/Workbox.mjs @@ -131,8 +131,10 @@ class Workbox extends EventTargetShim { sw: this._registration.waiting, wasWaitingBeforeRegister: true, })); - logger.warn('A service worker was already waiting to activate ' + - 'before this script was registered...'); + if (process.env.NODE_ENV !== 'production') { + logger.warn('A service worker was already waiting to activate ' + + 'before this script was registered...'); + } }); } From 604835a8518111c7570ecce841ec0cc9a2a68fdb Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Fri, 15 Feb 2019 12:31:44 -0800 Subject: [PATCH 5/6] Remove extra space in log message --- packages/workbox-window/Workbox.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/workbox-window/Workbox.mjs b/packages/workbox-window/Workbox.mjs index 4d9ff7be1..e4bb3b11c 100644 --- a/packages/workbox-window/Workbox.mjs +++ b/packages/workbox-window/Workbox.mjs @@ -412,7 +412,7 @@ class Workbox extends EventTargetShim { 'waiting for this client to close before activating...'); } else { logger.warn('The service worker has installed but is waiting ' + - ' for existing clients to close before activating...'); + 'for existing clients to close before activating...'); } } } From 5650dccad0bfba45a949a9642a860318bf6ab2e7 Mon Sep 17 00:00:00 2001 From: Philip Walton Date: Fri, 15 Feb 2019 13:02:14 -0800 Subject: [PATCH 6/6] Fix integration test for Firefox --- test/workbox-window/integration/test.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/workbox-window/integration/test.js b/test/workbox-window/integration/test.js index aec39c835..c214f76b3 100644 --- a/test/workbox-window/integration/test.js +++ b/test/workbox-window/integration/test.js @@ -121,7 +121,9 @@ describe(`[workbox-window] Workbox`, function() { } }); - expect(result.isUpdate).to.equal(undefined); + // Test for truthiness because some browsers structure clone + // `undefined` to `null`. + expect(result.isUpdate).to.not.be.ok; expect(result.installedSpyCallCount).to.equal(1); expect(result.activatedSpyCallCount).to.equal(1); expect(result.controllingSpyCallCount).to.equal(1); @@ -168,7 +170,9 @@ describe(`[workbox-window] Workbox`, function() { } }); - expect(result.wb1IsUpdate).to.equal(undefined); + // Test for truthiness because some browsers structure clone + // `undefined` to `null`. + expect(result.wb1IsUpdate).to.not.be.ok; expect(result.wb2IsUpdate).to.equal(true); expect(result.installedSpyCallCount).to.equal(1); expect(result.waitingSpyCallCount).to.equal(0);