Skip to content

Commit

Permalink
Precaching temp (#1342)
Browse files Browse the repository at this point in the history
* Caches to a temp directory until activate event occurs

* Remove private

* Changing to actual activation vs just controller change

* Delete entries as we move them acorss

* Adding extra logic to wait for controller change on top of activate
  • Loading branch information
Matt Gaunt committed Mar 5, 2018
1 parent 047aac5 commit f5792fd
Show file tree
Hide file tree
Showing 25 changed files with 256 additions and 126 deletions.
66 changes: 66 additions & 0 deletions infra/testing/activate-and-control.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const activateSWSafari = require('./activate-sw-safari');

module.exports = async (swUrl) => {
if (global.__workbox.seleniumBrowser.getId() === 'safari') {
return activateSWSafari(swUrl);
}

const error = await global.__workbox.webdriver.executeAsyncScript((swUrl, cb) => {
function _onStateChangePromise(registration, desiredState) {
return new Promise((resolve, reject) => {
if (registration.installing === null) {
throw new Error('Service worker is not installing.');
}

let serviceWorker = registration.installing;

// We unregister all service workers after each test - this should
// always trigger an install state change
let stateChangeListener = function(evt) {
if (evt.target.state === desiredState) {
serviceWorker.removeEventListener('statechange', stateChangeListener);
resolve();
return;
}

if (evt.target.state === 'redundant') {
serviceWorker.removeEventListener('statechange', stateChangeListener);

// Must call reject rather than throw error here due to this
// being inside the scope of the callback function stateChangeListener
reject(new Error('Installing servier worker became redundant'));
return;
}
};

serviceWorker.addEventListener('statechange', stateChangeListener);
});
}

navigator.serviceWorker.register(swUrl)
.then((registration) => {
return _onStateChangePromise(registration, 'activated');
})
.then(() => {
// Ensure the page is being controlled by the SW.
if (navigator.serviceWorker.controller &&
navigator.serviceWorker.controller.scriptURL === swUrl) {
return;
} else {
return new Promise((resolve) => {
navigator.serviceWorker.addEventListener('controllerchange', () => {
if (navigator.serviceWorker.controller.scriptURL === swUrl) {
resolve();
}
});
});
}
})
.then(() => cb())
.catch((err) => cb(err));
}, swUrl);

if (error) {
throw error;
}
};
25 changes: 0 additions & 25 deletions infra/testing/activate-sw.js

This file was deleted.

2 changes: 1 addition & 1 deletion packages/workbox-precaching/_default.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ moduleExports.precache = (entries) => {
}));
});
self.addEventListener('activate', (event) => {
event.waitUntil(precacheController.cleanup());
event.waitUntil(precacheController.activate());
});
};

Expand Down
51 changes: 46 additions & 5 deletions packages/workbox-precaching/controllers/PrecacheController.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,15 @@ class PrecacheController {
}
}

// Clear any existing temp cache
await caches.delete(this._getTempCacheName());

const entriesToPrecache = [];
const entriesAlreadyPrecached = [];

