feat(service-worker): support timeout in registerWhenStable SW registration strategy (#35870)

Previously, when using the `registerWhenStable` ServiceWorker
registration strategy (which is also the default) Angular would wait
indefinitely for the [app to stabilize][1], before registering the
ServiceWorker script. This could lead to a situation where the
ServiceWorker would never be registered when there was a long-running
task (such as an interval or recurring timeout).

Such tasks can often be started by a 3rd-party dependency (beyond the
developer's control or even without them realizing). In addition, this
situation is particularly hard to detect, because the ServiceWorker is
typically not used during development and on production builds a
previous ServiceWorker instance might be already active.

This commit enhances the `registerWhenStable` registration strategy by
adding support for an optional `<timeout>` argument, which guarantees
that the ServiceWorker will be registered when the timeout expires, even
if the app has not stabilized yet.

For example, with `registerWhenStable:5000` the ServiceWorker will be
registered as soon as the app stabilizes or after 5 seconds if the app
has not stabilized by then.

Related to #34464.

[1]: https://angular.io/api/core/ApplicationRef#is-stable-examples

PR Close #35870
This commit is contained in:
Sonu Kapoor
2020-03-24 22:54:51 +02:00
committed by Alex Rickabaugh
parent d96995b4e3
commit 00efacf561
2 changed files with 91 additions and 8 deletions

View File

@ -141,7 +141,7 @@ describe('ServiceWorkerModule', () => {
],
});
// Dummy `get()` call to initialize the test "app".
// Dummy `inject()` call to initialize the test "app".
TestBed.inject(ApplicationRef);
return isStableSub;
@ -156,13 +156,80 @@ describe('ServiceWorkerModule', () => {
tick();
expect(swRegisterSpy).not.toHaveBeenCalled();
tick(60000);
expect(swRegisterSpy).not.toHaveBeenCalled();
isStableSub.next(true);
tick();
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
}));
it('registers the SW when the app stabilizes with `registerWhenStable`', fakeAsync(() => {
it('registers the SW when the app stabilizes with `registerWhenStable:<timeout>`',
fakeAsync(() => {
const isStableSub = configTestBedWithMockedStability('registerWhenStable:1000');
isStableSub.next(false);
isStableSub.next(false);
tick();
expect(swRegisterSpy).not.toHaveBeenCalled();
tick(500);
expect(swRegisterSpy).not.toHaveBeenCalled();
isStableSub.next(true);
tick();
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
}));
it('registers the SW after `timeout` if the app does not stabilize with `registerWhenStable:<timeout>`',
fakeAsync(() => {
configTestBedWithMockedStability('registerWhenStable:1000');
tick(999);
expect(swRegisterSpy).not.toHaveBeenCalled();
tick(1);
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
}));
it('registers the SW asap (asynchronously) before the app stabilizes with `registerWhenStable:0`',
fakeAsync(() => {
const isStableSub = configTestBedWithMockedStability('registerWhenStable:0');
// Create a microtask.
Promise.resolve();
flushMicrotasks();
expect(swRegisterSpy).not.toHaveBeenCalled();
tick(0);
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
}));
it('registers the SW only when the app stabilizes with `registerWhenStable:`',
fakeAsync(() => {
const isStableSub = configTestBedWithMockedStability('registerWhenStable:');
isStableSub.next(false);
isStableSub.next(false);
tick();
expect(swRegisterSpy).not.toHaveBeenCalled();
tick(60000);
expect(swRegisterSpy).not.toHaveBeenCalled();
isStableSub.next(true);
tick();
expect(swRegisterSpy).toHaveBeenCalledWith('sw.js', {scope: undefined});
}));
it('registers the SW only when the app stabilizes with `registerWhenStable`',
fakeAsync(() => {
const isStableSub = configTestBedWithMockedStability('registerWhenStable');
isStableSub.next(false);
@ -171,6 +238,9 @@ describe('ServiceWorkerModule', () => {
tick();
expect(swRegisterSpy).not.toHaveBeenCalled();
tick(60000);
expect(swRegisterSpy).not.toHaveBeenCalled();
isStableSub.next(true);
tick();