Skip to content

Commit 0d4fe38

Browse files
gkalpakalxhub
authored andcommitted
fix(service-worker): ignore invalid only-if-cached requests (#22883)
Under some circumstances (possibly related to opening Chrome DevTools), requests are made with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request will eventually fail, because `only-if-cached` is only allowed to be used with `mode: 'same-origin'`. This is likely a bug in Chrome DevTools. This commit avoids errors related to such requests by not handling them. Fixes #22362 PR Close #22883
1 parent ae9c25f commit 0d4fe38

File tree

3 files changed

+43
-6
lines changed

3 files changed

+43
-6
lines changed

packages/service-worker/worker/src/driver.ts

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ export class Driver implements Debuggable, UpdateSource {
9494
*/
9595
private scheduledNavUpdateCheck: boolean = false;
9696

97+
/**
98+
* Keep track of whether we have logged an invalid `only-if-cached` request.
99+
* (See `.onFetch()` for details.)
100+
*/
101+
private loggedInvalidOnlyIfCachedRequest: boolean = false;
102+
97103
/**
98104
* A scheduler which manages a queue of tasks that need to be executed when the SW is
99105
* not doing any other work (not processing any other requests).
@@ -156,12 +162,13 @@ export class Driver implements Debuggable, UpdateSource {
156162
* asynchronous execution that eventually resolves for respondWith() and waitUntil().
157163
*/
158164
private onFetch(event: FetchEvent): void {
165+
const req = event.request;
166+
159167
// The only thing that is served unconditionally is the debug page.
160-
if (this.adapter.parseUrl(event.request.url, this.scope.registration.scope).path ===
161-
'/ngsw/state') {
168+
if (this.adapter.parseUrl(req.url, this.scope.registration.scope).path === '/ngsw/state') {
162169
// Allow the debugger to handle the request, but don't affect SW state in any
163170
// other way.
164-
event.respondWith(this.debugger.handleFetch(event.request));
171+
event.respondWith(this.debugger.handleFetch(req));
165172
return;
166173
}
167174

@@ -177,6 +184,24 @@ export class Driver implements Debuggable, UpdateSource {
177184
return;
178185
}
179186

187+
// When opening DevTools in Chrome, a request is made for the current URL (and possibly related
188+
// resources, e.g. scripts) with `cache: 'only-if-cached'` and `mode: 'no-cors'`. These request
189+
// will eventually fail, because `only-if-cached` is only allowed to be used with
190+
// `mode: 'same-origin'`.
191+
// This is likely a bug in Chrome DevTools. Avoid handling such requests.
192+
// (See also https://github.com/angular/angular/issues/22362.)
193+
// TODO(gkalpak): Remove once no longer necessary (i.e. fixed in Chrome DevTools).
194+
if ((req.cache as string) === 'only-if-cached' && req.mode !== 'same-origin') {
195+
// Log the incident only the first time it happens, to avoid spamming the logs.
196+
if (!this.loggedInvalidOnlyIfCachedRequest) {
197+
this.loggedInvalidOnlyIfCachedRequest = true;
198+
this.debugger.log(
199+
`Ignoring invalid request: 'only-if-cached' can be set only with 'same-origin' mode`,
200+
`Driver.fetch(${req.url}, cache: ${req.cache}, mode: ${req.mode})`);
201+
}
202+
return;
203+
}
204+
180205
// Past this point, the SW commits to handling the request itself. This could still
181206
// fail (and result in `state` being set to `SAFE_MODE`), but even in that case the
182207
// SW will still deliver a response.
@@ -611,8 +636,8 @@ export class Driver implements Debuggable, UpdateSource {
611636
* offline (detected as response status 504).
612637
*/
613638
private async fetchLatestManifest(ignoreOfflineError?: false): Promise<Manifest>;
614-
private async fetchLatestManifest(ignoreOfflineError: true): Promise<Manifest | null>;
615-
private async fetchLatestManifest(ignoreOfflineError = false): Promise<Manifest | null> {
639+
private async fetchLatestManifest(ignoreOfflineError: true): Promise<Manifest|null>;
640+
private async fetchLatestManifest(ignoreOfflineError = false): Promise<Manifest|null> {
616641
const res =
617642
await this.safeFetch(this.adapter.newRequest('ngsw.json?ngsw-cache-bust=' + Math.random()));
618643
if (!res.ok) {

packages/service-worker/worker/test/happy_spec.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,15 @@ const manifestUpdateHash = sha1(JSON.stringify(manifestUpdate));
708708

709709
expect(driver.state).toEqual(DriverReadyState.EXISTING_CLIENTS_ONLY);
710710
});
711+
712+
async_it('ignores invalid `only-if-cached` requests ', async() => {
713+
const requestFoo = (cache: RequestCache | 'only-if-cached', mode: RequestMode) =>
714+
makeRequest(scope, '/foo.txt', undefined, {cache, mode});
715+
716+
expect(await requestFoo('default', 'no-cors')).toBe('this is foo');
717+
expect(await requestFoo('only-if-cached', 'same-origin')).toBe('this is foo');
718+
expect(await requestFoo('only-if-cached', 'no-cors')).toBeNull();
719+
});
711720
});
712721
});
713722
})();

packages/service-worker/worker/testing/fetch.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ export class MockRequest extends MockBody implements Request {
108108
Object.keys(headers).forEach(header => { this.headers.set(header, headers[header]); });
109109
}
110110
}
111+
if (init.cache !== undefined) {
112+
this.cache = init.cache;
113+
}
111114
if (init.mode !== undefined) {
112115
this.mode = init.mode;
113116
}
@@ -168,4 +171,4 @@ export class MockResponse extends MockBody implements Response {
168171
return new MockResponse(
169172
this._body, {status: this.status, statusText: this.statusText, headers: this.headers});
170173
}
171-
}
174+
}

0 commit comments

Comments
 (0)