fix(progress): clamp loaded to total for computable upload/download events (#7458)

* fix(progress): clamp loaded value to total in progress reducer

* test(progress): cover out-of-order events in reducer clamp case

* fix(progress): keep bytesNotified monotonic for out-of-order events

---------

Co-authored-by: Jay <jasonsaayman@gmail.com>
This commit is contained in:
Eve 2026-04-09 11:40:50 -04:00 committed by GitHub
parent 0912bde937
commit 914bc2605a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 32 additions and 5 deletions

View File

@ -7,13 +7,13 @@ export const progressEventReducer = (listener, isDownloadStream, freq = 3) => {
const _speedometer = speedometer(50, 250);
return throttle((e) => {
const loaded = e.loaded;
const rawLoaded = e.loaded;
const total = e.lengthComputable ? e.total : undefined;
const progressBytes = loaded - bytesNotified;
const loaded = total != null ? Math.min(rawLoaded, total) : rawLoaded;
const progressBytes = Math.max(0, loaded - bytesNotified);
const rate = _speedometer(progressBytes);
const inRange = loaded <= total;
bytesNotified = loaded;
bytesNotified = Math.max(bytesNotified, loaded);
const data = {
loaded,
@ -21,7 +21,7 @@ export const progressEventReducer = (listener, isDownloadStream, freq = 3) => {
progress: total ? loaded / total : undefined,
bytes: progressBytes,
rate: rate ? rate : undefined,
estimated: rate && total && inRange ? (total - loaded) / rate : undefined,
estimated: rate && total ? (total - loaded) / rate : undefined,
event: e,
lengthComputable: total != null,
[isDownloadStream ? 'download' : 'upload']: true,

View File

@ -0,0 +1,27 @@
import assert from 'assert';
import { progressEventReducer } from '../../../lib/helpers/progressEventReducer.js';
describe('helpers::progressEventReducer', () => {
it('should clamp loaded/progress and avoid negative bytes for out-of-order events', () => {
const events = [];
const [onProgress, flush] = progressEventReducer((data) => {
events.push(data);
}, false, Number.POSITIVE_INFINITY);
onProgress({ lengthComputable: true, loaded: 80, total: 100 });
onProgress({ lengthComputable: true, loaded: 60, total: 100 });
onProgress({ lengthComputable: true, loaded: 180, total: 100 });
flush();
assert.strictEqual(events.length, 3);
assert.strictEqual(events[0].bytes, 80);
assert.strictEqual(events[1].bytes, 0);
const last = events[events.length - 1];
assert.strictEqual(last.loaded, 100);
assert.strictEqual(last.total, 100);
assert.strictEqual(last.progress, 1);
assert.strictEqual(last.upload, true);
assert.strictEqual(last.bytes, 20);
});
});