diff --git a/packages/service-worker/worker/src/adapter.ts b/packages/service-worker/worker/src/adapter.ts index ad56aee12a..1bee5c20a4 100644 --- a/packages/service-worker/worker/src/adapter.ts +++ b/packages/service-worker/worker/src/adapter.ts @@ -18,7 +18,7 @@ import {NamedCacheStorage} from './named-cache-storage'; */ export class Adapter { readonly caches: NamedCacheStorage; - private readonly origin: string; + readonly origin: string; constructor(protected readonly scopeUrl: string, caches: T) { const parsedScopeUrl = this.parseUrl(this.scopeUrl); diff --git a/packages/service-worker/worker/src/service-worker.d.ts b/packages/service-worker/worker/src/service-worker.d.ts index 976a4a70f0..69171abe3d 100644 --- a/packages/service-worker/worker/src/service-worker.d.ts +++ b/packages/service-worker/worker/src/service-worker.d.ts @@ -106,23 +106,54 @@ interface ExtendableMessageEvent extends ExtendableEvent { readonly source: Client|ServiceWorker|MessagePort|null; } +// WorkerGlobalScope + +// Explicitly omit the `caches` property to disallow accessing `CacheStorage` APIs directly. All +// interactions with `CacheStorage` should go through a `NamedCacheStorage` instance (exposed by the +// `Adapter`). +interface WorkerGlobalScope extends EventTarget, Omit { + readonly location: WorkerLocation; + readonly navigator: WorkerNavigator; + readonly self: WorkerGlobalScope & typeof globalThis; + + importScripts(...urls: string[]): void; + addEventListener(type: K, listener: (this: WorkerGlobalScope, ev: WorkerGlobalScopeEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: WorkerGlobalScope, ev: WorkerGlobalScopeEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; +} + +interface WorkerGlobalScopeEventMap { + error: ErrorEvent; + languagechange: Event; + offline: Event; + online: Event; + rejectionhandled: PromiseRejectionEvent; + unhandledrejection: PromiseRejectionEvent; +} + // ServiceWorkerGlobalScope -interface ServiceWorkerGlobalScope { - // Intentionally does not include a `caches` property to disallow accessing `CacheStorage` APIs - // directly. All interactions with `CacheStorage` should go through a `NamedCacheStorage` instance - // (exposed by the `Adapter`). - clients: Clients; - registration: ServiceWorkerRegistration; +interface ServiceWorkerGlobalScope extends WorkerGlobalScope { + readonly clients: Clients; + readonly registration: ServiceWorkerRegistration; + readonly serviceWorker: ServiceWorker; - addEventListener(event: 'activate', fn: (event?: ExtendableEvent) => any): void; - addEventListener(event: 'message', fn: (event?: ExtendableMessageEvent) => any): void; - addEventListener(event: 'fetch', fn: (event?: FetchEvent) => any): void; - addEventListener(event: 'install', fn: (event?: ExtendableEvent) => any): void; - addEventListener(event: 'push', fn: (event?: PushEvent) => any): void; - addEventListener(event: 'notificationclick', fn: (event?: NotificationEvent) => any): void; - addEventListener(event: 'sync', fn: (event?: SyncEvent) => any): void; - - fetch(request: Request|string): Promise; skipWaiting(): Promise; + addEventListener(type: K, listener: (this: ServiceWorkerGlobalScope, ev: ServiceWorkerGlobalScopeEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void; + addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; + removeEventListener(type: K, listener: (this: ServiceWorkerGlobalScope, ev: ServiceWorkerGlobalScopeEventMap[K]) => any, options?: boolean | EventListenerOptions): void; + removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; +} + +interface ServiceWorkerGlobalScopeEventMap extends WorkerGlobalScopeEventMap { + activate: ExtendableEvent; + fetch: FetchEvent; + install: ExtendableEvent; + message: ExtendableMessageEvent; + messageerror: MessageEvent; + notificationclick: NotificationEvent; + notificationclose: NotificationEvent; + push: PushEvent; + sync: SyncEvent; } diff --git a/packages/service-worker/worker/testing/scope.ts b/packages/service-worker/worker/testing/scope.ts index 5acf62e354..f635e69034 100644 --- a/packages/service-worker/worker/testing/scope.ts +++ b/packages/service-worker/worker/testing/scope.ts @@ -37,13 +37,16 @@ export class SwTestHarnessBuilder { } build(): SwTestHarness { - return new SwTestHarness(this.server, this.caches, this.scopeUrl); + return new SwTestHarnessImpl(this.server, this.caches, this.scopeUrl) as SwTestHarness; } } -export class SwTestHarness extends Adapter implements ServiceWorkerGlobalScope { +export type SwTestHarness = SwTestHarnessImpl&ServiceWorkerGlobalScope; + +export class SwTestHarnessImpl extends Adapter implements + Partial { readonly clients = new MockClients(); - private eventHandlers = new Map(); + private eventHandlers = new Map(); private skippedWaiting = false; private selfMessageQueue: any[] = []; @@ -131,12 +134,26 @@ export class SwTestHarness extends Adapter implements ServiceW } } - addEventListener(event: string, handler: Function): void { - this.eventHandlers.set(event, handler); + addEventListener( + type: string, listener: EventListenerOrEventListenerObject, + options?: boolean|AddEventListenerOptions): void { + if (options !== undefined) { + throw new Error('Mock `addEventListener()` does not support `options`.'); + } + + const handler: EventListener = + (typeof listener === 'function') ? listener : evt => listener.handleEvent(evt); + this.eventHandlers.set(type, handler); } - removeEventListener(event: string, handler?: Function): void { - this.eventHandlers.delete(event); + removeEventListener( + type: string, listener: EventListenerOrEventListenerObject, + options?: boolean|AddEventListenerOptions): void { + if (options !== undefined) { + throw new Error('Mock `removeEventListener()` does not support `options`.'); + } + + this.eventHandlers.delete(type); } newRequest(url: string, init: Object = {}): Request {