UNPKG

chrome-devtools-frontend

Version:
381 lines (337 loc) • 11.2 kB
// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../common/common.js'; import {NameValue} from './NetworkRequest.js'; // eslint-disable-line no-unused-vars import {Capability, SDKModel, Target} from './SDKModel.js'; // eslint-disable-line no-unused-vars import {Events as SecurityOriginManagerEvents, SecurityOriginManager} from './SecurityOriginManager.js'; /** * @implements {ProtocolProxyApi.StorageDispatcher} */ export class ServiceWorkerCacheModel extends SDKModel { /** * Invariant: This model can only be constructed on a ServiceWorker target. * @param {!Target} target */ constructor(target) { super(target); target.registerStorageDispatcher(this); /** @type {!Map<string, !Cache>} */ this._caches = new Map(); this._cacheAgent = target.cacheStorageAgent(); this._storageAgent = target.storageAgent(); this._securityOriginManager = /** @type {!SecurityOriginManager} */ (target.model(SecurityOriginManager)); this._originsUpdated = new Set(); this._throttler = new Common.Throttler.Throttler(2000); /** @type {boolean} */ this._enabled = false; } enable() { if (this._enabled) { return; } this._securityOriginManager.addEventListener( SecurityOriginManagerEvents.SecurityOriginAdded, this._securityOriginAdded, this); this._securityOriginManager.addEventListener( SecurityOriginManagerEvents.SecurityOriginRemoved, this._securityOriginRemoved, this); for (const securityOrigin of this._securityOriginManager.securityOrigins()) { this._addOrigin(securityOrigin); } this._enabled = true; } /** * @param {string} origin */ clearForOrigin(origin) { this._removeOrigin(origin); this._addOrigin(origin); } refreshCacheNames() { for (const cache of this._caches.values()) { this._cacheRemoved(cache); } this._caches.clear(); const securityOrigins = this._securityOriginManager.securityOrigins(); for (const securityOrigin of securityOrigins) { this._loadCacheNames(securityOrigin); } } /** * @param {!Cache} cache */ async deleteCache(cache) { const response = await this._cacheAgent.invoke_deleteCache({cacheId: cache.cacheId}); if (response.getError()) { console.error(`ServiceWorkerCacheAgent error deleting cache ${cache.toString()}: ${response.getError()}`); return; } this._caches.delete(cache.cacheId); this._cacheRemoved(cache); } /** * @param {!Cache} cache * @param {string} request * @return {!Promise<void>} */ async deleteCacheEntry(cache, request) { const response = await this._cacheAgent.invoke_deleteEntry({cacheId: cache.cacheId, request}); if (response.getError()) { Common.Console.Console.instance().error(Common.UIString.UIString( 'ServiceWorkerCacheAgent error deleting cache entry %s in cache: %s', cache.toString(), response.getError())); return; } } /** * @param {!Cache} cache * @param {number} skipCount * @param {number} pageSize * @param {string} pathFilter * @param {function(!Array.<!Protocol.CacheStorage.DataEntry>, number):void} callback */ loadCacheData(cache, skipCount, pageSize, pathFilter, callback) { this._requestEntries(cache, skipCount, pageSize, pathFilter, callback); } /** * @param {!Cache} cache * @param {string} pathFilter * @param {function(!Array.<!Protocol.CacheStorage.DataEntry>, number):void} callback */ loadAllCacheData(cache, pathFilter, callback) { this._requestAllEntries(cache, pathFilter, callback); } /** * @return {!Array.<!Cache>} */ caches() { const caches = new Array(); for (const cache of this._caches.values()) { caches.push(cache); } return caches; } /** * @override */ dispose() { for (const cache of this._caches.values()) { this._cacheRemoved(cache); } this._caches.clear(); if (this._enabled) { this._securityOriginManager.removeEventListener( SecurityOriginManagerEvents.SecurityOriginAdded, this._securityOriginAdded, this); this._securityOriginManager.removeEventListener( SecurityOriginManagerEvents.SecurityOriginRemoved, this._securityOriginRemoved, this); } } /** * @param {string} securityOrigin */ _addOrigin(securityOrigin) { this._loadCacheNames(securityOrigin); if (this._isValidSecurityOrigin(securityOrigin)) { this._storageAgent.invoke_trackCacheStorageForOrigin({origin: securityOrigin}); } } /** * @param {string} securityOrigin */ _removeOrigin(securityOrigin) { for (const [opaqueId, cache] of this._caches.entries()) { if (cache.securityOrigin === securityOrigin) { // TODO: Remove the closure-style casts once we are typescript-only. this._caches.delete(/** @type {string} */ (opaqueId)); this._cacheRemoved(/** @type {!Cache} */ (cache)); } } if (this._isValidSecurityOrigin(securityOrigin)) { this._storageAgent.invoke_untrackCacheStorageForOrigin({origin: securityOrigin}); } } /** * @param {string} securityOrigin * @return {boolean} */ _isValidSecurityOrigin(securityOrigin) { const parsedURL = Common.ParsedURL.ParsedURL.fromString(securityOrigin); return parsedURL !== null && parsedURL.scheme.startsWith('http'); } /** * @param {string} securityOrigin */ async _loadCacheNames(securityOrigin) { const response = await this._cacheAgent.invoke_requestCacheNames({securityOrigin: securityOrigin}); if (response.getError()) { return; } this._updateCacheNames(securityOrigin, response.caches); } /** * @param {string} securityOrigin * @param {!Array<!Protocol.CacheStorage.Cache>} cachesJson */ _updateCacheNames(securityOrigin, cachesJson) { /** * @param {!Cache} cache * @this {ServiceWorkerCacheModel} */ function deleteAndSaveOldCaches(cache) { if (cache.securityOrigin === securityOrigin && !updatingCachesIds.has(cache.cacheId)) { oldCaches.set(cache.cacheId, cache); this._caches.delete(cache.cacheId); } } /** @type {!Set<string>} */ const updatingCachesIds = new Set(); /** @type {!Map<string, !Cache>} */ const newCaches = new Map(); /** @type {!Map<string, !Cache>} */ const oldCaches = new Map(); for (const cacheJson of cachesJson) { const cache = new Cache(this, cacheJson.securityOrigin, cacheJson.cacheName, cacheJson.cacheId); updatingCachesIds.add(cache.cacheId); if (this._caches.has(cache.cacheId)) { continue; } newCaches.set(cache.cacheId, cache); this._caches.set(cache.cacheId, cache); } this._caches.forEach(deleteAndSaveOldCaches, this); newCaches.forEach(this._cacheAdded, this); oldCaches.forEach(this._cacheRemoved, this); } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _securityOriginAdded(event) { const securityOrigin = /** @type {string} */ (event.data); this._addOrigin(securityOrigin); } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _securityOriginRemoved(event) { const securityOrigin = /** @type {string} */ (event.data); this._removeOrigin(securityOrigin); } /** * @param {!Cache} cache */ _cacheAdded(cache) { this.dispatchEventToListeners(Events.CacheAdded, {model: this, cache: cache}); } /** * @param {!Cache} cache */ _cacheRemoved(cache) { this.dispatchEventToListeners(Events.CacheRemoved, {model: this, cache: cache}); } /** * @param {!Cache} cache * @param {number} skipCount * @param {number} pageSize * @param {string} pathFilter * @param {function(!Array<!Protocol.CacheStorage.DataEntry>, number):void} callback */ async _requestEntries(cache, skipCount, pageSize, pathFilter, callback) { const response = await this._cacheAgent.invoke_requestEntries({cacheId: cache.cacheId, skipCount, pageSize, pathFilter}); if (response.getError()) { console.error('ServiceWorkerCacheAgent error while requesting entries: ', response.getError()); return; } callback(response.cacheDataEntries, response.returnCount); } /** * @param {!Cache} cache * @param {string} pathFilter * @param {function(!Array<!Protocol.CacheStorage.DataEntry>, number):void} callback */ async _requestAllEntries(cache, pathFilter, callback) { const response = await this._cacheAgent.invoke_requestEntries({cacheId: cache.cacheId, pathFilter}); if (response.getError()) { console.error('ServiceWorkerCacheAgent error while requesting entries: ', response.getError()); return; } callback(response.cacheDataEntries, response.returnCount); } /** * @param {!Protocol.Storage.CacheStorageListUpdatedEvent} event * @override */ cacheStorageListUpdated({origin}) { this._originsUpdated.add(origin); this._throttler.schedule(() => { const promises = Array.from(this._originsUpdated, origin => this._loadCacheNames(origin)); this._originsUpdated.clear(); return Promise.all(promises); }); } /** * @param {!Protocol.Storage.CacheStorageContentUpdatedEvent} event * @override */ cacheStorageContentUpdated({origin, cacheName}) { this.dispatchEventToListeners(Events.CacheStorageContentUpdated, {origin, cacheName}); } /** * @param {!Protocol.Storage.IndexedDBListUpdatedEvent} event * @override */ indexedDBListUpdated(event) { } /** * @param {!Protocol.Storage.IndexedDBContentUpdatedEvent} event * @override */ indexedDBContentUpdated(event) { } } /** @enum {symbol} */ export const Events = { CacheAdded: Symbol('CacheAdded'), CacheRemoved: Symbol('CacheRemoved'), CacheStorageContentUpdated: Symbol('CacheStorageContentUpdated') }; export class Cache { /** * @param {!ServiceWorkerCacheModel} model * @param {string} securityOrigin * @param {string} cacheName * @param {string} cacheId */ constructor(model, securityOrigin, cacheName, cacheId) { this._model = model; this.securityOrigin = securityOrigin; this.cacheName = cacheName; this.cacheId = cacheId; } /** * @param {!Cache} cache * @return {boolean} */ equals(cache) { return this.cacheId === cache.cacheId; } /** * @override * @return {string} */ toString() { return this.securityOrigin + this.cacheName; } /** * @param {string} url * @param {!Array.<!NameValue>} requestHeaders * @return {!Promise<?Protocol.CacheStorage.CachedResponse>} */ async requestCachedResponse(url, requestHeaders) { const response = await this._model._cacheAgent.invoke_requestCachedResponse( {cacheId: this.cacheId, requestURL: url, requestHeaders}); if (response.getError()) { return null; } return response.response; } } SDKModel.register(ServiceWorkerCacheModel, Capability.Storage, false);