From 0331374241a55415cbae37d386f47da428ede3db Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Fri, 3 Sep 2021 22:33:58 -0400 Subject: [PATCH] Dictionary media import improvements (#1926) * Add base64ToArrayBuffer to StringUtil * Remove unnecessary media-util.js import * Run async requirements in serial rather than parallel * Update API.getMedia handler to convert ArrayBuffer content to base64 * Rename getImageResolution to getImageDetails * Change parameter order of getImageDetails * Pre-process and store media as an ArrayBuffer * Remove MediaUtil.createBlobFromBase64Content * Fix Anki media injection --- dev/database-vm.js | 4 ++-- ext/js/background/backend.js | 16 ++++++++++++++-- ext/js/data/sandbox/string-util.js | 15 +++++++++++++++ .../dictionary-importer-media-loader.js | 19 +++++++------------ ext/js/language/dictionary-importer.js | 9 +++------ ext/js/language/dictionary-worker-handler.js | 2 +- .../dictionary-worker-media-loader.js | 16 +++++++--------- ext/js/language/dictionary-worker.js | 13 +++++++------ ext/js/media/media-loader.js | 5 +++-- ext/js/media/media-util.js | 16 ---------------- ext/popup.html | 1 + ext/search.html | 1 + ext/settings.html | 1 - ext/welcome.html | 1 - 14 files changed, 61 insertions(+), 58 deletions(-) diff --git a/dev/database-vm.js b/dev/database-vm.js index ebde5a2a..a7ad2508 100644 --- a/dev/database-vm.js +++ b/dev/database-vm.js @@ -69,9 +69,9 @@ class DatabaseVM extends VM { } class DatabaseVMDictionaryImporterMediaLoader { - async getImageResolution() { + async getImageDetails(content) { // Placeholder values - return {width: 100, height: 100}; + return {content, width: 100, height: 100}; } } diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index dea67091..8e05ee68 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -31,6 +31,7 @@ * PermissionsUtil * ProfileConditionsUtil * RequestBuilder + * StringUtil * Translator * wanakana */ @@ -621,7 +622,7 @@ class Backend { } async _onApiGetMedia({targets}) { - return await this._dictionaryDatabase.getMedia(targets); + return await this._getNormalizedDictionaryDatabaseMedia(targets); } _onApiLog({error, level, context}) { @@ -1847,7 +1848,7 @@ class Backend { detailsList.push(details); detailsMap.set(key, details); } - const mediaList = await this._dictionaryDatabase.getMedia(targets); + const mediaList = await this._getNormalizedDictionaryDatabaseMedia(targets); for (const media of mediaList) { const {dictionary, path} = media; @@ -2283,4 +2284,15 @@ class Backend { return await this._injectScript(file, tab.id, frameId); } + + async _getNormalizedDictionaryDatabaseMedia(targets) { + const results = await this._dictionaryDatabase.getMedia(targets); + for (const item of results) { + const {content} = item; + if (content instanceof ArrayBuffer) { + item.content = StringUtil.arrayBufferToBase64(content); + } + } + return results; + } } diff --git a/ext/js/data/sandbox/string-util.js b/ext/js/data/sandbox/string-util.js index 37e021c9..b523c39d 100644 --- a/ext/js/data/sandbox/string-util.js +++ b/ext/js/data/sandbox/string-util.js @@ -58,4 +58,19 @@ class StringUtil { return binary; } } + + /** + * Converts a base64 string to an ArrayBuffer. + * @param content The binary content string encoded in base64. + * @returns A new `ArrayBuffer` object corresponding to the specified content. + */ + static base64ToArrayBuffer(content) { + const binaryContent = atob(content); + const length = binaryContent.length; + const array = new Uint8Array(length); + for (let i = 0; i < length; ++i) { + array[i] = binaryContent.charCodeAt(i); + } + return array.buffer; + } } diff --git a/ext/js/language/dictionary-importer-media-loader.js b/ext/js/language/dictionary-importer-media-loader.js index bbcc5476..27ddde34 100644 --- a/ext/js/language/dictionary-importer-media-loader.js +++ b/ext/js/language/dictionary-importer-media-loader.js @@ -15,23 +15,17 @@ * along with this program. If not, see . */ -/* global - * MediaUtil - */ - /** * Class used for loading and validating media during the dictionary import process. */ class DictionaryImporterMediaLoader { /** - * Attempts to load an image using a base64 encoded content and a media type - * and returns its resolution. + * Attempts to load an image using an ArrayBuffer and a media type to return details about it. + * @param content The binary content for the image, encoded as an ArrayBuffer. * @param mediaType The media type for the image content. - * @param content The binary content for the image, encoded in base64. - * @returns A Promise which resolves with {width, height} on success, - * otherwise an error is thrown. + * @returns A Promise which resolves with {content, width, height} on success, otherwise an error is thrown. */ - getImageResolution(mediaType, content) { + getImageDetails(content, mediaType, transfer) { return new Promise((resolve, reject) => { const image = new Image(); const eventListeners = new EventListenerCollection(); @@ -42,14 +36,15 @@ class DictionaryImporterMediaLoader { }; eventListeners.addEventListener(image, 'load', () => { const {naturalWidth: width, naturalHeight: height} = image; + if (Array.isArray(transfer)) { transfer.push(content); } cleanup(); - resolve({width, height}); + resolve({content, width, height}); }, false); eventListeners.addEventListener(image, 'error', () => { cleanup(); reject(new Error('Image failed to load')); }, false); - const blob = MediaUtil.createBlobFromBase64Content(content, mediaType); + const blob = new Blob([content], {type: mediaType}); const url = URL.createObjectURL(blob); image.src = url; }); diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 98cddf76..5629e197 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -328,13 +328,10 @@ class DictionaryImporter { const media = new Map(); const context = {archive, media}; - const promises = []; for (const requirement of requirements) { - promises.push(this._resolveAsyncRequirement(context, requirement)); + await this._resolveAsyncRequirement(context, requirement); } - await Promise.all(promises); - return { media: [...media.values()] }; @@ -425,7 +422,7 @@ class DictionaryImporter { } // Load file content - const content = await file.async('base64'); + let content = await file.async('arraybuffer'); const mediaType = MediaUtil.getImageMediaTypeFromFileName(path); if (mediaType === null) { throw createError('Could not determine media type for image'); @@ -435,7 +432,7 @@ class DictionaryImporter { let width; let height; try { - ({width, height} = await this._mediaLoader.getImageResolution(mediaType, content)); + ({content, width, height} = await this._mediaLoader.getImageDetails(content, mediaType)); } catch (e) { throw createError('Could not load image'); } diff --git a/ext/js/language/dictionary-worker-handler.js b/ext/js/language/dictionary-worker-handler.js index 1d6b4aab..3ce744f9 100644 --- a/ext/js/language/dictionary-worker-handler.js +++ b/ext/js/language/dictionary-worker-handler.js @@ -44,7 +44,7 @@ class DictionaryWorkerHandler { case 'getDictionaryCounts': this._onMessageWithProgress(params, this._getDictionaryCounts.bind(this)); break; - case 'getImageResolution.response': + case 'getImageDetails.response': this._mediaLoader.handleMessage(params); break; } diff --git a/ext/js/language/dictionary-worker-media-loader.js b/ext/js/language/dictionary-worker-media-loader.js index 90ee513f..25f8de72 100644 --- a/ext/js/language/dictionary-worker-media-loader.js +++ b/ext/js/language/dictionary-worker-media-loader.js @@ -45,21 +45,19 @@ class DictionaryWorkerMediaLoader { } /** - * Attempts to load an image using a base64 encoded content and a media type - * and returns its resolution. + * Attempts to load an image using an ArrayBuffer and a media type to return details about it. + * @param content The binary content for the image, encoded as an ArrayBuffer. * @param mediaType The media type for the image content. - * @param content The binary content for the image, encoded in base64. - * @returns A Promise which resolves with {width, height} on success, - * otherwise an error is thrown. + * @returns A Promise which resolves with {content, width, height} on success, otherwise an error is thrown. */ - getImageResolution(mediaType, content) { + getImageDetails(content, mediaType) { return new Promise((resolve, reject) => { const id = generateId(16); this._requests.set(id, {resolve, reject}); self.postMessage({ - action: 'getImageResolution', - params: {id, mediaType, content} - }); + action: 'getImageDetails', + params: {id, content, mediaType} + }, [content]); }); } } diff --git a/ext/js/language/dictionary-worker.js b/ext/js/language/dictionary-worker.js index 92faa3dc..be94c397 100644 --- a/ext/js/language/dictionary-worker.js +++ b/ext/js/language/dictionary-worker.js @@ -85,8 +85,8 @@ class DictionaryWorker { case 'progress': this._onMessageProgress(params, details.onProgress); break; - case 'getImageResolution': - this._onMessageGetImageResolution(params, details.worker); + case 'getImageDetails': + this._onMessageGetImageDetails(params, details.worker); break; } } @@ -115,16 +115,17 @@ class DictionaryWorker { onProgress(...args); } - async _onMessageGetImageResolution(params, worker) { - const {id, mediaType, content} = params; + async _onMessageGetImageDetails(params, worker) { + const {id, content, mediaType} = params; + const transfer = []; let response; try { - const result = await this._dictionaryImporterMediaLoader.getImageResolution(mediaType, content); + const result = await this._dictionaryImporterMediaLoader.getImageDetails(content, mediaType, transfer); response = {id, result}; } catch (e) { response = {id, error: serializeError(e)}; } - worker.postMessage({action: 'getImageResolution.response', params: response}); + worker.postMessage({action: 'getImageDetails.response', params: response}, transfer); } _formatimportDictionaryResult(result) { diff --git a/ext/js/media/media-loader.js b/ext/js/media/media-loader.js index 7dafc2a3..8c7e356c 100644 --- a/ext/js/media/media-loader.js +++ b/ext/js/media/media-loader.js @@ -16,7 +16,7 @@ */ /* global - * MediaUtil + * StringUtil */ class MediaLoader { @@ -86,7 +86,8 @@ class MediaLoader { const token = this._token; const data = (await yomichan.api.getMedia([{path, dictionary}]))[0]; if (token === this._token && data !== null) { - const blob = MediaUtil.createBlobFromBase64Content(data.content, data.mediaType); + const buffer = StringUtil.base64ToArrayBuffer(data.content); + const blob = new Blob([buffer], {type: data.mediaType}); const url = URL.createObjectURL(blob); cachedData.data = data; cachedData.url = url; diff --git a/ext/js/media/media-util.js b/ext/js/media/media-util.js index f783038a..11172c5c 100644 --- a/ext/js/media/media-util.js +++ b/ext/js/media/media-util.js @@ -129,20 +129,4 @@ class MediaUtil { return null; } } - - /** - * Creates a new `Blob` object from a base64 string of content. - * @param content The binary content string encoded in base64. - * @param mediaType The type of the media. - * @returns A new `Blob` object corresponding to the specified content. - */ - static createBlobFromBase64Content(content, mediaType) { - const binaryContent = atob(content); - const length = binaryContent.length; - const array = new Uint8Array(length); - for (let i = 0; i < length; ++i) { - array[i] = binaryContent.charCodeAt(i); - } - return new Blob([array.buffer], {type: mediaType}); - } } diff --git a/ext/popup.html b/ext/popup.html index 1f336943..98ee7c45 100644 --- a/ext/popup.html +++ b/ext/popup.html @@ -100,6 +100,7 @@ + diff --git a/ext/search.html b/ext/search.html index f32b5412..de0cab58 100644 --- a/ext/search.html +++ b/ext/search.html @@ -86,6 +86,7 @@ + diff --git a/ext/settings.html b/ext/settings.html index 0e1fafcc..12ea7629 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -3488,7 +3488,6 @@ - diff --git a/ext/welcome.html b/ext/welcome.html index cbb4e4ad..6fda4d44 100644 --- a/ext/welcome.html +++ b/ext/welcome.html @@ -407,7 +407,6 @@ -