aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/file-utils.js37
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);