From 4da4827bcbcdd1ef163f635d9b29416ff272b0bb Mon Sep 17 00:00:00 2001 From: toasted-nutbread <toasted-nutbread@users.noreply.github.com> Date: Mon, 27 Nov 2023 12:48:14 -0500 Subject: Add JSDoc type annotations to project (rebased) --- ext/js/general/cache-map.js | 45 ++++++++++++++++++++++------- ext/js/general/object-property-accessor.js | 29 ++++++++++--------- ext/js/general/regex-util.js | 8 ++++-- ext/js/general/task-accumulator.js | 46 ++++++++++++++++++++++++++---- ext/js/general/text-source-map.js | 34 ++++++++++++++++++++++ 5 files changed, 128 insertions(+), 34 deletions(-) (limited to 'ext/js/general') diff --git a/ext/js/general/cache-map.js b/ext/js/general/cache-map.js index 1ee385bf..cc706380 100644 --- a/ext/js/general/cache-map.js +++ b/ext/js/general/cache-map.js @@ -18,6 +18,7 @@ /** + * @template K,V * Class which caches a map of values, keeping the most recently accessed values. */ export class CacheMap { @@ -35,9 +36,13 @@ export class CacheMap { throw new Error('Invalid maxCount'); } + /** @type {number} */ this._maxSize = maxSize; + /** @type {Map<K, import('cache-map').Node<K, V>>} */ this._map = new Map(); + /** @type {import('cache-map').Node<K, V>} */ this._listFirst = this._createNode(null, null); + /** @type {import('cache-map').Node<K, V>} */ this._listLast = this._createNode(null, null); this._resetEndNodes(); } @@ -60,7 +65,7 @@ export class CacheMap { /** * Returns whether or not an element exists at the given key. - * @param {*} key The key of the element. + * @param {K} key The key of the element. * @returns {boolean} `true` if an element with the specified key exists, `false` otherwise. */ has(key) { @@ -69,20 +74,20 @@ export class CacheMap { /** * Gets an element at the given key, if it exists. Otherwise, returns undefined. - * @param {*} key The key of the element. - * @returns {*} The existing value at the key, if any; `undefined` otherwise. + * @param {K} key The key of the element. + * @returns {V|undefined} The existing value at the key, if any; `undefined` otherwise. */ get(key) { const node = this._map.get(key); if (typeof node === 'undefined') { return void 0; } this._updateRecency(node); - return node.value; + return /** @type {V} */ (node.value); } /** * Sets a value at a given key. - * @param {*} key The key of the element. - * @param {*} value The value to store in the cache. + * @param {K} key The key of the element. + * @param {V} value The value to store in the cache. */ set(key, value) { let node = this._map.get(key); @@ -98,9 +103,9 @@ export class CacheMap { // Remove for (let removeCount = this._map.size - this._maxSize; removeCount > 0; --removeCount) { - node = this._listLast.previous; + node = /** @type {import('cache-map').Node<K, V>} */ (this._listLast.previous); this._removeNode(node); - this._map.delete(node.key); + this._map.delete(/** @type {K} */ (node.key)); } } } @@ -115,28 +120,46 @@ export class CacheMap { // Private + /** + * @param {import('cache-map').Node<K, V>} node + */ _updateRecency(node) { this._removeNode(node); this._addNode(node, this._listFirst); } + /** + * @param {?K} key + * @param {?V} value + * @returns {import('cache-map').Node<K, V>} + */ _createNode(key, value) { return {key, value, previous: null, next: null}; } + /** + * @param {import('cache-map').Node<K, V>} node + * @param {import('cache-map').Node<K, V>} previous + */ _addNode(node, previous) { const next = previous.next; node.next = next; node.previous = previous; previous.next = node; - next.previous = node; + /** @type {import('cache-map').Node<K, V>} */ (next).previous = node; } + /** + * @param {import('cache-map').Node<K, V>} node + */ _removeNode(node) { - node.next.previous = node.previous; - node.previous.next = node.next; + /** @type {import('cache-map').Node<K, V>} */ (node.next).previous = node.previous; + /** @type {import('cache-map').Node<K, V>} */ (node.previous).next = node.next; } + /** + * @returns {void} + */ _resetEndNodes() { this._listFirst.next = this._listLast; this._listLast.previous = this._listFirst; diff --git a/ext/js/general/object-property-accessor.js b/ext/js/general/object-property-accessor.js index b8309ed2..d818c9d1 100644 --- a/ext/js/general/object-property-accessor.js +++ b/ext/js/general/object-property-accessor.js @@ -22,9 +22,10 @@ export class ObjectPropertyAccessor { /** * Create a new accessor for a specific object. - * @param {object} target The object which the getter and mutation methods are applied to. + * @param {unknown} target The object which the getter and mutation methods are applied to. */ constructor(target) { + /** @type {unknown} */ this._target = target; } @@ -33,7 +34,7 @@ export class ObjectPropertyAccessor { * @param {(string|number)[]} pathArray The path to the property on the target object. * @param {number} [pathLength] How many parts of the pathArray to use. * This parameter is optional and defaults to the length of pathArray. - * @returns {*} The value found at the path. + * @returns {unknown} The value found at the path. * @throws {Error} An error is thrown if pathArray is not valid for the target object. */ get(pathArray, pathLength) { @@ -44,7 +45,7 @@ export class ObjectPropertyAccessor { if (!ObjectPropertyAccessor.hasProperty(target, key)) { throw new Error(`Invalid path: ${ObjectPropertyAccessor.getPathString(pathArray.slice(0, i + 1))}`); } - target = target[key]; + target = /** @type {import('core').SerializableObject} */ (target)[key]; } return target; } @@ -52,7 +53,7 @@ export class ObjectPropertyAccessor { /** * Sets the value at the specified path. * @param {(string|number)[]} pathArray The path to the property on the target object. - * @param {*} value The value to assign to the property. + * @param {unknown} value The value to assign to the property. * @throws {Error} An error is thrown if pathArray is not valid for the target object. */ set(pathArray, value) { @@ -65,7 +66,7 @@ export class ObjectPropertyAccessor { throw new Error(`Invalid path: ${ObjectPropertyAccessor.getPathString(pathArray)}`); } - target[key] = value; + /** @type {import('core').SerializableObject} */ (target)[key] = value; } /** @@ -87,7 +88,7 @@ export class ObjectPropertyAccessor { throw new Error('Invalid type'); } - delete target[key]; + delete /** @type {import('core').SerializableObject} */ (target)[key]; } /** @@ -110,16 +111,16 @@ export class ObjectPropertyAccessor { const key2 = pathArray2[ii2]; if (!ObjectPropertyAccessor.isValidPropertyType(target2, key2)) { throw new Error(`Invalid path 2: ${ObjectPropertyAccessor.getPathString(pathArray2)}`); } - const value1 = target1[key1]; - const value2 = target2[key2]; + const value1 = /** @type {import('core').SerializableObject} */ (target1)[key1]; + const value2 = /** @type {import('core').SerializableObject} */ (target2)[key2]; - target1[key1] = value2; + /** @type {import('core').SerializableObject} */ (target1)[key1] = value2; try { - target2[key2] = value1; + /** @type {import('core').SerializableObject} */ (target2)[key2] = value1; } catch (e) { // Revert try { - target1[key1] = value1; + /** @type {import('core').SerializableObject} */ (target1)[key1] = value1; } catch (e2) { // NOP } @@ -178,7 +179,7 @@ export class ObjectPropertyAccessor { let value = ''; let escaped = false; for (const c of pathString) { - const v = c.codePointAt(0); + const v = /** @type {number} */ (c.codePointAt(0)); switch (state) { case 'empty': // Empty case 'id-start': // Expecting identifier start @@ -288,7 +289,7 @@ export class ObjectPropertyAccessor { /** * Checks whether an object or array has the specified property. - * @param {*} object The object to test. + * @param {unknown} object The object to test. * @param {string|number} property The property to check for existence. * This value should be a string if the object is a non-array object. * For arrays, it should be an integer. @@ -317,7 +318,7 @@ export class ObjectPropertyAccessor { /** * Checks whether a property is valid for the given object - * @param {object} object The object to test. + * @param {unknown} object The object to test. * @param {string|number} property The property to check for existence. * @returns {boolean} `true` if the property is correct for the given object type, otherwise `false`. * For arrays, this means that the property should be a positive integer. diff --git a/ext/js/general/regex-util.js b/ext/js/general/regex-util.js index 298189d4..726ce9f2 100644 --- a/ext/js/general/regex-util.js +++ b/ext/js/general/regex-util.js @@ -61,7 +61,7 @@ export class RegexUtil { * Applies the replacement string for a given regular expression match. * @param {string} replacement The replacement string that follows the format of the standard * JavaScript regular expression replacement string. - * @param {object} match A match object returned from RegExp.match. + * @param {RegExpMatchArray} match A match object returned from RegExp.match. * @returns {string} A new string with the pattern replacement applied. */ static applyMatchReplacement(replacement, match) { @@ -79,11 +79,13 @@ export class RegexUtil { return groups[g2]; } } else { + let {index} = match; + if (typeof index !== 'number') { index = 0; } switch (g0) { case '$': return '$'; case '&': return match[0]; - case '`': return replacement.substring(0, match.index); - case '\'': return replacement.substring(match.index + g0.length); + case '`': return replacement.substring(0, index); + case '\'': return replacement.substring(index + g0.length); } } return g0; diff --git a/ext/js/general/task-accumulator.js b/ext/js/general/task-accumulator.js index cae58b94..cb136908 100644 --- a/ext/js/general/task-accumulator.js +++ b/ext/js/general/task-accumulator.js @@ -18,25 +18,46 @@ import {log} from '../core.js'; +/** + * @template K,V + */ export class TaskAccumulator { + /** + * @param {(tasks: [key: ?K, task: import('task-accumulator').Task<V>][]) => Promise<void>} runTasks + */ constructor(runTasks) { + /** @type {?Promise<void>} */ this._deferPromise = null; + /** @type {?Promise<void>} */ this._activePromise = null; + /** @type {import('task-accumulator').Task<V>[]} */ this._tasks = []; + /** @type {import('task-accumulator').Task<V>[]} */ this._tasksActive = []; + /** @type {Map<K, import('task-accumulator').Task<V>>} */ this._uniqueTasks = new Map(); + /** @type {Map<K, import('task-accumulator').Task<V>>} */ this._uniqueTasksActive = new Map(); + /** @type {() => Promise<void>} */ this._runTasksBind = this._runTasks.bind(this); + /** @type {() => void} */ this._tasksCompleteBind = this._tasksComplete.bind(this); - this._runTasks = runTasks; + /** @type {(tasks: [key: ?K, task: import('task-accumulator').Task<V>][]) => Promise<void>} */ + this._runTasksCallback = runTasks; } + /** + * @param {?K} key + * @param {V} data + * @returns {Promise<void>} + */ enqueue(key, data) { if (this._deferPromise === null) { const promise = this._activePromise !== null ? this._activePromise : Promise.resolve(); this._deferPromise = promise.then(this._runTasksBind); } + /** @type {import('task-accumulator').Task<V>} */ const task = {data, stale: false}; if (key !== null) { const activeTaskInfo = this._uniqueTasksActive.get(key); @@ -52,6 +73,9 @@ export class TaskAccumulator { return this._deferPromise; } + /** + * @returns {Promise<void>} + */ _runTasks() { this._deferPromise = null; @@ -64,18 +88,28 @@ export class TaskAccumulator { return this._activePromise; } + /** + * @returns {Promise<void>} + */ async _runTasksAsync() { try { - const allTasks = [ - ...this._tasksActive.map((taskInfo) => [null, taskInfo]), - ...this._uniqueTasksActive.entries() - ]; - await this._runTasks(allTasks); + /** @type {[key: ?K, task: import('task-accumulator').Task<V>][]} */ + const allTasks = []; + for (const taskInfo of this._tasksActive) { + allTasks.push([null, taskInfo]); + } + for (const [key, taskInfo] of this._uniqueTasksActive) { + allTasks.push([key, taskInfo]); + } + await this._runTasksCallback(allTasks); } catch (e) { log.error(e); } } + /** + * @returns {void} + */ _tasksComplete() { this._tasksActive.length = 0; this._uniqueTasksActive.clear(); diff --git a/ext/js/general/text-source-map.js b/ext/js/general/text-source-map.js index 6a136451..b03f6eb2 100644 --- a/ext/js/general/text-source-map.js +++ b/ext/js/general/text-source-map.js @@ -17,15 +17,26 @@ */ export class TextSourceMap { + /** + * @param {string} source + * @param {number[]|null} [mapping=null] + */ constructor(source, mapping=null) { + /** @type {string} */ this._source = source; + /** @type {?number[]} */ this._mapping = (mapping !== null ? TextSourceMap.normalizeMapping(mapping) : null); } + /** @type {string} */ get source() { return this._source; } + /** + * @param {unknown} other + * @returns {boolean} + */ equals(other) { if (this === other) { return true; @@ -61,6 +72,10 @@ export class TextSourceMap { return true; } + /** + * @param {number} finalLength + * @returns {number} + */ getSourceLength(finalLength) { const mapping = this._mapping; if (mapping === null) { @@ -74,6 +89,10 @@ export class TextSourceMap { return sourceLength; } + /** + * @param {number} index + * @param {number} count + */ combine(index, count) { if (count <= 0) { return; } @@ -89,6 +108,10 @@ export class TextSourceMap { this._mapping[index] = sum; } + /** + * @param {number} index + * @param {number[]} items + */ insert(index, ...items) { if (this._mapping === null) { this._mapping = TextSourceMap.createMapping(this._source); @@ -97,14 +120,25 @@ export class TextSourceMap { this._mapping.splice(index, 0, ...items); } + /** + * @returns {?number[]} + */ getMappingCopy() { return this._mapping !== null ? [...this._mapping] : null; } + /** + * @param {string} text + * @returns {number[]} + */ static createMapping(text) { return new Array(text.length).fill(1); } + /** + * @param {number[]} mapping + * @returns {number[]} + */ static normalizeMapping(mapping) { const result = []; for (const value of mapping) { -- cgit v1.2.3 From 7aed9a371b0d74c0d75179a08068e8935b76d780 Mon Sep 17 00:00:00 2001 From: toasted-nutbread <toasted-nutbread@users.noreply.github.com> Date: Mon, 27 Nov 2023 14:55:27 -0500 Subject: Update types --- ext/js/background/backend.js | 22 ++++----- ext/js/background/offscreen-proxy.js | 27 +++++++++-- ext/js/background/offscreen.js | 9 +++- ext/js/background/request-builder.js | 13 ++---- ext/js/comm/api.js | 6 +-- ext/js/comm/clipboard-reader.js | 4 +- ext/js/display/search-action-popup-controller.js | 4 +- ext/js/dom/sandbox/css-style-applier.js | 2 +- ext/js/dom/text-source-element.js | 2 +- ext/js/dom/text-source-range.js | 2 +- ext/js/general/regex-util.js | 2 +- .../__mocks__/dictionary-importer-media-loader.js | 1 + ext/js/language/dictionary-importer.js | 2 +- ext/js/language/dictionary-worker.js | 2 + ext/js/language/sandbox/japanese-util.js | 8 ++-- ext/js/language/text-scanner.js | 1 + ext/js/language/translator.js | 4 +- ext/js/media/audio-downloader.js | 6 +-- ext/js/pages/settings/backup-controller.js | 54 +++++++++------------- .../settings/recommended-permissions-controller.js | 36 +++++++++++++-- types/ext/api.d.ts | 12 +++++ types/ext/request-builder.d.ts | 2 + 22 files changed, 139 insertions(+), 82 deletions(-) (limited to 'ext/js/general') diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 14877cf1..be68ecf4 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -96,7 +96,7 @@ export class Backend { }); /** @type {?import('settings').Options} */ this._options = null; - /** @type {JsonSchema[]} */ + /** @type {import('../data/json-schema.js').JsonSchema[]} */ this._profileConditionsSchemaCache = []; /** @type {ProfileConditionsUtil} */ this._profileConditionsUtil = new ProfileConditionsUtil(); @@ -665,7 +665,7 @@ export class Backend { async _onApiInjectStylesheet({type, value}, sender) { const {frameId, tab} = sender; if (typeof tab !== 'object' || tab === null || typeof tab.id !== 'number') { throw new Error('Invalid tab'); } - return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false, true, 'document_start'); + return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false); } /** @type {import('api').Handler<import('api').GetStylesheetContentDetails, import('api').GetStylesheetContentResult>} */ @@ -895,13 +895,7 @@ export class Backend { } } - /** - * - * @param root0 - * @param root0.targetTabId - * @param root0.targetFrameId - * @param sender - */ + /** @type {import('api').Handler<import('api').OpenCrossFramePortDetails, import('api').OpenCrossFramePortResult, true>} */ _onApiOpenCrossFramePort({targetTabId, targetFrameId}, sender) { const sourceTabId = (sender && sender.tab ? sender.tab.id : null); if (typeof sourceTabId !== 'number') { @@ -922,7 +916,9 @@ export class Backend { otherTabId: sourceTabId, otherFrameId: sourceFrameId }; + /** @type {?chrome.runtime.Port} */ let sourcePort = chrome.tabs.connect(sourceTabId, {frameId: sourceFrameId, name: JSON.stringify(sourceDetails)}); + /** @type {?chrome.runtime.Port} */ let targetPort = chrome.tabs.connect(targetTabId, {frameId: targetFrameId, name: JSON.stringify(targetDetails)}); const cleanup = () => { @@ -937,8 +933,12 @@ export class Backend { } }; - sourcePort.onMessage.addListener((message) => { targetPort.postMessage(message); }); - targetPort.onMessage.addListener((message) => { sourcePort.postMessage(message); }); + sourcePort.onMessage.addListener((message) => { + if (targetPort !== null) { targetPort.postMessage(message); } + }); + targetPort.onMessage.addListener((message) => { + if (sourcePort !== null) { sourcePort.postMessage(message); } + }); sourcePort.onDisconnect.addListener(cleanup); targetPort.onDisconnect.addListener(cleanup); diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index c01f523d..0fb2f269 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -16,7 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import {deserializeError, isObject} from '../core.js'; +import {isObject} from '../core.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; export class OffscreenProxy { @@ -158,15 +158,36 @@ export class TranslatorProxy { } export class ClipboardReaderProxy { + /** + * @param {OffscreenProxy} offscreen + */ constructor(offscreen) { + /** @type {?import('environment').Browser} */ + this._browser = null; + /** @type {OffscreenProxy} */ this._offscreen = offscreen; } + /** @type {?import('environment').Browser} */ + get browser() { return this._browser; } + set browser(value) { + if (this._browser === value) { return; } + this._browser = value; + this._offscreen.sendMessagePromise({action: 'clipboardSetBrowserOffsecreen', params: {value}}); + } + + /** + * @param {boolean} useRichText + * @returns {Promise<string>} + */ async getText(useRichText) { - return this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}}); + return await this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}}); } + /** + * @returns {Promise<?string>} + */ async getImage() { - return this._offscreen.sendMessagePromise({action: 'clipboardGetImageOffscreen'}); + return await this._offscreen.sendMessagePromise({action: 'clipboardGetImageOffscreen'}); } } diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 27cee8c4..6302aa84 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -50,6 +50,7 @@ export class Offscreen { this._messageHandlers = new Map([ ['clipboardGetTextOffscreen', {async: true, contentScript: true, handler: this._getTextHandler.bind(this)}], ['clipboardGetImageOffscreen', {async: true, contentScript: true, handler: this._getImageHandler.bind(this)}], + ['clipboardSetBrowserOffsecreen', {async: false, contentScript: true, handler: this._setClipboardBrowser.bind(this)}], ['databasePrepareOffscreen', {async: true, contentScript: true, handler: this._prepareDatabaseHandler.bind(this)}], ['getDictionaryInfoOffscreen', {async: true, contentScript: true, handler: this._getDictionaryInfoHandler.bind(this)}], ['databasePurgeOffscreen', {async: true, contentScript: true, handler: this._purgeDatabaseHandler.bind(this)}], @@ -59,7 +60,6 @@ export class Offscreen { ['findTermsOffscreen', {async: true, contentScript: true, handler: this._findTermsHandler.bind(this)}], ['getTermFrequenciesOffscreen', {async: true, contentScript: true, handler: this._getTermFrequenciesHandler.bind(this)}], ['clearDatabaseCachesOffscreen', {async: false, contentScript: true, handler: this._clearDatabaseCachesHandler.bind(this)}] - ]); const onMessage = this._onMessage.bind(this); @@ -76,6 +76,13 @@ export class Offscreen { return this._clipboardReader.getImage(); } + /** + * @param {{value: import('environment').Browser}} details + */ + _setClipboardBrowser({value}) { + this._clipboardReader.browser = value; + } + _prepareDatabaseHandler() { if (this._prepareDatabasePromise !== null) { return this._prepareDatabasePromise; diff --git a/ext/js/background/request-builder.js b/ext/js/background/request-builder.js index 48fe2dd9..5ae7fbf5 100644 --- a/ext/js/background/request-builder.js +++ b/ext/js/background/request-builder.js @@ -21,12 +21,6 @@ * with additional controls over anonymity and error handling. */ export class RequestBuilder { - /** - * A progress callback for a fetch read. - * @callback ProgressCallback - * @param {boolean} complete Whether or not the data has been completely read. - */ - /** * Creates a new instance. */ @@ -109,14 +103,17 @@ export class RequestBuilder { /** * Reads the array buffer body of a fetch response, with an optional `onProgress` callback. * @param {Response} response The response of a `fetch` call. - * @param {ProgressCallback} onProgress The progress callback + * @param {?import('request-builder.js').ProgressCallback} onProgress The progress callback * @returns {Promise<Uint8Array>} The resulting binary data. */ static async readFetchResponseArrayBuffer(response, onProgress) { let reader; try { if (typeof onProgress === 'function') { - reader = response.body.getReader(); + const {body} = response; + if (body !== null) { + reader = body.getReader(); + } } } catch (e) { // Not supported diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 62dc98b1..0cfdba59 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -415,9 +415,9 @@ export class API { } /** - * - * @param targetTabId - * @param targetFrameId + * @param {import('api').OpenCrossFramePortDetails['targetTabId']} targetTabId + * @param {import('api').OpenCrossFramePortDetails['targetFrameId']} targetFrameId + * @returns {Promise<import('api').OpenCrossFramePortResult>} */ openCrossFramePort(targetTabId, targetFrameId) { return this._invoke('openCrossFramePort', {targetTabId, targetFrameId}); diff --git a/ext/js/comm/clipboard-reader.js b/ext/js/comm/clipboard-reader.js index c7b45a7c..364e31a3 100644 --- a/ext/js/comm/clipboard-reader.js +++ b/ext/js/comm/clipboard-reader.js @@ -29,7 +29,7 @@ export class ClipboardReader { constructor({document=null, pasteTargetSelector=null, richContentPasteTargetSelector=null}) { /** @type {?Document} */ this._document = document; - /** @type {?string} */ + /** @type {?import('environment').Browser} */ this._browser = null; /** @type {?HTMLTextAreaElement} */ this._pasteTarget = null; @@ -43,7 +43,7 @@ export class ClipboardReader { /** * Gets the browser being used. - * @type {?string} + * @type {?import('environment').Browser} */ get browser() { return this._browser; diff --git a/ext/js/display/search-action-popup-controller.js b/ext/js/display/search-action-popup-controller.js index 733fd70a..3a2057a1 100644 --- a/ext/js/display/search-action-popup-controller.js +++ b/ext/js/display/search-action-popup-controller.js @@ -18,10 +18,10 @@ export class SearchActionPopupController { /** - * @param {SearchPersistentStateController} searchPersistentStateController + * @param {import('./search-persistent-state-controller.js').SearchPersistentStateController} searchPersistentStateController */ constructor(searchPersistentStateController) { - /** @type {SearchPersistentStateController} */ + /** @type {import('./search-persistent-state-controller.js').SearchPersistentStateController} */ this._searchPersistentStateController = searchPersistentStateController; } diff --git a/ext/js/dom/sandbox/css-style-applier.js b/ext/js/dom/sandbox/css-style-applier.js index 332ca4f2..ea36a02d 100644 --- a/ext/js/dom/sandbox/css-style-applier.js +++ b/ext/js/dom/sandbox/css-style-applier.js @@ -24,7 +24,7 @@ export class CssStyleApplier { /** * Creates a new instance of the class. * @param {string} styleDataUrl The local URL to the JSON file continaing the style rules. - * The style rules should follow the format of {@link CssStyleApplierRawStyleData}. + * The style rules should follow the format of `CssStyleApplierRawStyleData`. */ constructor(styleDataUrl) { /** @type {string} */ diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index 47c18e30..40ff5cc9 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -173,7 +173,7 @@ export class TextSourceElement { /** * Checks whether another text source has the same starting point. - * @param {TextSourceElement|TextSourceRange} other The other source to test. + * @param {import('text-source').TextSource} other The other source to test. * @returns {boolean} `true` if the starting points are equivalent, `false` otherwise. */ hasSameStart(other) { diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index 5dbbd636..fd09fdda 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -206,7 +206,7 @@ export class TextSourceRange { /** * Checks whether another text source has the same starting point. - * @param {TextSourceElement|TextSourceRange} other The other source to test. + * @param {import('text-source').TextSource} other The other source to test. * @returns {boolean} `true` if the starting points are equivalent, `false` otherwise. * @throws {Error} An exception can be thrown if `Range.compareBoundaryPoints` fails, * which shouldn't happen, but the handler is kept in case of unexpected errors. diff --git a/ext/js/general/regex-util.js b/ext/js/general/regex-util.js index 726ce9f2..62248968 100644 --- a/ext/js/general/regex-util.js +++ b/ext/js/general/regex-util.js @@ -25,7 +25,7 @@ export class RegexUtil { * Applies string.replace using a regular expression and replacement string as arguments. * A source map of the changes is also maintained. * @param {string} text A string of the text to replace. - * @param {TextSourceMap} sourceMap An instance of `TextSourceMap` which corresponds to `text`. + * @param {import('./text-source-map.js').TextSourceMap} sourceMap An instance of `TextSourceMap` which corresponds to `text`. * @param {RegExp} pattern A regular expression to use as the replacement. * @param {string} replacement A replacement string that follows the format of the standard * JavaScript regular expression replacement string. diff --git a/ext/js/language/__mocks__/dictionary-importer-media-loader.js b/ext/js/language/__mocks__/dictionary-importer-media-loader.js index 96f0f6dd..ffda29b3 100644 --- a/ext/js/language/__mocks__/dictionary-importer-media-loader.js +++ b/ext/js/language/__mocks__/dictionary-importer-media-loader.js @@ -17,6 +17,7 @@ */ export class DictionaryImporterMediaLoader { + /** @type {import('dictionary-importer-media-loader').GetImageDetailsFunction} */ async getImageDetails(content) { // Placeholder values return {content, width: 100, height: 100}; diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index aa6d7ae6..2a2f4063 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -36,7 +36,7 @@ export class DictionaryImporter { } /** - * @param {DictionaryDatabase} dictionaryDatabase + * @param {import('./dictionary-database.js').DictionaryDatabase} dictionaryDatabase * @param {ArrayBuffer} archiveContent * @param {import('dictionary-importer').ImportDetails} details * @returns {Promise<import('dictionary-importer').ImportResult>} diff --git a/ext/js/language/dictionary-worker.js b/ext/js/language/dictionary-worker.js index 3e78a6ff..3119dd7b 100644 --- a/ext/js/language/dictionary-worker.js +++ b/ext/js/language/dictionary-worker.js @@ -157,6 +157,8 @@ export class DictionaryWorker { resolve(result2); } else { // If formatResult is not provided, the response is assumed to be the same type + // For some reason, eslint thinks the TResponse type is undefined + // eslint-disable-next-line jsdoc/no-undefined-types resolve(/** @type {TResponse} */ (/** @type {unknown} */ (result))); } } diff --git a/ext/js/language/sandbox/japanese-util.js b/ext/js/language/sandbox/japanese-util.js index f7f20b3b..4c9c46bd 100644 --- a/ext/js/language/sandbox/japanese-util.js +++ b/ext/js/language/sandbox/japanese-util.js @@ -466,7 +466,7 @@ export class JapaneseUtil { /** * @param {string} text - * @param {?TextSourceMap} [sourceMap] + * @param {?import('../../general/text-source-map.js').TextSourceMap} [sourceMap] * @returns {string} */ convertHalfWidthKanaToFullWidth(text, sourceMap=null) { @@ -513,7 +513,7 @@ export class JapaneseUtil { /** * @param {string} text - * @param {?TextSourceMap} sourceMap + * @param {?import('../../general/text-source-map.js').TextSourceMap} sourceMap * @returns {string} */ convertAlphabeticToKana(text, sourceMap=null) { @@ -676,7 +676,7 @@ export class JapaneseUtil { /** * @param {string} text * @param {boolean} fullCollapse - * @param {?TextSourceMap} [sourceMap] + * @param {?import('../../general/text-source-map.js').TextSourceMap} [sourceMap] * @returns {string} */ collapseEmphaticSequences(text, fullCollapse, sourceMap=null) { @@ -816,7 +816,7 @@ export class JapaneseUtil { /** * @param {string} text - * @param {?TextSourceMap} sourceMap + * @param {?import('../../general/text-source-map.js').TextSourceMap} sourceMap * @param {number} sourceMapStart * @returns {string} */ diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index b4d9a642..f6bcde8d 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -18,6 +18,7 @@ import {EventDispatcher, EventListenerCollection, clone, log} from '../core.js'; import {DocumentUtil} from '../dom/document-util.js'; +import {TextSourceElement} from '../dom/text-source-element.js'; import {yomitan} from '../yomitan.js'; /** diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 67cc53c6..c21b16b1 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -29,9 +29,9 @@ export class Translator { * @param {import('translator').ConstructorDetails} details The details for the class. */ constructor({japaneseUtil, database}) { - /** @type {JapaneseUtil} */ + /** @type {import('./sandbox/japanese-util.js').JapaneseUtil} */ this._japaneseUtil = japaneseUtil; - /** @type {DictionaryDatabase} */ + /** @type {import('./dictionary-database.js').DictionaryDatabase} */ this._database = database; /** @type {?Deinflector} */ this._deinflector = null; diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index 7b236790..0847d479 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -25,10 +25,10 @@ import {SimpleDOMParser} from '../dom/simple-dom-parser.js'; export class AudioDownloader { /** - * @param {{japaneseUtil: JapaneseUtil, requestBuilder: RequestBuilder}} details + * @param {{japaneseUtil: import('../language/sandbox/japanese-util.js').JapaneseUtil, requestBuilder: RequestBuilder}} details */ constructor({japaneseUtil, requestBuilder}) { - /** @type {JapaneseUtil} */ + /** @type {import('../language/sandbox/japanese-util.js').JapaneseUtil} */ this._japaneseUtil = japaneseUtil; /** @type {RequestBuilder} */ this._requestBuilder = requestBuilder; @@ -314,7 +314,7 @@ export class AudioDownloader { */ async _downloadAudioFromUrl(url, sourceType, idleTimeout) { let signal; - /** @type {?(done: boolean) => void} */ + /** @type {?import('request-builder.js').ProgressCallback} */ let onProgress = null; /** @type {?number} */ let idleTimer = null; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 50a50b1a..52c5f418 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -534,12 +534,11 @@ export class BackupController { // Exporting Dictionaries Database /** - * - * @param message - * @param isWarning + * @param {string} message + * @param {boolean} [isWarning] */ _databaseExportImportErrorMessage(message, isWarning=false) { - const errorMessageContainer = document.querySelector('#db-ops-error-report'); + const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report')); errorMessageContainer.style.display = 'block'; errorMessageContainer.textContent = message; @@ -553,15 +552,11 @@ export class BackupController { } /** - * - * @param root0 - * @param root0.totalRows - * @param root0.completedRows - * @param root0.done + * @param {{totalRows: number, completedRows: number, done: boolean}} details */ _databaseExportProgressCallback({totalRows, completedRows, done}) { console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); - const messageContainer = document.querySelector('#db-ops-progress-report'); + const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report')); messageContainer.style.display = 'block'; messageContainer.textContent = `Export Progress: ${completedRows} of ${totalRows} rows completed`; @@ -572,8 +567,8 @@ export class BackupController { } /** - * - * @param databaseName + * @param {string} databaseName + * @returns {Promise<Blob>} */ async _exportDatabase(databaseName) { const db = await new Dexie(databaseName).open(); @@ -592,7 +587,7 @@ export class BackupController { return; } - const errorMessageContainer = document.querySelector('#db-ops-error-report'); + const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report')); errorMessageContainer.style.display = 'none'; const date = new Date(Date.now()); @@ -616,15 +611,11 @@ export class BackupController { // Importing Dictionaries Database /** - * - * @param root0 - * @param root0.totalRows - * @param root0.completedRows - * @param root0.done + * @param {{totalRows: number, completedRows: number, done: boolean}} details */ _databaseImportProgressCallback({totalRows, completedRows, done}) { console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); - const messageContainer = document.querySelector('#db-ops-progress-report'); + const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report')); messageContainer.style.display = 'block'; messageContainer.style.color = '#4169e1'; messageContainer.textContent = `Import Progress: ${completedRows} of ${totalRows} rows completed`; @@ -637,9 +628,8 @@ export class BackupController { } /** - * - * @param databaseName - * @param file + * @param {string} databaseName + * @param {File} file */ async _importDatabase(databaseName, file) { await yomitan.api.purgeDatabase(); @@ -648,16 +638,13 @@ export class BackupController { yomitan.trigger('storageChanged'); } - /** - * - */ + /** */ _onSettingsImportDatabaseClick() { - document.querySelector('#settings-import-db').click(); + /** @type {HTMLElement} */ (document.querySelector('#settings-import-db')).click(); } /** - * - * @param e + * @param {Event} e */ async _onSettingsImportDatabaseChange(e) { if (this._settingsExportDatabaseToken !== null) { @@ -666,22 +653,23 @@ export class BackupController { return; } - const errorMessageContainer = document.querySelector('#db-ops-error-report'); + const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report')); errorMessageContainer.style.display = 'none'; - const files = e.target.files; - if (files.length === 0) { return; } + const element = /** @type {HTMLInputElement} */ (e.currentTarget); + const files = element.files; + if (files === null || files.length === 0) { return; } const pageExitPrevention = this._settingsController.preventPageExit(); const file = files[0]; - e.target.value = null; + element.value = ''; try { const token = {}; this._settingsExportDatabaseToken = token; await this._importDatabase(this._dictionariesDatabaseName, file); } catch (error) { console.log(error); - const messageContainer = document.querySelector('#db-ops-progress-report'); + const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report')); messageContainer.style.color = 'red'; this._databaseExportImportErrorMessage('Encountered errors when importing. Please restart the browser and try again. If it continues to fail, reinstall Yomitan and import dictionaries one-by-one.'); } finally { diff --git a/ext/js/pages/settings/recommended-permissions-controller.js b/ext/js/pages/settings/recommended-permissions-controller.js index e04dbdf7..b19311aa 100644 --- a/ext/js/pages/settings/recommended-permissions-controller.js +++ b/ext/js/pages/settings/recommended-permissions-controller.js @@ -19,13 +19,21 @@ import {EventListenerCollection} from '../../core.js'; export class RecommendedPermissionsController { + /** + * @param {import('./settings-controller.js').SettingsController} settingsController + */ constructor(settingsController) { + /** @type {import('./settings-controller.js').SettingsController} */ this._settingsController = settingsController; + /** @type {?NodeListOf<HTMLInputElement>} */ this._originToggleNodes = null; + /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); + /** @type {?HTMLElement} */ this._errorContainer = null; } + /** */ async prepare() { this._originToggleNodes = document.querySelectorAll('.recommended-permissions-toggle'); this._errorContainer = document.querySelector('#recommended-permissions-error'); @@ -39,35 +47,53 @@ export class RecommendedPermissionsController { // Private + /** + * @param {import('settings-controller').PermissionsChangedEvent} details + */ _onPermissionsChanged({permissions}) { this._eventListeners.removeAllEventListeners(); const originsSet = new Set(permissions.origins); - for (const node of this._originToggleNodes) { - node.checked = originsSet.has(node.dataset.origin); + if (this._originToggleNodes !== null) { + for (const node of this._originToggleNodes) { + const {origin} = node.dataset; + node.checked = typeof origin === 'string' && originsSet.has(origin); + } } } + /** + * @param {Event} e + */ _onOriginToggleChange(e) { - const node = e.currentTarget; + const node = /** @type {HTMLInputElement} */ (e.currentTarget); const value = node.checked; node.checked = !value; const {origin} = node.dataset; + if (typeof origin !== 'string') { return; } this._setOriginPermissionEnabled(origin, value); } + /** */ async _updatePermissions() { const permissions = await this._settingsController.permissionsUtil.getAllPermissions(); this._onPermissionsChanged({permissions}); } + /** + * @param {string} origin + * @param {boolean} enabled + * @returns {Promise<boolean>} + */ async _setOriginPermissionEnabled(origin, enabled) { let added = false; try { added = await this._settingsController.permissionsUtil.setPermissionsGranted({origins: [origin]}, enabled); } catch (e) { - this._errorContainer.hidden = false; - this._errorContainer.textContent = e.message; + if (this._errorContainer !== null) { + this._errorContainer.hidden = false; + this._errorContainer.textContent = e instanceof Error ? e.message : `${e}`; + } } if (!added) { return false; } await this._updatePermissions(); diff --git a/types/ext/api.d.ts b/types/ext/api.d.ts index 19a62c1c..6b7b4b19 100644 --- a/types/ext/api.d.ts +++ b/types/ext/api.d.ts @@ -444,6 +444,18 @@ export type LoadExtensionScriptsDetails = { export type LoadExtensionScriptsResult = void; +// openCrossFramePort + +export type OpenCrossFramePortDetails = { + targetTabId: number; + targetFrameId: number; +}; + +export type OpenCrossFramePortResult = { + targetTabId: number; + targetFrameId: number; +}; + // requestBackendReadySignal export type RequestBackendReadySignalDetails = Record<string, never>; diff --git a/types/ext/request-builder.d.ts b/types/ext/request-builder.d.ts index 0acf5ede..8f375754 100644 --- a/types/ext/request-builder.d.ts +++ b/types/ext/request-builder.d.ts @@ -19,3 +19,5 @@ export type FetchEventListeners = { onBeforeSendHeaders: ((details: chrome.webRequest.WebRequestHeadersDetails) => (chrome.webRequest.BlockingResponse | void)) | null; onErrorOccurred: ((details: chrome.webRequest.WebResponseErrorDetails) => void) | null; }; + +export type ProgressCallback = (complete: boolean) => void; -- cgit v1.2.3