So the 504 gateway error is synthetic, being generated by the ServiceWorker for a fetch that throws:
```js
async safeFetch(req) {
try {
return await this.scope.fetch(req);
} catch (err) {
this.debugger.log(err, `Driver.fetch(${ req.url })`);
return this.adapter.newResponse(null, {
status: 504,
statusText: 'Gateway Timeout'
});
}
}
```
I had some trouble setting breakpoints/logpoints via the debugger via the prettified source (although it seemed to figure out how to map them back if I reloaded? but they just didn't work) so I hooked the synthetic response generation via:
```js
savedResponse = Response;
Response = function(...args) {
console.trace();
console.log("resp", ...args);
return new savedResponse(...args);}
```
and then I set a breakpoint in that code I'd added by clicking on the "debugger eval code:2.9" in the console to get the source view and set a breakpoint on the `console.trace`.
The request that is throwing looks like:
```json
bodyUsed: false
cache: "reload"
credentials: "same-origin"
destination: "font"
headers: Headers
integrity: ""
method: "GET"
mode: "cors"
redirect: "follow"
referrer: "https://fonts.googleapis.com/"
referrerPolicy: "strict-origin-when-cross-origin"
signal: AbortSignal
url: "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBBc4.woff2"
```
Manually calling [...req.headers.entries()] gets us:
```
Array(3) [ (2) […], (2) […], (2) […] ]
0: Array [ "accept", "application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8" ]
1: Array [ "accept-language", "en-US,en;q=0.5" ]
2: Array [ "user-agent", "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36" ]
length: 3
```
Note that there is logic that intentionally propagates the headers from the fetch event that was received:
```js
newRequestWithMetadata(url, options) {
return this.adapter.newRequest(url, {
headers: options.headers
});
}
```
The browser will help explain what's going on if we run the equivalent fetch from an uncontrolled voodoodreams page (use ctrl-shift-refresh to bypass serviceworker interception). Run:
```js
frh = new Headers()
frh.append("user-agent", "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36")
fr = new Request("https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBBc4.woff2", { mode: "cors", headers: frh})
z = fetch(fr)
```
We get errors:
```
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBBc4.woff2. (Reason: header ‘user-agent’ is not allowed according to header ‘Access-Control-Allow-Headers’ from CORS preflight response).
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBBc4.woff2. (Reason: CORS request did not succeed). Status code: (null).
Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource.
```
So the problem here, as I understand it is a somewhat emergent behavior from:
- We are surfacing the synthetic user-agent header to the intercepted channel.
- That header is then propagated through a "cors" request.
- The server does not allow-list that header.
- The fetch throws.
- The ServiceWorker turns it into a 504 response.
Presumably we should avoid propagating the synthetic header to the intercepted channel. Maybe the visitor could be made smart enough to understand that the user-agent override is synthetic and should not match the `eFilterSkipDefault` filter at (https://searchfox.org/mozilla-central/rev/f60bb10a5fe6936f9e9f9e8a90d52c18a0ffd818/netwerk/protocol/http/HttpBaseChannel.cpp#2041-2042)?:
```cpp
return mRequestHead.VisitHeaders(visitor,
nsHttpHeaderArray::eFilterSkipDefault);
```
Bug 1885308 Comment 13 Edit History
Note: The actual edited comment in the bug view page will always show the original commenter’s name and original timestamp.
So the 504 gateway error is synthetic, being generated by the ServiceWorker for a fetch that throws:
```js
async safeFetch(req) {
try {
return await this.scope.fetch(req);
} catch (err) {
this.debugger.log(err, `Driver.fetch(${ req.url })`);
return this.adapter.newResponse(null, {
status: 504,
statusText: 'Gateway Timeout'
});
}
}
```
I had some trouble setting breakpoints/logpoints via the debugger via the prettified source (although it seemed to figure out how to map them back if I reloaded? but they just didn't work) so I hooked the synthetic response generation via:
```js
savedResponse = Response;
Response = function(...args) {
console.trace();
console.log("resp", ...args);
return new savedResponse(...args);}
```
and then I set a breakpoint in that code I'd added by clicking on the "debugger eval code:2.9" in the console to get the source view and set a breakpoint on the `console.trace`.
The request that is throwing looks like:
```json
bodyUsed: false
cache: "reload"
credentials: "same-origin"
destination: "font"
headers: Headers
integrity: ""
method: "GET"
mode: "cors"
redirect: "follow"
referrer: "https://fonts.googleapis.com/"
referrerPolicy: "strict-origin-when-cross-origin"
signal: AbortSignal
url: "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBBc4.woff2"
```
Manually calling [...req.headers.entries()] gets us:
```
Array(3) [ (2) […], (2) […], (2) […] ]
0: Array [ "accept", "application/font-woff2;q=1.0,application/font-woff;q=0.9,*/*;q=0.8" ]
1: Array [ "accept-language", "en-US,en;q=0.5" ]
2: Array [ "user-agent", "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36" ]
length: 3
```
Note that there is logic that intentionally propagates the headers from the fetch event that was received:
```js
newRequestWithMetadata(url, options) {
return this.adapter.newRequest(url, {
headers: options.headers
});
}
```
The browser will help explain what's going on if we run the equivalent fetch from an uncontrolled voodoodreams page (use ctrl-shift-refresh to bypass serviceworker interception). Run:
```js
frh = new Headers()
frh.append("user-agent", "Mozilla/5.0 (Linux; Android 11; SAMSUNG SM-G973U) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/14.2 Chrome/87.0.4280.141 Mobile Safari/537.36")
fr = new Request("https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBBc4.woff2", { mode: "cors", headers: frh})
z = fetch(fr)
```
We get errors:
> Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBBc4.woff2. (Reason: header ‘user-agent’ is not allowed according to header ‘Access-Control-Allow-Headers’ from CORS preflight response).
>
> Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmSU5fBBc4.woff2. (Reason: CORS request did not succeed). Status code: (null).
>
> Uncaught (in promise) TypeError: NetworkError when attempting to fetch resource.
So the problem here, as I understand it is a somewhat emergent behavior from:
- We are surfacing the synthetic user-agent header to the intercepted channel.
- That header is then propagated through a "cors" request.
- The server does not allow-list that header.
- The fetch throws.
- The ServiceWorker turns it into a 504 response.
Presumably we should avoid propagating the synthetic header to the intercepted channel. Maybe the visitor could be made smart enough to understand that the user-agent override is synthetic and should not match the `eFilterSkipDefault` filter at (https://searchfox.org/mozilla-central/rev/f60bb10a5fe6936f9e9f9e8a90d52c18a0ffd818/netwerk/protocol/http/HttpBaseChannel.cpp#2041-2042)?:
```cpp
return mRequestHead.VisitHeaders(visitor,
nsHttpHeaderArray::eFilterSkipDefault);
```