From ceb12ac41551aca11bc195e5fad9984a28a5e291 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 11 Apr 2020 23:20:36 -0400 Subject: Add support for filtering frequency metadata based on readings --- test/test-database.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) (limited to 'test/test-database.js') diff --git a/test/test-database.js b/test/test-database.js index d27f92e1..8b7a163a 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -235,8 +235,8 @@ async function testDatabase1() { true ); vm.assert.deepStrictEqual(counts, { - counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 6, tagMeta: 14}], - total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 6, tagMeta: 14} + counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 12, tagMeta: 14}], + total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 12, tagMeta: 14} }); // Test find* functions @@ -626,9 +626,9 @@ async function testFindTermMetaBulk1(database, titles) { } ], expectedResults: { - total: 1, + total: 3, modes: [ - ['freq', 1] + ['freq', 3] ] } }, @@ -639,9 +639,9 @@ async function testFindTermMetaBulk1(database, titles) { } ], expectedResults: { - total: 1, + total: 3, modes: [ - ['freq', 1] + ['freq', 3] ] } }, @@ -652,9 +652,9 @@ async function testFindTermMetaBulk1(database, titles) { } ], expectedResults: { - total: 3, + total: 5, modes: [ - ['freq', 1], + ['freq', 3], ['pitch', 2] ] } -- cgit v1.2.3 From 5f49f0fed25b421b0142b306a968f2cd435804e1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 11 Apr 2020 14:24:03 -0400 Subject: Add tests --- test/data/dictionaries/valid-dictionary1/image.gif | Bin 0 -> 45 bytes .../valid-dictionary1/term_bank_1.json | 3 +- test/test-database.js | 52 ++++++++++++++++++++- test/yomichan-test.js | 15 ++++-- 4 files changed, 62 insertions(+), 8 deletions(-) create mode 100644 test/data/dictionaries/valid-dictionary1/image.gif (limited to 'test/test-database.js') diff --git a/test/data/dictionaries/valid-dictionary1/image.gif b/test/data/dictionaries/valid-dictionary1/image.gif new file mode 100644 index 00000000..f089d07c Binary files /dev/null and b/test/data/dictionaries/valid-dictionary1/image.gif differ diff --git a/test/data/dictionaries/valid-dictionary1/term_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_bank_1.json index 755d9f6a..a62ad117 100644 --- a/test/data/dictionaries/valid-dictionary1/term_bank_1.json +++ b/test/data/dictionaries/valid-dictionary1/term_bank_1.json @@ -30,5 +30,6 @@ ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 29, ["definition1a (打ち込む, ぶちこむ)", "definition1b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"], ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 30, ["definition2a (打ち込む, ぶちこむ)", "definition2b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"], ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 31, ["definition3a (打ち込む, ぶちこむ)", "definition3b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"], - ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 32, ["definition4a (打ち込む, ぶちこむ)", "definition4b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"] + ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 32, ["definition4a (打ち込む, ぶちこむ)", "definition4b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"], + ["画像", "がぞう", "tag1 tag2", "", 33, ["definition1a (画像, がぞう)", {"type": "image", "path": "image.gif", "width": 350, "height": 350, "description": "An image", "pixelated": true}], 7, "tag3 tag4 tag5"] ] \ No newline at end of file diff --git a/test/test-database.js b/test/test-database.js index 8b7a163a..e9ec3f0b 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -92,9 +92,56 @@ class XMLHttpRequest { } } +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); + } + } + } + + async _delayTriggerLoad() { + await Promise.resolve(); + for (const callback of this._loadCallbacks) { + callback(); + } + } +} + const vm = new VM({ chrome, + Image, XMLHttpRequest, indexedDB: global.indexedDB, IDBKeyRange: global.IDBKeyRange, @@ -106,6 +153,7 @@ vm.execute([ 'bg/js/json-schema.js', 'bg/js/dictionary.js', 'mixed/js/core.js', + 'bg/js/media-utility.js', 'bg/js/request.js', 'bg/js/dictionary-importer.js', 'bg/js/database.js' @@ -235,8 +283,8 @@ async function testDatabase1() { true ); vm.assert.deepStrictEqual(counts, { - counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 12, tagMeta: 14}], - total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 12, tagMeta: 14} + counts: [{kanji: 2, kanjiMeta: 2, terms: 33, termMeta: 12, tagMeta: 14}], + total: {kanji: 2, kanjiMeta: 2, terms: 33, termMeta: 12, tagMeta: 14} }); // Test find* functions diff --git a/test/yomichan-test.js b/test/yomichan-test.js index 3351ecdf..0b340abb 100644 --- a/test/yomichan-test.js +++ b/test/yomichan-test.js @@ -38,12 +38,17 @@ function createTestDictionaryArchive(dictionary, dictionaryName) { const archive = new (getJSZip())(); for (const fileName of fileNames) { - const source = fs.readFileSync(path.join(dictionaryDirectory, fileName), {encoding: 'utf8'}); - const json = JSON.parse(source); - if (fileName === 'index.json' && typeof dictionaryName === 'string') { - json.title = dictionaryName; + if (/\.json$/.test(fileName)) { + const source = fs.readFileSync(path.join(dictionaryDirectory, fileName), {encoding: 'utf8'}); + const json = JSON.parse(source); + if (fileName === 'index.json' && typeof dictionaryName === 'string') { + json.title = dictionaryName; + } + archive.file(fileName, JSON.stringify(json, null, 0)); + } else { + const source = fs.readFileSync(path.join(dictionaryDirectory, fileName), {encoding: null}); + archive.file(fileName, source); } - archive.file(fileName, JSON.stringify(json, null, 0)); } return archive; -- cgit v1.2.3 From 5b96559df819f496b39acb75c679f6b3d8c8e65d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 26 Apr 2020 16:55:25 -0400 Subject: Error logging refactoring (#454) * Create new logging methods on yomichan object * Use new yomichan.logError instead of global logError * Remove old logError * Handle unhandledrejection events * Add addEventListener stub * Update log function * Update error conversion to support more types * Add log event * Add API log function * Log errors to the backend * Make error/warning logs update the badge * Clear log error indicator on extension button click * Log correct URL on the background page * Fix incorrect error conversion * Remove unhandledrejection handling Firefox doesn't support it properly. * Remove unused argument type from log function * Improve function name * Change console.warn to yomichan.logWarning * Move log forwarding initialization into main scripts --- .eslintrc.json | 1 - ext/bg/js/backend.js | 50 ++++++++++- ext/bg/js/context-main.js | 5 ++ ext/bg/js/database.js | 2 +- ext/bg/js/mecab.js | 2 +- ext/bg/js/search-main.js | 2 + ext/bg/js/search-query-parser.js | 2 +- ext/bg/js/search.js | 2 +- ext/bg/js/settings/backup.js | 2 +- ext/bg/js/settings/dictionaries.js | 2 +- ext/bg/js/settings/main.js | 2 + ext/bg/js/settings/popup-preview-frame-main.js | 2 + ext/fg/js/content-script-main.js | 2 + ext/fg/js/float-main.js | 2 + ext/fg/js/float.js | 2 +- ext/fg/js/frontend-api-sender.js | 8 +- ext/fg/js/popup-proxy.js | 2 +- ext/mixed/js/api.js | 22 +++++ ext/mixed/js/core.js | 112 +++++++++++++++++++------ ext/mixed/js/text-scanner.js | 2 +- test/test-database.js | 5 +- 21 files changed, 186 insertions(+), 45 deletions(-) (limited to 'test/test-database.js') diff --git a/.eslintrc.json b/.eslintrc.json index 8882cb42..78fec27c 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -80,7 +80,6 @@ "yomichan": "readonly", "errorToJson": "readonly", "jsonToError": "readonly", - "logError": "readonly", "isObject": "readonly", "hasOwn": "readonly", "toIterable": "readonly", diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 693a9ad6..3c47b14e 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -78,6 +78,7 @@ class Backend { this._isPrepared = false; this._prepareError = false; this._badgePrepareDelayTimer = null; + this._logErrorLevel = null; this._messageHandlers = new Map([ ['yomichanCoreReady', {handler: this._onApiYomichanCoreReady.bind(this), async: false}], @@ -112,7 +113,9 @@ class Backend { ['getDictionaryInfo', {handler: this._onApiGetDictionaryInfo.bind(this), async: true}], ['getDictionaryCounts', {handler: this._onApiGetDictionaryCounts.bind(this), async: true}], ['purgeDatabase', {handler: this._onApiPurgeDatabase.bind(this), async: true}], - ['getMedia', {handler: this._onApiGetMedia.bind(this), async: true}] + ['getMedia', {handler: this._onApiGetMedia.bind(this), async: true}], + ['log', {handler: this._onApiLog.bind(this), async: false}], + ['logIndicatorClear', {handler: this._onApiLogIndicatorClear.bind(this), async: false}] ]); this._commandHandlers = new Map([ @@ -164,7 +167,7 @@ class Backend { this._isPrepared = true; } catch (e) { this._prepareError = true; - logError(e); + yomichan.logError(e); throw e; } finally { if (this._badgePrepareDelayTimer !== null) { @@ -260,7 +263,7 @@ class Backend { this.options = JsonSchema.getValidValueOrDefault(this.optionsSchema, utilIsolate(options)); } catch (e) { // This shouldn't happen, but catch errors just in case of bugs - logError(e); + yomichan.logError(e); } } @@ -767,8 +770,34 @@ class Backend { return await this.database.getMedia(targets); } + _onApiLog({error, level, context}) { + yomichan.log(jsonToError(error), level, context); + + const levelValue = this._getErrorLevelValue(level); + if (levelValue <= this._getErrorLevelValue(this._logErrorLevel)) { return; } + + this._logErrorLevel = level; + this._updateBadge(); + } + + _onApiLogIndicatorClear() { + if (this._logErrorLevel === null) { return; } + this._logErrorLevel = null; + this._updateBadge(); + } + // Command handlers + _getErrorLevelValue(errorLevel) { + switch (errorLevel) { + case 'info': return 0; + case 'debug': return 0; + case 'warn': return 1; + case 'error': return 2; + default: return 0; + } + } + async _onCommandSearch(params) { const {mode='existingOrNewTab', query} = params || {}; @@ -890,7 +919,20 @@ class Backend { let color = null; let status = null; - if (!this._isPrepared) { + if (this._logErrorLevel !== null) { + switch (this._logErrorLevel) { + case 'error': + text = '!!'; + color = '#f04e4e'; + status = 'Error'; + break; + default: // 'warn' + text = '!'; + color = '#f0ad4e'; + status = 'Warning'; + break; + } + } else if (!this._isPrepared) { if (this._prepareError) { text = '!!'; color = '#f04e4e'; diff --git a/ext/bg/js/context-main.js b/ext/bg/js/context-main.js index e2086a96..dbba0272 100644 --- a/ext/bg/js/context-main.js +++ b/ext/bg/js/context-main.js @@ -17,7 +17,9 @@ /* global * apiCommandExec + * apiForwardLogsToBackend * apiGetEnvironmentInfo + * apiLogIndicatorClear * apiOptionsGet */ @@ -52,8 +54,11 @@ function setupButtonEvents(selector, command, url) { } async function mainInner() { + apiForwardLogsToBackend(); await yomichan.prepare(); + await apiLogIndicatorClear(); + showExtensionInfo(); apiGetEnvironmentInfo().then(({browser}) => { diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index 16612403..a94aa720 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -104,7 +104,7 @@ class Database { }); return true; } catch (e) { - logError(e); + yomichan.logError(e); return false; } } diff --git a/ext/bg/js/mecab.js b/ext/bg/js/mecab.js index 597dceae..815ee860 100644 --- a/ext/bg/js/mecab.js +++ b/ext/bg/js/mecab.js @@ -24,7 +24,7 @@ class Mecab { } onError(error) { - logError(error, false); + yomichan.logError(error); } async checkVersion() { diff --git a/ext/bg/js/search-main.js b/ext/bg/js/search-main.js index 38b6d99a..5e4d7a20 100644 --- a/ext/bg/js/search-main.js +++ b/ext/bg/js/search-main.js @@ -17,6 +17,7 @@ /* global * DisplaySearch + * apiForwardLogsToBackend * apiOptionsGet */ @@ -53,6 +54,7 @@ function injectSearchFrontend() { } (async () => { + apiForwardLogsToBackend(); await yomichan.prepare(); const displaySearch = new DisplaySearch(); diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index eb3b681c..0001c9ff 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -45,7 +45,7 @@ class QueryParser extends TextScanner { } onError(error) { - logError(error, false); + yomichan.logError(error); } onClick(e) { diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index a5484fc3..cbd7b562 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -122,7 +122,7 @@ class DisplaySearch extends Display { } onError(error) { - logError(error, true); + yomichan.logError(error); } onSearchClear() { diff --git a/ext/bg/js/settings/backup.js b/ext/bg/js/settings/backup.js index bdfef658..faf4e592 100644 --- a/ext/bg/js/settings/backup.js +++ b/ext/bg/js/settings/backup.js @@ -133,7 +133,7 @@ async function _settingsImportSetOptionsFull(optionsFull) { } function _showSettingsImportError(error) { - logError(error); + yomichan.logError(error); document.querySelector('#settings-import-error-modal-message').textContent = `${error}`; $('#settings-import-error-modal').modal('show'); } diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 7eed4273..50add4c7 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -554,7 +554,7 @@ function dictionaryErrorsShow(errors) { if (errors !== null && errors.length > 0) { const uniqueErrors = new Map(); for (let e of errors) { - logError(e); + yomichan.logError(e); e = dictionaryErrorToString(e); let count = uniqueErrors.get(e); if (typeof count === 'undefined') { diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 308e92eb..f03cc631 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -21,6 +21,7 @@ * ankiInitialize * ankiTemplatesInitialize * ankiTemplatesUpdateValue + * apiForwardLogsToBackend * apiOptionsSave * appearanceInitialize * audioSettingsInitialize @@ -284,6 +285,7 @@ function showExtensionInformation() { async function onReady() { + apiForwardLogsToBackend(); await yomichan.prepare(); showExtensionInformation(); diff --git a/ext/bg/js/settings/popup-preview-frame-main.js b/ext/bg/js/settings/popup-preview-frame-main.js index 2ab6af6b..8228125f 100644 --- a/ext/bg/js/settings/popup-preview-frame-main.js +++ b/ext/bg/js/settings/popup-preview-frame-main.js @@ -17,8 +17,10 @@ /* global * SettingsPopupPreview + * apiForwardLogsToBackend */ (() => { + apiForwardLogsToBackend(); new SettingsPopupPreview(); })(); diff --git a/ext/fg/js/content-script-main.js b/ext/fg/js/content-script-main.js index 0b852644..277e6567 100644 --- a/ext/fg/js/content-script-main.js +++ b/ext/fg/js/content-script-main.js @@ -22,6 +22,7 @@ * PopupProxy * PopupProxyHost * apiBroadcastTab + * apiForwardLogsToBackend * apiOptionsGet */ @@ -62,6 +63,7 @@ async function createPopupProxy(depth, id, parentFrameId) { } (async () => { + apiForwardLogsToBackend(); await yomichan.prepare(); const data = window.frontendInitializationData || {}; diff --git a/ext/fg/js/float-main.js b/ext/fg/js/float-main.js index f056f707..5ef4b07c 100644 --- a/ext/fg/js/float-main.js +++ b/ext/fg/js/float-main.js @@ -17,6 +17,7 @@ /* global * DisplayFloat + * apiForwardLogsToBackend * apiOptionsGet */ @@ -68,5 +69,6 @@ async function popupNestedInitialize(id, depth, parentFrameId, url) { } (async () => { + apiForwardLogsToBackend(); new DisplayFloat(); })(); diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 2a5eba83..fd3b92cc 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -84,7 +84,7 @@ class DisplayFloat extends Display { if (this._orphaned) { this.setContent('orphaned'); } else { - logError(error, true); + yomichan.logError(error); } } diff --git a/ext/fg/js/frontend-api-sender.js b/ext/fg/js/frontend-api-sender.js index 1d539cab..0ad3f085 100644 --- a/ext/fg/js/frontend-api-sender.js +++ b/ext/fg/js/frontend-api-sender.js @@ -81,12 +81,12 @@ class FrontendApiSender { onAck(id) { const info = this.callbacks.get(id); if (typeof info === 'undefined') { - console.warn(`ID ${id} not found for ack`); + yomichan.logWarning(new Error(`ID ${id} not found for ack`)); return; } if (info.ack) { - console.warn(`Request ${id} already ack'd`); + yomichan.logWarning(new Error(`Request ${id} already ack'd`)); return; } @@ -98,12 +98,12 @@ class FrontendApiSender { onResult(id, data) { const info = this.callbacks.get(id); if (typeof info === 'undefined') { - console.warn(`ID ${id} not found`); + yomichan.logWarning(new Error(`ID ${id} not found`)); return; } if (!info.ack) { - console.warn(`Request ${id} not ack'd`); + yomichan.logWarning(new Error(`Request ${id} not ack'd`)); return; } diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index cd3c1bc9..93418202 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -148,7 +148,7 @@ class PopupProxy { } this._frameOffsetUpdatedAt = now; } catch (e) { - logError(e); + yomichan.logError(e); } finally { this._frameOffsetPromise = null; } diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 52f41646..afd68aa2 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -144,6 +144,14 @@ function apiGetMedia(targets) { return _apiInvoke('getMedia', {targets}); } +function apiLog(error, level, context) { + return _apiInvoke('log', {error, level, context}); +} + +function apiLogIndicatorClear() { + return _apiInvoke('logIndicatorClear'); +} + function _apiInvoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { @@ -171,3 +179,17 @@ function _apiInvoke(action, params={}) { function _apiCheckLastError() { // NOP } + +let _apiForwardLogsToBackendEnabled = false; +function apiForwardLogsToBackend() { + if (_apiForwardLogsToBackendEnabled) { return; } + _apiForwardLogsToBackendEnabled = true; + + yomichan.on('log', async ({error, level, context}) => { + try { + await apiLog(errorToJson(error), level, context); + } catch (e) { + // NOP + } + }); +} diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index 6a3298fc..fbe9943a 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -52,15 +52,28 @@ if (EXTENSION_IS_BROWSER_EDGE) { */ function errorToJson(error) { + try { + if (isObject(error)) { + return { + name: error.name, + message: error.message, + stack: error.stack, + data: error.data + }; + } + } catch (e) { + // NOP + } return { - name: error.name, - message: error.message, - stack: error.stack, - data: error.data + value: error, + hasValue: true }; } function jsonToError(jsonError) { + if (jsonError.hasValue) { + return jsonError.value; + } const error = new Error(jsonError.message); error.name = jsonError.name; error.stack = jsonError.stack; @@ -68,28 +81,6 @@ function jsonToError(jsonError) { return error; } -function logError(error, alert) { - const manifest = chrome.runtime.getManifest(); - let errorMessage = `${manifest.name} v${manifest.version} has encountered an error.\n`; - errorMessage += `Originating URL: ${window.location.href}\n`; - - const errorString = `${error.toString ? error.toString() : error}`; - const stack = `${error.stack}`.trimRight(); - if (!stack.startsWith(errorString)) { errorMessage += `${errorString}\n`; } - errorMessage += stack; - - const data = error.data; - if (typeof data !== 'undefined') { errorMessage += `\nData: ${JSON.stringify(data, null, 4)}`; } - - errorMessage += '\n\nIssues can be reported at https://github.com/FooSoft/yomichan/issues'; - - console.error(errorMessage); - - if (alert) { - window.alert(`${errorString}\n\nCheck the developer console for more details.`); - } -} - /* * Common helpers @@ -361,8 +352,77 @@ const yomichan = (() => { }); } + logWarning(error) { + this.log(error, 'warn'); + } + + logError(error) { + this.log(error, 'error'); + } + + log(error, level, context=null) { + if (!isObject(context)) { + context = this._getLogContext(); + } + + let errorString; + try { + errorString = error.toString(); + if (/^\[object \w+\]$/.test(errorString)) { + errorString = JSON.stringify(error); + } + } catch (e) { + errorString = `${error}`; + } + + let errorStack; + try { + errorStack = (typeof error.stack === 'string' ? error.stack.trimRight() : ''); + } catch (e) { + errorStack = ''; + } + + let errorData; + try { + errorData = error.data; + } catch (e) { + // NOP + } + + if (errorStack.startsWith(errorString)) { + errorString = errorStack; + } else if (errorStack.length > 0) { + errorString += `\n${errorStack}`; + } + + const manifest = chrome.runtime.getManifest(); + let message = `${manifest.name} v${manifest.version} has encountered a problem.`; + message += `\nOriginating URL: ${context.url}\n`; + message += errorString; + if (typeof errorData !== 'undefined') { + message += `\nData: ${JSON.stringify(errorData, null, 4)}`; + } + message += '\n\nIssues can be reported at https://github.com/FooSoft/yomichan/issues'; + + switch (level) { + case 'info': console.info(message); break; + case 'debug': console.debug(message); break; + case 'warn': console.warn(message); break; + case 'error': console.error(message); break; + default: console.log(message); break; + } + + this.trigger('log', {error, level, context}); + } + // Private + _getLogContext() { + return { + url: window.location.href + }; + } + _onMessage({action, params}, sender, callback) { const handler = this._messageHandlers.get(action); if (typeof handler !== 'function') { return false; } diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index 0cd12cd7..1c32714b 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -201,7 +201,7 @@ class TextScanner { } onError(error) { - logError(error, false); + yomichan.logError(error); } async scanTimerWait() { diff --git a/test/test-database.js b/test/test-database.js index e9ec3f0b..3684051b 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -145,7 +145,10 @@ const vm = new VM({ XMLHttpRequest, indexedDB: global.indexedDB, IDBKeyRange: global.IDBKeyRange, - JSZip: yomichanTest.JSZip + JSZip: yomichanTest.JSZip, + addEventListener() { + // NOP + } }); vm.context.window = vm.context; -- cgit v1.2.3 From 021ccb5ac3038f63d07ccc9575ee56480031a251 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 6 May 2020 19:28:26 -0400 Subject: Move util database modification functions (#499) * Update onProgress callback to handle multiple arguments * Add apiImportDictionaryArchive * Add apiDeleteDictionary * Make onProgress the last argument for consistency * Remove deprecated util functions * Fix issue with missing progress args * Remove function calls which modify the database from Translator * Update tests * Fix errors not being serialized correctly in _createActionListenerPort --- ext/bg/js/backend.js | 23 +++++++++++++++++++---- ext/bg/js/database.js | 2 +- ext/bg/js/dictionary-importer.js | 2 +- ext/bg/js/settings/dictionaries.js | 18 ++++++++++++++---- ext/bg/js/translator.js | 8 +------- ext/bg/js/util.js | 25 ------------------------- ext/mixed/js/api.js | 10 +++++++++- test/test-database.js | 18 +++++++++--------- 8 files changed, 54 insertions(+), 52 deletions(-) (limited to 'test/test-database.js') diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index c5173a2e..d454aa22 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -117,7 +117,10 @@ class Backend { ['logIndicatorClear', {handler: this._onApiLogIndicatorClear.bind(this), async: false}], ['createActionPort', {handler: this._onApiCreateActionPort.bind(this), async: false}] ]); - this._messageHandlersWithProgress = new Map(); + this._messageHandlersWithProgress = new Map([ + ['importDictionaryArchive', {handler: this._onApiImportDictionaryArchive.bind(this), async: true}], + ['deleteDictionary', {handler: this._onApiDeleteDictionary.bind(this), async: true}] + ]); this._commandHandlers = new Map([ ['search', this._onCommandSearch.bind(this)], @@ -771,7 +774,8 @@ class Backend { async _onApiPurgeDatabase(params, sender) { this._validatePrivilegedMessageSender(sender); - return await this.translator.purgeDatabase(); + this.translator.clearDatabaseCaches(); + await this.database.purge(); } async _onApiGetMedia({targets}) { @@ -814,12 +818,23 @@ class Backend { return portName; } + async _onApiImportDictionaryArchive({archiveContent, details}, sender, onProgress) { + this._validatePrivilegedMessageSender(sender); + return await this.dictionaryImporter.import(this.database, archiveContent, details, onProgress); + } + + async _onApiDeleteDictionary({dictionaryName}, sender, onProgress) { + this._validatePrivilegedMessageSender(sender); + this.translator.clearDatabaseCaches(); + await this.database.deleteDictionary(dictionaryName, {rate: 1000}, onProgress); + } + // Command handlers _createActionListenerPort(port, sender, handlers) { let hasStarted = false; - const onProgress = (data) => { + const onProgress = (...data) => { try { if (port === null) { return; } port.postMessage({type: 'progress', data}); @@ -847,7 +862,7 @@ class Backend { port.postMessage({type: 'complete', data: result}); } catch (e) { if (port !== null) { - port.postMessage({type: 'error', data: e}); + port.postMessage({type: 'error', data: errorToJson(e)}); } cleanup(); } diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index a94aa720..930cd0d0 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -129,7 +129,7 @@ class Database { await this.prepare(); } - async deleteDictionary(dictionaryName, onProgress, progressSettings) { + async deleteDictionary(dictionaryName, progressSettings, onProgress) { this._validate(); const targets = [ diff --git a/ext/bg/js/dictionary-importer.js b/ext/bg/js/dictionary-importer.js index 3727f7ee..10e30cec 100644 --- a/ext/bg/js/dictionary-importer.js +++ b/ext/bg/js/dictionary-importer.js @@ -27,7 +27,7 @@ class DictionaryImporter { this._schemas = new Map(); } - async import(database, archiveSource, onProgress, details) { + async import(database, archiveSource, details, onProgress) { if (!database) { throw new Error('Invalid database'); } diff --git a/ext/bg/js/settings/dictionaries.js b/ext/bg/js/settings/dictionaries.js index 50add4c7..632c01ea 100644 --- a/ext/bg/js/settings/dictionaries.js +++ b/ext/bg/js/settings/dictionaries.js @@ -17,8 +17,10 @@ /* global * PageExitPrevention + * apiDeleteDictionary * apiGetDictionaryCounts * apiGetDictionaryInfo + * apiImportDictionaryArchive * apiOptionsGet * apiOptionsGetFull * apiPurgeDatabase @@ -29,8 +31,6 @@ * storageEstimate * storageUpdateStats * utilBackgroundIsolate - * utilDatabaseDeleteDictionary - * utilDatabaseImport */ let dictionaryUI = null; @@ -312,7 +312,7 @@ class SettingsDictionaryEntryUI { progressBar.style.width = `${percent}%`; }; - await utilDatabaseDeleteDictionary(this.dictionaryInfo.title, onProgress, {rate: 1000}); + await apiDeleteDictionary(this.dictionaryInfo.title, onProgress); } catch (e) { dictionaryErrorsShow([e]); } finally { @@ -679,7 +679,8 @@ async function onDictionaryImport(e) { dictImportInfo.textContent = `(${i + 1} of ${ii})`; } - const {result, errors} = await utilDatabaseImport(files[i], updateProgress, importDetails); + const archiveContent = await dictReadFile(files[i]); + const {result, errors} = await apiImportDictionaryArchive(archiveContent, importDetails, updateProgress); for (const {options} of toIterable((await getOptionsFullMutable()).profiles)) { const dictionaryOptions = SettingsDictionaryListUI.createDictionaryOptions(); dictionaryOptions.enabled = true; @@ -713,6 +714,15 @@ async function onDictionaryImport(e) { } } +function dictReadFile(file) { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result); + reader.onerror = () => reject(reader.error); + reader.readAsBinaryString(file); + }); +} + async function onDatabaseEnablePrefixWildcardSearchesChanged(e) { const optionsFull = await getOptionsFullMutable(); diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 8708e4d8..3fd329d1 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -45,14 +45,8 @@ class Translator { this.deinflector = new Deinflector(reasons); } - async purgeDatabase() { + clearDatabaseCaches() { this.tagCache.clear(); - await this.database.purge(); - } - - async deleteDictionary(dictionaryName) { - this.tagCache.clear(); - await this.database.deleteDictionary(dictionaryName); } async getSequencedDefinitions(definitions, mainDictionary) { diff --git a/ext/bg/js/util.js b/ext/bg/js/util.js index d2fb0e49..8f86e47a 100644 --- a/ext/bg/js/util.js +++ b/ext/bg/js/util.js @@ -66,31 +66,6 @@ function utilBackend() { return backend; } -async function utilDatabaseDeleteDictionary(dictionaryName, onProgress) { - return utilIsolate(await utilBackend().translator.database.deleteDictionary( - utilBackgroundIsolate(dictionaryName), - utilBackgroundFunctionIsolate(onProgress) - )); -} - -async function utilDatabaseImport(data, onProgress, details) { - data = await utilReadFile(data); - return utilIsolate(await utilBackend().importDictionary( - utilBackgroundIsolate(data), - utilBackgroundFunctionIsolate(onProgress), - utilBackgroundIsolate(details) - )); -} - -function utilReadFile(file) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(reader.error); - reader.readAsBinaryString(file); - }); -} - function utilReadFileArrayBuffer(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index ca4bdd6c..af97ac3d 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -152,6 +152,14 @@ function apiLogIndicatorClear() { return _apiInvoke('logIndicatorClear'); } +function apiImportDictionaryArchive(archiveContent, details, onProgress) { + return _apiInvokeWithProgress('importDictionaryArchive', {archiveContent, details}, onProgress); +} + +function apiDeleteDictionary(dictionaryName, onProgress) { + return _apiInvokeWithProgress('deleteDictionary', {dictionaryName}, onProgress); +} + function _apiCreateActionPort(timeout=5000) { return new Promise((resolve, reject) => { let timer = null; @@ -213,7 +221,7 @@ function _apiInvokeWithProgress(action, params, onProgress, timeout=5000) { break; case 'progress': try { - onProgress(message.data); + onProgress(...message.data); } catch (e) { // NOP } diff --git a/test/test-database.js b/test/test-database.js index 3684051b..e8a4a343 100644 --- a/test/test-database.js +++ b/test/test-database.js @@ -233,10 +233,10 @@ async function testDatabase1() { let progressEvent = false; await database.deleteDictionary( title, + {rate: 1000}, () => { progressEvent = true; - }, - {rate: 1000} + } ); assert.ok(progressEvent); @@ -267,10 +267,10 @@ async function testDatabase1() { const {result, errors} = await dictionaryImporter.import( database, testDictionarySource, + {prefixWildcardsSupported: true}, () => { progressEvent = true; - }, - {prefixWildcardsSupported: true} + } ); vm.assert.deepStrictEqual(errors, []); vm.assert.deepStrictEqual(result, expectedSummary); @@ -908,7 +908,7 @@ async function testDatabase2() { // Error: not prepared await assert.rejects(async () => await database.purge()); - await assert.rejects(async () => await database.deleteDictionary(title, () => {}, {})); + await assert.rejects(async () => await database.deleteDictionary(title, {}, () => {})); await assert.rejects(async () => await database.findTermsBulk(['?'], titles, null)); await assert.rejects(async () => await database.findTermsExactBulk(['?'], ['?'], titles)); await assert.rejects(async () => await database.findTermsBySequenceBulk([1], title)); @@ -919,17 +919,17 @@ async function testDatabase2() { await assert.rejects(async () => await database.findTagForTitle('tag', title)); await assert.rejects(async () => await database.getDictionaryInfo()); await assert.rejects(async () => await database.getDictionaryCounts(titles, true)); - await assert.rejects(async () => await dictionaryImporter.import(database, testDictionarySource, () => {}, {})); + await assert.rejects(async () => await dictionaryImporter.import(database, testDictionarySource, {}, () => {})); await database.prepare(); // Error: already prepared await assert.rejects(async () => await database.prepare()); - await dictionaryImporter.import(database, testDictionarySource, () => {}, {}); + await dictionaryImporter.import(database, testDictionarySource, {}, () => {}); // Error: dictionary already imported - await assert.rejects(async () => await dictionaryImporter.import(database, testDictionarySource, () => {}, {})); + await assert.rejects(async () => await dictionaryImporter.import(database, testDictionarySource, {}, () => {})); await database.close(); } @@ -956,7 +956,7 @@ async function testDatabase3() { let error = null; try { - await dictionaryImporter.import(database, testDictionarySource, () => {}, {}); + await dictionaryImporter.import(database, testDictionarySource, {}, () => {}); } catch (e) { error = e; } -- cgit v1.2.3