diff --git a/infra/testing/validator/service-worker-runtime.js b/infra/testing/validator/service-worker-runtime.js index f99e56344..0aa22fe9c 100644 --- a/infra/testing/validator/service-worker-runtime.js +++ b/infra/testing/validator/service-worker-runtime.js @@ -35,6 +35,8 @@ function setupSpiesAndContext() { const importScripts = sinon.spy(); + const addEventListener = sinon.stub(); + const workbox = { cacheableResponse: { Plugin: CacheableResponsePlugin, @@ -69,9 +71,10 @@ function setupSpiesAndContext() { }; const context = Object.assign({ - workbox, importScripts, + workbox, }, makeServiceWorkerEnv()); + context.self.addEventListener = addEventListener; const methodsToSpies = { importScripts, @@ -91,7 +94,7 @@ function setupSpiesAndContext() { skipWaiting: workbox.core.skipWaiting, }; - return {context, methodsToSpies}; + return {addEventListener, context, methodsToSpies}; } function validateMethodCalls({methodsToSpies, expectedMethodCalls}) { @@ -123,7 +126,12 @@ function validateMethodCalls({methodsToSpies, expectedMethodCalls}) { * @param {Object} expectedMethodCalls * @return {Promise} Resolves if all of the expected method calls were made. */ -module.exports = async ({swFile, swString, expectedMethodCalls}) => { +module.exports = async ({ + addEventListenerValidation, + expectedMethodCalls, + swFile, + swString, +}) => { assert((swFile || swString) && !(swFile && swString), `Set swFile or swString, but not both.`); @@ -131,9 +139,14 @@ module.exports = async ({swFile, swString, expectedMethodCalls}) => { swString = await fse.readFile(swFile, 'utf8'); } - const {context, methodsToSpies} = setupSpiesAndContext(); + const {addEventListener, context, methodsToSpies} = setupSpiesAndContext(); vm.runInNewContext(swString, context); validateMethodCalls({methodsToSpies, expectedMethodCalls}); + + // Optionally check the usage of addEventListener(). + if (addEventListenerValidation) { + addEventListenerValidation(addEventListener); + } }; diff --git a/packages/workbox-build/src/templates/sw-template.js b/packages/workbox-build/src/templates/sw-template.js index aa00252e0..a618fc0cb 100644 --- a/packages/workbox-build/src/templates/sw-template.js +++ b/packages/workbox-build/src/templates/sw-template.js @@ -31,7 +31,15 @@ importScripts( <% if (cacheId) { %>workbox.core.setCacheNameDetails({prefix: <%= JSON.stringify(cacheId) %>});<% } %> -<% if (skipWaiting) { %>workbox.core.skipWaiting();<% } %> +<% if (skipWaiting) { %> +workbox.core.skipWaiting(); +<% } else { %> +self.addEventListener('message', (event) => { + if (event.data && event.data.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); +<% } %> <% if (clientsClaim) { %>workbox.core.clientsClaim();<% } %> <% if (Array.isArray(manifestEntries)) {%> diff --git a/test/workbox-build/node/entry-points/generate-sw.js b/test/workbox-build/node/entry-points/generate-sw.js index d6310716d..88fae27cc 100644 --- a/test/workbox-build/node/entry-points/generate-sw.js +++ b/test/workbox-build/node/entry-points/generate-sw.js @@ -359,6 +359,51 @@ describe(`[workbox-build] entry-points/generate-sw.js (End to End)`, function() url: 'webpackEntry.js', revision: '5b652181a25e96f255d0490203d3c47e', }], {directoryIndex, ignoreURLParametersMatching}]], + }, addEventListenerValidation: (addEventListenerStub) => { + // When skipWaiting is true, the 'message' addEventListener shouldn't be called. + expect(addEventListenerStub.called).to.be.false; + }}); + }); + + it(`should add a 'message' event listener when 'skipWaiting: false'`, async function() { + const swDest = tempy.file(); + const additionalOptions = { + skipWaiting: false, + }; + const options = Object.assign({}, BASE_OPTIONS, additionalOptions, {swDest}); + + const {count, size, warnings} = await generateSW(options); + expect(warnings).to.be.empty; + expect(count).to.eql(6); + expect(size).to.eql(2604); + await validateServiceWorkerRuntime({swFile: swDest, expectedMethodCalls: { + importScripts: [[WORKBOX_SW_CDN_URL]], + precacheAndRoute: [[[{ + url: 'index.html', + revision: '3883c45b119c9d7e9ad75a1b4a4672ac', + }, { + url: 'page-1.html', + revision: '544658ab25ee8762dc241e8b1c5ed96d', + }, { + url: 'page-2.html', + revision: 'a3a71ce0b9b43c459cf58bd37e911b74', + }, { + url: 'styles/stylesheet-1.css', + revision: '934823cbc67ccf0d67aa2a2eeb798f12', + }, { + url: 'styles/stylesheet-2.css', + revision: '884f6853a4fc655e4c2dc0c0f27a227c', + }, { + url: 'webpackEntry.js', + revision: '5b652181a25e96f255d0490203d3c47e', + }], {}]], + }, addEventListenerValidation: (addEventListenerStub) => { + expect(addEventListenerStub.calledOnce).to.be.true; + expect(addEventListenerStub.firstCall.args[0]).to.eql('message'); + // This isn't the *cleanest* possible way of testing the message event + // handler, but given the constraints of this node-based environment, + // it seems the most effective way to ensure the right code gets run. + expect(addEventListenerStub.firstCall.args[1].toString()).to.eql(`(event) => {\n if (event.data && event.data.type === 'SKIP_WAITING') {\n self.skipWaiting();\n }\n}`); }}); });