for (const precacheEntry of this._entriesToCacheMap.values()) {
if (await this._precacheDetailsModel._isEntryCached(precacheEntry)) {
if (await this._precacheDetailsModel._isEntryCached(
this._cacheName, precacheEntry)) {
entriesAlreadyPrecached.push(precacheEntry);
} else {
entriesToPrecache.push(precacheEntry);
Expand All @@ -177,7 +181,7 @@ class PrecacheController {

// Wait for all requests to be cached.
await Promise.all(entriesToPrecache.map((precacheEntry) => {
return this._cacheEntry(precacheEntry, options.plugins);
return this._cacheEntryInTemp(precacheEntry, options.plugins);
}));

if (process.env.NODE_ENV !== 'production') {
Expand All @@ -190,6 +194,41 @@ class PrecacheController {
};
}

/**
* Takes the current set of temporary files and moves them to the final
* cache, deleting the temporary cache once copying is complete.
*
* @return {
* Promise<workbox.precaching.CleanupResult>}
* Resolves with an object containing details of the deleted cache requests
* and precache revision details.
*/
async activate() {
const tempCache = await caches.open(this._getTempCacheName());

const requests = await tempCache.keys();
await Promise.all(requests.map(async (request) => {
const response = await tempCache.match(request);
await cacheWrapper.put(this._cacheName, request, response);
await tempCache.delete(request);
}));

await caches.delete(this._getTempCacheName());

return this._cleanup();
}

/**
* Returns the name of the temporary cache.
*
* @return {string}
*
* @private
*/
_getTempCacheName() {
return `${this._cacheName}-temp`;
}

/**
* Requests the entry and saves it to the cache if the response
* is valid.
Expand All @@ -203,7 +242,7 @@ class PrecacheController {
* promise resolves with true if the entry was cached / updated and
* false if the entry is already cached and up-to-date.
*/
async _cacheEntry(precacheEntry, plugins) {
async _cacheEntryInTemp(precacheEntry, plugins) {
let response = await fetchWrapper.fetch(
precacheEntry._networkRequest,
null,
Expand All @@ -214,7 +253,7 @@ class PrecacheController {
response = await cleanRedirect(response);
}

await cacheWrapper.put(this._cacheName,
await cacheWrapper.put(this._getTempCacheName(),
precacheEntry._cacheRequest, response, plugins);

await this._precacheDetailsModel._addEntry(precacheEntry);
Expand All @@ -232,8 +271,10 @@ class PrecacheController {
* Promise<workbox.precaching.CleanupResult>}
* Resolves with an object containing details of the deleted cache requests
* and precache revision details.
*
* @private
*/
async cleanup() {
async _cleanup() {
const expectedCacheUrls = [];
this._entriesToCacheMap.forEach((entry) => {
const fullUrl = new URL(entry._cacheRequest.url, location).toString();
Expand Down
11 changes: 4 additions & 7 deletions packages/workbox-precaching/models/PrecachedDetailsModel.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
*/

import {DBWrapper} from 'workbox-core/_private/DBWrapper.mjs';
import {cacheNames} from 'workbox-core/_private/cacheNames.mjs';
import '../_version.mjs';

// Allows minifier to mangle this name
Expand All @@ -32,12 +31,9 @@ class PrecachedDetailsModel {
/**
* Construct a new model for a specific cache.
*
* @param {string} cacheName
*
* @private
*/
constructor(cacheName) {
this._cacheName = cacheNames.getPrecacheName(cacheName);
constructor() {
this._db = new DBWrapper(`workbox-precaching`, 2, {
onupgradeneeded: this._handleUpgrade,
});
Expand Down Expand Up @@ -70,18 +66,19 @@ class PrecachedDetailsModel {
* Check if an entry is already cached. Returns false if
* the entry isn't cached or the revision has changed.
*
* @param {string} cacheName
* @param {PrecacheEntry} precacheEntry
* @return {boolean}
*
* @private
*/
async _isEntryCached(precacheEntry) {
async _isEntryCached(cacheName, precacheEntry) {
const revisionDetails = await this._getRevision(precacheEntry._entryId);
if (revisionDetails !== precacheEntry._revision) {
return false;
}

const openCache = await caches.open(this._cacheName);
const openCache = await caches.open(cacheName);
const cachedResponse = await openCache.match(precacheEntry._cacheRequest);
return !!cachedResponse;
}
Expand Down
4 changes: 2 additions & 2 deletions test/workbox-background-sync/integration/test-bg-sync.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const expect = require('chai').expect;

const activateSW = require('../../../infra/testing/activate-sw');
const activateAndControlSW = require('../../../infra/testing/activate-and-control');

describe(`[workbox-background-sync] Load and use Background Sync`, function() {
const testServerAddress = global.__workbox.server.getAddress();
Expand All @@ -27,7 +27,7 @@ describe(`[workbox-background-sync] Load and use Background Sync`, function() {
it(`should load a page with service worker`, async function() {
// Load the page and wait for the first service worker to register and activate.
await global.__workbox.webdriver.get(testingUrl);
await activateSW(swUrl);
await activateAndControlSW(swUrl);

const err = await global.__workbox.webdriver.executeAsyncScript((testingUrl, cb) => {
return fetch(`${testingUrl}example.txt`)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const expect = require('chai').expect;

const activateSW = require('../../../infra/testing/activate-sw');
const activateAndControlSW = require('../../../infra/testing/activate-and-control');

describe(`broadcastCacheUpdate.Plugin`, function() {
const testServerAddress = global.__workbox.server.getAddress();
Expand All @@ -10,7 +10,7 @@ describe(`broadcastCacheUpdate.Plugin`, function() {

it(`should broadcast a message on the expected channel when there's a cache update`, async function() {
await global.__workbox.webdriver.get(testingUrl);
await activateSW(swUrl);
await activateAndControlSW(swUrl);

const supported = await global.__workbox.webdriver.executeScript(() => {
return 'BroadcastChannel' in window;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const expect = require('chai').expect;

const activateSW = require('../../../infra/testing/activate-sw');
const activateAndControlSW = require('../../../infra/testing/activate-and-control');
const cleanSWEnv = require('../../../infra/testing/clean-sw');

describe(`expiration.Plugin`, function() {
Expand Down Expand Up @@ -30,7 +30,7 @@ describe(`expiration.Plugin`, function() {
const swUrl = `${testingUrl}sw-max-entries.js`;

// Wait for the service worker to register and activate.
await activateSW(swUrl);
await activateAndControlSW(swUrl);

await global.__workbox.webdriver.executeAsyncScript((testingUrl, cb) => {
fetch(`${testingUrl}example-1.txt`).then(() => cb()).catch((err) => cb(err.message));
Expand Down Expand Up @@ -86,7 +86,7 @@ describe(`expiration.Plugin`, function() {

// Load the page and wait for the service worker to register and activate.
await global.__workbox.webdriver.get(testingUrl);
await activateSW(swUrl);
await activateAndControlSW(swUrl);

await global.__workbox.webdriver.executeAsyncScript((testingUrl, cb) => {
fetch(`${testingUrl}example-1.txt`).then(() => cb()).catch((err) => cb(err.message));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const expect = require('chai').expect;

const activateSW = require('../../../infra/testing/activate-sw');
const activateAndControlSW = require('../../../infra/testing/activate-and-control');

describe(`cacheableResponse.Plugin`, function() {
const testServerAddress = global.__workbox.server.getAddress();
Expand Down Expand Up @@ -33,7 +33,7 @@ describe(`cacheableResponse.Plugin`, function() {

it(`should load a page and cache entries`, async function() {
// Wait for the service worker to register and activate.
await activateSW(swUrl);
await activateAndControlSW(swUrl);

await global.__workbox.webdriver.executeAsyncScript((testingUrl, cb) => {
fetch(`${testingUrl}example-1.txt`).then(() => cb()).catch((err) => cb(err.message));
Expand Down
4 changes: 2 additions & 2 deletions test/workbox-core/integration/test-core.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const activateSW = require('../../../infra/testing/activate-sw');
const activateAndControlSW = require('../../../infra/testing/activate-and-control');

describe(`[workbox-core] Load core in the browser`, function() {
const testServerAddress = global.__workbox.server.getAddress();
Expand All @@ -7,7 +7,7 @@ describe(`[workbox-core] Load core in the browser`, function() {

it(`should load workbox-core in a service worker.`, async function() {
await global.__workbox.webdriver.get(testingUrl);
await activateSW(swUrl);
await activateAndControlSW(swUrl);

// If the service worker activated, it meant the assertions in sw.js were
// met and workbox-core exposes the expected API and defaults that were
Expand Down
9 changes: 6 additions & 3 deletions test/workbox-google-analytics/integration/basic-example.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const expect = require('chai').expect;
const qs = require('qs');
const {By} = require('selenium-webdriver');
const activateSW = require('../../../infra/testing/activate-sw');
const activateAndControlSW = require('../../../infra/testing/activate-and-control');

describe(`[workbox-google-analytics] Load and use Google Analytics`,
function() {
Expand All @@ -27,11 +27,14 @@ describe(`[workbox-google-analytics] Load and use Google Analytics`,
data, [messageChannel.port2]);
};

beforeEach(async function() {
before(async function() {
// Load the page and wait for the first service worker to activate.
await driver.get(testingUrl);
await activateSW(swUrl);

await activateAndControlSW(swUrl);
});

beforeEach(async function() {
// Reset the spied requests array.
await driver.executeAsyncScript(messageSw, {
action: 'clear-spied-requests',
Expand Down
Loading

0 comments on commit f5792fd

Please sign in to comment.