From 4da4827bcbcdd1ef163f635d9b29416ff272b0bb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 12:48:14 -0500 Subject: Add JSDoc type annotations to project (rebased) --- ext/js/comm/api.js | 356 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 309 insertions(+), 47 deletions(-) (limited to 'ext/js/comm/api.js') diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 05f95464..62dc98b1 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -16,184 +16,428 @@ * along with this program. If not, see . */ -import {deferPromise, deserializeError, isObject} from '../core.js'; +import {deferPromise} from '../core.js'; +import {ExtensionError} from '../core/extension-error.js'; export class API { + /** + * @param {import('../yomitan.js').Yomitan} yomitan + */ constructor(yomitan) { + /** @type {import('../yomitan.js').Yomitan} */ this._yomitan = yomitan; } + /** + * @param {import('api').OptionsGetDetails['optionsContext']} optionsContext + * @returns {Promise} + */ optionsGet(optionsContext) { - return this._invoke('optionsGet', {optionsContext}); + /** @type {import('api').OptionsGetDetails} */ + const details = {optionsContext}; + return this._invoke('optionsGet', details); } + /** + * @returns {Promise} + */ optionsGetFull() { return this._invoke('optionsGetFull'); } + /** + * @param {import('api').TermsFindDetails['text']} text + * @param {import('api').TermsFindDetails['details']} details + * @param {import('api').TermsFindDetails['optionsContext']} optionsContext + * @returns {Promise} + */ termsFind(text, details, optionsContext) { - return this._invoke('termsFind', {text, details, optionsContext}); - } - + /** @type {import('api').TermsFindDetails} */ + const details2 = {text, details, optionsContext}; + return this._invoke('termsFind', details2); + } + + /** + * @param {import('api').ParseTextDetails['text']} text + * @param {import('api').ParseTextDetails['optionsContext']} optionsContext + * @param {import('api').ParseTextDetails['scanLength']} scanLength + * @param {import('api').ParseTextDetails['useInternalParser']} useInternalParser + * @param {import('api').ParseTextDetails['useMecabParser']} useMecabParser + * @returns {Promise} + */ parseText(text, optionsContext, scanLength, useInternalParser, useMecabParser) { - return this._invoke('parseText', {text, optionsContext, scanLength, useInternalParser, useMecabParser}); + /** @type {import('api').ParseTextDetails} */ + const details = {text, optionsContext, scanLength, useInternalParser, useMecabParser}; + return this._invoke('parseText', details); } + /** + * @param {import('api').KanjiFindDetails['text']} text + * @param {import('api').KanjiFindDetails['optionsContext']} optionsContext + * @returns {Promise} + */ kanjiFind(text, optionsContext) { - return this._invoke('kanjiFind', {text, optionsContext}); + /** @type {import('api').KanjiFindDetails} */ + const details = {text, optionsContext}; + return this._invoke('kanjiFind', details); } + /** + * @returns {Promise} + */ isAnkiConnected() { return this._invoke('isAnkiConnected'); } + /** + * @returns {Promise} + */ getAnkiConnectVersion() { return this._invoke('getAnkiConnectVersion'); } + /** + * @param {import('api').AddAnkiNoteDetails['note']} note + * @returns {Promise} + */ addAnkiNote(note) { - return this._invoke('addAnkiNote', {note}); + /** @type {import('api').AddAnkiNoteDetails} */ + const details = {note}; + return this._invoke('addAnkiNote', details); } + /** + * @param {import('api').GetAnkiNoteInfoDetails['notes']} notes + * @param {import('api').GetAnkiNoteInfoDetails['fetchAdditionalInfo']} fetchAdditionalInfo + * @returns {Promise} + */ getAnkiNoteInfo(notes, fetchAdditionalInfo) { - return this._invoke('getAnkiNoteInfo', {notes, fetchAdditionalInfo}); - } - + /** @type {import('api').GetAnkiNoteInfoDetails} */ + const details = {notes, fetchAdditionalInfo}; + return this._invoke('getAnkiNoteInfo', details); + } + + /** + * @param {import('api').InjectAnkiNoteMediaDetails['timestamp']} timestamp + * @param {import('api').InjectAnkiNoteMediaDetails['definitionDetails']} definitionDetails + * @param {import('api').InjectAnkiNoteMediaDetails['audioDetails']} audioDetails + * @param {import('api').InjectAnkiNoteMediaDetails['screenshotDetails']} screenshotDetails + * @param {import('api').InjectAnkiNoteMediaDetails['clipboardDetails']} clipboardDetails + * @param {import('api').InjectAnkiNoteMediaDetails['dictionaryMediaDetails']} dictionaryMediaDetails + * @returns {Promise} + */ injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) { - return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}); + /** @type {import('api').InjectAnkiNoteMediaDetails} */ + const details = {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}; + return this._invoke('injectAnkiNoteMedia', details); } + /** + * @param {import('api').NoteViewDetails['noteId']} noteId + * @param {import('api').NoteViewDetails['mode']} mode + * @param {import('api').NoteViewDetails['allowFallback']} allowFallback + * @returns {Promise} + */ noteView(noteId, mode, allowFallback) { - return this._invoke('noteView', {noteId, mode, allowFallback}); + /** @type {import('api').NoteViewDetails} */ + const details = {noteId, mode, allowFallback}; + return this._invoke('noteView', details); } + /** + * @param {import('api').SuspendAnkiCardsForNoteDetails['noteId']} noteId + * @returns {Promise} + */ suspendAnkiCardsForNote(noteId) { - return this._invoke('suspendAnkiCardsForNote', {noteId}); + /** @type {import('api').SuspendAnkiCardsForNoteDetails} */ + const details = {noteId}; + return this._invoke('suspendAnkiCardsForNote', details); } + /** + * @param {import('api').GetTermAudioInfoListDetails['source']} source + * @param {import('api').GetTermAudioInfoListDetails['term']} term + * @param {import('api').GetTermAudioInfoListDetails['reading']} reading + * @returns {Promise} + */ getTermAudioInfoList(source, term, reading) { - return this._invoke('getTermAudioInfoList', {source, term, reading}); + /** @type {import('api').GetTermAudioInfoListDetails} */ + const details = {source, term, reading}; + return this._invoke('getTermAudioInfoList', details); } + /** + * @param {import('api').CommandExecDetails['command']} command + * @param {import('api').CommandExecDetails['params']} [params] + * @returns {Promise} + */ commandExec(command, params) { - return this._invoke('commandExec', {command, params}); + /** @type {import('api').CommandExecDetails} */ + const details = {command, params}; + return this._invoke('commandExec', details); } + /** + * @param {import('api').SendMessageToFrameDetails['frameId']} frameId + * @param {import('api').SendMessageToFrameDetails['action']} action + * @param {import('api').SendMessageToFrameDetails['params']} [params] + * @returns {Promise} + */ sendMessageToFrame(frameId, action, params) { - return this._invoke('sendMessageToFrame', {frameId, action, params}); + /** @type {import('api').SendMessageToFrameDetails} */ + const details = {frameId, action, params}; + return this._invoke('sendMessageToFrame', details); } + /** + * @param {import('api').BroadcastTabDetails['action']} action + * @param {import('api').BroadcastTabDetails['params']} params + * @returns {Promise} + */ broadcastTab(action, params) { - return this._invoke('broadcastTab', {action, params}); + /** @type {import('api').BroadcastTabDetails} */ + const details = {action, params}; + return this._invoke('broadcastTab', details); } + /** + * @returns {Promise} + */ frameInformationGet() { return this._invoke('frameInformationGet'); } + /** + * @param {import('api').InjectStylesheetDetails['type']} type + * @param {import('api').InjectStylesheetDetails['value']} value + * @returns {Promise} + */ injectStylesheet(type, value) { - return this._invoke('injectStylesheet', {type, value}); + /** @type {import('api').InjectStylesheetDetails} */ + const details = {type, value}; + return this._invoke('injectStylesheet', details); } + /** + * @param {import('api').GetStylesheetContentDetails['url']} url + * @returns {Promise} + */ getStylesheetContent(url) { - return this._invoke('getStylesheetContent', {url}); + /** @type {import('api').GetStylesheetContentDetails} */ + const details = {url}; + return this._invoke('getStylesheetContent', details); } + /** + * @returns {Promise} + */ getEnvironmentInfo() { return this._invoke('getEnvironmentInfo'); } + /** + * @returns {Promise} + */ clipboardGet() { return this._invoke('clipboardGet'); } + /** + * @returns {Promise} + */ getDisplayTemplatesHtml() { return this._invoke('getDisplayTemplatesHtml'); } + /** + * @returns {Promise} + */ getZoom() { return this._invoke('getZoom'); } + /** + * @returns {Promise} + */ getDefaultAnkiFieldTemplates() { return this._invoke('getDefaultAnkiFieldTemplates'); } + /** + * @returns {Promise} + */ getDictionaryInfo() { return this._invoke('getDictionaryInfo'); } + /** + * @returns {Promise} + */ purgeDatabase() { return this._invoke('purgeDatabase'); } + /** + * @param {import('api').GetMediaDetails['targets']} targets + * @returns {Promise} + */ getMedia(targets) { - return this._invoke('getMedia', {targets}); + /** @type {import('api').GetMediaDetails} */ + const details = {targets}; + return this._invoke('getMedia', details); } + /** + * @param {import('api').LogDetails['error']} error + * @param {import('api').LogDetails['level']} level + * @param {import('api').LogDetails['context']} context + * @returns {Promise} + */ log(error, level, context) { - return this._invoke('log', {error, level, context}); + /** @type {import('api').LogDetails} */ + const details = {error, level, context}; + return this._invoke('log', details); } + /** + * @returns {Promise} + */ logIndicatorClear() { return this._invoke('logIndicatorClear'); } + /** + * @param {import('api').ModifySettingsDetails['targets']} targets + * @param {import('api').ModifySettingsDetails['source']} source + * @returns {Promise} + */ modifySettings(targets, source) { - return this._invoke('modifySettings', {targets, source}); + const details = {targets, source}; + return this._invoke('modifySettings', details); } + /** + * @param {import('api').GetSettingsDetails['targets']} targets + * @returns {Promise} + */ getSettings(targets) { - return this._invoke('getSettings', {targets}); + /** @type {import('api').GetSettingsDetails} */ + const details = {targets}; + return this._invoke('getSettings', details); } + /** + * @param {import('api').SetAllSettingsDetails['value']} value + * @param {import('api').SetAllSettingsDetails['source']} source + * @returns {Promise} + */ setAllSettings(value, source) { - return this._invoke('setAllSettings', {value, source}); + /** @type {import('api').SetAllSettingsDetails} */ + const details = {value, source}; + return this._invoke('setAllSettings', details); } + /** + * @param {import('api').GetOrCreateSearchPopupDetails} details + * @returns {Promise} + */ getOrCreateSearchPopup(details) { - return this._invoke('getOrCreateSearchPopup', isObject(details) ? details : {}); + return this._invoke('getOrCreateSearchPopup', details); } + /** + * @param {import('api').IsTabSearchPopupDetails['tabId']} tabId + * @returns {Promise} + */ isTabSearchPopup(tabId) { - return this._invoke('isTabSearchPopup', {tabId}); + /** @type {import('api').IsTabSearchPopupDetails} */ + const details = {tabId}; + return this._invoke('isTabSearchPopup', details); } + /** + * @param {import('api').TriggerDatabaseUpdatedDetails['type']} type + * @param {import('api').TriggerDatabaseUpdatedDetails['cause']} cause + * @returns {Promise} + */ triggerDatabaseUpdated(type, cause) { - return this._invoke('triggerDatabaseUpdated', {type, cause}); + /** @type {import('api').TriggerDatabaseUpdatedDetails} */ + const details = {type, cause}; + return this._invoke('triggerDatabaseUpdated', details); } + /** + * @returns {Promise} + */ testMecab() { - return this._invoke('testMecab', {}); + return this._invoke('testMecab'); } + /** + * @param {import('api').TextHasJapaneseCharactersDetails['text']} text + * @returns {Promise} + */ textHasJapaneseCharacters(text) { - return this._invoke('textHasJapaneseCharacters', {text}); + /** @type {import('api').TextHasJapaneseCharactersDetails} */ + const details = {text}; + return this._invoke('textHasJapaneseCharacters', details); } + /** + * @param {import('api').GetTermFrequenciesDetails['termReadingList']} termReadingList + * @param {import('api').GetTermFrequenciesDetails['dictionaries']} dictionaries + * @returns {Promise} + */ getTermFrequencies(termReadingList, dictionaries) { - return this._invoke('getTermFrequencies', {termReadingList, dictionaries}); + /** @type {import('api').GetTermFrequenciesDetails} */ + const details = {termReadingList, dictionaries}; + return this._invoke('getTermFrequencies', details); } + /** + * @param {import('api').FindAnkiNotesDetails['query']} query + * @returns {Promise} + */ findAnkiNotes(query) { - return this._invoke('findAnkiNotes', {query}); + /** @type {import('api').FindAnkiNotesDetails} */ + const details = {query}; + return this._invoke('findAnkiNotes', details); } + /** + * @param {import('api').LoadExtensionScriptsDetails['files']} files + * @returns {Promise} + */ loadExtensionScripts(files) { - return this._invoke('loadExtensionScripts', {files}); + /** @type {import('api').LoadExtensionScriptsDetails} */ + const details = {files}; + return this._invoke('loadExtensionScripts', details); } + /** + * + * @param targetTabId + * @param targetFrameId + */ openCrossFramePort(targetTabId, targetFrameId) { return this._invoke('openCrossFramePort', {targetTabId, targetFrameId}); } // Utilities - _createActionPort(timeout=5000) { + /** + * @param {number} timeout + * @returns {Promise} + */ + _createActionPort(timeout) { return new Promise((resolve, reject) => { + /** @type {?number} */ let timer = null; const portDetails = deferPromise(); + /** + * @param {chrome.runtime.Port} port + */ const onConnect = async (port) => { try { const {name: expectedName, id: expectedId} = await portDetails.promise; @@ -210,6 +454,9 @@ export class API { resolve(port); }; + /** + * @param {Error} e + */ const onError = (e) => { if (timer !== null) { clearTimeout(timer); @@ -227,14 +474,24 @@ export class API { }); } - _invokeWithProgress(action, params, onProgress, timeout=5000) { + /** + * @template [TReturn=unknown] + * @param {string} action + * @param {import('core').SerializableObject} params + * @param {?(...args: unknown[]) => void} onProgress0 + * @param {number} [timeout] + * @returns {Promise} + */ + _invokeWithProgress(action, params, onProgress0, timeout=5000) { return new Promise((resolve, reject) => { + /** @type {?chrome.runtime.Port} */ let port = null; - if (typeof onProgress !== 'function') { - onProgress = () => {}; - } + const onProgress = typeof onProgress0 === 'function' ? onProgress0 : () => {}; + /** + * @param {import('backend').InvokeWithProgressResponseMessage} message + */ const onMessage = (message) => { switch (message.type) { case 'progress': @@ -250,7 +507,7 @@ export class API { break; case 'error': cleanup(); - reject(deserializeError(message.data)); + reject(ExtensionError.deserialize(message.data)); break; } }; @@ -267,7 +524,6 @@ export class API { port.disconnect(); port = null; } - onProgress = null; }; (async () => { @@ -281,20 +537,23 @@ export class API { const fragmentSize = 1e7; // 10 MB for (let i = 0, ii = messageString.length; i < ii; i += fragmentSize) { const data = messageString.substring(i, i + fragmentSize); - port.postMessage({action: 'fragment', data}); + port.postMessage(/** @type {import('backend').InvokeWithProgressRequestFragmentMessage} */ ({action: 'fragment', data})); } - port.postMessage({action: 'invoke'}); + port.postMessage(/** @type {import('backend').InvokeWithProgressRequestInvokeMessage} */ ({action: 'invoke'})); } catch (e) { cleanup(); reject(e); - } finally { - action = null; - params = null; } })(); }); } + /** + * @template [TReturn=unknown] + * @param {string} action + * @param {import('core').SerializableObject} [params] + * @returns {Promise} + */ _invoke(action, params={}) { const data = {action, params}; return new Promise((resolve, reject) => { @@ -303,7 +562,7 @@ export class API { this._checkLastError(chrome.runtime.lastError); if (response !== null && typeof response === 'object') { if (typeof response.error !== 'undefined') { - reject(deserializeError(response.error)); + reject(ExtensionError.deserialize(response.error)); } else { resolve(response.result); } @@ -318,7 +577,10 @@ export class API { }); } - _checkLastError() { + /** + * @param {chrome.runtime.LastError|undefined} _ignore + */ + _checkLastError(_ignore) { // NOP } } -- cgit v1.2.3 From 7aed9a371b0d74c0d75179a08068e8935b76d780 Mon Sep 17 00:00:00 2001 From: toasted-nutbread 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/comm/api.js') 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} */ @@ -895,13 +895,7 @@ export class Backend { } } - /** - * - * @param root0 - * @param root0.targetTabId - * @param root0.targetFrameId - * @param sender - */ + /** @type {import('api').Handler} */ _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 . */ -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} + */ async getText(useRichText) { - return this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}}); + return await this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}}); } + /** + * @returns {Promise} + */ 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} 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} */ 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} 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} */ 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} */ 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} + */ 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; 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 From 14d12f6ba20b837a04c638b935773f3120e194ff Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 19:33:01 -0500 Subject: Update timer types and such --- dev/jsconfig.json | 2 +- ext/js/app/frontend.js | 11 +++++++++-- ext/js/background/backend.js | 4 ++-- ext/js/background/offscreen.js | 4 +++- ext/js/comm/api.js | 2 +- ext/js/comm/clipboard-monitor.js | 2 +- ext/js/comm/cross-frame-api.js | 21 +++++++++++++-------- ext/js/comm/frame-ancestry-handler.js | 2 +- ext/js/comm/frame-client.js | 2 +- ext/js/comm/mecab.js | 2 +- ext/js/core.js | 2 +- ext/js/display/display-audio.js | 2 +- ext/js/display/display-notification.js | 2 +- ext/js/display/display.js | 2 +- ext/js/display/element-overflow-controller.js | 8 ++++---- ext/js/display/option-toggle-hotkey-handler.js | 2 +- ext/js/display/search-display-controller.js | 2 +- ext/js/dom/document-util.js | 2 +- ext/js/dom/panel-element.js | 2 +- ext/js/language/text-scanner.js | 6 +++--- ext/js/media/audio-downloader.js | 2 +- ext/js/pages/settings/popup-preview-frame.js | 2 +- test/jsconfig.json | 2 +- types/ext/core.d.ts | 2 ++ types/ext/cross-frame-api.d.ts | 2 +- types/ext/offscreen.d.ts | 2 +- types/other/globals.d.ts | 22 ++++++++++++++++++++++ types/other/web-set-timeout.d.ts | 24 ------------------------ 28 files changed, 77 insertions(+), 63 deletions(-) create mode 100644 types/other/globals.d.ts delete mode 100644 types/other/web-set-timeout.d.ts (limited to 'ext/js/comm/api.js') diff --git a/dev/jsconfig.json b/dev/jsconfig.json index 518f97ad..d4efe694 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -71,7 +71,7 @@ "../ext/js/language/translator.js", "../ext/js/media/media-util.js", "../types/dev/**/*.ts", - "../types/other/web-set-timeout.d.ts" + "../types/other/globals.d.ts" ], "exclude": [ "../node_modules" diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index 628c504e..e1f8d8c9 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -99,7 +99,7 @@ export class Frontend { this._popupEventListeners = new EventListenerCollection(); /** @type {?import('core').TokenObject} */ this._updatePopupToken = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._clearSelectionTimer = null; /** @type {boolean} */ this._isPointerOverPopup = false; @@ -840,7 +840,7 @@ export class Frontend { */ async _waitForFrontendReady(frameId, timeout) { return new Promise((resolve, reject) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timeoutId = null; const cleanup = () => { @@ -973,6 +973,13 @@ export class Frontend { await yomitan.api.loadExtensionScripts([ '/js/accessibility/google-docs-util.js' ]); + this._prepareGoogleDocs2(); + } + + /** + * @returns {Promise} + */ + async _prepareGoogleDocs2() { if (typeof GoogleDocsUtil === 'undefined') { return; } DocumentUtil.registerGetRangeFromPointHandler(GoogleDocsUtil.getRangeFromPoint.bind(GoogleDocsUtil)); } diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 44f5a42d..f1a47e7f 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -138,7 +138,7 @@ export class Backend { /** @type {?string} */ this._defaultBrowserActionTitle = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._badgePrepareDelayTimer = null; /** @type {?import('log').LogLevel} */ this._logErrorLevel = null; @@ -1981,7 +1981,7 @@ export class Backend { */ _waitUntilTabFrameIsReady(tabId, frameId, timeout=null) { return new Promise((resolve, reject) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; /** @type {?import('extension').ChromeRuntimeOnMessageCallback} */ let onMessage = (message, sender) => { diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 45345c01..8da661ad 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -51,7 +51,7 @@ export class Offscreen { }); /** @type {import('offscreen').MessageHandlerMap} */ - this._messageHandlers = new Map([ + const messageHandlers = new Map([ ['clipboardGetTextOffscreen', {async: true, handler: this._getTextHandler.bind(this)}], ['clipboardGetImageOffscreen', {async: true, handler: this._getImageHandler.bind(this)}], ['clipboardSetBrowserOffscreen', {async: false, handler: this._setClipboardBrowser.bind(this)}], @@ -65,6 +65,8 @@ export class Offscreen { ['getTermFrequenciesOffscreen', {async: true, handler: this._getTermFrequenciesHandler.bind(this)}], ['clearDatabaseCachesOffscreen', {async: false, handler: this._clearDatabaseCachesHandler.bind(this)}] ]); + /** @type {import('offscreen').MessageHandlerMap} */ + this._messageHandlers = messageHandlers; const onMessage = this._onMessage.bind(this); chrome.runtime.onMessage.addListener(onMessage); diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 0cfdba59..26218595 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -431,7 +431,7 @@ export class API { */ _createActionPort(timeout) { return new Promise((resolve, reject) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; const portDetails = deferPromise(); diff --git a/ext/js/comm/clipboard-monitor.js b/ext/js/comm/clipboard-monitor.js index 06e95438..3b3a56a9 100644 --- a/ext/js/comm/clipboard-monitor.js +++ b/ext/js/comm/clipboard-monitor.js @@ -31,7 +31,7 @@ export class ClipboardMonitor extends EventDispatcher { this._japaneseUtil = japaneseUtil; /** @type {import('clipboard-monitor').ClipboardReaderLike} */ this._clipboardReader = clipboardReader; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._timerId = null; /** @type {?import('core').TokenObject} */ this._timerToken = null; diff --git a/ext/js/comm/cross-frame-api.js b/ext/js/comm/cross-frame-api.js index 34f3f36a..3ac38cf2 100644 --- a/ext/js/comm/cross-frame-api.js +++ b/ext/js/comm/cross-frame-api.js @@ -25,14 +25,14 @@ import {yomitan} from '../yomitan.js'; */ class CrossFrameAPIPort extends EventDispatcher { /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @param {chrome.runtime.Port} port * @param {import('core').MessageHandlerMap} messageHandlers */ constructor(otherTabId, otherFrameId, port, messageHandlers) { super(); - /** @type {?number} */ + /** @type {number} */ this._otherTabId = otherTabId; /** @type {number} */ this._otherFrameId = otherFrameId; @@ -48,7 +48,7 @@ class CrossFrameAPIPort extends EventDispatcher { this._eventListeners = new EventListenerCollection(); } - /** @type {?number} */ + /** @type {number} */ get otherTabId() { return this._otherTabId; } @@ -299,7 +299,7 @@ export class CrossFrameAPI { this._ackTimeout = 3000; // 3 seconds /** @type {number} */ this._responseTimeout = 10000; // 10 seconds - /** @type {Map>} */ + /** @type {Map>} */ this._commPorts = new Map(); /** @type {import('core').MessageHandlerMap} */ this._messageHandlers = new Map(); @@ -339,7 +339,12 @@ export class CrossFrameAPI { * @returns {Promise} */ async invokeTab(targetTabId, targetFrameId, action, params) { - if (typeof targetTabId !== 'number') { targetTabId = this._tabId; } + if (typeof targetTabId !== 'number') { + targetTabId = this._tabId; + if (typeof targetTabId !== 'number') { + throw new Error('Unknown target tab id for invocation'); + } + } const commPort = await this._getOrCreateCommPort(targetTabId, targetFrameId); return await commPort.invoke(action, params, this._ackTimeout, this._responseTimeout); } @@ -405,7 +410,7 @@ export class CrossFrameAPI { } /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @returns {Promise} */ @@ -420,7 +425,7 @@ export class CrossFrameAPI { return await this._createCommPort(otherTabId, otherFrameId); } /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @returns {Promise} */ @@ -438,7 +443,7 @@ export class CrossFrameAPI { } /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @param {chrome.runtime.Port} port * @returns {CrossFrameAPIPort} diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index 49c96c22..687ec368 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -122,7 +122,7 @@ export class FrameAncestryHandler { const responseMessageId = `${this._responseMessageIdBase}${uniqueId}`; /** @type {number[]} */ const results = []; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; const cleanup = () => { diff --git a/ext/js/comm/frame-client.js b/ext/js/comm/frame-client.js index 1519cf7f..8aa8c6d6 100644 --- a/ext/js/comm/frame-client.js +++ b/ext/js/comm/frame-client.js @@ -83,7 +83,7 @@ export class FrameClient { _connectInternal(frame, targetOrigin, hostFrameId, setupFrame, timeout) { return new Promise((resolve, reject) => { const tokenMap = new Map(); - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; const deferPromiseDetails = /** @type {import('core').DeferredPromiseDetails} */ (deferPromise()); const frameLoadedPromise = deferPromiseDetails.promise; diff --git a/ext/js/comm/mecab.js b/ext/js/comm/mecab.js index 072b42f3..0a87463b 100644 --- a/ext/js/comm/mecab.js +++ b/ext/js/comm/mecab.js @@ -31,7 +31,7 @@ export class Mecab { this._port = null; /** @type {number} */ this._sequence = 0; - /** @type {Map void, reject: (reason?: unknown) => void, timer: number}>} */ + /** @type {Map void, reject: (reason?: unknown) => void, timer: import('core').Timeout}>} */ this._invocations = new Map(); /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); diff --git a/ext/js/core.js b/ext/js/core.js index cdac2020..a53d5572 100644 --- a/ext/js/core.js +++ b/ext/js/core.js @@ -307,7 +307,7 @@ export function promiseAnimationFrame(timeout) { return; } - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; /** @type {?number} */ let frameRequest = null; diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index 3fbfc3c8..3576decb 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -36,7 +36,7 @@ export class DisplayAudio { this._playbackVolume = 1.0; /** @type {boolean} */ this._autoPlay = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._autoPlayAudioTimer = null; /** @type {number} */ this._autoPlayAudioDelay = 400; diff --git a/ext/js/display/display-notification.js b/ext/js/display/display-notification.js index b3f20700..d1cfa537 100644 --- a/ext/js/display/display-notification.js +++ b/ext/js/display/display-notification.js @@ -34,7 +34,7 @@ export class DisplayNotification { this._closeButton = /** @type {HTMLElement} */ (node.querySelector('.footer-notification-close-button')); /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._closeTimer = null; } diff --git a/ext/js/display/display.js b/ext/js/display/display.js index f14291d1..6e1450c3 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -117,7 +117,7 @@ export class Display extends EventDispatcher { this._queryOffset = 0; /** @type {HTMLElement} */ this._progressIndicator = /** @type {HTMLElement} */ (document.querySelector('#progress-indicator')); - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._progressIndicatorTimer = null; /** @type {DynamicProperty} */ this._progressIndicatorVisible = new DynamicProperty(false); diff --git a/ext/js/display/element-overflow-controller.js b/ext/js/display/element-overflow-controller.js index 1d2c808f..35277fa5 100644 --- a/ext/js/display/element-overflow-controller.js +++ b/ext/js/display/element-overflow-controller.js @@ -22,7 +22,7 @@ export class ElementOverflowController { constructor() { /** @type {Element[]} */ this._elements = []; - /** @type {?number} */ + /** @type {?(number|import('core').Timeout)} */ this._checkTimer = null; /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); @@ -154,7 +154,7 @@ export class ElementOverflowController { /** * @param {() => void} callback * @param {number} timeout - * @returns {number} + * @returns {number|import('core').Timeout} */ _requestIdleCallback(callback, timeout) { if (typeof requestIdleCallback === 'function') { @@ -165,11 +165,11 @@ export class ElementOverflowController { } /** - * @param {number} handle + * @param {number|import('core').Timeout} handle */ _cancelIdleCallback(handle) { if (typeof cancelIdleCallback === 'function') { - cancelIdleCallback(handle); + cancelIdleCallback(/** @type {number} */ (handle)); } else { clearTimeout(handle); } diff --git a/ext/js/display/option-toggle-hotkey-handler.js b/ext/js/display/option-toggle-hotkey-handler.js index 531e208d..edd7de5b 100644 --- a/ext/js/display/option-toggle-hotkey-handler.js +++ b/ext/js/display/option-toggle-hotkey-handler.js @@ -29,7 +29,7 @@ export class OptionToggleHotkeyHandler { this._display = display; /** @type {?import('./display-notification.js').DisplayNotification} */ this._notification = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._notificationHideTimer = null; /** @type {number} */ this._notificationHideTimeout = 5000; diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index b93757c2..98a4666b 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -63,7 +63,7 @@ export class SearchDisplayController { this._wanakanaBound = false; /** @type {boolean} */ this._introVisible = true; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._introAnimationTimer = null; /** @type {boolean} */ this._clipboardMonitorEnabled = false; diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index cf58d39f..549a8195 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -360,7 +360,7 @@ export class DocumentUtil { /** * Adds a fullscreen change event listener. This function handles all of the browser-specific variants. * @param {EventListener} onFullscreenChanged The event callback. - * @param {?EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. + * @param {?import('../core.js').EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. */ static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection=null) { const target = document; diff --git a/ext/js/dom/panel-element.js b/ext/js/dom/panel-element.js index 314eb2fd..748c3a36 100644 --- a/ext/js/dom/panel-element.js +++ b/ext/js/dom/panel-element.js @@ -37,7 +37,7 @@ export class PanelElement extends EventDispatcher { this._mutationObserver = null; /** @type {boolean} */ this._visible = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._closeTimer = null; } diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index f6bcde8d..d1b033e6 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -120,7 +120,7 @@ export class TextScanner extends EventDispatcher { /** @type {boolean} */ this._preventNextClickScan = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._preventNextClickScanTimer = null; /** @type {number} */ this._preventNextClickScanTimerDuration = 50; @@ -145,7 +145,7 @@ export class TextScanner extends EventDispatcher { /** @type {boolean} */ this._canClearSelection = true; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._textSelectionTimer = null; /** @type {boolean} */ this._yomitanIsChangingTextSelectionNow = false; @@ -995,7 +995,7 @@ export class TextScanner extends EventDispatcher { async _scanTimerWait() { const delay = this._delay; const promise = /** @type {Promise} */ (new Promise((resolve) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timeout = setTimeout(() => { timeout = null; resolve(true); diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index 0847d479..e041cc67 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -316,7 +316,7 @@ export class AudioDownloader { let signal; /** @type {?import('request-builder.js').ProgressCallback} */ let onProgress = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let idleTimer = null; if (typeof idleTimeout === 'number') { const abortController = new AbortController(); diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index bb00829f..dab21f4d 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -43,7 +43,7 @@ export class PopupPreviewFrame { this._apiOptionsGetOld = null; /** @type {boolean} */ this._popupShown = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._themeChangeTimeout = null; /** @type {?import('text-source').TextSource} */ this._textSource = null; diff --git a/test/jsconfig.json b/test/jsconfig.json index 261ec345..934aab81 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -31,7 +31,7 @@ "../ext/**/*.js", "../types/ext/**/*.ts", "../types/dev/**/*.ts", - "../types/other/web-set-timeout.d.ts" + "../types/other/globals.d.ts" ], "exclude": [ "../node_modules", diff --git a/types/ext/core.d.ts b/types/ext/core.d.ts index ce3a09f0..b83e6a74 100644 --- a/types/ext/core.d.ts +++ b/types/ext/core.d.ts @@ -100,3 +100,5 @@ export type MessageHandlerDetails = { export type MessageHandlerMap = Map; export type MessageHandlerArray = [key: string, handlerDetails: MessageHandlerDetails][]; + +export type Timeout = number | NodeJS.Timeout; diff --git a/types/ext/cross-frame-api.d.ts b/types/ext/cross-frame-api.d.ts index 5cedbec9..88ce59a7 100644 --- a/types/ext/cross-frame-api.d.ts +++ b/types/ext/cross-frame-api.d.ts @@ -50,5 +50,5 @@ export type Invocation = { responseTimeout: number; action: string; ack: boolean; - timer: number | null; + timer: Core.Timeout | null; }; diff --git a/types/ext/offscreen.d.ts b/types/ext/offscreen.d.ts index 7dd64d1e..ddf7eadc 100644 --- a/types/ext/offscreen.d.ts +++ b/types/ext/offscreen.d.ts @@ -112,4 +112,4 @@ export type MessageHandler< details: MessageDetailsMap[TMessage], ) => (TIsAsync extends true ? Promise> : MessageReturn); -export type MessageHandlerMap = Map; +export type MessageHandlerMap = Map; diff --git a/types/other/globals.d.ts b/types/other/globals.d.ts new file mode 100644 index 00000000..330f16c2 --- /dev/null +++ b/types/other/globals.d.ts @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Yomitan 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 . + */ + +declare global { + function clearTimeout(timeoutId: NodeJS.Timeout | string | number | undefined): void; +} + +export {}; diff --git a/types/other/web-set-timeout.d.ts b/types/other/web-set-timeout.d.ts deleted file mode 100644 index 98e40dab..00000000 --- a/types/other/web-set-timeout.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan 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 . - */ - -declare module 'web-set-timeout' { - global { - // These types are used to ensure that setTimeout returns a number - function setTimeout(callback: (...args: TArgs) => void, ms?: number, ...args: TArgs): number; - function setTimeout(callback: (args: void) => void, ms?: number): number; - } -} -- cgit v1.2.3