diff --git a/dev/database-vm.js b/dev/database-vm.js
index 07d9bd5a..ebde5a2a 100644
--- a/dev/database-vm.js
+++ b/dev/database-vm.js
@@ -30,63 +30,6 @@ const chrome = {
}
};
-class Image {
- constructor() {
- this._src = '';
- this._loadCallbacks = [];
- }
-
- get src() {
- return this._src;
- }
-
- set src(value) {
- this._src = value;
- this._delayTriggerLoad();
- }
-
- get naturalWidth() {
- return 100;
- }
-
- get naturalHeight() {
- return 100;
- }
-
- addEventListener(eventName, callback) {
- if (eventName === 'load') {
- this._loadCallbacks.push(callback);
- }
- }
-
- removeEventListener(eventName, callback) {
- if (eventName === 'load') {
- const index = this._loadCallbacks.indexOf(callback);
- if (index >= 0) {
- this._loadCallbacks.splice(index, 1);
- }
- }
- }
-
- removeAttribute() {
- // NOP
- }
-
- async _delayTriggerLoad() {
- await Promise.resolve();
- for (const callback of this._loadCallbacks) {
- callback();
- }
- }
-}
-
-class Blob {
- constructor(array, options) {
- this._array = array;
- this._options = options;
- }
-}
-
async function fetch(url2) {
const extDir = path.join(__dirname, '..', 'ext');
let filePath;
@@ -114,8 +57,6 @@ class DatabaseVM extends VM {
constructor(globals={}) {
super(Object.assign({
chrome,
- Image,
- Blob,
fetch,
indexedDB: global.indexedDB,
IDBKeyRange: global.IDBKeyRange,
@@ -127,6 +68,14 @@ class DatabaseVM extends VM {
}
}
+class DatabaseVMDictionaryImporterMediaLoader {
+ async getImageResolution() {
+ // Placeholder values
+ return {width: 100, height: 100};
+ }
+}
+
module.exports = {
- DatabaseVM
+ DatabaseVM,
+ DatabaseVMDictionaryImporterMediaLoader
};
diff --git a/dev/translator-vm.js b/dev/translator-vm.js
index 3c3886ba..d616afc5 100644
--- a/dev/translator-vm.js
+++ b/dev/translator-vm.js
@@ -18,7 +18,7 @@
const fs = require('fs');
const path = require('path');
const assert = require('assert');
-const {DatabaseVM} = require('./database-vm');
+const {DatabaseVM, DatabaseVMDictionaryImporterMediaLoader} = require('./database-vm');
const {createDictionaryArchive} = require('./util');
function clone(value) {
@@ -75,7 +75,8 @@ class TranslatorVM extends DatabaseVM {
const testDictionaryContent = await testDictionary.generateAsync({type: 'arraybuffer'});
// Setup database
- const dictionaryImporter = new DictionaryImporter();
+ const dictionaryImporterMediaLoader = new DatabaseVMDictionaryImporterMediaLoader();
+ const dictionaryImporter = new DictionaryImporter(dictionaryImporterMediaLoader);
const dictionaryDatabase = new DictionaryDatabase();
await dictionaryDatabase.prepare();
diff --git a/dev/vm.js b/dev/vm.js
index 363cc115..9db87cdf 100644
--- a/dev/vm.js
+++ b/dev/vm.js
@@ -116,7 +116,7 @@ function deepStrictEqual(actual, expected) {
}
-function createURLClass(urlMap) {
+function createURLClass() {
const BaseURL = URL;
const result = function URL(url) {
const u = new BaseURL(url);
@@ -133,23 +133,13 @@ function createURLClass(urlMap) {
this.searchParams = u.searchParams;
this.username = u.username;
};
- result.createObjectURL = (object) => {
- const id = crypto.randomBytes(16).toString('hex');
- const url = `blob:${id}`;
- urlMap.set(url, object);
- return url;
- };
- result.revokeObjectURL = (url) => {
- urlMap.delete(url);
- };
return result;
}
class VM {
constructor(context={}) {
- this._urlMap = new Map();
- context.URL = createURLClass(this._urlMap);
+ context.URL = createURLClass();
context.crypto = {
getRandomValues: (array) => {
const buffer = crypto.randomBytes(array.byteLength);
@@ -205,10 +195,6 @@ class VM {
return single ? results[0] : results;
}
-
- getUrlObject(url) {
- return this._urlMap.get(url);
- }
}
diff --git a/ext/js/language/dictionary-importer-media-loader.js b/ext/js/language/dictionary-importer-media-loader.js
new file mode 100644
index 00000000..bbcc5476
--- /dev/null
+++ b/ext/js/language/dictionary-importer-media-loader.js
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2021 Yomichan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * 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.
+ * @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.
+ */
+ getImageResolution(mediaType, content) {
+ return new Promise((resolve, reject) => {
+ const image = new Image();
+ const eventListeners = new EventListenerCollection();
+ const cleanup = () => {
+ image.removeAttribute('src');
+ URL.revokeObjectURL(url);
+ eventListeners.removeAllEventListeners();
+ };
+ eventListeners.addEventListener(image, 'load', () => {
+ const {naturalWidth: width, naturalHeight: height} = image;
+ cleanup();
+ resolve({width, height});
+ }, false);
+ eventListeners.addEventListener(image, 'error', () => {
+ cleanup();
+ reject(new Error('Image failed to load'));
+ }, false);
+ const blob = MediaUtil.createBlobFromBase64Content(content, 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 27b3c44e..b931c929 100644
--- a/ext/js/language/dictionary-importer.js
+++ b/ext/js/language/dictionary-importer.js
@@ -22,11 +22,12 @@
*/
class DictionaryImporter {
- constructor() {
+ constructor(mediaLoader) {
+ this._mediaLoader = mediaLoader;
this._schemas = new Map();
}
- async importDictionary(dictionaryDatabase, archiveSource, details, onProgress) {
+ async importDictionary(dictionaryDatabase, archiveContent, details, onProgress) {
if (!dictionaryDatabase) {
throw new Error('Invalid database');
}
@@ -37,7 +38,7 @@ class DictionaryImporter {
const hasOnProgress = (typeof onProgress === 'function');
// Read archive
- const archive = await JSZip.loadAsync(archiveSource);
+ const archive = await JSZip.loadAsync(archiveContent);
// Read and validate index
const indexFileName = 'index.json';
@@ -469,7 +470,7 @@ class DictionaryImporter {
let width;
let height;
try {
- ({width, height} = await this._getImageResolution(mediaType, content));
+ ({width, height} = await this._mediaLoader.getImageResolution(mediaType, content));
} catch (e) {
throw createError('Could not load image');
}
@@ -502,36 +503,4 @@ class DictionaryImporter {
}
return await response.json();
}
-
- /**
- * Attempts to load an image using a base64 encoded content and a media type
- * and returns its resolution.
- * @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.
- */
- _getImageResolution(mediaType, content) {
- return new Promise((resolve, reject) => {
- const image = new Image();
- const eventListeners = new EventListenerCollection();
- const cleanup = () => {
- image.removeAttribute('src');
- URL.revokeObjectURL(url);
- eventListeners.removeAllEventListeners();
- };
- eventListeners.addEventListener(image, 'load', () => {
- const {naturalWidth: width, naturalHeight: height} = image;
- cleanup();
- resolve({width, height});
- }, false);
- eventListeners.addEventListener(image, 'error', () => {
- cleanup();
- reject(new Error('Image failed to load'));
- }, false);
- const blob = MediaUtil.createBlobFromBase64Content(content, mediaType);
- const url = URL.createObjectURL(blob);
- image.src = url;
- });
- }
}
diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js
index db9a1a50..128e18cb 100644
--- a/ext/js/pages/settings/dictionary-import-controller.js
+++ b/ext/js/pages/settings/dictionary-import-controller.js
@@ -19,6 +19,7 @@
* DictionaryController
* DictionaryDatabase
* DictionaryImporter
+ * DictionaryImporterMediaLoader
*/
class DictionaryImportController {
@@ -184,7 +185,8 @@ class DictionaryImportController {
async _importDictionary(file, importDetails, onProgress) {
const dictionaryDatabase = await this._getPreparedDictionaryDatabase();
try {
- const dictionaryImporter = new DictionaryImporter();
+ const dictionaryImporterMediaLoader = new DictionaryImporterMediaLoader();
+ const dictionaryImporter = new DictionaryImporter(dictionaryImporterMediaLoader);
const archiveContent = await this._readFile(file);
const {result, errors} = await dictionaryImporter.importDictionary(dictionaryDatabase, archiveContent, importDetails, onProgress);
yomichan.api.triggerDatabaseUpdated('dictionary', 'import');
diff --git a/ext/settings.html b/ext/settings.html
index 9d2d9791..cd2376d6 100644
--- a/ext/settings.html
+++ b/ext/settings.html
@@ -3451,6 +3451,7 @@
+
diff --git a/ext/welcome.html b/ext/welcome.html
index 85022e2c..5c9f4469 100644
--- a/ext/welcome.html
+++ b/ext/welcome.html
@@ -409,6 +409,7 @@
+
diff --git a/test/test-database.js b/test/test-database.js
index 19ac49e2..ac7e825b 100644
--- a/test/test-database.js
+++ b/test/test-database.js
@@ -18,7 +18,7 @@
const path = require('path');
const assert = require('assert');
const {createDictionaryArchive, testMain} = require('../dev/util');
-const {DatabaseVM} = require('../dev/database-vm');
+const {DatabaseVM, DatabaseVMDictionaryImporterMediaLoader} = require('../dev/database-vm');
const vm = new DatabaseVM();
@@ -41,6 +41,12 @@ function createTestDictionaryArchive(dictionary, dictionaryName) {
}
+function createDictionaryImporter() {
+ const dictionaryImporterMediaLoader = new DatabaseVMDictionaryImporterMediaLoader();
+ return new DictionaryImporter(dictionaryImporterMediaLoader);
+}
+
+
function countDictionaryDatabaseEntriesWithTerm(dictionaryDatabaseEntries, term) {
return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.term === term ? 1 : 0)), 0);
}
@@ -125,7 +131,7 @@ async function testDatabase1() {
];
// Setup database
- const dictionaryImporter = new DictionaryImporter();
+ const dictionaryImporter = createDictionaryImporter();
const dictionaryDatabase = new DictionaryDatabase();
await dictionaryDatabase.prepare();
@@ -775,7 +781,7 @@ async function testDatabase2() {
]);
// Setup database
- const dictionaryImporter = new DictionaryImporter();
+ const dictionaryImporter = createDictionaryImporter();
const dictionaryDatabase = new DictionaryDatabase();
// Error: not prepared
@@ -817,7 +823,7 @@ async function testDatabase3() {
];
// Setup database
- const dictionaryImporter = new DictionaryImporter();
+ const dictionaryImporter = createDictionaryImporter();
const dictionaryDatabase = new DictionaryDatabase();
await dictionaryDatabase.prepare();