From 935cf433ed58127bf0f29c33c40a755dba3cc422 Mon Sep 17 00:00:00 2001 From: George Kalpakas Date: Thu, 12 Nov 2020 17:43:51 +0200 Subject: [PATCH] fix(docs-infra): support recovering from unrecoverable SW states (#39651) Occasionally, the SW would end up in a broken state where some of the eagerly cached resources of an older version were available in the local cache, but others (such as lazy-loaded bundles) were not. This would leave the app in a broken state and a blank screen would be displayed. See #28114 for a more detailed discussion. This commit takes advantage of the newly introduced (in v11) [SwUpdate#unrecoverable][1] API to detect these bad states and recover by doing a full page reload whenever an [UnrecoverableStateEvent][2] is emitted. Partially addresses #28114. NOTE: Currently, `SwUpdate.unrecoverable` only works if the app has already bootstrapped; i.e. if only lazy-loaded bundles have been purged from the cache. That should be fine in practice, since the cache entries are removed in least-recently-used order. Thus the eagerly loaded bundles will be the last to be removed from the cache (which rarely happens in practice). [1]: https://v11.angular.io/api/service-worker/SwUpdate#unrecoverable [2]: https://v11.angular.io/api/service-worker/UnrecoverableStateEvent PR Close #39651 --- aio/src/app/shared/location.service.ts | 4 +++ .../app/sw-updates/sw-updates.service.spec.ts | 30 +++++++++++++++++++ aio/src/app/sw-updates/sw-updates.service.ts | 8 +++++ aio/src/testing/location.service.ts | 1 + goldens/size-tracking/aio-payloads.json | 4 +-- 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/aio/src/app/shared/location.service.ts b/aio/src/app/shared/location.service.ts index b1867dbbfe..95c4dd16a1 100644 --- a/aio/src/app/shared/location.service.ts +++ b/aio/src/app/shared/location.service.ts @@ -70,6 +70,10 @@ export class LocationService { window.location.replace(url); } + reloadPage(): void { + window.location.reload(); + } + private stripSlashes(url: string) { return url.replace(/^\/+/, '').replace(/\/+(\?|#|$)/, '$1'); } diff --git a/aio/src/app/sw-updates/sw-updates.service.spec.ts b/aio/src/app/sw-updates/sw-updates.service.spec.ts index ee1d0dd1d7..0cb1891077 100644 --- a/aio/src/app/sw-updates/sw-updates.service.spec.ts +++ b/aio/src/app/sw-updates/sw-updates.service.spec.ts @@ -119,6 +119,16 @@ describe('SwUpdatesService', () => { expect(location.fullPageNavigationNeeded).toHaveBeenCalledTimes(2); })); + it('should request a page reload when an unrecoverable state has been detected', run(() => { + expect(location.reloadPage).toHaveBeenCalledTimes(0); + + swu.$$unrecoverableSubj.next({reason: 'Something bad happened'}); + expect(location.reloadPage).toHaveBeenCalledTimes(1); + + swu.$$unrecoverableSubj.next({reason: 'Something worse happened'}); + expect(location.reloadPage).toHaveBeenCalledTimes(2); + })); + describe('when `SwUpdate` is not enabled', () => { const runDeactivated = (specFn: VoidFunction) => run(specFn, false); @@ -150,6 +160,13 @@ describe('SwUpdatesService', () => { expect(location.fullPageNavigationNeeded).not.toHaveBeenCalled(); })); + + it('should never request a page reload', runDeactivated(() => { + swu.$$unrecoverableSubj.next({reason: 'Something bad happened'}); + swu.$$unrecoverableSubj.next({reason: 'Something worse happened'}); + + expect(location.reloadPage).not.toHaveBeenCalled(); + })); }); describe('when destroyed', () => { @@ -201,6 +218,17 @@ describe('SwUpdatesService', () => { swu.$$activatedSubj.next({current: {hash: 'qux'}}); expect(location.fullPageNavigationNeeded).not.toHaveBeenCalled(); })); + + it('should stop requesting page reloads when unrecoverable states are detected', run(() => { + swu.$$unrecoverableSubj.next({reason: 'Something bad happened'}); + expect(location.reloadPage).toHaveBeenCalledTimes(1); + + service.ngOnDestroy(); + location.reloadPage.calls.reset(); + + swu.$$unrecoverableSubj.next({reason: 'Something worse happened'}); + expect(location.reloadPage).not.toHaveBeenCalled(); + })); }); }); @@ -212,9 +240,11 @@ class MockApplicationRef { class MockSwUpdate { $$availableSubj = new Subject<{available: {hash: string}}>(); $$activatedSubj = new Subject<{current: {hash: string}}>(); + $$unrecoverableSubj = new Subject<{reason: string}>(); available = this.$$availableSubj.asObservable(); activated = this.$$activatedSubj.asObservable(); + unrecoverable = this.$$unrecoverableSubj.asObservable(); activateUpdate = jasmine.createSpy('MockSwUpdate.activateUpdate') .and.callFake(() => Promise.resolve()); diff --git a/aio/src/app/sw-updates/sw-updates.service.ts b/aio/src/app/sw-updates/sw-updates.service.ts index 31763f2031..1f47e13e43 100644 --- a/aio/src/app/sw-updates/sw-updates.service.ts +++ b/aio/src/app/sw-updates/sw-updates.service.ts @@ -51,6 +51,14 @@ export class SwUpdatesService implements OnDestroy { takeUntil(this.onDestroy), ) .subscribe(() => location.fullPageNavigationNeeded()); + + // Request an immediate page reload once an unrecoverable state has been detected. + this.swu.unrecoverable + .pipe( + tap(evt => this.log(`Unrecoverable state: ${evt.reason}\nReloading...`)), + takeUntil(this.onDestroy), + ) + .subscribe(() => location.reloadPage()); } ngOnDestroy() { diff --git a/aio/src/testing/location.service.ts b/aio/src/testing/location.service.ts index 7f73181466..2991580801 100644 --- a/aio/src/testing/location.service.ts +++ b/aio/src/testing/location.service.ts @@ -13,6 +13,7 @@ export class MockLocationService { .callFake((url: string) => this.urlSubject.next(url)); goExternal = jasmine.createSpy('Location.goExternal'); replace = jasmine.createSpy('Location.replace'); + reloadPage = jasmine.createSpy('Location.reloadPage'); handleAnchorClick = jasmine.createSpy('Location.handleAnchorClick') .and.returnValue(false); // prevent click from causing a browser navigation diff --git a/goldens/size-tracking/aio-payloads.json b/goldens/size-tracking/aio-payloads.json index e76a6780a3..c9869baede 100755 --- a/goldens/size-tracking/aio-payloads.json +++ b/goldens/size-tracking/aio-payloads.json @@ -3,7 +3,7 @@ "master": { "uncompressed": { "runtime-es2015": 3033, - "main-es2015": 447565, + "main-es2015": 447766, "polyfills-es2015": 52343 } } @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime-es2015": 3033, - "main-es2015": 447774, + "main-es2015": 447975, "polyfills-es2015": 52493 } }