v157 · javascript · service workers
Streaming download relay
The use case that motivated the change. A service worker fetches a real same-origin asset, counts bytes off the response’s ReadableStream, and reports progress to this page — building a genuine ProgressEvent to do it where the platform allows, and falling back to a hand-rolled object where it does not. The label above the bar says which one produced the numbers.
Relay a download through the worker
progress values came from:nothing yet
what the worker does
const response = await fetch(url);
const total = Number(response.headers.get("content-length")) || 0;
const reader = response.body.getReader();
let loaded = 0;
for (;;) {
const { done, value } = await reader.read();
if (done) break;
loaded += value.byteLength;
// Chrome 157+: the standard shape, constructible in a service worker
const event = new ProgressEvent("progress", {
lengthComputable: total > 0,
loaded,
total,
});
client.postMessage({
lengthComputable: event.lengthComputable,
loaded: event.loaded,
total: event.total,
});
}
lengthComputable is set from whether the server actually sent a content-length, not hard-coded to true — that is the attribute’s entire job, and a relay that always claims a known length is lying about the one field it exists to communicate.
why the event rather than a plain object
The values are identical either way; an Event cannot cross postMessage, so both paths end up sending a plain object. What changes is where the contract lives. With ProgressEvent in scope, the field names, their types, and the meaning of lengthComputable come from the XMLHttpRequest Standard. Without it, every worker invents its own shape and every page has to learn that worker’s private convention — which is exactly the situation this feature ends.
It also composes with the Chrome 136 change that made loaded and total IDL double: a relay that forwards platform-constructed values inherits that precision instead of re-deriving it. See ProgressEvent loaded/total as double.