aboutsummaryrefslogtreecommitdiff
path: root/ext/js/background
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2022-08-20 11:17:24 -0400
committerGitHub <noreply@github.com>2022-08-20 11:17:24 -0400
commit310303ca1a123a77f9bd116af4dc64ad9c3256c5 (patch)
treeaf8bad0ec544625970a5f2a4613fff27773b162c /ext/js/background
parent02483a45b1b7fb0654b3f37571b92400b76734a5 (diff)
Audio download timeout (#2187)
* Add support for an idle timeout when downloading audio * Update eslint rules * Pass idleTimeout to the downloader from DisplayAnki * Add anki.downloadTimeout setting * Update tests * Assign _audioDownloadIdleTimeout using settings * Show info about cancelled downloads * Handle Firefox bug * Improve audio errors * Refactor * Move functions to RequestBuilder
Diffstat (limited to 'ext/js/background')
-rw-r--r--ext/js/background/backend.js32
-rw-r--r--ext/js/background/request-builder.js90
2 files changed, 113 insertions, 9 deletions
diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js
index 75ff7bee..f3c76311 100644
--- a/ext/js/background/backend.js
+++ b/ext/js/background/backend.js
@@ -1809,7 +1809,7 @@ class Backend {
return null;
}
- const {sources, preferredAudioIndex} = details;
+ const {sources, preferredAudioIndex, idleTimeout} = details;
let data;
let contentType;
try {
@@ -1817,7 +1817,8 @@ class Backend {
sources,
preferredAudioIndex,
term,
- reading
+ reading,
+ idleTimeout
));
} catch (e) {
const error = this._getAudioDownloadError(e);
@@ -1918,6 +1919,9 @@ class Backend {
const {errors} = error.data;
if (Array.isArray(errors)) {
for (const error2 of errors) {
+ if (error2.name === 'AbortError') {
+ return this._createAudioDownloadError('Audio download was cancelled due to an idle timeout', 'audio-download-idle-timeout', errors);
+ }
if (!isObject(error2.data)) { continue; }
const {details} = error2.data;
if (!isObject(details)) { continue; }
@@ -1925,12 +1929,7 @@ class Backend {
// This is potentially an error due to the extension not having enough URL privileges.
// The message logged to the console looks like this:
// Access to fetch at '<URL>' from origin 'chrome-extension://<ID>' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
- const result = new Error('Audio download failed due to possible extension permissions error');
- result.data = {
- errors,
- referenceUrl: '/issues.html#audio-download-failed'
- };
- return result;
+ return this._createAudioDownloadError('Audio download failed due to possible extension permissions error', 'audio-download-failed', errors);
}
}
}
@@ -1938,6 +1937,23 @@ class Backend {
return null;
}
+ _createAudioDownloadError(message, issueId, errors) {
+ const error = new Error(message);
+ const hasErrors = Array.isArray(errors);
+ const hasIssueId = (typeof issueId === 'string');
+ if (hasErrors || hasIssueId) {
+ error.data = {};
+ if (hasErrors) {
+ // Errors need to be serialized since they are passed to other frames
+ error.data.errors = errors.map((e) => serializeError(e));
+ }
+ if (hasIssueId) {
+ error.data.referenceUrl = `/issues.html#${issueId}`;
+ }
+ }
+ return error;
+ }
+
_generateAnkiNoteMediaFileName(prefix, extension, timestamp, definitionDetails) {
let fileName = prefix;
diff --git a/ext/js/background/request-builder.js b/ext/js/background/request-builder.js
index ad1536f1..2cdd6f0e 100644
--- a/ext/js/background/request-builder.js
+++ b/ext/js/background/request-builder.js
@@ -42,6 +42,58 @@ class RequestBuilder {
return await this._fetchInternal(url, init, headerModifications);
}
+ static async readFetchResponseArrayBuffer(response, onProgress) {
+ let reader;
+ try {
+ if (typeof onProgress === 'function') {
+ reader = response.body.getReader();
+ }
+ } catch (e) {
+ // Not supported
+ }
+
+ if (typeof reader === 'undefined') {
+ const result = await response.arrayBuffer();
+ if (typeof onProgress === 'function') {
+ onProgress(true);
+ }
+ return result;
+ }
+
+ const contentLengthString = response.headers.get('Content-Length');
+ const contentLength = contentLengthString !== null ? Number.parseInt(contentLengthString, 10) : null;
+ let target = Number.isFinite(contentLength) ? new Uint8Array(contentLength) : null;
+ let targetPosition = 0;
+ let totalLength = 0;
+ const targets = [];
+
+ while (true) {
+ const {done, value} = await reader.read();
+ if (done) { break; }
+ onProgress(false);
+ if (target === null) {
+ targets.push({array: value, length: value.length});
+ } else if (targetPosition + value.length > target.length) {
+ targets.push({array: target, length: targetPosition});
+ target = null;
+ } else {
+ target.set(value, targetPosition);
+ targetPosition += value.length;
+ }
+ totalLength += value.length;
+ }
+
+ if (target === null) {
+ target = this._joinUint8Arrays(targets, totalLength);
+ } else if (totalLength < target.length) {
+ target = target.slice(0, totalLength);
+ }
+
+ onProgress(true);
+
+ return target;
+ }
+
// Private
async _fetchInternal(url, init, headerModifications) {
@@ -92,7 +144,10 @@ class RequestBuilder {
}, 100);
}
const details = await errorDetailsPromise;
- e.data = {details};
+ if (details !== null) {
+ const data = {details};
+ this._assignErrorData(e, data);
+ }
throw e;
} finally {
this._removeWebRequestEventListeners(eventListeners);
@@ -295,4 +350,37 @@ class RequestBuilder {
}
return result;
}
+
+ _assignErrorData(error, data) {
+ try {
+ error.data = data;
+ } catch (e) {
+ // On Firefox, assigning DOMException.data can fail in certain contexts.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1776555
+ try {
+ Object.defineProperty(error, 'data', {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: data
+ });
+ } catch (e2) {
+ // NOP
+ }
+ }
+ }
+
+ static _joinUint8Arrays(items, totalLength) {
+ if (items.length === 1) {
+ const {array, length} = items[0];
+ if (array.length === length) { return array; }
+ }
+ const result = new Uint8Array(totalLength);
+ let position = 0;
+ for (const {array, length} of items) {
+ result.set(array, position);
+ position += length;
+ }
+ return result;
+ }
}