refactor(service-worker): remove duplicate `Context` type (in favor of `ExtendableEvent`) (#42736)
This commit removes the duplicate `Context` interface and uses the `ExtendableEvent` interface instead. This is in preparation of switching from our custom typings to the official TypeScript typings (`lib.webworker.d.ts`). PR Close #42736
This commit is contained in:
parent
ad00d0830f
commit
fe135e1198
|
@ -104,14 +104,3 @@ export class Adapter<T extends CacheStorage = CacheStorage> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* An event context in which an operation is taking place, which allows
|
|
||||||
* the delaying of Service Worker shutdown until certain triggers occur.
|
|
||||||
*/
|
|
||||||
export interface Context {
|
|
||||||
/**
|
|
||||||
* Delay shutdown of the Service Worker until the given promise resolves.
|
|
||||||
*/
|
|
||||||
waitUntil(fn: Promise<any>): void;
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Adapter, Context} from './adapter';
|
import {Adapter} from './adapter';
|
||||||
import {CacheState, NormalizedUrl, UpdateCacheStatus, UpdateSource} from './api';
|
import {CacheState, NormalizedUrl, UpdateCacheStatus, UpdateSource} from './api';
|
||||||
import {AssetGroup, LazyAssetGroup, PrefetchAssetGroup} from './assets';
|
import {AssetGroup, LazyAssetGroup, PrefetchAssetGroup} from './assets';
|
||||||
import {DataGroup} from './data';
|
import {DataGroup} from './data';
|
||||||
|
@ -135,7 +135,7 @@ export class AppVersion implements UpdateSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async handleFetch(req: Request, context: Context): Promise<Response|null> {
|
async handleFetch(req: Request, event: ExtendableEvent): Promise<Response|null> {
|
||||||
// Check the request against each `AssetGroup` in sequence. If an `AssetGroup` can't handle the
|
// Check the request against each `AssetGroup` in sequence. If an `AssetGroup` can't handle the
|
||||||
// request,
|
// request,
|
||||||
// it will return `null`. Thus, the first non-null response is the SW's answer to the request.
|
// it will return `null`. Thus, the first non-null response is the SW's answer to the request.
|
||||||
|
@ -152,7 +152,7 @@ export class AppVersion implements UpdateSource {
|
||||||
}
|
}
|
||||||
|
|
||||||
// No response has been found yet. Maybe this group will have one.
|
// No response has been found yet. Maybe this group will have one.
|
||||||
return group.handleFetch(req, context);
|
return group.handleFetch(req, event);
|
||||||
}, Promise.resolve<Response|null>(null));
|
}, Promise.resolve<Response|null>(null));
|
||||||
|
|
||||||
// The result of the above is the asset response, if there is any, or null otherwise. Return the
|
// The result of the above is the asset response, if there is any, or null otherwise. Return the
|
||||||
|
@ -170,7 +170,7 @@ export class AppVersion implements UpdateSource {
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return group.handleFetch(req, context);
|
return group.handleFetch(req, event);
|
||||||
}, Promise.resolve<Response|null>(null));
|
}, Promise.resolve<Response|null>(null));
|
||||||
|
|
||||||
// If the data caching group returned a response, go with it.
|
// If the data caching group returned a response, go with it.
|
||||||
|
@ -195,7 +195,7 @@ export class AppVersion implements UpdateSource {
|
||||||
|
|
||||||
// This was a navigation request. Re-enter `handleFetch` with a request for
|
// This was a navigation request. Re-enter `handleFetch` with a request for
|
||||||
// the URL.
|
// the URL.
|
||||||
return this.handleFetch(this.adapter.newRequest(this.indexUrl), context);
|
return this.handleFetch(this.adapter.newRequest(this.indexUrl), event);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Adapter, Context} from './adapter';
|
import {Adapter} from './adapter';
|
||||||
import {CacheState, NormalizedUrl, UpdateCacheStatus, UpdateSource, UrlMetadata} from './api';
|
import {CacheState, NormalizedUrl, UpdateCacheStatus, UpdateSource, UrlMetadata} from './api';
|
||||||
import {Database, Table} from './database';
|
import {Database, Table} from './database';
|
||||||
import {CacheTable} from './db-cache';
|
import {CacheTable} from './db-cache';
|
||||||
|
@ -114,7 +114,7 @@ export abstract class AssetGroup {
|
||||||
/**
|
/**
|
||||||
* Process a request for a given resource and return it, or return null if it's not available.
|
* Process a request for a given resource and return it, or return null if it's not available.
|
||||||
*/
|
*/
|
||||||
async handleFetch(req: Request, ctx: Context): Promise<Response|null> {
|
async handleFetch(req: Request, _event: ExtendableEvent): Promise<Response|null> {
|
||||||
const url = this.adapter.normalizeUrl(req.url);
|
const url = this.adapter.normalizeUrl(req.url);
|
||||||
// Either the request matches one of the known resource URLs, one of the patterns for
|
// Either the request matches one of the known resource URLs, one of the patterns for
|
||||||
// dynamically matched URLs, or neither. Determine which is the case for this request in
|
// dynamically matched URLs, or neither. Determine which is the case for this request in
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {Adapter, Context} from './adapter';
|
import {Adapter} from './adapter';
|
||||||
import {Database, Table} from './database';
|
import {Database, Table} from './database';
|
||||||
import {CacheTable} from './db-cache';
|
import {CacheTable} from './db-cache';
|
||||||
import {DebugHandler} from './debug';
|
import {DebugHandler} from './debug';
|
||||||
|
@ -294,7 +294,7 @@ export class DataGroup {
|
||||||
* Process a fetch event and return a `Response` if the resource is covered by this group,
|
* Process a fetch event and return a `Response` if the resource is covered by this group,
|
||||||
* or `null` otherwise.
|
* or `null` otherwise.
|
||||||
*/
|
*/
|
||||||
async handleFetch(req: Request, ctx: Context): Promise<Response|null> {
|
async handleFetch(req: Request, event: ExtendableEvent): Promise<Response|null> {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
if (!this.patterns.some(pattern => pattern.test(req.url))) {
|
if (!this.patterns.some(pattern => pattern.test(req.url))) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -314,9 +314,9 @@ export class DataGroup {
|
||||||
// Handle the request with whatever strategy was selected.
|
// Handle the request with whatever strategy was selected.
|
||||||
switch (this.config.strategy) {
|
switch (this.config.strategy) {
|
||||||
case 'freshness':
|
case 'freshness':
|
||||||
return this.handleFetchWithFreshness(req, ctx, lru);
|
return this.handleFetchWithFreshness(req, event, lru);
|
||||||
case 'performance':
|
case 'performance':
|
||||||
return this.handleFetchWithPerformance(req, ctx, lru);
|
return this.handleFetchWithPerformance(req, event, lru);
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown strategy: ${this.config.strategy}`);
|
throw new Error(`Unknown strategy: ${this.config.strategy}`);
|
||||||
}
|
}
|
||||||
|
@ -337,7 +337,7 @@ export class DataGroup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleFetchWithPerformance(req: Request, ctx: Context, lru: LruList):
|
private async handleFetchWithPerformance(req: Request, event: ExtendableEvent, lru: LruList):
|
||||||
Promise<Response|null> {
|
Promise<Response|null> {
|
||||||
let res: Response|null|undefined = null;
|
let res: Response|null|undefined = null;
|
||||||
|
|
||||||
|
@ -348,7 +348,7 @@ export class DataGroup {
|
||||||
res = fromCache.res;
|
res = fromCache.res;
|
||||||
// Check the age of the resource.
|
// Check the age of the resource.
|
||||||
if (this.config.refreshAheadMs !== undefined && fromCache.age >= this.config.refreshAheadMs) {
|
if (this.config.refreshAheadMs !== undefined && fromCache.age >= this.config.refreshAheadMs) {
|
||||||
ctx.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));
|
event.waitUntil(this.safeCacheResponse(req, this.safeFetch(req), lru));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -367,7 +367,7 @@ export class DataGroup {
|
||||||
res = this.adapter.newResponse(null, {status: 504, statusText: 'Gateway Timeout'});
|
res = this.adapter.newResponse(null, {status: 504, statusText: 'Gateway Timeout'});
|
||||||
|
|
||||||
// Cache the network response eventually.
|
// Cache the network response eventually.
|
||||||
ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru));
|
event.waitUntil(this.safeCacheResponse(req, networkFetch, lru));
|
||||||
} else {
|
} else {
|
||||||
// The request completed in time, so cache it inline with the response flow.
|
// The request completed in time, so cache it inline with the response flow.
|
||||||
await this.safeCacheResponse(req, res, lru);
|
await this.safeCacheResponse(req, res, lru);
|
||||||
|
@ -376,7 +376,7 @@ export class DataGroup {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleFetchWithFreshness(req: Request, ctx: Context, lru: LruList):
|
private async handleFetchWithFreshness(req: Request, event: ExtendableEvent, lru: LruList):
|
||||||
Promise<Response|null> {
|
Promise<Response|null> {
|
||||||
// Start with a network fetch.
|
// Start with a network fetch.
|
||||||
const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
|
const [timeoutFetch, networkFetch] = this.networkFetchWithTimeout(req);
|
||||||
|
@ -392,7 +392,7 @@ export class DataGroup {
|
||||||
|
|
||||||
// If the network fetch times out or errors, fall back on the cache.
|
// If the network fetch times out or errors, fall back on the cache.
|
||||||
if (res === undefined) {
|
if (res === undefined) {
|
||||||
ctx.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));
|
event.waitUntil(this.safeCacheResponse(req, networkFetch, lru, true));
|
||||||
|
|
||||||
// Ignore the age, the network response will be cached anyway due to the
|
// Ignore the age, the network response will be cached anyway due to the
|
||||||
// behavior of freshness.
|
// behavior of freshness.
|
||||||
|
|
|
@ -12,7 +12,7 @@ import {IdleScheduler} from '../src/idle';
|
||||||
import {MockCache} from '../testing/cache';
|
import {MockCache} from '../testing/cache';
|
||||||
import {MockRequest} from '../testing/fetch';
|
import {MockRequest} from '../testing/fetch';
|
||||||
import {MockFileSystemBuilder, MockServerStateBuilder, tmpHashTable, tmpManifestSingleAssetGroup} from '../testing/mock';
|
import {MockFileSystemBuilder, MockServerStateBuilder, tmpHashTable, tmpManifestSingleAssetGroup} from '../testing/mock';
|
||||||
import {SwTestHarness, SwTestHarnessBuilder} from '../testing/scope';
|
import {MockExtendableEvent, SwTestHarness, SwTestHarnessBuilder} from '../testing/scope';
|
||||||
|
|
||||||
(function() {
|
(function() {
|
||||||
// Skip environments that don't support the minimum APIs needed to run the SW tests.
|
// Skip environments that don't support the minimum APIs needed to run the SW tests.
|
||||||
|
@ -33,6 +33,8 @@ const scope = new SwTestHarnessBuilder().withServerState(server).build();
|
||||||
|
|
||||||
const db = new CacheDatabase(scope);
|
const db = new CacheDatabase(scope);
|
||||||
|
|
||||||
|
const testEvent = new MockExtendableEvent('test');
|
||||||
|
|
||||||
|
|
||||||
describe('prefetch assets', () => {
|
describe('prefetch assets', () => {
|
||||||
let group: PrefetchAssetGroup;
|
let group: PrefetchAssetGroup;
|
||||||
|
@ -50,8 +52,8 @@ describe('prefetch assets', () => {
|
||||||
it('fully caches the two files', async () => {
|
it('fully caches the two files', async () => {
|
||||||
await group.initializeFully();
|
await group.initializeFully();
|
||||||
scope.updateServerState();
|
scope.updateServerState();
|
||||||
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), scope);
|
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), testEvent);
|
||||||
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), scope);
|
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), testEvent);
|
||||||
expect(await res1!.text()).toEqual('this is foo');
|
expect(await res1!.text()).toEqual('this is foo');
|
||||||
expect(await res2!.text()).toEqual('this is bar');
|
expect(await res2!.text()).toEqual('this is bar');
|
||||||
});
|
});
|
||||||
|
@ -63,14 +65,14 @@ describe('prefetch assets', () => {
|
||||||
freshScope, freshScope, idle, manifest.assetGroups![0], tmpHashTable(manifest),
|
freshScope, freshScope, idle, manifest.assetGroups![0], tmpHashTable(manifest),
|
||||||
new CacheDatabase(freshScope), 'test');
|
new CacheDatabase(freshScope), 'test');
|
||||||
await group.initializeFully();
|
await group.initializeFully();
|
||||||
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), scope);
|
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), testEvent);
|
||||||
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), scope);
|
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), testEvent);
|
||||||
expect(await res1!.text()).toEqual('this is foo');
|
expect(await res1!.text()).toEqual('this is foo');
|
||||||
expect(await res2!.text()).toEqual('this is bar');
|
expect(await res2!.text()).toEqual('this is bar');
|
||||||
});
|
});
|
||||||
it('caches properly if resources are requested before initialization', async () => {
|
it('caches properly if resources are requested before initialization', async () => {
|
||||||
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), scope);
|
const res1 = await group.handleFetch(scope.newRequest('/foo.txt'), testEvent);
|
||||||
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), scope);
|
const res2 = await group.handleFetch(scope.newRequest('/bar.txt'), testEvent);
|
||||||
expect(await res1!.text()).toEqual('this is foo');
|
expect(await res1!.text()).toEqual('this is foo');
|
||||||
expect(await res2!.text()).toEqual('this is bar');
|
expect(await res2!.text()).toEqual('this is bar');
|
||||||
scope.updateServerState();
|
scope.updateServerState();
|
||||||
|
@ -90,7 +92,7 @@ describe('prefetch assets', () => {
|
||||||
it('CacheQueryOptions are passed through', async () => {
|
it('CacheQueryOptions are passed through', async () => {
|
||||||
await group.initializeFully();
|
await group.initializeFully();
|
||||||
const matchSpy = spyOn(MockCache.prototype, 'match').and.callThrough();
|
const matchSpy = spyOn(MockCache.prototype, 'match').and.callThrough();
|
||||||
await group.handleFetch(scope.newRequest('/foo.txt'), scope);
|
await group.handleFetch(scope.newRequest('/foo.txt'), testEvent);
|
||||||
expect(matchSpy).toHaveBeenCalledWith(new MockRequest('/foo.txt'), {ignoreVary: true});
|
expect(matchSpy).toHaveBeenCalledWith(new MockRequest('/foo.txt'), {ignoreVary: true});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
import {Subject} from 'rxjs';
|
import {Subject} from 'rxjs';
|
||||||
|
|
||||||
import {Adapter, Context} from '../src/adapter';
|
import {Adapter} from '../src/adapter';
|
||||||
import {AssetGroupConfig, Manifest} from '../src/manifest';
|
import {AssetGroupConfig, Manifest} from '../src/manifest';
|
||||||
import {sha1} from '../src/sha1';
|
import {sha1} from '../src/sha1';
|
||||||
|
|
||||||
|
@ -105,8 +105,7 @@ export class MockClients implements Clients {
|
||||||
async claim(): Promise<any> {}
|
async claim(): Promise<any> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SwTestHarness extends Adapter<MockCacheStorage> implements Context,
|
export class SwTestHarness extends Adapter<MockCacheStorage> implements ServiceWorkerGlobalScope {
|
||||||
ServiceWorkerGlobalScope {
|
|
||||||
readonly clients = new MockClients();
|
readonly clients = new MockClients();
|
||||||
private eventHandlers = new Map<string, Function>();
|
private eventHandlers = new Map<string, Function>();
|
||||||
private skippedWaiting = false;
|
private skippedWaiting = false;
|
||||||
|
@ -239,8 +238,6 @@ export class SwTestHarness extends Adapter<MockCacheStorage> implements Context,
|
||||||
this.skippedWaiting = true;
|
this.skippedWaiting = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
waitUntil(promise: Promise<void>): void {}
|
|
||||||
|
|
||||||
handleFetch(req: Request, clientId = ''): [Promise<Response|undefined>, Promise<void>] {
|
handleFetch(req: Request, clientId = ''): [Promise<Response|undefined>, Promise<void>] {
|
||||||
if (!this.eventHandlers.has('fetch')) {
|
if (!this.eventHandlers.has('fetch')) {
|
||||||
throw new Error('No fetch handler registered');
|
throw new Error('No fetch handler registered');
|
||||||
|
@ -376,7 +373,49 @@ export class ConfigBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OneTimeContext implements Context {
|
class MockEvent implements Event {
|
||||||
|
readonly AT_TARGET = -1;
|
||||||
|
readonly BUBBLING_PHASE = -1;
|
||||||
|
readonly CAPTURING_PHASE = -1;
|
||||||
|
readonly NONE = -1;
|
||||||
|
|
||||||
|
readonly bubbles = false;
|
||||||
|
cancelBubble = false;
|
||||||
|
readonly cancelable = false;
|
||||||
|
readonly composed = false;
|
||||||
|
readonly currentTarget = null;
|
||||||
|
readonly defaultPrevented = false;
|
||||||
|
readonly eventPhase = -1;
|
||||||
|
readonly isTrusted = false;
|
||||||
|
returnValue = false;
|
||||||
|
readonly srcElement = null;
|
||||||
|
readonly target = null;
|
||||||
|
readonly timeStamp = Date.now();
|
||||||
|
|
||||||
|
constructor(readonly type: string) {}
|
||||||
|
|
||||||
|
composedPath(): EventTarget[] {
|
||||||
|
this.notImplemented();
|
||||||
|
}
|
||||||
|
initEvent(type: string, bubbles?: boolean, cancelable?: boolean): void {
|
||||||
|
this.notImplemented();
|
||||||
|
}
|
||||||
|
preventDefault(): void {
|
||||||
|
this.notImplemented();
|
||||||
|
}
|
||||||
|
stopImmediatePropagation(): void {
|
||||||
|
this.notImplemented();
|
||||||
|
}
|
||||||
|
stopPropagation(): void {
|
||||||
|
this.notImplemented();
|
||||||
|
}
|
||||||
|
|
||||||
|
private notImplemented(): never {
|
||||||
|
throw new Error('Method not implemented in `MockEvent`.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MockExtendableEvent extends MockEvent implements ExtendableEvent {
|
||||||
private queue: Promise<void>[] = [];
|
private queue: Promise<void>[] = [];
|
||||||
|
|
||||||
waitUntil(promise: Promise<void>): void {
|
waitUntil(promise: Promise<void>): void {
|
||||||
|
@ -392,14 +431,12 @@ class OneTimeContext implements Context {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockExtendableEvent extends OneTimeContext {}
|
|
||||||
|
|
||||||
class MockFetchEvent extends MockExtendableEvent {
|
class MockFetchEvent extends MockExtendableEvent {
|
||||||
response: Promise<Response|undefined> = Promise.resolve(undefined);
|
response: Promise<Response|undefined> = Promise.resolve(undefined);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
readonly request: Request, readonly clientId: string, readonly resultingClientId: string) {
|
readonly request: Request, readonly clientId: string, readonly resultingClientId: string) {
|
||||||
super();
|
super('fetch');
|
||||||
}
|
}
|
||||||
|
|
||||||
respondWith(promise: Promise<Response>): Promise<Response> {
|
respondWith(promise: Promise<Response>): Promise<Response> {
|
||||||
|
@ -410,13 +447,13 @@ class MockFetchEvent extends MockExtendableEvent {
|
||||||
|
|
||||||
class MockMessageEvent extends MockExtendableEvent {
|
class MockMessageEvent extends MockExtendableEvent {
|
||||||
constructor(readonly data: Object, readonly source: MockClient|null) {
|
constructor(readonly data: Object, readonly source: MockClient|null) {
|
||||||
super();
|
super('message');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockPushEvent extends MockExtendableEvent {
|
class MockPushEvent extends MockExtendableEvent {
|
||||||
constructor(private _data: Object) {
|
constructor(private _data: Object) {
|
||||||
super();
|
super('push');
|
||||||
}
|
}
|
||||||
data = {
|
data = {
|
||||||
json: () => this._data,
|
json: () => this._data,
|
||||||
|
@ -425,11 +462,19 @@ class MockPushEvent extends MockExtendableEvent {
|
||||||
|
|
||||||
class MockNotificationEvent extends MockExtendableEvent {
|
class MockNotificationEvent extends MockExtendableEvent {
|
||||||
constructor(private _notification: any, readonly action?: string) {
|
constructor(private _notification: any, readonly action?: string) {
|
||||||
super();
|
super('notification');
|
||||||
}
|
}
|
||||||
readonly notification = {...this._notification, close: () => undefined};
|
readonly notification = {...this._notification, close: () => undefined};
|
||||||
}
|
}
|
||||||
|
|
||||||
class MockInstallEvent extends MockExtendableEvent {}
|
class MockInstallEvent extends MockExtendableEvent {
|
||||||
|
constructor() {
|
||||||
|
super('install');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class MockActivateEvent extends MockExtendableEvent {}
|
class MockActivateEvent extends MockExtendableEvent {
|
||||||
|
constructor() {
|
||||||
|
super('activate');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue