mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-09 05:08:28 +08:00
R=jacktfranklin@chromium.org Bug: 1082789 Change-Id: I100a53f7abd5d35565a3062769273e61c00cbafd Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2612902 Auto-Submit: Tim van der Lippe <tvanderlippe@chromium.org> Commit-Queue: Jack Franklin <jacktfranklin@chromium.org> Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
381 lines
11 KiB
JavaScript
381 lines
11 KiB
JavaScript
// 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);
|