diff options
Diffstat (limited to 'packages/meshbay-hub/src')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/file-utils.js | 37 |
1 files changed, 36 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js b/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js index 065079b..ef074dd 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/file-utils.js @@ -218,16 +218,42 @@ async function _openDownloadTarget(filename, size = 0, pickerOpts = {}, // // So the queue is explicit now, and it is the *targets* that queue, not the // rows: every download still appears the moment it is asked for. +// +// Two things keep the queue from becoming the problem it was meant to solve. +// It only ever holds openings that could actually put a dialog on screen, and +// no opening waits behind another for longer than a budget. let _targetQueue = Promise.resolve(); let _targetsInFlight = 0; +// How long an opening waits for the one ahead of it before going anyway. +// +// A queue with no bound is a way for one stuck opening to freeze every later +// download for the life of the page, since `_targetQueue` is never reset. That +// is what turned a slow first download into four rows stuck at "preparing" on +// Firefox. Generous, because a dialog legitimately waits for a person and +// cutting in front of one would be worse than waiting; finite, because the +// alternative is a download panel that never recovers. +// +// Going anyway is safe: whatever was ahead is still the only unbatched opening, +// so the one released here takes the streamed path and opens no second dialog. +const TARGET_QUEUE_BUDGET_MS = 90000; + function _openTargetInTurn(filename, size, pickerOpts, swSize) { + // Only an opening that could show a dialog has any reason to wait. Firefox + // and Safari have no `showSaveFilePicker` at all, so nothing there can race + // anything, and queueing them bought nothing while costing everything: four + // downloads that used to open their targets at the same time became four + // that waited on the slowest. + const canPick = typeof window !== 'undefined' + && typeof window.showSaveFilePicker === 'function'; + if (!canPick) return _openDownloadTarget(filename, size, pickerOpts, swSize); + // Anything that has to wait its turn is, by definition, not the first of the // batch — so it will not be the one holding the user's gesture. const batched = _targetsInFlight > 0; _targetsInFlight += 1; - const mine = _targetQueue + const mine = _waitBriefly(_targetQueue, TARGET_QUEUE_BUDGET_MS) .then(() => _openDownloadTarget(filename, size, pickerOpts, swSize, { batched })) .finally(() => { _targetsInFlight -= 1; }); @@ -237,6 +263,15 @@ function _openTargetInTurn(filename, size, pickerOpts, swSize) { return mine; } +/** Settles with `promise`, or after `ms`, whichever comes first. */ +function _waitBriefly(promise, ms) { + return new Promise((resolve) => { + const timer = setTimeout(resolve, ms); + promise.then(() => { clearTimeout(timer); resolve(); }, + () => { clearTimeout(timer); resolve(); }); + }); +} + /** The download of last resort, for browsers with no way to stream to disk. */ function _saveBlob(blob, filename) { const url = URL.createObjectURL(blob); |