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/background/backend.js | 1001 ++++++++++++++++++++------ ext/js/background/profile-conditions-util.js | 155 +++- ext/js/background/request-builder.js | 50 ++ ext/js/background/script-manager.js | 186 +++-- 4 files changed, 1092 insertions(+), 300 deletions(-) (limited to 'ext/js/background') diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index bf4841f8..a8683b3f 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -22,8 +22,10 @@ import {AnkiConnect} from '../comm/anki-connect.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; import {ClipboardReader} from '../comm/clipboard-reader.js'; import {Mecab} from '../comm/mecab.js'; -import {clone, deferPromise, deserializeError, generateId, invokeMessageHandler, isObject, log, promiseTimeout, serializeError} from '../core.js'; +import {clone, deferPromise, generateId, invokeMessageHandler, isObject, log, promiseTimeout} from '../core.js'; +import {ExtensionError} from '../core/extension-error.js'; import {AnkiUtil} from '../data/anki-util.js'; +import {JsonSchema} from '../data/json-schema.js'; import {OptionsUtil} from '../data/options-util.js'; import {PermissionsUtil} from '../data/permissions-util.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; @@ -35,7 +37,7 @@ import {Translator} from '../language/translator.js'; import {AudioDownloader} from '../media/audio-downloader.js'; import {MediaUtil} from '../media/media-util.js'; import {yomitan} from '../yomitan.js'; -import {OffscreenProxy, DictionaryDatabaseProxy, TranslatorProxy, ClipboardReaderProxy} from './offscreen-proxy.js'; +import {ClipboardReaderProxy, DictionaryDatabaseProxy, OffscreenProxy, TranslatorProxy} from './offscreen-proxy.js'; import {ProfileConditionsUtil} from './profile-conditions-util.js'; import {RequestBuilder} from './request-builder.js'; import {ScriptManager} from './script-manager.js'; @@ -49,17 +51,28 @@ export class Backend { * Creates a new instance. */ constructor() { + /** @type {JapaneseUtil} */ this._japaneseUtil = new JapaneseUtil(wanakana); + /** @type {Environment} */ this._environment = new Environment(); + /** + * + */ this._anki = new AnkiConnect(); + /** @type {Mecab} */ this._mecab = new Mecab(); if (!chrome.offscreen) { + /** @type {?OffscreenProxy} */ + this._offscreen = null; + /** @type {DictionaryDatabase|DictionaryDatabaseProxy} */ this._dictionaryDatabase = new DictionaryDatabase(); + /** @type {Translator|TranslatorProxy} */ this._translator = new Translator({ japaneseUtil: this._japaneseUtil, database: this._dictionaryDatabase }); + /** @type {ClipboardReader|ClipboardReaderProxy} */ this._clipboardReader = new ClipboardReader({ // eslint-disable-next-line no-undef document: (typeof document === 'object' && document !== null ? document : null), @@ -67,54 +80,83 @@ export class Backend { richContentPasteTargetSelector: '#clipboard-rich-content-paste-target' }); } else { + /** @type {?OffscreenProxy} */ this._offscreen = new OffscreenProxy(); + /** @type {DictionaryDatabase|DictionaryDatabaseProxy} */ this._dictionaryDatabase = new DictionaryDatabaseProxy(this._offscreen); + /** @type {Translator|TranslatorProxy} */ this._translator = new TranslatorProxy(this._offscreen); + /** @type {ClipboardReader|ClipboardReaderProxy} */ this._clipboardReader = new ClipboardReaderProxy(this._offscreen); } + /** @type {ClipboardMonitor} */ this._clipboardMonitor = new ClipboardMonitor({ japaneseUtil: this._japaneseUtil, clipboardReader: this._clipboardReader }); + /** @type {?import('settings').Options} */ this._options = null; + /** @type {JsonSchema[]} */ this._profileConditionsSchemaCache = []; + /** @type {ProfileConditionsUtil} */ this._profileConditionsUtil = new ProfileConditionsUtil(); + /** @type {?string} */ this._defaultAnkiFieldTemplates = null; + /** @type {RequestBuilder} */ this._requestBuilder = new RequestBuilder(); + /** @type {AudioDownloader} */ this._audioDownloader = new AudioDownloader({ japaneseUtil: this._japaneseUtil, requestBuilder: this._requestBuilder }); + /** @type {OptionsUtil} */ this._optionsUtil = new OptionsUtil(); + /** @type {ScriptManager} */ this._scriptManager = new ScriptManager(); + /** @type {AccessibilityController} */ this._accessibilityController = new AccessibilityController(this._scriptManager); + /** @type {?number} */ this._searchPopupTabId = null; + /** @type {?Promise<{tab: chrome.tabs.Tab, created: boolean}>} */ this._searchPopupTabCreatePromise = null; + /** @type {boolean} */ this._isPrepared = false; + /** @type {boolean} */ this._prepareError = false; + /** @type {?Promise} */ this._preparePromise = null; + /** @type {import('core').DeferredPromiseDetails} */ const {promise, resolve, reject} = deferPromise(); + /** @type {Promise} */ this._prepareCompletePromise = promise; + /** @type {() => void} */ this._prepareCompleteResolve = resolve; + /** @type {(reason?: unknown) => void} */ this._prepareCompleteReject = reject; + /** @type {?string} */ this._defaultBrowserActionTitle = null; + /** @type {?number} */ this._badgePrepareDelayTimer = null; + /** @type {?import('log').LogLevel} */ this._logErrorLevel = null; + /** @type {?chrome.permissions.Permissions} */ this._permissions = null; + /** @type {PermissionsUtil} */ this._permissionsUtil = new PermissionsUtil(); - this._messageHandlers = new Map([ + /** @type {import('backend').MessageHandlerMap} */ + this._messageHandlers = new Map(/** @type {import('backend').MessageHandlerMapInit} */ ([ ['requestBackendReadySignal', {async: false, contentScript: true, handler: this._onApiRequestBackendReadySignal.bind(this)}], ['optionsGet', {async: false, contentScript: true, handler: this._onApiOptionsGet.bind(this)}], ['optionsGetFull', {async: false, contentScript: true, handler: this._onApiOptionsGetFull.bind(this)}], ['kanjiFind', {async: true, contentScript: true, handler: this._onApiKanjiFind.bind(this)}], ['termsFind', {async: true, contentScript: true, handler: this._onApiTermsFind.bind(this)}], ['parseText', {async: true, contentScript: true, handler: this._onApiParseText.bind(this)}], - ['getAnkiConnectVersion', {async: true, contentScript: true, handler: this._onApGetAnkiConnectVersion.bind(this)}], + ['getAnkiConnectVersion', {async: true, contentScript: true, handler: this._onApiGetAnkiConnectVersion.bind(this)}], ['isAnkiConnected', {async: true, contentScript: true, handler: this._onApiIsAnkiConnected.bind(this)}], ['addAnkiNote', {async: true, contentScript: true, handler: this._onApiAddAnkiNote.bind(this)}], ['getAnkiNoteInfo', {async: true, contentScript: true, handler: this._onApiGetAnkiNoteInfo.bind(this)}], @@ -151,17 +193,20 @@ export class Backend { ['findAnkiNotes', {async: true, contentScript: true, handler: this._onApiFindAnkiNotes.bind(this)}], ['loadExtensionScripts', {async: true, contentScript: true, handler: this._onApiLoadExtensionScripts.bind(this)}], ['openCrossFramePort', {async: false, contentScript: true, handler: this._onApiOpenCrossFramePort.bind(this)}] - ]); - this._messageHandlersWithProgress = new Map([ - ]); - - this._commandHandlers = new Map([ + ])); + /** @type {import('backend').MessageHandlerWithProgressMap} */ + this._messageHandlersWithProgress = new Map(/** @type {import('backend').MessageHandlerWithProgressMapInit} */ ([ + // Empty + ])); + + /** @type {Map void>} */ + this._commandHandlers = new Map(/** @type {[name: string, handler: (params?: import('core').SerializableObject) => void][]} */ ([ ['toggleTextScanning', this._onCommandToggleTextScanning.bind(this)], ['openInfoPage', this._onCommandOpenInfoPage.bind(this)], ['openSettingsPage', this._onCommandOpenSettingsPage.bind(this)], ['openSearchPage', this._onCommandOpenSearchPage.bind(this)], ['openPopupWindow', this._onCommandOpenPopupWindow.bind(this)] - ]); + ])); } /** @@ -172,9 +217,9 @@ export class Backend { if (this._preparePromise === null) { const promise = this._prepareInternal(); promise.then( - (value) => { + () => { this._isPrepared = true; - this._prepareCompleteResolve(value); + this._prepareCompleteResolve(); }, (error) => { this._prepareError = true; @@ -189,6 +234,9 @@ export class Backend { // Private + /** + * @returns {void} + */ _prepareInternalSync() { if (isObject(chrome.commands) && isObject(chrome.commands.onCommand)) { const onCommand = this._onWebExtensionEventWrapper(this._onCommand.bind(this)); @@ -212,6 +260,9 @@ export class Backend { chrome.runtime.onInstalled.addListener(this._onInstalled.bind(this)); } + /** + * @returns {Promise} + */ async _prepareInternal() { try { this._prepareInternalSync(); @@ -224,11 +275,11 @@ export class Backend { }, 1000); this._updateBadge(); - yomitan.on('log', this._onLog.bind(this)); + log.on('log', this._onLog.bind(this)); await this._requestBuilder.prepare(); await this._environment.prepare(); - if (chrome.offscreen) { + if (this._offscreen !== null) { await this._offscreen.prepare(); } this._clipboardReader.browser = this._environment.getInfo().browser; @@ -239,16 +290,16 @@ export class Backend { log.error(e); } - const deinflectionReasons = await this._fetchAsset('/data/deinflect.json', true); + const deinflectionReasons = /** @type {import('deinflector').ReasonsRaw} */ (await this._fetchJson('/data/deinflect.json')); this._translator.prepare(deinflectionReasons); await this._optionsUtil.prepare(); - this._defaultAnkiFieldTemplates = (await this._fetchAsset('/data/templates/default-anki-field-templates.handlebars')).trim(); + this._defaultAnkiFieldTemplates = (await this._fetchText('/data/templates/default-anki-field-templates.handlebars')).trim(); this._options = await this._optionsUtil.load(); this._applyOptions('background'); - const options = this._getProfileOptions({current: true}); + const options = this._getProfileOptions({current: true}, false); if (options.general.showGuide) { this._openWelcomeGuidePageOnce(); } @@ -270,20 +321,30 @@ export class Backend { // Event handlers + /** + * @param {{text: string}} params + */ async _onClipboardTextChange({text}) { - const {clipboard: {maximumSearchLength}} = this._getProfileOptions({current: true}); + const {clipboard: {maximumSearchLength}} = this._getProfileOptions({current: true}, false); if (text.length > maximumSearchLength) { text = text.substring(0, maximumSearchLength); } try { const {tab, created} = await this._getOrCreateSearchPopup(); + const {id} = tab; + if (typeof id !== 'number') { + throw new Error('Tab does not have an id'); + } await this._focusTab(tab); - await this._updateSearchQuery(tab.id, text, !created); + await this._updateSearchQuery(id, text, !created); } catch (e) { // NOP } } + /** + * @param {{level: import('log').LogLevel}} params + */ _onLog({level}) { const levelValue = this._getErrorLevelValue(level); if (levelValue <= this._getErrorLevelValue(this._logErrorLevel)) { return; } @@ -294,8 +355,13 @@ export class Backend { // WebExtension event handlers (with prepared checks) + /** + * @template {(...args: import('core').SafeAny[]) => void} T + * @param {T} handler + * @returns {T} + */ _onWebExtensionEventWrapper(handler) { - return (...args) => { + return /** @type {T} */ ((...args) => { if (this._isPrepared) { handler(...args); return; @@ -305,9 +371,10 @@ export class Backend { () => { handler(...args); }, () => {} // NOP ); - }; + }); } + /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ _onMessageWrapper(message, sender, sendResponse) { if (this._isPrepared) { return this._onMessage(message, sender, sendResponse); @@ -322,10 +389,19 @@ export class Backend { // WebExtension event handlers + /** + * @param {string} command + */ _onCommand(command) { - this._runCommand(command); + this._runCommand(command, void 0); } + /** + * @param {{action: string, params?: import('core').SerializableObject}} message + * @param {chrome.runtime.MessageSender} sender + * @param {(response?: unknown) => void} callback + * @returns {boolean} + */ _onMessage({action, params}, sender, callback) { const messageHandler = this._messageHandlers.get(action); if (typeof messageHandler === 'undefined') { return false; } @@ -334,7 +410,7 @@ export class Backend { try { this._validatePrivilegedMessageSender(sender); } catch (error) { - callback({error: serializeError(error)}); + callback({error: ExtensionError.serialize(error)}); return false; } } @@ -342,14 +418,23 @@ export class Backend { return invokeMessageHandler(messageHandler, params, callback, sender); } + /** + * @param {chrome.tabs.ZoomChangeInfo} event + */ _onZoomChange({tabId, oldZoomFactor, newZoomFactor}) { - this._sendMessageTabIgnoreResponse(tabId, {action: 'Yomitan.zoomChanged', params: {oldZoomFactor, newZoomFactor}}); + this._sendMessageTabIgnoreResponse(tabId, {action: 'Yomitan.zoomChanged', params: {oldZoomFactor, newZoomFactor}}, {}); } + /** + * @returns {void} + */ _onPermissionsChanged() { this._checkPermissions(); } + /** + * @param {chrome.runtime.InstalledDetails} event + */ _onInstalled({reason}) { if (reason !== 'install') { return; } this._requestPersistentStorage(); @@ -357,6 +442,7 @@ export class Backend { // Message handlers + /** @type {import('api').Handler} */ _onApiRequestBackendReadySignal(_params, sender) { // tab ID isn't set in background (e.g. browser_action) const data = {action: 'Yomitan.backendReady', params: {}}; @@ -364,21 +450,27 @@ export class Backend { this._sendMessageIgnoreResponse(data); return false; } else { - this._sendMessageTabIgnoreResponse(sender.tab.id, data); + const {id} = sender.tab; + if (typeof id === 'number') { + this._sendMessageTabIgnoreResponse(id, data, {}); + } return true; } } + /** @type {import('api').Handler} */ _onApiOptionsGet({optionsContext}) { - return this._getProfileOptions(optionsContext); + return this._getProfileOptions(optionsContext, false); } + /** @type {import('api').Handler} */ _onApiOptionsGetFull() { - return this._getOptionsFull(); + return this._getOptionsFull(false); } + /** @type {import('api').Handler} */ async _onApiKanjiFind({text, optionsContext}) { - const options = this._getProfileOptions(optionsContext); + const options = this._getProfileOptions(optionsContext, false); const {general: {maxResults}} = options; const findKanjiOptions = this._getTranslatorFindKanjiOptions(options); const dictionaryEntries = await this._translator.findKanji(text, findKanjiOptions); @@ -386,8 +478,9 @@ export class Backend { return dictionaryEntries; } + /** @type {import('api').Handler} */ async _onApiTermsFind({text, details, optionsContext}) { - const options = this._getProfileOptions(optionsContext); + const options = this._getProfileOptions(optionsContext, false); const {general: {resultOutputMode: mode, maxResults}} = options; const findTermsOptions = this._getTranslatorFindTermsOptions(mode, details, options); const {dictionaryEntries, originalTextLength} = await this._translator.findTerms(mode, text, findTermsOptions); @@ -395,12 +488,14 @@ export class Backend { return {dictionaryEntries, originalTextLength}; } + /** @type {import('api').Handler} */ async _onApiParseText({text, optionsContext, scanLength, useInternalParser, useMecabParser}) { const [internalResults, mecabResults] = await Promise.all([ (useInternalParser ? this._textParseScanning(text, scanLength, optionsContext) : null), (useMecabParser ? this._textParseMecab(text) : null) ]); + /** @type {import('api').ParseTextResultItem[]} */ const results = []; if (internalResults !== null) { @@ -426,20 +521,26 @@ export class Backend { return results; } - async _onApGetAnkiConnectVersion() { + /** @type {import('api').Handler} */ + async _onApiGetAnkiConnectVersion() { return await this._anki.getVersion(); } + /** @type {import('api').Handler} */ async _onApiIsAnkiConnected() { return await this._anki.isConnected(); } + /** @type {import('api').Handler} */ async _onApiAddAnkiNote({note}) { return await this._anki.addNote(note); } + /** @type {import('api').Handler} */ async _onApiGetAnkiNoteInfo({notes, fetchAdditionalInfo}) { + /** @type {import('anki').NoteInfoWrapper[]} */ const results = []; + /** @type {{note: import('anki').Note, info: import('anki').NoteInfoWrapper}[]} */ const cannotAdd = []; const canAddArray = await this._anki.canAddNotes(notes); @@ -472,6 +573,7 @@ export class Backend { return results; } + /** @type {import('api').Handler} */ async _onApiInjectAnkiNoteMedia({timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}) { return await this._injectAnkNoteMedia( this._anki, @@ -484,13 +586,14 @@ export class Backend { ); } + /** @type {import('api').Handler} */ async _onApiNoteView({noteId, mode, allowFallback}) { if (mode === 'edit') { try { await this._anki.guiEditNote(noteId); return 'edit'; } catch (e) { - if (!this._anki.isErrorUnsupportedAction(e)) { + if (!(e instanceof Error && this._anki.isErrorUnsupportedAction(e))) { throw e; } else if (!allowFallback) { throw new Error('Mode not supported'); @@ -502,6 +605,7 @@ export class Backend { return 'browse'; } + /** @type {import('api').Handler} */ async _onApiSuspendAnkiCardsForNote({noteId}) { const cardIds = await this._anki.findCardsForNote(noteId); const count = cardIds.length; @@ -512,76 +616,93 @@ export class Backend { return count; } + /** @type {import('api').Handler} */ _onApiCommandExec({command, params}) { return this._runCommand(command, params); } + /** @type {import('api').Handler} */ async _onApiGetTermAudioInfoList({source, term, reading}) { return await this._audioDownloader.getTermAudioInfoList(source, term, reading); } + /** @type {import('api').Handler} */ _onApiSendMessageToFrame({frameId: targetFrameId, action, params}, sender) { - if (!(sender && sender.tab)) { - return false; - } - - const tabId = sender.tab.id; + if (!sender) { return false; } + const {tab} = sender; + if (!tab) { return false; } + const {id} = tab; + if (typeof id !== 'number') { return false; } const frameId = sender.frameId; - this._sendMessageTabIgnoreResponse(tabId, {action, params, frameId}, {frameId: targetFrameId}); + /** @type {import('extension').ChromeRuntimeMessageWithFrameId} */ + const message = {action, params, frameId}; + this._sendMessageTabIgnoreResponse(id, message, {frameId: targetFrameId}); return true; } + /** @type {import('api').Handler} */ _onApiBroadcastTab({action, params}, sender) { - if (!(sender && sender.tab)) { - return false; - } - - const tabId = sender.tab.id; + if (!sender) { return false; } + const {tab} = sender; + if (!tab) { return false; } + const {id} = tab; + if (typeof id !== 'number') { return false; } const frameId = sender.frameId; - this._sendMessageTabIgnoreResponse(tabId, {action, params, frameId}); + /** @type {import('extension').ChromeRuntimeMessageWithFrameId} */ + const message = {action, params, frameId}; + this._sendMessageTabIgnoreResponse(id, message, {}); return true; } - _onApiFrameInformationGet(params, sender) { + /** @type {import('api').Handler} */ + _onApiFrameInformationGet(_params, sender) { const tab = sender.tab; const tabId = tab ? tab.id : void 0; const frameId = sender.frameId; return Promise.resolve({tabId, frameId}); } + /** @type {import('api').Handler} */ async _onApiInjectStylesheet({type, value}, sender) { const {frameId, tab} = sender; - if (!isObject(tab)) { throw new Error('Invalid tab'); } - return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false); + 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'); } + /** @type {import('api').Handler} */ async _onApiGetStylesheetContent({url}) { if (!url.startsWith('/') || url.startsWith('//') || !url.endsWith('.css')) { throw new Error('Invalid URL'); } - return await this._fetchAsset(url); + return await this._fetchText(url); } + /** @type {import('api').Handler} */ _onApiGetEnvironmentInfo() { return this._environment.getInfo(); } + /** @type {import('api').Handler} */ async _onApiClipboardGet() { return this._clipboardReader.getText(false); } + /** @type {import('api').Handler} */ async _onApiGetDisplayTemplatesHtml() { - return await this._fetchAsset('/display-templates.html'); + return await this._fetchText('/display-templates.html'); } - _onApiGetZoom(params, sender) { - if (!sender || !sender.tab) { - return Promise.reject(new Error('Invalid tab')); - } - + /** @type {import('api').Handler} */ + _onApiGetZoom(_params, sender) { return new Promise((resolve, reject) => { + if (!sender || !sender.tab) { + reject(new Error('Invalid tab')); + return; + } + const tabId = sender.tab.id; if (!( + typeof tabId === 'number' && chrome.tabs !== null && typeof chrome.tabs === 'object' && typeof chrome.tabs.getZoom === 'function' @@ -601,34 +722,41 @@ export class Backend { }); } + /** @type {import('api').Handler} */ _onApiGetDefaultAnkiFieldTemplates() { - return this._defaultAnkiFieldTemplates; + return /** @type {string} */ (this._defaultAnkiFieldTemplates); } + /** @type {import('api').Handler} */ async _onApiGetDictionaryInfo() { return await this._dictionaryDatabase.getDictionaryInfo(); } + /** @type {import('api').Handler} */ async _onApiPurgeDatabase() { await this._dictionaryDatabase.purge(); this._triggerDatabaseUpdated('dictionary', 'purge'); } + /** @type {import('api').Handler} */ async _onApiGetMedia({targets}) { return await this._getNormalizedDictionaryDatabaseMedia(targets); } + /** @type {import('api').Handler} */ _onApiLog({error, level, context}) { - log.log(deserializeError(error), level, context); + log.log(ExtensionError.deserialize(error), level, context); } + /** @type {import('api').Handler} */ _onApiLogIndicatorClear() { if (this._logErrorLevel === null) { return; } this._logErrorLevel = null; this._updateBadge(); } - _onApiCreateActionPort(params, sender) { + /** @type {import('api').Handler} */ + _onApiCreateActionPort(_params, sender) { if (!sender || !sender.tab) { throw new Error('Invalid sender'); } const tabId = sender.tab.id; if (typeof tabId !== 'number') { throw new Error('Sender has invalid tab ID'); } @@ -651,10 +779,12 @@ export class Backend { return details; } + /** @type {import('api').Handler} */ _onApiModifySettings({targets, source}) { return this._modifySettings(targets, source); } + /** @type {import('api').Handler} */ _onApiGetSettings({targets}) { const results = []; for (const target of targets) { @@ -662,39 +792,48 @@ export class Backend { const result = this._getSetting(target); results.push({result: clone(result)}); } catch (e) { - results.push({error: serializeError(e)}); + results.push({error: ExtensionError.serialize(e)}); } } return results; } + /** @type {import('api').Handler} */ async _onApiSetAllSettings({value, source}) { this._optionsUtil.validate(value); this._options = clone(value); await this._saveOptions(source); } - async _onApiGetOrCreateSearchPopup({focus=false, text=null}) { + /** @type {import('api').Handler} */ + async _onApiGetOrCreateSearchPopup({focus=false, text}) { const {tab, created} = await this._getOrCreateSearchPopup(); if (focus === true || (focus === 'ifCreated' && created)) { await this._focusTab(tab); } if (typeof text === 'string') { - await this._updateSearchQuery(tab.id, text, !created); + const {id} = tab; + if (typeof id === 'number') { + await this._updateSearchQuery(id, text, !created); + } } - return {tabId: tab.id, windowId: tab.windowId}; + const {id} = tab; + return {tabId: typeof id === 'number' ? id : null, windowId: tab.windowId}; } + /** @type {import('api').Handler} */ async _onApiIsTabSearchPopup({tabId}) { const baseUrl = chrome.runtime.getURL('/search.html'); - const tab = typeof tabId === 'number' ? await this._checkTabUrl(tabId, (url) => url.startsWith(baseUrl)) : null; + const tab = typeof tabId === 'number' ? await this._checkTabUrl(tabId, (url) => url !== null && url.startsWith(baseUrl)) : null; return (tab !== null); } + /** @type {import('api').Handler} */ _onApiTriggerDatabaseUpdated({type, cause}) { this._triggerDatabaseUpdated(type, cause); } + /** @type {import('api').Handler} */ async _onApiTestMecab() { if (!this._mecab.isEnabled()) { throw new Error('MeCab not enabled'); @@ -731,18 +870,22 @@ export class Backend { return true; } + /** @type {import('api').Handler} */ _onApiTextHasJapaneseCharacters({text}) { return this._japaneseUtil.isStringPartiallyJapanese(text); } + /** @type {import('api').Handler} */ async _onApiGetTermFrequencies({termReadingList, dictionaries}) { return await this._translator.getTermFrequencies(termReadingList, dictionaries); } + /** @type {import('api').Handler} */ async _onApiFindAnkiNotes({query}) { return await this._anki.findNotes(query); } + /** @type {import('api').Handler} */ async _onApiLoadExtensionScripts({files}, sender) { if (!sender || !sender.tab) { throw new Error('Invalid sender'); } const tabId = sender.tab.id; @@ -753,6 +896,13 @@ export class Backend { } } + /** + * + * @param root0 + * @param root0.targetTabId + * @param root0.targetFrameId + * @param sender + */ _onApiOpenCrossFramePort({targetTabId, targetFrameId}, sender) { const sourceTabId = (sender && sender.tab ? sender.tab.id : null); if (typeof sourceTabId !== 'number') { @@ -798,18 +948,30 @@ export class Backend { // Command handlers + /** + * @param {undefined|{mode: 'existingOrNewTab'|'newTab', query?: string}} params + */ async _onCommandOpenSearchPage(params) { - const {mode='existingOrNewTab', query} = params || {}; + /** @type {'existingOrNewTab'|'newTab'} */ + let mode = 'existingOrNewTab'; + let query = ''; + if (typeof params === 'object' && params !== null) { + mode = this._normalizeOpenSettingsPageMode(params.mode, mode); + const paramsQuery = params.query; + if (typeof paramsQuery === 'string') { query = paramsQuery; } + } const baseUrl = chrome.runtime.getURL('/search.html'); + /** @type {{[key: string]: string}} */ const queryParams = {}; - if (query && query.length > 0) { queryParams.query = query; } + if (query.length > 0) { queryParams.query = query; } const queryString = new URLSearchParams(queryParams).toString(); let url = baseUrl; if (queryString.length > 0) { url += `?${queryString}`; } + /** @type {import('backend').FindTabsPredicate} */ const predicate = ({url: url2}) => { if (url2 === null || !url2.startsWith(baseUrl)) { return false; } const parsedUrl = new URL(url2); @@ -819,15 +981,19 @@ export class Backend { }; const openInTab = async () => { - const tabInfo = await this._findTabs(1000, false, predicate, false); + const tabInfo = /** @type {?import('backend').TabInfo} */ (await this._findTabs(1000, false, predicate, false)); if (tabInfo !== null) { const {tab} = tabInfo; - await this._focusTab(tab); - if (queryParams.query) { - await this._updateSearchQuery(tab.id, queryParams.query, true); + const {id} = tab; + if (typeof id === 'number') { + await this._focusTab(tab); + if (queryParams.query) { + await this._updateSearchQuery(id, queryParams.query, true); + } + return true; } - return true; } + return false; }; switch (mode) { @@ -845,46 +1011,73 @@ export class Backend { } } + /** + * @returns {Promise} + */ async _onCommandOpenInfoPage() { await this._openInfoPage(); } + /** + * @param {undefined|{mode: 'existingOrNewTab'|'newTab'}} params + */ async _onCommandOpenSettingsPage(params) { - const {mode='existingOrNewTab'} = params || {}; + /** @type {'existingOrNewTab'|'newTab'} */ + let mode = 'existingOrNewTab'; + if (typeof params === 'object' && params !== null) { + mode = this._normalizeOpenSettingsPageMode(params.mode, mode); + } await this._openSettingsPage(mode); } + /** + * @returns {Promise} + */ async _onCommandToggleTextScanning() { - const options = this._getProfileOptions({current: true}); - await this._modifySettings([{ + const options = this._getProfileOptions({current: true}, false); + /** @type {import('settings-modifications').ScopedModificationSet} */ + const modification = { action: 'set', path: 'general.enable', value: !options.general.enable, scope: 'profile', optionsContext: {current: true} - }], 'backend'); + }; + await this._modifySettings([modification], 'backend'); } + /** + * @returns {Promise} + */ async _onCommandOpenPopupWindow() { await this._onApiGetOrCreateSearchPopup({focus: true}); } // Utilities + /** + * @param {import('settings-modifications').ScopedModification[]} targets + * @param {string} source + * @returns {Promise[]>} + */ async _modifySettings(targets, source) { + /** @type {import('core').Response[]} */ const results = []; for (const target of targets) { try { const result = this._modifySetting(target); results.push({result: clone(result)}); } catch (e) { - results.push({error: serializeError(e)}); + results.push({error: ExtensionError.serialize(e)}); } } await this._saveOptions(source); return results; } + /** + * @returns {Promise<{tab: chrome.tabs.Tab, created: boolean}>} + */ _getOrCreateSearchPopup() { if (this._searchPopupTabCreatePromise === null) { const promise = this._getOrCreateSearchPopup2(); @@ -894,9 +1087,16 @@ export class Backend { return this._searchPopupTabCreatePromise; } + /** + * @returns {Promise<{tab: chrome.tabs.Tab, created: boolean}>} + */ async _getOrCreateSearchPopup2() { // Use existing tab const baseUrl = chrome.runtime.getURL('/search.html'); + /** + * @param {?string} url + * @returns {boolean} + */ const urlPredicate = (url) => url !== null && url.startsWith(baseUrl); if (this._searchPopupTabId !== null) { const tab = await this._checkTabUrl(this._searchPopupTabId, urlPredicate); @@ -910,8 +1110,11 @@ export class Backend { const existingTabInfo = await this._findSearchPopupTab(urlPredicate); if (existingTabInfo !== null) { const existingTab = existingTabInfo.tab; - this._searchPopupTabId = existingTab.id; - return {tab: existingTab, created: false}; + const {id} = existingTab; + if (typeof id === 'number') { + this._searchPopupTabId = id; + return {tab: existingTab, created: false}; + } } // chrome.windows not supported (e.g. on Firefox mobile) @@ -920,38 +1123,48 @@ export class Backend { } // Create a new window - const options = this._getProfileOptions({current: true}); + const options = this._getProfileOptions({current: true}, false); const createData = this._getSearchPopupWindowCreateData(baseUrl, options); const {popupWindow: {windowState}} = options; const popupWindow = await this._createWindow(createData); - if (windowState !== 'normal') { + if (windowState !== 'normal' && typeof popupWindow.id === 'number') { await this._updateWindow(popupWindow.id, {state: windowState}); } const {tabs} = popupWindow; - if (tabs.length === 0) { + if (!Array.isArray(tabs) || tabs.length === 0) { throw new Error('Created window did not contain a tab'); } const tab = tabs[0]; - await this._waitUntilTabFrameIsReady(tab.id, 0, 2000); + const {id} = tab; + if (typeof id !== 'number') { + throw new Error('Tab does not have an id'); + } + await this._waitUntilTabFrameIsReady(id, 0, 2000); await this._sendMessageTabPromise( - tab.id, + id, {action: 'SearchDisplayController.setMode', params: {mode: 'popup'}}, {frameId: 0} ); - this._searchPopupTabId = tab.id; + this._searchPopupTabId = id; return {tab, created: true}; } + /** + * @param {(url: ?string) => boolean} urlPredicate + * @returns {Promise} + */ async _findSearchPopupTab(urlPredicate) { + /** @type {import('backend').FindTabsPredicate} */ const predicate = async ({url, tab}) => { - if (!urlPredicate(url)) { return false; } + const {id} = tab; + if (typeof id === 'undefined' || !urlPredicate(url)) { return false; } try { const mode = await this._sendMessageTabPromise( - tab.id, + id, {action: 'SearchDisplayController.getMode', params: {}}, {frameId: 0} ); @@ -960,9 +1173,14 @@ export class Backend { return false; } }; - return await this._findTabs(1000, false, predicate, true); + return /** @type {?import('backend').TabInfo} */ (await this._findTabs(1000, false, predicate, true)); } + /** + * @param {string} url + * @param {import('settings').ProfileOptions} options + * @returns {chrome.windows.CreateData} + */ _getSearchPopupWindowCreateData(url, options) { const {popupWindow: {width, height, left, top, useLeft, useTop, windowType}} = options; return { @@ -976,6 +1194,10 @@ export class Backend { }; } + /** + * @param {chrome.windows.CreateData} createData + * @returns {Promise} + */ _createWindow(createData) { return new Promise((resolve, reject) => { chrome.windows.create( @@ -985,13 +1207,18 @@ export class Backend { if (error) { reject(new Error(error.message)); } else { - resolve(result); + resolve(/** @type {chrome.windows.Window} */ (result)); } } ); }); } + /** + * @param {number} windowId + * @param {chrome.windows.UpdateInfo} updateInfo + * @returns {Promise} + */ _updateWindow(windowId, updateInfo) { return new Promise((resolve, reject) => { chrome.windows.update( @@ -1009,21 +1236,31 @@ export class Backend { }); } - _updateSearchQuery(tabId, text, animate) { - return this._sendMessageTabPromise( + /** + * @param {number} tabId + * @param {string} text + * @param {boolean} animate + * @returns {Promise} + */ + async _updateSearchQuery(tabId, text, animate) { + await this._sendMessageTabPromise( tabId, {action: 'SearchDisplayController.updateSearchQuery', params: {text, animate}}, {frameId: 0} ); } + /** + * @param {string} source + */ _applyOptions(source) { - const options = this._getProfileOptions({current: true}); + const options = this._getProfileOptions({current: true}, false); this._updateBadge(); const enabled = options.general.enable; - let {apiKey} = options.anki; + /** @type {?string} */ + let apiKey = options.anki.apiKey; if (apiKey === '') { apiKey = null; } this._anki.server = options.anki.server; this._anki.enabled = options.anki.enable && enabled; @@ -1042,16 +1279,33 @@ export class Backend { this._sendMessageAllTabsIgnoreResponse('Yomitan.optionsUpdated', {source}); } - _getOptionsFull(useSchema=false) { + /** + * @param {boolean} useSchema + * @returns {import('settings').Options} + * @throws {Error} + */ + _getOptionsFull(useSchema) { const options = this._options; - return useSchema ? this._optionsUtil.createValidatingProxy(options) : options; + if (options === null) { throw new Error('Options is null'); } + return useSchema ? /** @type {import('settings').Options} */ (this._optionsUtil.createValidatingProxy(options)) : options; } - _getProfileOptions(optionsContext, useSchema=false) { + /** + * @param {import('settings').OptionsContext} optionsContext + * @param {boolean} useSchema + * @returns {import('settings').ProfileOptions} + */ + _getProfileOptions(optionsContext, useSchema) { return this._getProfile(optionsContext, useSchema).options; } - _getProfile(optionsContext, useSchema=false) { + /** + * @param {import('settings').OptionsContext} optionsContext + * @param {boolean} useSchema + * @returns {import('settings').Profile} + * @throws {Error} + */ + _getProfile(optionsContext, useSchema) { const options = this._getOptionsFull(useSchema); const profiles = options.profiles; if (!optionsContext.current) { @@ -1077,8 +1331,13 @@ export class Backend { return profiles[profileCurrent]; } + /** + * @param {import('settings').Options} options + * @param {import('settings').OptionsContext} optionsContext + * @returns {?import('settings').Profile} + */ _getProfileFromContext(options, optionsContext) { - optionsContext = this._profileConditionsUtil.normalizeContext(optionsContext); + const normalizedOptionsContext = this._profileConditionsUtil.normalizeContext(optionsContext); let index = 0; for (const profile of options.profiles) { @@ -1092,7 +1351,7 @@ export class Backend { this._profileConditionsSchemaCache.push(schema); } - if (conditionGroups.length > 0 && schema.isValid(optionsContext)) { + if (conditionGroups.length > 0 && schema.isValid(normalizedOptionsContext)) { return profile; } ++index; @@ -1101,20 +1360,36 @@ export class Backend { return null; } + /** + * @param {string} message + * @param {unknown} data + * @returns {ExtensionError} + */ _createDataError(message, data) { - const error = new Error(message); + const error = new ExtensionError(message); error.data = data; return error; } + /** + * @returns {void} + */ _clearProfileConditionsSchemaCache() { this._profileConditionsSchemaCache = []; } - _checkLastError() { + /** + * @param {unknown} _ignore + */ + _checkLastError(_ignore) { // NOP } + /** + * @param {string} command + * @param {import('core').SerializableObject|undefined} params + * @returns {boolean} + */ _runCommand(command, params) { const handler = this._commandHandlers.get(command); if (typeof handler !== 'function') { return false; } @@ -1123,12 +1398,20 @@ export class Backend { return true; } + /** + * @param {string} text + * @param {number} scanLength + * @param {import('settings').OptionsContext} optionsContext + * @returns {Promise} + */ async _textParseScanning(text, scanLength, optionsContext) { const jp = this._japaneseUtil; + /** @type {import('translator').FindTermsMode} */ const mode = 'simple'; - const options = this._getProfileOptions(optionsContext); - const details = {matchType: 'exact', deinflect: true}; + const options = this._getProfileOptions(optionsContext, false); + const details = {matchType: /** @type {import('translation').FindTermsMatchType} */ ('exact'), deinflect: true}; const findTermsOptions = this._getTranslatorFindTermsOptions(mode, details, options); + /** @type {import('api').ParseTextLine[]} */ const results = []; let previousUngroupedSegment = null; let i = 0; @@ -1139,7 +1422,7 @@ export class Backend { text.substring(i, i + scanLength), findTermsOptions ); - const codePoint = text.codePointAt(i); + const codePoint = /** @type {number} */ (text.codePointAt(i)); const character = String.fromCodePoint(codePoint); if ( dictionaryEntries.length > 0 && @@ -1168,6 +1451,10 @@ export class Backend { return results; } + /** + * @param {string} text + * @returns {Promise} + */ async _textParseMecab(text) { const jp = this._japaneseUtil; @@ -1178,8 +1465,10 @@ export class Backend { return []; } + /** @type {import('backend').MecabParseResults} */ const results = []; for (const {name, lines} of parseTextResults) { + /** @type {import('api').ParseTextLine[]} */ const result = []; for (const line of lines) { for (const {term, reading, source} of line) { @@ -1200,30 +1489,43 @@ export class Backend { return results; } + /** + * @param {chrome.runtime.Port} port + * @param {chrome.runtime.MessageSender} sender + * @param {import('backend').MessageHandlerWithProgressMap} handlers + */ _createActionListenerPort(port, sender, handlers) { + let done = false; let hasStarted = false; + /** @type {?string} */ let messageString = ''; + /** + * @param {...unknown} data + */ const onProgress = (...data) => { try { - if (port === null) { return; } - port.postMessage({type: 'progress', data}); + if (done) { return; } + port.postMessage(/** @type {import('backend').InvokeWithProgressResponseProgressMessage} */ ({type: 'progress', data})); } catch (e) { // NOP } }; + /** + * @param {import('backend').InvokeWithProgressRequestMessage} message + */ const onMessage = (message) => { if (hasStarted) { return; } try { - const {action, data} = message; + const {action} = message; switch (action) { case 'fragment': - messageString += data; + messageString += message.data; break; case 'invoke': - { + if (messageString !== null) { hasStarted = true; port.onMessage.removeListener(onMessage); @@ -1238,10 +1540,13 @@ export class Backend { } }; + /** + * @param {{action: string, params?: import('core').SerializableObject}} message + */ const onMessageComplete = async (message) => { try { const {action, params} = message; - port.postMessage({type: 'ack'}); + port.postMessage(/** @type {import('backend').InvokeWithProgressResponseAcknowledgeMessage} */ ({type: 'ack'})); const messageHandler = handlers.get(action); if (typeof messageHandler === 'undefined') { @@ -1255,7 +1560,7 @@ export class Backend { const promiseOrResult = handler(params, sender, onProgress); const result = async ? await promiseOrResult : promiseOrResult; - port.postMessage({type: 'complete', data: result}); + port.postMessage(/** @type {import('backend').InvokeWithProgressResponseCompleteMessage} */ ({type: 'complete', data: result})); } catch (e) { cleanup(e); } @@ -1265,23 +1570,29 @@ export class Backend { cleanup(null); }; + /** + * @param {unknown} error + */ const cleanup = (error) => { - if (port === null) { return; } + if (done) { return; } if (error !== null) { - port.postMessage({type: 'error', data: serializeError(error)}); + port.postMessage(/** @type {import('backend').InvokeWithProgressResponseErrorMessage} */ ({type: 'error', data: ExtensionError.serialize(error)})); } if (!hasStarted) { port.onMessage.removeListener(onMessage); } port.onDisconnect.removeListener(onDisconnect); - port = null; - handlers = null; + done = true; }; port.onMessage.addListener(onMessage); port.onDisconnect.addListener(onDisconnect); } + /** + * @param {?import('log').LogLevel} errorLevel + * @returns {number} + */ _getErrorLevelValue(errorLevel) { switch (errorLevel) { case 'info': return 0; @@ -1292,19 +1603,32 @@ export class Backend { } } + /** + * @param {import('settings-modifications').OptionsScope} target + * @returns {import('settings').Options|import('settings').ProfileOptions} + * @throws {Error} + */ _getModifySettingObject(target) { const scope = target.scope; switch (scope) { case 'profile': - if (!isObject(target.optionsContext)) { throw new Error('Invalid optionsContext'); } - return this._getProfileOptions(target.optionsContext, true); + { + const {optionsContext} = target; + if (typeof optionsContext !== 'object' || optionsContext === null) { throw new Error('Invalid optionsContext'); } + return /** @type {import('settings').ProfileOptions} */ (this._getProfileOptions(optionsContext, true)); + } case 'global': - return this._getOptionsFull(true); + return /** @type {import('settings').Options} */ (this._getOptionsFull(true)); default: throw new Error(`Invalid scope: ${scope}`); } } + /** + * @param {import('settings-modifications').OptionsScope&import('settings-modifications').Read} target + * @returns {unknown} + * @throws {Error} + */ _getSetting(target) { const options = this._getModifySettingObject(target); const accessor = new ObjectPropertyAccessor(options); @@ -1313,6 +1637,11 @@ export class Backend { return accessor.get(ObjectPropertyAccessor.getPathArray(path)); } + /** + * @param {import('settings-modifications').ScopedModification} target + * @returns {import('settings-modifications').ModificationResult} + * @throws {Error} + */ _modifySetting(target) { const options = this._getModifySettingObject(target); const accessor = new ObjectPropertyAccessor(options); @@ -1368,10 +1697,14 @@ export class Backend { } } + /** + * @param {chrome.runtime.MessageSender} sender + * @throws {Error} + */ _validatePrivilegedMessageSender(sender) { let {url} = sender; if (typeof url === 'string' && yomitan.isExtensionUrl(url)) { return; } - const {tab} = url; + const {tab} = sender; if (typeof tab === 'object' && tab !== null) { ({url} = tab); if (typeof url === 'string' && yomitan.isExtensionUrl(url)) { return; } @@ -1379,6 +1712,9 @@ export class Backend { throw new Error('Invalid message sender'); } + /** + * @returns {Promise} + */ _getBrowserIconTitle() { return ( isObject(chrome.action) && @@ -1388,6 +1724,9 @@ export class Backend { ); } + /** + * @returns {void} + */ _updateBadge() { let title = this._defaultBrowserActionTitle; if (title === null || !isObject(chrome.action)) { @@ -1423,7 +1762,7 @@ export class Backend { status = 'Loading'; } } else { - const options = this._getProfileOptions({current: true}); + const options = this._getProfileOptions({current: true}, false); if (!options.general.enable) { text = 'off'; color = '#555555'; @@ -1453,6 +1792,10 @@ export class Backend { } } + /** + * @param {import('settings').ProfileOptions} options + * @returns {boolean} + */ _isAnyDictionaryEnabled(options) { for (const {enabled} of options.dictionaries) { if (enabled) { @@ -1462,21 +1805,18 @@ export class Backend { return false; } - _anyOptionsMatches(predicate) { - for (const {options} of this._options.profiles) { - const value = predicate(options); - if (value) { return value; } - } - return false; - } - + /** + * @param {number} tabId + * @returns {Promise} + */ async _getTabUrl(tabId) { try { - const {url} = await this._sendMessageTabPromise( + const response = await this._sendMessageTabPromise( tabId, {action: 'Yomitan.getUrl', params: {}}, {frameId: 0} ); + const url = typeof response === 'object' && response !== null ? /** @type {import('core').SerializableObject} */ (response).url : void 0; if (typeof url === 'string') { return url; } @@ -1486,6 +1826,9 @@ export class Backend { return null; } + /** + * @returns {Promise} + */ _getAllTabs() { return new Promise((resolve, reject) => { chrome.tabs.query({}, (tabs) => { @@ -1499,21 +1842,33 @@ export class Backend { }); } + /** + * @param {number} timeout + * @param {boolean} multiple + * @param {import('backend').FindTabsPredicate} predicate + * @param {boolean} predicateIsAsync + * @returns {Promise} + */ async _findTabs(timeout, multiple, predicate, predicateIsAsync) { // This function works around the need to have the "tabs" permission to access tab.url. const tabs = await this._getAllTabs(); let done = false; + /** + * @param {chrome.tabs.Tab} tab + * @param {(tabInfo: import('backend').TabInfo) => boolean} add + */ const checkTab = async (tab, add) => { - const url = await this._getTabUrl(tab.id); + const {id} = tab; + const url = typeof id === 'number' ? await this._getTabUrl(id) : null; if (done) { return; } let okay = false; const item = {tab, url}; try { - okay = predicate(item); - if (predicateIsAsync) { okay = await okay; } + const okayOrPromise = predicate(item); + okay = predicateIsAsync ? await okayOrPromise : /** @type {boolean} */ (okayOrPromise); } catch (e) { // NOP } @@ -1526,7 +1881,12 @@ export class Backend { }; if (multiple) { + /** @type {import('backend').TabInfo[]} */ const results = []; + /** + * @param {import('backend').TabInfo} value + * @returns {boolean} + */ const add = (value) => { results.push(value); return false; @@ -1538,8 +1898,13 @@ export class Backend { ]); return results; } else { - const {promise, resolve} = deferPromise(); + const {promise, resolve} = /** @type {import('core').DeferredPromiseDetails} */ (deferPromise()); + /** @type {?import('backend').TabInfo} */ let result = null; + /** + * @param {import('backend').TabInfo} value + * @returns {boolean} + */ const add = (value) => { result = value; resolve(); @@ -1556,9 +1921,17 @@ export class Backend { } } + /** + * @param {chrome.tabs.Tab} tab + */ async _focusTab(tab) { - await new Promise((resolve, reject) => { - chrome.tabs.update(tab.id, {active: true}, () => { + await /** @type {Promise} */ (new Promise((resolve, reject) => { + const {id} = tab; + if (typeof id !== 'number') { + reject(new Error('Cannot focus a tab without an id')); + return; + } + chrome.tabs.update(id, {active: true}, () => { const e = chrome.runtime.lastError; if (e) { reject(new Error(e.message)); @@ -1566,7 +1939,7 @@ export class Backend { resolve(); } }); - }); + })); if (!(typeof chrome.windows === 'object' && chrome.windows !== null)) { // Windows not supported (e.g. on Firefox mobile) @@ -1585,7 +1958,7 @@ export class Backend { }); }); if (!tabWindow.focused) { - await new Promise((resolve, reject) => { + await /** @type {Promise} */ (new Promise((resolve, reject) => { chrome.windows.update(tab.windowId, {focused: true}, () => { const e = chrome.runtime.lastError; if (e) { @@ -1594,23 +1967,31 @@ export class Backend { resolve(); } }); - }); + })); } } catch (e) { // Edge throws exception for no reason here. } } + /** + * @param {number} tabId + * @param {number} frameId + * @param {?number} [timeout=null] + * @returns {Promise} + */ _waitUntilTabFrameIsReady(tabId, frameId, timeout=null) { return new Promise((resolve, reject) => { + /** @type {?number} */ let timer = null; + /** @type {?import('extension').ChromeRuntimeOnMessageCallback} */ let onMessage = (message, sender) => { if ( !sender.tab || sender.tab.id !== tabId || sender.frameId !== frameId || - !isObject(message) || - message.action !== 'yomitanReady' + !(typeof message === 'object' && message !== null) || + /** @type {import('core').SerializableObject} */ (message).action !== 'yomitanReady' ) { return; } @@ -1651,7 +2032,11 @@ export class Backend { }); } - async _fetchAsset(url, json=false) { + /** + * @param {string} url + * @returns {Promise} + */ + async _fetchAsset(url) { const response = await fetch(chrome.runtime.getURL(url), { method: 'GET', mode: 'no-cors', @@ -1663,30 +2048,71 @@ export class Backend { if (!response.ok) { throw new Error(`Failed to fetch ${url}: ${response.status}`); } - return await (json ? response.json() : response.text()); + return response; + } + + /** + * @param {string} url + * @returns {Promise} + */ + async _fetchText(url) { + const response = await this._fetchAsset(url); + return await response.text(); + } + + /** + * @param {string} url + * @returns {Promise} + */ + async _fetchJson(url) { + const response = await this._fetchAsset(url); + return await response.json(); } - _sendMessageIgnoreResponse(...args) { + /** + * @param {{action: string, params: import('core').SerializableObject}} message + */ + _sendMessageIgnoreResponse(message) { const callback = () => this._checkLastError(chrome.runtime.lastError); - chrome.runtime.sendMessage(...args, callback); + chrome.runtime.sendMessage(message, callback); } - _sendMessageTabIgnoreResponse(...args) { + /** + * @param {number} tabId + * @param {{action: string, params?: import('core').SerializableObject, frameId?: number}} message + * @param {chrome.tabs.MessageSendOptions} options + */ + _sendMessageTabIgnoreResponse(tabId, message, options) { const callback = () => this._checkLastError(chrome.runtime.lastError); - chrome.tabs.sendMessage(...args, callback); + chrome.tabs.sendMessage(tabId, message, options, callback); } + /** + * @param {string} action + * @param {import('core').SerializableObject} params + */ _sendMessageAllTabsIgnoreResponse(action, params) { const callback = () => this._checkLastError(chrome.runtime.lastError); chrome.tabs.query({}, (tabs) => { for (const tab of tabs) { - chrome.tabs.sendMessage(tab.id, {action, params}, callback); + const {id} = tab; + if (typeof id !== 'number') { continue; } + chrome.tabs.sendMessage(id, {action, params}, callback); } }); } - _sendMessageTabPromise(...args) { + /** + * @param {number} tabId + * @param {{action: string, params?: import('core').SerializableObject}} message + * @param {chrome.tabs.MessageSendOptions} options + * @returns {Promise} + */ + _sendMessageTabPromise(tabId, message, options) { return new Promise((resolve, reject) => { + /** + * @param {unknown} response + */ const callback = (response) => { try { resolve(this._getMessageResponseResult(response)); @@ -1695,25 +2121,35 @@ export class Backend { } }; - chrome.tabs.sendMessage(...args, callback); + chrome.tabs.sendMessage(tabId, message, options, callback); }); } + /** + * @param {unknown} response + * @returns {unknown} + * @throws {Error} + */ _getMessageResponseResult(response) { - let error = chrome.runtime.lastError; + const error = chrome.runtime.lastError; if (error) { throw new Error(error.message); } - if (!isObject(response)) { + if (typeof response !== 'object' || response === null) { throw new Error('Tab did not respond'); } - error = response.error; - if (error) { - throw deserializeError(error); + const responseError = /** @type {import('core').SerializedError|undefined} */ (/** @type {import('core').SerializableObject} */ (response).error); + if (typeof responseError === 'object' && responseError !== null) { + throw ExtensionError.deserialize(responseError); } - return response.result; + return /** @type {import('core').SerializableObject} */ (response).result; } + /** + * @param {number} tabId + * @param {(url: ?string) => boolean} urlPredicate + * @returns {Promise} + */ async _checkTabUrl(tabId, urlPredicate) { let tab; try { @@ -1727,6 +2163,13 @@ export class Backend { return isValidTab ? tab : null; } + /** + * @param {number} tabId + * @param {number} frameId + * @param {'jpeg'|'png'} format + * @param {number} quality + * @returns {Promise} + */ async _getScreenshot(tabId, frameId, format, quality) { const tab = await this._getTabById(tabId); const {windowId} = tab; @@ -1762,6 +2205,16 @@ export class Backend { } } + /** + * @param {AnkiConnect} ankiConnect + * @param {number} timestamp + * @param {import('api').InjectAnkiNoteMediaDefinitionDetails} definitionDetails + * @param {?import('api').InjectAnkiNoteMediaAudioDetails} audioDetails + * @param {?import('api').InjectAnkiNoteMediaScreenshotDetails} screenshotDetails + * @param {?import('api').InjectAnkiNoteMediaClipboardDetails} clipboardDetails + * @param {import('api').InjectAnkiNoteMediaDictionaryMediaDetails[]} dictionaryMediaDetails + * @returns {Promise} + */ async _injectAnkNoteMedia(ankiConnect, timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) { let screenshotFileName = null; let clipboardImageFileName = null; @@ -1774,7 +2227,7 @@ export class Backend { screenshotFileName = await this._injectAnkiNoteScreenshot(ankiConnect, timestamp, definitionDetails, screenshotDetails); } } catch (e) { - errors.push(serializeError(e)); + errors.push(ExtensionError.serialize(e)); } try { @@ -1782,7 +2235,7 @@ export class Backend { clipboardImageFileName = await this._injectAnkiNoteClipboardImage(ankiConnect, timestamp, definitionDetails); } } catch (e) { - errors.push(serializeError(e)); + errors.push(ExtensionError.serialize(e)); } try { @@ -1790,7 +2243,7 @@ export class Backend { clipboardText = await this._clipboardReader.getText(false); } } catch (e) { - errors.push(serializeError(e)); + errors.push(ExtensionError.serialize(e)); } try { @@ -1798,19 +2251,20 @@ export class Backend { audioFileName = await this._injectAnkiNoteAudio(ankiConnect, timestamp, definitionDetails, audioDetails); } } catch (e) { - errors.push(serializeError(e)); + errors.push(ExtensionError.serialize(e)); } + /** @type {import('api').InjectAnkiNoteDictionaryMediaResult[]} */ let dictionaryMedia; try { let errors2; ({results: dictionaryMedia, errors: errors2} = await this._injectAnkiNoteDictionaryMedia(ankiConnect, timestamp, definitionDetails, dictionaryMediaDetails)); for (const error of errors2) { - errors.push(serializeError(error)); + errors.push(ExtensionError.serialize(error)); } } catch (e) { dictionaryMedia = []; - errors.push(serializeError(e)); + errors.push(ExtensionError.serialize(e)); } return { @@ -1823,16 +2277,17 @@ export class Backend { }; } + /** + * @param {AnkiConnect} ankiConnect + * @param {number} timestamp + * @param {import('api').InjectAnkiNoteMediaDefinitionDetails} definitionDetails + * @param {import('api').InjectAnkiNoteMediaAudioDetails} details + * @returns {Promise} + */ async _injectAnkiNoteAudio(ankiConnect, timestamp, definitionDetails, details) { - const {type, term, reading} = definitionDetails; - if ( - type === 'kanji' || - typeof term !== 'string' || - typeof reading !== 'string' || - (term.length === 0 && reading.length === 0) - ) { - return null; - } + if (definitionDetails.type !== 'term') { return null; } + const {term, reading} = definitionDetails; + if (term.length === 0 && reading.length === 0) { return null; } const {sources, preferredAudioIndex, idleTimeout} = details; let data; @@ -1852,15 +2307,20 @@ export class Backend { return null; } - let extension = MediaUtil.getFileExtensionFromAudioMediaType(contentType); + let extension = contentType !== null ? MediaUtil.getFileExtensionFromAudioMediaType(contentType) : null; if (extension === null) { extension = '.mp3'; } let fileName = this._generateAnkiNoteMediaFileName('yomitan_audio', extension, timestamp, definitionDetails); fileName = fileName.replace(/\]/g, ''); - fileName = await ankiConnect.storeMediaFile(fileName, data); - - return fileName; + return await ankiConnect.storeMediaFile(fileName, data); } + /** + * @param {AnkiConnect} ankiConnect + * @param {number} timestamp + * @param {import('api').InjectAnkiNoteMediaDefinitionDetails} definitionDetails + * @param {import('api').InjectAnkiNoteMediaScreenshotDetails} details + * @returns {Promise} + */ async _injectAnkiNoteScreenshot(ankiConnect, timestamp, definitionDetails, details) { const {tabId, frameId, format, quality} = details; const dataUrl = await this._getScreenshot(tabId, frameId, format, quality); @@ -1871,12 +2331,16 @@ export class Backend { throw new Error('Unknown media type for screenshot image'); } - let fileName = this._generateAnkiNoteMediaFileName('yomitan_browser_screenshot', extension, timestamp, definitionDetails); - fileName = await ankiConnect.storeMediaFile(fileName, data); - - return fileName; + const fileName = this._generateAnkiNoteMediaFileName('yomitan_browser_screenshot', extension, timestamp, definitionDetails); + return await ankiConnect.storeMediaFile(fileName, data); } + /** + * @param {AnkiConnect} ankiConnect + * @param {number} timestamp + * @param {import('api').InjectAnkiNoteMediaDefinitionDetails} definitionDetails + * @returns {Promise} + */ async _injectAnkiNoteClipboardImage(ankiConnect, timestamp, definitionDetails) { const dataUrl = await this._clipboardReader.getImage(); if (dataUrl === null) { @@ -1889,12 +2353,17 @@ export class Backend { throw new Error('Unknown media type for clipboard image'); } - let fileName = this._generateAnkiNoteMediaFileName('yomitan_clipboard_image', extension, timestamp, definitionDetails); - fileName = await ankiConnect.storeMediaFile(fileName, data); - - return fileName; + const fileName = this._generateAnkiNoteMediaFileName('yomitan_clipboard_image', extension, timestamp, definitionDetails); + return await ankiConnect.storeMediaFile(fileName, data); } + /** + * @param {AnkiConnect} ankiConnect + * @param {number} timestamp + * @param {import('api').InjectAnkiNoteMediaDefinitionDetails} definitionDetails + * @param {import('api').InjectAnkiNoteMediaDictionaryMediaDetails[]} dictionaryMediaDetails + * @returns {Promise<{results: import('api').InjectAnkiNoteDictionaryMediaResult[], errors: unknown[]}>} + */ async _injectAnkiNoteDictionaryMedia(ankiConnect, timestamp, definitionDetails, dictionaryMediaDetails) { const targets = []; const detailsList = []; @@ -1918,6 +2387,7 @@ export class Backend { } const errors = []; + /** @type {import('api').InjectAnkiNoteDictionaryMediaResult[]} */ const results = []; for (let i = 0, ii = detailsList.length; i < ii; ++i) { const {dictionary, path, media} = detailsList[i]; @@ -1925,7 +2395,12 @@ export class Backend { if (media !== null) { const {content, mediaType} = media; const extension = MediaUtil.getFileExtensionFromImageMediaType(mediaType); - fileName = this._generateAnkiNoteMediaFileName(`yomitan_dictionary_media_${i + 1}`, extension, timestamp, definitionDetails); + fileName = this._generateAnkiNoteMediaFileName( + `yomitan_dictionary_media_${i + 1}`, + extension !== null ? extension : '', + timestamp, + definitionDetails + ); try { fileName = await ankiConnect.storeMediaFile(fileName, content); } catch (e) { @@ -1939,18 +2414,27 @@ export class Backend { return {results, errors}; } + /** + * @param {unknown} error + * @returns {?ExtensionError} + */ _getAudioDownloadError(error) { - if (isObject(error.data)) { - const {errors} = error.data; + if (error instanceof ExtensionError && typeof error.data === 'object' && error.data !== null) { + const {errors} = /** @type {import('core').SerializableObject} */ (error.data); if (Array.isArray(errors)) { for (const error2 of errors) { + if (!(error2 instanceof Error)) { continue; } if (error2.name === 'AbortError') { return this._createAudioDownloadError('Audio download was cancelled due to an idle timeout', 'audio-download-idle-timeout', errors); } - if (!isObject(error2.data)) { continue; } - const {details} = error2.data; - if (!isObject(details)) { continue; } - switch (details.error) { + if (!(error2 instanceof ExtensionError)) { continue; } + const {data} = error2; + if (!(typeof data === 'object' && data !== null)) { continue; } + const {details} = /** @type {import('core').SerializableObject} */ (data); + if (!(typeof details === 'object' && details !== null)) { continue; } + const error3 = /** @type {import('core').SerializableObject} */ (details).error; + if (typeof error3 !== 'string') { continue; } + switch (error3) { case 'net::ERR_FAILED': // This is potentially an error due to the extension not having enough URL privileges. // The message logged to the console looks like this: @@ -1967,23 +2451,38 @@ export class Backend { return null; } + /** + * @param {string} message + * @param {?string} issueId + * @param {?(Error[])} errors + * @returns {ExtensionError} + */ _createAudioDownloadError(message, issueId, errors) { - const error = new Error(message); + const error = new ExtensionError(message); const hasErrors = Array.isArray(errors); const hasIssueId = (typeof issueId === 'string'); if (hasErrors || hasIssueId) { + /** @type {{errors?: import('core').SerializedError[], referenceUrl?: string}} */ + const data = {}; error.data = {}; if (hasErrors) { // Errors need to be serialized since they are passed to other frames - error.data.errors = errors.map((e) => serializeError(e)); + data.errors = errors.map((e) => ExtensionError.serialize(e)); } if (hasIssueId) { - error.data.referenceUrl = `/issues.html#${issueId}`; + data.referenceUrl = `/issues.html#${issueId}`; } } return error; } + /** + * @param {string} prefix + * @param {string} extension + * @param {number} timestamp + * @param {import('api').InjectAnkiNoteMediaDefinitionDetails} definitionDetails + * @returns {string} + */ _generateAnkiNoteMediaFileName(prefix, extension, timestamp, definitionDetails) { let fileName = prefix; @@ -2011,11 +2510,19 @@ export class Backend { return fileName; } + /** + * @param {string} fileName + * @returns {string} + */ _replaceInvalidFileNameCharacters(fileName) { // eslint-disable-next-line no-control-regex return fileName.replace(/[<>:"/\\|?*\x00-\x1F]/g, '-'); } + /** + * @param {Date} date + * @returns {string} + */ _ankNoteDateToString(date) { const year = date.getUTCFullYear(); const month = date.getUTCMonth().toString().padStart(2, '0'); @@ -2026,6 +2533,11 @@ export class Backend { return `${year}-${month}-${day}-${hours}-${minutes}-${seconds}`; } + /** + * @param {string} dataUrl + * @returns {{mediaType: string, data: string}} + * @throws {Error} + */ _getDataUrlInfo(dataUrl) { const match = /^data:([^,]*?)(;base64)?,/.exec(dataUrl); if (match === null) { @@ -2041,28 +2553,35 @@ export class Backend { return {mediaType, data}; } + /** + * @param {import('backend').DatabaseUpdateType} type + * @param {import('backend').DatabaseUpdateCause} cause + */ _triggerDatabaseUpdated(type, cause) { this._translator.clearDatabaseCaches(); this._sendMessageAllTabsIgnoreResponse('Yomitan.databaseUpdated', {type, cause}); } + /** + * @param {string} source + */ async _saveOptions(source) { this._clearProfileConditionsSchemaCache(); - const options = this._getOptionsFull(); + const options = this._getOptionsFull(false); await this._optionsUtil.save(options); this._applyOptions(source); } /** * Creates an options object for use with `Translator.findTerms`. - * @param {string} mode The display mode for the dictionary entries. - * @param {{matchType: string, deinflect: boolean}} details Custom info for finding terms. - * @param {object} options The options. - * @returns {FindTermsOptions} An options object. + * @param {import('translator').FindTermsMode} mode The display mode for the dictionary entries. + * @param {import('api').FindTermsDetails} details Custom info for finding terms. + * @param {import('settings').ProfileOptions} options The options. + * @returns {import('translation').FindTermsOptions} An options object. */ _getTranslatorFindTermsOptions(mode, details, options) { let {matchType, deinflect} = details; - if (typeof matchType !== 'string') { matchType = 'exact'; } + if (typeof matchType !== 'string') { matchType = /** @type {import('translation').FindTermsMatchType} */ ('exact'); } if (typeof deinflect !== 'boolean') { deinflect = true; } const enabledDictionaryMap = this._getTranslatorEnabledDictionaryMap(options); const { @@ -2110,14 +2629,18 @@ export class Backend { /** * Creates an options object for use with `Translator.findKanji`. - * @param {object} options The options. - * @returns {FindKanjiOptions} An options object. + * @param {import('settings').ProfileOptions} options The options. + * @returns {import('translation').FindKanjiOptions} An options object. */ _getTranslatorFindKanjiOptions(options) { const enabledDictionaryMap = this._getTranslatorEnabledDictionaryMap(options); return {enabledDictionaryMap}; } + /** + * @param {import('settings').ProfileOptions} options + * @returns {Map} + */ _getTranslatorEnabledDictionaryMap(options) { const enabledDictionaryMap = new Map(); for (const dictionary of options.dictionaries) { @@ -2131,18 +2654,25 @@ export class Backend { return enabledDictionaryMap; } + /** + * @param {import('settings').TranslationTextReplacementOptions} textReplacementsOptions + * @returns {(?(import('translation').FindTermsTextReplacement[]))[]} + */ _getTranslatorTextReplacements(textReplacementsOptions) { + /** @type {(?(import('translation').FindTermsTextReplacement[]))[]} */ const textReplacements = []; for (const group of textReplacementsOptions.groups) { + /** @type {import('translation').FindTermsTextReplacement[]} */ const textReplacementsEntries = []; - for (let {pattern, ignoreCase, replacement} of group) { + for (const {pattern, ignoreCase, replacement} of group) { + let patternRegExp; try { - pattern = new RegExp(pattern, ignoreCase ? 'gi' : 'g'); + patternRegExp = new RegExp(pattern, ignoreCase ? 'gi' : 'g'); } catch (e) { // Invalid pattern continue; } - textReplacementsEntries.push({pattern, replacement}); + textReplacementsEntries.push({pattern: patternRegExp, replacement}); } if (textReplacementsEntries.length > 0) { textReplacements.push(textReplacementsEntries); @@ -2154,6 +2684,9 @@ export class Backend { return textReplacements; } + /** + * @returns {Promise} + */ async _openWelcomeGuidePageOnce() { chrome.storage.session.get(['openedWelcomePage']).then((result) => { if (!result.openedWelcomePage) { @@ -2163,20 +2696,33 @@ export class Backend { }); } + /** + * @returns {Promise} + */ async _openWelcomeGuidePage() { await this._createTab(chrome.runtime.getURL('/welcome.html')); } + /** + * @returns {Promise} + */ async _openInfoPage() { await this._createTab(chrome.runtime.getURL('/info.html')); } + /** + * @param {'existingOrNewTab'|'newTab'} mode + */ async _openSettingsPage(mode) { const manifest = chrome.runtime.getManifest(); - const url = chrome.runtime.getURL(manifest.options_ui.page); + const optionsUI = manifest.options_ui; + if (typeof optionsUI === 'undefined') { throw new Error('Failed to find options_ui'); } + const {page} = optionsUI; + if (typeof page === 'undefined') { throw new Error('Failed to find options_ui.page'); } + const url = chrome.runtime.getURL(page); switch (mode) { case 'existingOrNewTab': - await new Promise((resolve, reject) => { + await /** @type {Promise} */ (new Promise((resolve, reject) => { chrome.runtime.openOptionsPage(() => { const e = chrome.runtime.lastError; if (e) { @@ -2185,7 +2731,7 @@ export class Backend { resolve(); } }); - }); + })); break; case 'newTab': await this._createTab(url); @@ -2193,6 +2739,10 @@ export class Backend { } } + /** + * @param {string} url + * @returns {Promise} + */ _createTab(url) { return new Promise((resolve, reject) => { chrome.tabs.create({url}, (tab) => { @@ -2206,6 +2756,10 @@ export class Backend { }); } + /** + * @param {number} tabId + * @returns {Promise} + */ _getTabById(tabId) { return new Promise((resolve, reject) => { chrome.tabs.get( @@ -2222,20 +2776,33 @@ export class Backend { }); } + /** + * @returns {Promise} + */ async _checkPermissions() { this._permissions = await this._permissionsUtil.getAllPermissions(); this._updateBadge(); } + /** + * @returns {boolean} + */ _canObservePermissionsChanges() { return isObject(chrome.permissions) && isObject(chrome.permissions.onAdded) && isObject(chrome.permissions.onRemoved); } + /** + * @param {import('settings').ProfileOptions} options + * @returns {boolean} + */ _hasRequiredPermissionsForSettings(options) { if (!this._canObservePermissionsChanges()) { return true; } return this._permissions === null || this._permissionsUtil.hasRequiredPermissionsForOptions(this._permissions, options); } + /** + * @returns {Promise} + */ async _requestPersistentStorage() { try { if (await navigator.storage.persisted()) { return; } @@ -2257,14 +2824,32 @@ export class Backend { } } + /** + * @param {{path: string, dictionary: string}[]} targets + * @returns {Promise} + */ async _getNormalizedDictionaryDatabaseMedia(targets) { - const results = await this._dictionaryDatabase.getMedia(targets); - for (const item of results) { - const {content} = item; - if (content instanceof ArrayBuffer) { - item.content = ArrayBufferUtil.arrayBufferToBase64(content); - } + const results = []; + for (const item of await this._dictionaryDatabase.getMedia(targets)) { + const {content, dictionary, height, mediaType, path, width} = item; + const content2 = ArrayBufferUtil.arrayBufferToBase64(content); + results.push({content: content2, dictionary, height, mediaType, path, width}); } return results; } + + /** + * @param {unknown} mode + * @param {'existingOrNewTab'|'newTab'} defaultValue + * @returns {'existingOrNewTab'|'newTab'} + */ + _normalizeOpenSettingsPageMode(mode, defaultValue) { + switch (mode) { + case 'existingOrNewTab': + case 'newTab': + return mode; + default: + return defaultValue; + } + } } diff --git a/ext/js/background/profile-conditions-util.js b/ext/js/background/profile-conditions-util.js index 55b287d7..ceade070 100644 --- a/ext/js/background/profile-conditions-util.js +++ b/ext/js/background/profile-conditions-util.js @@ -22,68 +22,56 @@ import {JsonSchema} from '../data/json-schema.js'; * Utility class to help processing profile conditions. */ export class ProfileConditionsUtil { - /** - * A group of conditions. - * @typedef {object} ProfileConditionGroup - * @property {ProfileCondition[]} conditions The list of conditions for this group. - */ - - /** - * A single condition. - * @typedef {object} ProfileCondition - * @property {string} type The type of the condition. - * @property {string} operator The condition operator. - * @property {string} value The value to compare against. - */ - /** * Creates a new instance. */ constructor() { + /** @type {RegExp} */ this._splitPattern = /[,;\s]+/; + /** @type {Map}>} */ this._descriptors = new Map([ [ 'popupLevel', { - operators: new Map([ + operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([ ['equal', this._createSchemaPopupLevelEqual.bind(this)], ['notEqual', this._createSchemaPopupLevelNotEqual.bind(this)], ['lessThan', this._createSchemaPopupLevelLessThan.bind(this)], ['greaterThan', this._createSchemaPopupLevelGreaterThan.bind(this)], ['lessThanOrEqual', this._createSchemaPopupLevelLessThanOrEqual.bind(this)], ['greaterThanOrEqual', this._createSchemaPopupLevelGreaterThanOrEqual.bind(this)] - ]) + ])) } ], [ 'url', { - operators: new Map([ + operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([ ['matchDomain', this._createSchemaUrlMatchDomain.bind(this)], ['matchRegExp', this._createSchemaUrlMatchRegExp.bind(this)] - ]) + ])) } ], [ 'modifierKeys', { - operators: new Map([ + operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([ ['are', this._createSchemaModifierKeysAre.bind(this)], ['areNot', this._createSchemaModifierKeysAreNot.bind(this)], ['include', this._createSchemaModifierKeysInclude.bind(this)], ['notInclude', this._createSchemaModifierKeysNotInclude.bind(this)] - ]) + ])) } ], [ 'flags', { - operators: new Map([ + operators: new Map(/** @type {import('profile-conditions-util').OperatorMapArray} */ ([ ['are', this._createSchemaFlagsAre.bind(this)], ['areNot', this._createSchemaFlagsAreNot.bind(this)], ['include', this._createSchemaFlagsInclude.bind(this)], ['notInclude', this._createSchemaFlagsNotInclude.bind(this)] - ]) + ])) } ] ]); @@ -91,7 +79,7 @@ export class ProfileConditionsUtil { /** * Creates a new JSON schema descriptor for the given set of condition groups. - * @param {ProfileConditionGroup[]} conditionGroups An array of condition groups. + * @param {import('settings').ProfileConditionGroup[]} conditionGroups An array of condition groups. * For a profile match, all of the items must return successfully in at least one of the groups. * @returns {JsonSchema} A new `JsonSchema` object. */ @@ -127,11 +115,11 @@ export class ProfileConditionsUtil { /** * Creates a normalized version of the context object to test, * assigning dependent fields as needed. - * @param {object} context A context object which is used during schema validation. - * @returns {object} A normalized context object. + * @param {import('settings').OptionsContext} context A context object which is used during schema validation. + * @returns {import('profile-conditions-util').NormalizedOptionsContext} A normalized context object. */ normalizeContext(context) { - const normalizedContext = Object.assign({}, context); + const normalizedContext = /** @type {import('profile-conditions-util').NormalizedOptionsContext} */ (Object.assign({}, context)); const {url} = normalizedContext; if (typeof url === 'string') { try { @@ -149,10 +137,18 @@ export class ProfileConditionsUtil { // Private + /** + * @param {string} value + * @returns {string[]} + */ _split(value) { return value.split(this._splitPattern); } + /** + * @param {string} value + * @returns {number} + */ _stringToNumber(value) { const number = Number.parseFloat(value); return Number.isFinite(number) ? number : 0; @@ -160,64 +156,94 @@ export class ProfileConditionsUtil { // popupLevel schema creation functions + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaPopupLevelEqual(value) { - value = this._stringToNumber(value); + const number = this._stringToNumber(value); return { required: ['depth'], properties: { - depth: {const: value} + depth: {const: number} } }; } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaPopupLevelNotEqual(value) { return { - not: [this._createSchemaPopupLevelEqual(value)] + not: { + anyOf: [this._createSchemaPopupLevelEqual(value)] + } }; } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaPopupLevelLessThan(value) { - value = this._stringToNumber(value); + const number = this._stringToNumber(value); return { required: ['depth'], properties: { - depth: {type: 'number', exclusiveMaximum: value} + depth: {type: 'number', exclusiveMaximum: number} } }; } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaPopupLevelGreaterThan(value) { - value = this._stringToNumber(value); + const number = this._stringToNumber(value); return { required: ['depth'], properties: { - depth: {type: 'number', exclusiveMinimum: value} + depth: {type: 'number', exclusiveMinimum: number} } }; } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaPopupLevelLessThanOrEqual(value) { - value = this._stringToNumber(value); + const number = this._stringToNumber(value); return { required: ['depth'], properties: { - depth: {type: 'number', maximum: value} + depth: {type: 'number', maximum: number} } }; } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaPopupLevelGreaterThanOrEqual(value) { - value = this._stringToNumber(value); + const number = this._stringToNumber(value); return { required: ['depth'], properties: { - depth: {type: 'number', minimum: value} + depth: {type: 'number', minimum: number} } }; } // url schema creation functions + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaUrlMatchDomain(value) { const oneOf = []; for (let domain of this._split(value)) { @@ -233,6 +259,10 @@ export class ProfileConditionsUtil { }; } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaUrlMatchRegExp(value) { return { required: ['url'], @@ -244,47 +274,91 @@ export class ProfileConditionsUtil { // modifierKeys schema creation functions + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaModifierKeysAre(value) { return this._createSchemaArrayCheck('modifierKeys', value, true, false); } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaModifierKeysAreNot(value) { return { - not: [this._createSchemaArrayCheck('modifierKeys', value, true, false)] + not: { + anyOf: [this._createSchemaArrayCheck('modifierKeys', value, true, false)] + } }; } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaModifierKeysInclude(value) { return this._createSchemaArrayCheck('modifierKeys', value, false, false); } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaModifierKeysNotInclude(value) { return this._createSchemaArrayCheck('modifierKeys', value, false, true); } // modifierKeys schema creation functions + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaFlagsAre(value) { return this._createSchemaArrayCheck('flags', value, true, false); } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaFlagsAreNot(value) { return { - not: [this._createSchemaArrayCheck('flags', value, true, false)] + not: { + anyOf: [this._createSchemaArrayCheck('flags', value, true, false)] + } }; } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaFlagsInclude(value) { return this._createSchemaArrayCheck('flags', value, false, false); } + /** + * @param {string} value + * @returns {import('json-schema').Schema} + */ _createSchemaFlagsNotInclude(value) { return this._createSchemaArrayCheck('flags', value, false, true); } // Generic + /** + * @param {string} key + * @param {string} value + * @param {boolean} exact + * @param {boolean} none + * @returns {import('json-schema').Schema} + */ _createSchemaArrayCheck(key, value, exact, none) { + /** @type {import('json-schema').Schema[]} */ const containsList = []; for (const item of this._split(value)) { if (item.length === 0) { continue; } @@ -295,6 +369,7 @@ export class ProfileConditionsUtil { }); } const containsListCount = containsList.length; + /** @type {import('json-schema').Schema} */ const schema = { type: 'array' }; @@ -303,7 +378,7 @@ export class ProfileConditionsUtil { } if (none) { if (containsListCount > 0) { - schema.not = containsList; + schema.not = {anyOf: containsList}; } } else { schema.minItems = containsListCount; diff --git a/ext/js/background/request-builder.js b/ext/js/background/request-builder.js index f4f685be..48fe2dd9 100644 --- a/ext/js/background/request-builder.js +++ b/ext/js/background/request-builder.js @@ -31,7 +31,13 @@ export class RequestBuilder { * Creates a new instance. */ constructor() { + /** + * + */ this._textEncoder = new TextEncoder(); + /** + * + */ this._ruleIds = new Set(); } @@ -160,6 +166,9 @@ export class RequestBuilder { // Private + /** + * + */ async _clearSessionRules() { const rules = await this._getSessionRules(); @@ -173,6 +182,9 @@ export class RequestBuilder { await this._updateSessionRules({removeRuleIds}); } + /** + * + */ _getSessionRules() { return new Promise((resolve, reject) => { chrome.declarativeNetRequest.getSessionRules((result) => { @@ -186,6 +198,10 @@ export class RequestBuilder { }); } + /** + * + * @param options + */ _updateSessionRules(options) { return new Promise((resolve, reject) => { chrome.declarativeNetRequest.updateSessionRules(options, () => { @@ -199,6 +215,10 @@ export class RequestBuilder { }); } + /** + * + * @param options + */ async _tryUpdateSessionRules(options) { try { await this._updateSessionRules(options); @@ -208,6 +228,9 @@ export class RequestBuilder { } } + /** + * + */ async _clearDynamicRules() { const rules = await this._getDynamicRules(); @@ -221,6 +244,9 @@ export class RequestBuilder { await this._updateDynamicRules({removeRuleIds}); } + /** + * + */ _getDynamicRules() { return new Promise((resolve, reject) => { chrome.declarativeNetRequest.getDynamicRules((result) => { @@ -234,6 +260,10 @@ export class RequestBuilder { }); } + /** + * + * @param options + */ _updateDynamicRules(options) { return new Promise((resolve, reject) => { chrome.declarativeNetRequest.updateDynamicRules(options, () => { @@ -247,6 +277,9 @@ export class RequestBuilder { }); } + /** + * + */ _getNewRuleId() { let id = 1; while (this._ruleIds.has(id)) { @@ -257,15 +290,27 @@ export class RequestBuilder { return id; } + /** + * + * @param url + */ _getOriginURL(url) { const url2 = new URL(url); return `${url2.protocol}//${url2.host}`; } + /** + * + * @param url + */ _escapeDnrUrl(url) { return url.replace(/[|*^]/g, (char) => this._urlEncodeUtf8(char)); } + /** + * + * @param text + */ _urlEncodeUtf8(text) { const array = this._textEncoder.encode(text); let result = ''; @@ -275,6 +320,11 @@ export class RequestBuilder { return result; } + /** + * + * @param items + * @param totalLength + */ static _joinUint8Arrays(items, totalLength) { if (items.length === 1) { const {array, length} = items[0]; diff --git a/ext/js/background/script-manager.js b/ext/js/background/script-manager.js index 3671b854..98f67bb0 100644 --- a/ext/js/background/script-manager.js +++ b/ext/js/background/script-manager.js @@ -17,6 +17,7 @@ */ import {isObject} from '../core.js'; + /** * This class is used to manage script injection into content tabs. */ @@ -25,18 +26,19 @@ export class ScriptManager { * Creates a new instance of the class. */ constructor() { + /** @type {Map} */ this._contentScriptRegistrations = new Map(); } /** * Injects a stylesheet into a tab. - * @param {string} type The type of content to inject; either 'file' or 'code'. + * @param {'file'|'code'} type The type of content to inject; either 'file' or 'code'. * @param {string} content The content to inject. * If type is 'file', this argument should be a path to a file. * If type is 'code', this argument should be the CSS content. * @param {number} tabId The id of the tab to inject into. - * @param {number} [frameId] The id of the frame to inject into. - * @param {boolean} [allFrames] Whether or not the stylesheet should be injected into all frames. + * @param {number|undefined} frameId The id of the frame to inject into. + * @param {boolean} allFrames Whether or not the stylesheet should be injected into all frames. * @returns {Promise} */ injectStylesheet(type, content, tabId, frameId, allFrames) { @@ -51,9 +53,9 @@ export class ScriptManager { * Injects a script into a tab. * @param {string} file The path to a file to inject. * @param {number} tabId The id of the tab to inject into. - * @param {number} [frameId] The id of the frame to inject into. - * @param {boolean} [allFrames] Whether or not the script should be injected into all frames. - * @returns {Promise<{frameId: number, result: object}>} The id of the frame and the result of the script injection. + * @param {number|undefined} frameId The id of the frame to inject into. + * @param {boolean} allFrames Whether or not the script should be injected into all frames. + * @returns {Promise<{frameId: number|undefined, result: unknown}>} The id of the frame and the result of the script injection. */ injectScript(file, tabId, frameId, allFrames) { if (isObject(chrome.scripting) && typeof chrome.scripting.executeScript === 'function') { @@ -98,16 +100,7 @@ export class ScriptManager { * there is a possibility that the script can be injected more than once due to the events used. * Therefore, a reentrant check may need to be performed by the content script. * @param {string} id A unique identifier for the registration. - * @param {object} details The script registration details. - * @param {boolean} [details.allFrames] Same as `all_frames` in the `content_scripts` manifest key. - * @param {string[]} [details.css] List of CSS paths. - * @param {string[]} [details.excludeMatches] Same as `exclude_matches` in the `content_scripts` manifest key. - * @param {string[]} [details.js] List of script paths. - * @param {boolean} [details.matchAboutBlank] Same as `match_about_blank` in the `content_scripts` manifest key. - * @param {string[]} details.matches Same as `matches` in the `content_scripts` manifest key. - * @param {string} [details.urlMatches] Regex match pattern to use as a fallback - * when native content script registration isn't supported. Should be equivalent to `matches`. - * @param {string} [details.runAt] Same as `run_at` in the `content_scripts` manifest key. + * @param {import('script-manager').RegistrationDetails} details The script registration details. * @throws An error is thrown if the id is already in use. */ async registerContentScript(id, details) { @@ -116,8 +109,8 @@ export class ScriptManager { } if (isObject(chrome.scripting) && typeof chrome.scripting.registerContentScripts === 'function') { - const details2 = this._convertContentScriptRegistrationDetails(details, id, false); - await new Promise((resolve, reject) => { + const details2 = this._createContentScriptRegistrationOptionsChrome(details, id); + await /** @type {Promise} */ (new Promise((resolve, reject) => { chrome.scripting.registerContentScripts([details2], () => { const e = chrome.runtime.lastError; if (e) { @@ -126,7 +119,7 @@ export class ScriptManager { resolve(); } }); - }); + })); this._contentScriptRegistrations.set(id, null); return; } @@ -155,7 +148,7 @@ export class ScriptManager { const registration = this._contentScriptRegistrations.get(id); if (typeof registration === 'undefined') { return false; } this._contentScriptRegistrations.delete(id); - if (isObject(registration) && typeof registration.unregister === 'function') { + if (registration !== null && typeof registration.unregister === 'function') { await registration.unregister(); } return true; @@ -176,17 +169,27 @@ export class ScriptManager { // Private + /** + * @param {'file'|'code'} type + * @param {string} content + * @param {number} tabId + * @param {number|undefined} frameId + * @param {boolean} allFrames + * @returns {Promise} + */ _injectStylesheetMV3(type, content, tabId, frameId, allFrames) { return new Promise((resolve, reject) => { - const details = ( - type === 'file' ? - {origin: 'AUTHOR', files: [content]} : - {origin: 'USER', css: content} - ); - details.target = { + /** @type {chrome.scripting.InjectionTarget} */ + const target = { tabId, allFrames }; + /** @type {chrome.scripting.CSSInjection} */ + const details = ( + type === 'file' ? + {origin: 'AUTHOR', files: [content], target} : + {origin: 'USER', css: content, target} + ); if (!allFrames && typeof frameId === 'number') { details.target.frameIds = [frameId]; } @@ -201,8 +204,16 @@ export class ScriptManager { }); } + /** + * @param {string} file + * @param {number} tabId + * @param {number|undefined} frameId + * @param {boolean} allFrames + * @returns {Promise<{frameId: number|undefined, result: unknown}>} The id of the frame and the result of the script injection. + */ _injectScriptMV3(file, tabId, frameId, allFrames) { return new Promise((resolve, reject) => { + /** @type {chrome.scripting.ScriptInjection} */ const details = { injectImmediately: true, files: [file], @@ -223,6 +234,10 @@ export class ScriptManager { }); } + /** + * @param {string} id + * @returns {Promise} + */ _unregisterContentScriptMV3(id) { return new Promise((resolve, reject) => { chrome.scripting.unregisterContentScripts({ids: [id]}, () => { @@ -236,73 +251,132 @@ export class ScriptManager { }); } - _convertContentScriptRegistrationDetails(details, id, firefoxConvention) { - const {allFrames, css, excludeMatches, js, matchAboutBlank, matches, runAt} = details; - const details2 = {}; - if (!firefoxConvention) { - details2.id = id; - details2.persistAcrossSessions = true; + /** + * @param {import('script-manager').RegistrationDetails} details + * @returns {browser.contentScripts.RegisteredContentScriptOptions} + */ + _createContentScriptRegistrationOptionsFirefox(details) { + const {css, js, matchAboutBlank} = details; + /** @type {browser.contentScripts.RegisteredContentScriptOptions} */ + const options = {}; + if (typeof matchAboutBlank !== 'undefined') { + options.matchAboutBlank = matchAboutBlank; } + if (Array.isArray(css)) { + options.css = css.map((file) => ({file})); + } + if (Array.isArray(js)) { + options.js = js.map((file) => ({file})); + } + this._initializeContentScriptRegistrationOptionsGeneric(details, options); + return options; + } + + /** + * @param {import('script-manager').RegistrationDetails} details + * @param {string} id + * @returns {chrome.scripting.RegisteredContentScript} + */ + _createContentScriptRegistrationOptionsChrome(details, id) { + const {css, js} = details; + /** @type {chrome.scripting.RegisteredContentScript} */ + const options = { + id: id, + persistAcrossSessions: true + }; + if (Array.isArray(css)) { + options.css = [...css]; + } + if (Array.isArray(js)) { + options.js = [...js]; + } + this._initializeContentScriptRegistrationOptionsGeneric(details, options); + return options; + } + + /** + * @param {import('script-manager').RegistrationDetails} details + * @param {chrome.scripting.RegisteredContentScript|browser.contentScripts.RegisteredContentScriptOptions} options + */ + _initializeContentScriptRegistrationOptionsGeneric(details, options) { + const {allFrames, excludeMatches, matches, runAt} = details; if (typeof allFrames !== 'undefined') { - details2.allFrames = allFrames; + options.allFrames = allFrames; } if (Array.isArray(excludeMatches)) { - details2.excludeMatches = [...excludeMatches]; + options.excludeMatches = [...excludeMatches]; } if (Array.isArray(matches)) { - details2.matches = [...matches]; + options.matches = [...matches]; } if (typeof runAt !== 'undefined') { - details2.runAt = runAt; - } - if (firefoxConvention && typeof matchAboutBlank !== 'undefined') { - details2.matchAboutBlank = matchAboutBlank; + options.runAt = runAt; } - if (Array.isArray(css)) { - details2.css = this._convertFileArray(css, firefoxConvention); - } - if (Array.isArray(js)) { - details2.js = this._convertFileArray(js, firefoxConvention); - } - return details2; } + /** + * @param {string[]} array + * @param {boolean} firefoxConvention + * @returns {string[]|browser.extensionTypes.ExtensionFileOrCode[]} + */ _convertFileArray(array, firefoxConvention) { return firefoxConvention ? array.map((file) => ({file})) : [...array]; } + /** + * @param {string} id + * @param {import('script-manager').RegistrationDetails} details + */ _registerContentScriptFallback(id, details) { const {allFrames, css, js, matchAboutBlank, runAt, urlMatches} = details; - const details2 = {allFrames, css, js, matchAboutBlank, runAt, urlRegex: null}; + /** @type {import('script-manager').ContentScriptInjectionDetails} */ + const details2 = {allFrames, css, js, matchAboutBlank, runAt, urlRegex: /** @type {?RegExp} */ (null)}; + /** @type {() => Promise} */ let unregister; const webNavigationEvent = this._getWebNavigationEvent(runAt); - if (isObject(webNavigationEvent)) { + if (typeof webNavigationEvent === 'object' && webNavigationEvent !== null) { + /** + * @param {chrome.webNavigation.WebNavigationFramedCallbackDetails} details + */ const onTabCommitted = ({url, tabId, frameId}) => { this._injectContentScript(true, details2, null, url, tabId, frameId); }; const filter = {url: [{urlMatches}]}; webNavigationEvent.addListener(onTabCommitted, filter); - unregister = () => webNavigationEvent.removeListener(onTabCommitted); + unregister = async () => webNavigationEvent.removeListener(onTabCommitted); } else { + /** + * @param {number} tabId + * @param {chrome.tabs.TabChangeInfo} changeInfo + * @param {chrome.tabs.Tab} tab + */ const onTabUpdated = (tabId, {status}, {url}) => { if (typeof status === 'string' && typeof url === 'string') { this._injectContentScript(false, details2, status, url, tabId, void 0); } }; - const extraParameters = {url: [urlMatches], properties: ['status']}; try { // Firefox - chrome.tabs.onUpdated.addListener(onTabUpdated, extraParameters); + /** @type {browser.tabs.UpdateFilter} */ + const extraParameters = {urls: [urlMatches], properties: ['status']}; + browser.tabs.onUpdated.addListener( + /** @type {(tabId: number, changeInfo: browser.tabs._OnUpdatedChangeInfo, tab: browser.tabs.Tab) => void} */ (onTabUpdated), + extraParameters + ); } catch (e) { // Chrome details2.urlRegex = new RegExp(urlMatches); chrome.tabs.onUpdated.addListener(onTabUpdated); } - unregister = () => chrome.tabs.onUpdated.removeListener(onTabUpdated); + unregister = async () => chrome.tabs.onUpdated.removeListener(onTabUpdated); } this._contentScriptRegistrations.set(id, {unregister}); } + /** + * @param {import('script-manager').RunAt} runAt + * @returns {?(chrome.webNavigation.WebNavigationFramedEvent|chrome.webNavigation.WebNavigationTransitionalEvent)} + */ _getWebNavigationEvent(runAt) { const {webNavigation} = chrome; if (!isObject(webNavigation)) { return null; } @@ -316,6 +390,14 @@ export class ScriptManager { } } + /** + * @param {boolean} isWebNavigation + * @param {import('script-manager').ContentScriptInjectionDetails} details + * @param {?string} status + * @param {string} url + * @param {number} tabId + * @param {number|undefined} frameId + */ async _injectContentScript(isWebNavigation, details, status, url, tabId, frameId) { const {urlRegex} = details; if (urlRegex !== null && !urlRegex.test(url)) { return; } -- cgit v1.2.3 From aabd761ee9064f6a46703f234e016f31f6441fa0 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 13:36:04 -0500 Subject: Remove unneeded references --- ext/js/accessibility/accessibility-controller.js | 5 ++--- ext/js/accessibility/google-docs-util.js | 1 - ext/js/app/frontend.js | 6 ++---- ext/js/app/popup-proxy.js | 14 ++++++-------- ext/js/app/popup-window.js | 9 ++++----- ext/js/background/backend.js | 1 - ext/js/comm/anki-connect.js | 1 - ext/js/dom/document-util.js | 1 - ext/js/dom/text-source-element.js | 1 - ext/js/dom/text-source-range.js | 1 - ext/js/language/dictionary-worker-handler.js | 1 - ext/js/language/dictionary-worker-media-loader.js | 2 +- ext/js/language/translator.js | 1 - ext/js/templates/template-renderer-proxy.js | 2 +- 14 files changed, 16 insertions(+), 30 deletions(-) (limited to 'ext/js/background') diff --git a/ext/js/accessibility/accessibility-controller.js b/ext/js/accessibility/accessibility-controller.js index a4239947..8250b369 100644 --- a/ext/js/accessibility/accessibility-controller.js +++ b/ext/js/accessibility/accessibility-controller.js @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import {ScriptManager} from '../background/script-manager.js'; import {log} from '../core.js'; /** @@ -25,10 +24,10 @@ import {log} from '../core.js'; export class AccessibilityController { /** * Creates a new instance. - * @param {ScriptManager} scriptManager An instance of the `ScriptManager` class. + * @param {import('../background/script-manager.js').ScriptManager} scriptManager An instance of the `ScriptManager` class. */ constructor(scriptManager) { - /** @type {ScriptManager} */ + /** @type {import('../background/script-manager.js').ScriptManager} */ this._scriptManager = scriptManager; /** @type {?import('core').TokenObject} */ this._updateGoogleDocsAccessibilityToken = null; diff --git a/ext/js/accessibility/google-docs-util.js b/ext/js/accessibility/google-docs-util.js index 9db45cc1..4321f082 100644 --- a/ext/js/accessibility/google-docs-util.js +++ b/ext/js/accessibility/google-docs-util.js @@ -17,7 +17,6 @@ */ import {DocumentUtil} from '../dom/document-util.js'; -import {TextSourceElement} from '../dom/text-source-element.js'; import {TextSourceRange} from '../dom/text-source-range.js'; /** diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index fec933f8..628c504e 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -21,10 +21,8 @@ import {EventListenerCollection, invokeMessageHandler, log, promiseAnimationFram import {DocumentUtil} from '../dom/document-util.js'; import {TextSourceElement} from '../dom/text-source-element.js'; import {TextSourceRange} from '../dom/text-source-range.js'; -import {HotkeyHandler} from '../input/hotkey-handler.js'; import {TextScanner} from '../language/text-scanner.js'; import {yomitan} from '../yomitan.js'; -import {PopupFactory} from './popup-factory.js'; /** * This is the main class responsible for scanning and handling webpage content. @@ -50,7 +48,7 @@ export class Frontend { }) { /** @type {import('frontend').PageType} */ this._pageType = pageType; - /** @type {PopupFactory} */ + /** @type {import('./popup-factory.js').PopupFactory} */ this._popupFactory = popupFactory; /** @type {number} */ this._depth = depth; @@ -70,7 +68,7 @@ export class Frontend { this._allowRootFramePopupProxy = allowRootFramePopupProxy; /** @type {boolean} */ this._childrenSupported = childrenSupported; - /** @type {HotkeyHandler} */ + /** @type {import('../input/hotkey-handler.js').HotkeyHandler} */ this._hotkeyHandler = hotkeyHandler; /** @type {?import('popup').PopupAny} */ this._popup = null; diff --git a/ext/js/app/popup-proxy.js b/ext/js/app/popup-proxy.js index 9b5b0214..924175e2 100644 --- a/ext/js/app/popup-proxy.js +++ b/ext/js/app/popup-proxy.js @@ -16,10 +16,8 @@ * along with this program. If not, see . */ -import {FrameOffsetForwarder} from '../comm/frame-offset-forwarder.js'; import {EventDispatcher, log} from '../core.js'; import {yomitan} from '../yomitan.js'; -import {Popup} from './popup.js'; /** * This class is a proxy for a Popup that is hosted in a different frame. @@ -44,7 +42,7 @@ export class PopupProxy extends EventDispatcher { this._depth = depth; /** @type {number} */ this._frameId = frameId; - /** @type {?FrameOffsetForwarder} */ + /** @type {?import('../comm/frame-offset-forwarder.js').FrameOffsetForwarder} */ this._frameOffsetForwarder = frameOffsetForwarder; /** @type {number} */ @@ -70,7 +68,7 @@ export class PopupProxy extends EventDispatcher { /** * The parent of the popup, which is always `null` for `PopupProxy` instances, * since any potential parent popups are in a different frame. - * @type {?Popup} + * @type {?import('./popup.js').Popup} */ get parent() { return null; @@ -78,7 +76,7 @@ export class PopupProxy extends EventDispatcher { /** * Attempts to set the parent popup. - * @param {Popup} _value The parent to assign. + * @param {import('./popup.js').Popup} _value The parent to assign. * @throws {Error} Throws an error, since this class doesn't support a direct parent. */ set parent(_value) { @@ -88,7 +86,7 @@ export class PopupProxy extends EventDispatcher { /** * The popup child popup, which is always null for `PopupProxy` instances, * since any potential child popups are in a different frame. - * @type {?Popup} + * @type {?import('./popup.js').Popup} */ get child() { return null; @@ -96,7 +94,7 @@ export class PopupProxy extends EventDispatcher { /** * Attempts to set the child popup. - * @param {Popup} _child The child to assign. + * @param {import('./popup.js').Popup} _child The child to assign. * @throws {Error} Throws an error, since this class doesn't support children. */ set child(_child) { @@ -354,7 +352,7 @@ export class PopupProxy extends EventDispatcher { * @param {number} now */ async _updateFrameOffsetInner(now) { - this._frameOffsetPromise = /** @type {FrameOffsetForwarder} */ (this._frameOffsetForwarder).getOffset(); + this._frameOffsetPromise = /** @type {import('../comm/frame-offset-forwarder.js').FrameOffsetForwarder} */ (this._frameOffsetForwarder).getOffset(); try { const offset = await this._frameOffsetPromise; if (offset !== null) { diff --git a/ext/js/app/popup-window.js b/ext/js/app/popup-window.js index af1ac1e4..9a0f8011 100644 --- a/ext/js/app/popup-window.js +++ b/ext/js/app/popup-window.js @@ -18,7 +18,6 @@ import {EventDispatcher} from '../core.js'; import {yomitan} from '../yomitan.js'; -import {Popup} from './popup.js'; /** * This class represents a popup that is hosted in a new native window. @@ -54,7 +53,7 @@ export class PopupWindow extends EventDispatcher { } /** - * @type {?Popup} + * @type {?import('./popup.js').Popup} */ get parent() { return null; @@ -63,7 +62,7 @@ export class PopupWindow extends EventDispatcher { /** * The parent of the popup, which is always `null` for `PopupWindow` instances, * since any potential parent popups are in a different frame. - * @param {Popup} _value The parent to assign. + * @param {import('./popup.js').Popup} _value The parent to assign. * @throws {Error} Throws an error, since this class doesn't support children. */ set parent(_value) { @@ -73,7 +72,7 @@ export class PopupWindow extends EventDispatcher { /** * The popup child popup, which is always null for `PopupWindow` instances, * since any potential child popups are in a different frame. - * @type {?Popup} + * @type {?import('./popup.js').Popup} */ get child() { return null; @@ -81,7 +80,7 @@ export class PopupWindow extends EventDispatcher { /** * Attempts to set the child popup. - * @param {Popup} _value The child to assign. + * @param {import('./popup.js').Popup} _value The child to assign. * @throws Throws an error, since this class doesn't support children. */ set child(_value) { diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index a8683b3f..14877cf1 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -25,7 +25,6 @@ import {Mecab} from '../comm/mecab.js'; import {clone, deferPromise, generateId, invokeMessageHandler, isObject, log, promiseTimeout} from '../core.js'; import {ExtensionError} from '../core/extension-error.js'; import {AnkiUtil} from '../data/anki-util.js'; -import {JsonSchema} from '../data/json-schema.js'; import {OptionsUtil} from '../data/options-util.js'; import {PermissionsUtil} from '../data/permissions-util.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/comm/anki-connect.js b/ext/js/comm/anki-connect.js index 3262af41..b876703f 100644 --- a/ext/js/comm/anki-connect.js +++ b/ext/js/comm/anki-connect.js @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import {isObject} from '../core.js'; import {AnkiUtil} from '../data/anki-util.js'; /** diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index f53d55fd..cf58d39f 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import {EventListenerCollection} from '../core.js'; import {DOMTextScanner} from './dom-text-scanner.js'; import {TextSourceElement} from './text-source-element.js'; import {TextSourceRange} from './text-source-range.js'; diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index fbe89a61..47c18e30 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -18,7 +18,6 @@ import {StringUtil} from '../data/sandbox/string-util.js'; import {DocumentUtil} from './document-util.js'; -import {TextSourceRange} from './text-source-range.js'; /** * This class represents a text source that is attached to a HTML element, such as an diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index 5c3d4184..5dbbd636 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -18,7 +18,6 @@ import {DocumentUtil} from './document-util.js'; import {DOMTextScanner} from './dom-text-scanner.js'; -import {TextSourceElement} from './text-source-element.js'; /** * This class represents a text source that comes from text nodes in the document. diff --git a/ext/js/language/dictionary-worker-handler.js b/ext/js/language/dictionary-worker-handler.js index 8ac342b2..3a85cb71 100644 --- a/ext/js/language/dictionary-worker-handler.js +++ b/ext/js/language/dictionary-worker-handler.js @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import {serializeError} from '../core.js'; import {DictionaryDatabase} from './dictionary-database.js'; import {DictionaryImporter} from './dictionary-importer.js'; import {DictionaryWorkerMediaLoader} from './dictionary-worker-media-loader.js'; diff --git a/ext/js/language/dictionary-worker-media-loader.js b/ext/js/language/dictionary-worker-media-loader.js index 9e3fd67e..2701389e 100644 --- a/ext/js/language/dictionary-worker-media-loader.js +++ b/ext/js/language/dictionary-worker-media-loader.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {deserializeError, generateId} from '../core.js'; +import {generateId} from '../core.js'; /** * Class used for loading and validating media from a worker thread diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 9b01c1ff..67cc53c6 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -19,7 +19,6 @@ import {RegexUtil} from '../general/regex-util.js'; import {TextSourceMap} from '../general/text-source-map.js'; import {Deinflector} from './deinflector.js'; -import {DictionaryDatabase} from './dictionary-database.js'; /** * Class which finds term and kanji dictionary entries for text. diff --git a/ext/js/templates/template-renderer-proxy.js b/ext/js/templates/template-renderer-proxy.js index 6d019d14..e67b715a 100644 --- a/ext/js/templates/template-renderer-proxy.js +++ b/ext/js/templates/template-renderer-proxy.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {deserializeError, generateId} from '../core.js'; +import {generateId} from '../core.js'; export class TemplateRendererProxy { constructor() { -- 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/background') 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 a96be7d413c8f1714f0d1b44b3987f2eb87b22d8 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 16:14:05 -0500 Subject: Update request builder --- ext/js/background/request-builder.js | 87 ++++++++++++++++++------------------ 1 file changed, 44 insertions(+), 43 deletions(-) (limited to 'ext/js/background') diff --git a/ext/js/background/request-builder.js b/ext/js/background/request-builder.js index 5ae7fbf5..23f10ed3 100644 --- a/ext/js/background/request-builder.js +++ b/ext/js/background/request-builder.js @@ -25,13 +25,9 @@ export class RequestBuilder { * Creates a new instance. */ constructor() { - /** - * - */ + /** @type {TextEncoder} */ this._textEncoder = new TextEncoder(); - /** - * - */ + /** @type {Set} */ this._ruleIds = new Set(); } @@ -60,29 +56,32 @@ export class RequestBuilder { this._ruleIds.add(id); try { + /** @type {chrome.declarativeNetRequest.Rule[]} */ const addRules = [{ id, priority: 1, condition: { urlFilter: `|${this._escapeDnrUrl(url)}|`, - resourceTypes: ['xmlhttprequest'] + resourceTypes: [ + /** @type {chrome.declarativeNetRequest.ResourceType} */ ('xmlhttprequest') + ] }, action: { - type: 'modifyHeaders', + type: /** @type {chrome.declarativeNetRequest.RuleActionType} */ ('modifyHeaders'), requestHeaders: [ { - operation: 'remove', + operation: /** @type {chrome.declarativeNetRequest.HeaderOperation} */ ('remove'), header: 'Cookie' }, { - operation: 'set', + operation: /** @type {chrome.declarativeNetRequest.HeaderOperation} */ ('set'), header: 'Origin', value: originUrl } ], responseHeaders: [ { - operation: 'remove', + operation: /** @type {chrome.declarativeNetRequest.HeaderOperation} */ ('remove'), header: 'Set-Cookie' } ] @@ -103,13 +102,14 @@ 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 {?import('request-builder.js').ProgressCallback} onProgress The progress callback + * @param {?(done: boolean) => void} onProgress The progress callback. * @returns {Promise} The resulting binary data. */ static async readFetchResponseArrayBuffer(response, onProgress) { + /** @type {ReadableStreamDefaultReader|undefined} */ let reader; try { - if (typeof onProgress === 'function') { + if (onProgress !== null) { const {body} = response; if (body !== null) { reader = body.getReader(); @@ -121,15 +121,15 @@ export class RequestBuilder { if (typeof reader === 'undefined') { const result = await response.arrayBuffer(); - if (typeof onProgress === 'function') { + if (onProgress !== null) { onProgress(true); } - return result; + return new Uint8Array(result); } const contentLengthString = response.headers.get('Content-Length'); const contentLength = contentLengthString !== null ? Number.parseInt(contentLengthString, 10) : null; - let target = Number.isFinite(contentLength) ? new Uint8Array(contentLength) : null; + let target = contentLength !== null && Number.isFinite(contentLength) ? new Uint8Array(contentLength) : null; let targetPosition = 0; let totalLength = 0; const targets = []; @@ -137,7 +137,9 @@ export class RequestBuilder { while (true) { const {done, value} = await reader.read(); if (done) { break; } - onProgress(false); + if (onProgress !== null) { + onProgress(false); + } if (target === null) { targets.push({array: value, length: value.length}); } else if (targetPosition + value.length > target.length) { @@ -156,16 +158,16 @@ export class RequestBuilder { target = target.slice(0, totalLength); } - onProgress(true); + if (onProgress !== null) { + onProgress(true); + } - return target; + return /** @type {Uint8Array} */ (target); } // Private - /** - * - */ + /** */ async _clearSessionRules() { const rules = await this._getSessionRules(); @@ -180,7 +182,7 @@ export class RequestBuilder { } /** - * + * @returns {Promise} */ _getSessionRules() { return new Promise((resolve, reject) => { @@ -196,8 +198,8 @@ export class RequestBuilder { } /** - * - * @param options + * @param {chrome.declarativeNetRequest.UpdateRuleOptions} options + * @returns {Promise} */ _updateSessionRules(options) { return new Promise((resolve, reject) => { @@ -213,8 +215,8 @@ export class RequestBuilder { } /** - * - * @param options + * @param {chrome.declarativeNetRequest.UpdateRuleOptions} options + * @returns {Promise} */ async _tryUpdateSessionRules(options) { try { @@ -225,9 +227,7 @@ export class RequestBuilder { } } - /** - * - */ + /** */ async _clearDynamicRules() { const rules = await this._getDynamicRules(); @@ -242,7 +242,7 @@ export class RequestBuilder { } /** - * + * @returns {Promise} */ _getDynamicRules() { return new Promise((resolve, reject) => { @@ -258,8 +258,8 @@ export class RequestBuilder { } /** - * - * @param options + * @param {chrome.declarativeNetRequest.UpdateRuleOptions} options + * @returns {Promise} */ _updateDynamicRules(options) { return new Promise((resolve, reject) => { @@ -275,7 +275,8 @@ export class RequestBuilder { } /** - * + * @returns {number} + * @throws {Error} */ _getNewRuleId() { let id = 1; @@ -288,8 +289,8 @@ export class RequestBuilder { } /** - * - * @param url + * @param {string} url + * @returns {string} */ _getOriginURL(url) { const url2 = new URL(url); @@ -297,16 +298,16 @@ export class RequestBuilder { } /** - * - * @param url + * @param {string} url + * @returns {string} */ _escapeDnrUrl(url) { return url.replace(/[|*^]/g, (char) => this._urlEncodeUtf8(char)); } /** - * - * @param text + * @param {string} text + * @returns {string} */ _urlEncodeUtf8(text) { const array = this._textEncoder.encode(text); @@ -318,9 +319,9 @@ export class RequestBuilder { } /** - * - * @param items - * @param totalLength + * @param {{array: Uint8Array, length: number}[]} items + * @param {number} totalLength + * @returns {Uint8Array} */ static _joinUint8Arrays(items, totalLength) { if (items.length === 1) { -- cgit v1.2.3 From 29317da4ea237557e1805a834913dad73c51ed8a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 17:43:53 -0500 Subject: Update offscreen --- ext/js/background/offscreen-proxy.js | 137 +++++++++++++++++++++++++---------- ext/js/background/offscreen.js | 121 +++++++++++++++++-------------- ext/js/language/translator.js | 4 +- types/ext/dictionary-database.d.ts | 2 +- types/ext/offscreen.d.ts | 115 +++++++++++++++++++++++++++++ types/ext/translator.d.ts | 12 +++ 6 files changed, 298 insertions(+), 93 deletions(-) create mode 100644 types/ext/offscreen.d.ts (limited to 'ext/js/background') diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index 0fb2f269..757d78d5 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -18,13 +18,17 @@ import {isObject} from '../core.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; +import {ExtensionError} from '../core/extension-error.js'; export class OffscreenProxy { constructor() { + /** @type {?Promise} */ this._creatingOffscreen = null; } - // https://developer.chrome.com/docs/extensions/reference/offscreen/ + /** + * @see https://developer.chrome.com/docs/extensions/reference/offscreen/ + */ async prepare() { if (await this._hasOffscreenDocument()) { return; @@ -36,20 +40,30 @@ export class OffscreenProxy { this._creatingOffscreen = chrome.offscreen.createDocument({ url: 'offscreen.html', - reasons: ['CLIPBOARD'], + reasons: [ + /** @type {chrome.offscreen.Reason} */ ('CLIPBOARD') + ], justification: 'Access to the clipboard' }); await this._creatingOffscreen; this._creatingOffscreen = null; } + /** + * @returns {Promise} + */ async _hasOffscreenDocument() { const offscreenUrl = chrome.runtime.getURL('offscreen.html'); - if (!chrome.runtime.getContexts) { // chrome version <116 + // @ts-ignore - API not defined yet + if (!chrome.runtime.getContexts) { // chrome version below 116 + // Clients: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/clients + // @ts-ignore - Types not set up for service workers yet const matchedClients = await clients.matchAll(); + // @ts-ignore - Types not set up for service workers yet return await matchedClients.some((client) => client.url === offscreenUrl); } + // @ts-ignore - API not defined yet const contexts = await chrome.runtime.getContexts({ contextTypes: ['OFFSCREEN_DOCUMENT'], documentUrls: [offscreenUrl] @@ -57,103 +71,152 @@ export class OffscreenProxy { return !!contexts.length; } - sendMessagePromise(...args) { + /** + * @template {import('offscreen').MessageType} TMessageType + * @param {import('offscreen').Message} message + * @returns {Promise>} + */ + sendMessagePromise(message) { return new Promise((resolve, reject) => { - const callback = (response) => { + chrome.runtime.sendMessage(message, (response) => { try { resolve(this._getMessageResponseResult(response)); } catch (error) { reject(error); } - }; - - chrome.runtime.sendMessage(...args, callback); + }); }); } + /** + * @template [TReturn=unknown] + * @param {import('core').Response} response + * @returns {TReturn} + * @throws {Error} + */ _getMessageResponseResult(response) { - let error = chrome.runtime.lastError; + const error = chrome.runtime.lastError; if (error) { throw new Error(error.message); } if (!isObject(response)) { throw new Error('Offscreen document did not respond'); } - error = response.error; - if (error) { - throw deserializeError(error); + const error2 = response.error; + if (error2) { + throw ExtensionError.deserialize(error2); } return response.result; } } export class DictionaryDatabaseProxy { + /** + * @param {OffscreenProxy} offscreen + */ constructor(offscreen) { + /** @type {OffscreenProxy} */ this._offscreen = offscreen; } - prepare() { - return this._offscreen.sendMessagePromise({action: 'databasePrepareOffscreen'}); + /** + * @returns {Promise} + */ + async prepare() { + await this._offscreen.sendMessagePromise({action: 'databasePrepareOffscreen'}); } - getDictionaryInfo() { + /** + * @returns {Promise} + */ + async getDictionaryInfo() { return this._offscreen.sendMessagePromise({action: 'getDictionaryInfoOffscreen'}); } - purge() { - return this._offscreen.sendMessagePromise({action: 'databasePurgeOffscreen'}); + /** + * @returns {Promise} + */ + async purge() { + return await this._offscreen.sendMessagePromise({action: 'databasePurgeOffscreen'}); } + /** + * @param {import('dictionary-database').MediaRequest[]} targets + * @returns {Promise} + */ async getMedia(targets) { - const serializedMedia = await this._offscreen.sendMessagePromise({action: 'databaseGetMediaOffscreen', params: {targets}}); + const serializedMedia = /** @type {import('dictionary-database').Media[]} */ (await this._offscreen.sendMessagePromise({action: 'databaseGetMediaOffscreen', params: {targets}})); const media = serializedMedia.map((m) => ({...m, content: ArrayBufferUtil.base64ToArrayBuffer(m.content)})); return media; } } export class TranslatorProxy { + /** + * @param {OffscreenProxy} offscreen + */ constructor(offscreen) { + /** @type {OffscreenProxy} */ this._offscreen = offscreen; } - prepare(deinflectionReasons) { - return this._offscreen.sendMessagePromise({action: 'translatorPrepareOffscreen', params: {deinflectionReasons}}); + /** + * @param {import('deinflector').ReasonsRaw} deinflectionReasons + */ + async prepare(deinflectionReasons) { + await this._offscreen.sendMessagePromise({action: 'translatorPrepareOffscreen', params: {deinflectionReasons}}); } - async findKanji(text, findKanjiOptions) { - const enabledDictionaryMapList = [...findKanjiOptions.enabledDictionaryMap]; - const modifiedKanjiOptions = { - ...findKanjiOptions, + /** + * @param {string} text + * @param {import('translation').FindKanjiOptions} options + * @returns {Promise} + */ + async findKanji(text, options) { + const enabledDictionaryMapList = [...options.enabledDictionaryMap]; + /** @type {import('offscreen').FindKanjiOptionsOffscreen} */ + const modifiedOptions = { + ...options, enabledDictionaryMap: enabledDictionaryMapList }; - return this._offscreen.sendMessagePromise({action: 'findKanjiOffscreen', params: {text, findKanjiOptions: modifiedKanjiOptions}}); + return this._offscreen.sendMessagePromise({action: 'findKanjiOffscreen', params: {text, options: modifiedOptions}}); } - async findTerms(mode, text, findTermsOptions) { - const {enabledDictionaryMap, excludeDictionaryDefinitions, textReplacements} = findTermsOptions; + /** + * @param {import('translator').FindTermsMode} mode + * @param {string} text + * @param {import('translation').FindTermsOptions} options + * @returns {Promise} + */ + async findTerms(mode, text, options) { + const {enabledDictionaryMap, excludeDictionaryDefinitions, textReplacements} = options; const enabledDictionaryMapList = [...enabledDictionaryMap]; const excludeDictionaryDefinitionsList = excludeDictionaryDefinitions ? [...excludeDictionaryDefinitions] : null; const textReplacementsSerialized = textReplacements.map((group) => { - if (!group) { - return group; - } - return group.map((opt) => ({...opt, pattern: opt.pattern.toString()})); + return group !== null ? group.map((opt) => ({...opt, pattern: opt.pattern.toString()})) : null; }); - const modifiedFindTermsOptions = { - ...findTermsOptions, + /** @type {import('offscreen').FindTermsOptionsOffscreen} */ + const modifiedOptions = { + ...options, enabledDictionaryMap: enabledDictionaryMapList, excludeDictionaryDefinitions: excludeDictionaryDefinitionsList, textReplacements: textReplacementsSerialized }; - return this._offscreen.sendMessagePromise({action: 'findTermsOffscreen', params: {mode, text, findTermsOptions: modifiedFindTermsOptions}}); + return this._offscreen.sendMessagePromise({action: 'findTermsOffscreen', params: {mode, text, options: modifiedOptions}}); } + /** + * @param {import('translator').TermReadingList} termReadingList + * @param {string[]} dictionaries + * @returns {Promise} + */ async getTermFrequencies(termReadingList, dictionaries) { return this._offscreen.sendMessagePromise({action: 'getTermFrequenciesOffscreen', params: {termReadingList, dictionaries}}); } - clearDatabaseCaches() { - return this._offscreen.sendMessagePromise({action: 'clearDatabaseCachesOffscreen'}); + /** */ + async clearDatabaseCaches() { + await this._offscreen.sendMessagePromise({action: 'clearDatabaseCachesOffscreen'}); } } @@ -173,7 +236,7 @@ export class ClipboardReaderProxy { set browser(value) { if (this._browser === value) { return; } this._browser = value; - this._offscreen.sendMessagePromise({action: 'clipboardSetBrowserOffsecreen', params: {value}}); + this._offscreen.sendMessagePromise({action: 'clipboardSetBrowserOffscreen', params: {value}}); } /** diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 6302aa84..1b68887b 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -23,7 +23,6 @@ import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; import {DictionaryDatabase} from '../language/dictionary-database.js'; import {JapaneseUtil} from '../language/sandbox/japanese-util.js'; import {Translator} from '../language/translator.js'; -import {yomitan} from '../yomitan.js'; /** * This class controls the core logic of the extension, including API calls @@ -34,12 +33,16 @@ export class Offscreen { * Creates a new instance. */ constructor() { + /** @type {JapaneseUtil} */ this._japaneseUtil = new JapaneseUtil(wanakana); + /** @type {DictionaryDatabase} */ this._dictionaryDatabase = new DictionaryDatabase(); + /** @type {Translator} */ this._translator = new Translator({ japaneseUtil: this._japaneseUtil, database: this._dictionaryDatabase }); + /** @type {ClipboardReader} */ this._clipboardReader = new ClipboardReader({ // eslint-disable-next-line no-undef document: (typeof document === 'object' && document !== null ? document : null), @@ -47,42 +50,45 @@ export class Offscreen { richContentPasteTargetSelector: '#clipboard-rich-content-paste-target' }); + /** @type {import('offscreen').MessageHandlerMap} */ 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)}], - ['databaseGetMediaOffscreen', {async: true, contentScript: true, handler: this._getMediaHandler.bind(this)}], - ['translatorPrepareOffscreen', {async: false, contentScript: true, handler: this._prepareTranslatorHandler.bind(this)}], - ['findKanjiOffscreen', {async: true, contentScript: true, handler: this._findKanjiHandler.bind(this)}], - ['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)}] + ['clipboardGetTextOffscreen', {async: true, handler: this._getTextHandler.bind(this)}], + ['clipboardGetImageOffscreen', {async: true, handler: this._getImageHandler.bind(this)}], + ['clipboardSetBrowserOffscreen', {async: false, handler: this._setClipboardBrowser.bind(this)}], + ['databasePrepareOffscreen', {async: true, handler: this._prepareDatabaseHandler.bind(this)}], + ['getDictionaryInfoOffscreen', {async: true, handler: this._getDictionaryInfoHandler.bind(this)}], + ['databasePurgeOffscreen', {async: true, handler: this._purgeDatabaseHandler.bind(this)}], + ['databaseGetMediaOffscreen', {async: true, handler: this._getMediaHandler.bind(this)}], + ['translatorPrepareOffscreen', {async: false, handler: this._prepareTranslatorHandler.bind(this)}], + ['findKanjiOffscreen', {async: true, handler: this._findKanjiHandler.bind(this)}], + ['findTermsOffscreen', {async: true, handler: this._findTermsHandler.bind(this)}], + ['getTermFrequenciesOffscreen', {async: true, handler: this._getTermFrequenciesHandler.bind(this)}], + ['clearDatabaseCachesOffscreen', {async: false, handler: this._clearDatabaseCachesHandler.bind(this)}] ]); const onMessage = this._onMessage.bind(this); chrome.runtime.onMessage.addListener(onMessage); + /** @type {?Promise} */ this._prepareDatabasePromise = null; } - _getTextHandler({useRichText}) { - return this._clipboardReader.getText(useRichText); + /** @type {import('offscreen').MessageHandler<'clipboardGetTextOffscreen', true>} */ + async _getTextHandler({useRichText}) { + return await this._clipboardReader.getText(useRichText); } - _getImageHandler() { - return this._clipboardReader.getImage(); + /** @type {import('offscreen').MessageHandler<'clipboardGetImageOffscreen', true>} */ + async _getImageHandler() { + return await this._clipboardReader.getImage(); } - /** - * @param {{value: import('environment').Browser}} details - */ + /** @type {import('offscreen').MessageHandler<'clipboardSetBrowserOffscreen', false>} */ _setClipboardBrowser({value}) { this._clipboardReader.browser = value; } + /** @type {import('offscreen').MessageHandler<'databasePrepareOffscreen', true>} */ _prepareDatabaseHandler() { if (this._prepareDatabasePromise !== null) { return this._prepareDatabasePromise; @@ -91,70 +97,79 @@ export class Offscreen { return this._prepareDatabasePromise; } - _getDictionaryInfoHandler() { - return this._dictionaryDatabase.getDictionaryInfo(); + /** @type {import('offscreen').MessageHandler<'getDictionaryInfoOffscreen', true>} */ + async _getDictionaryInfoHandler() { + return await this._dictionaryDatabase.getDictionaryInfo(); } - _purgeDatabaseHandler() { - return this._dictionaryDatabase.purge(); + /** @type {import('offscreen').MessageHandler<'databasePurgeOffscreen', true>} */ + async _purgeDatabaseHandler() { + return await this._dictionaryDatabase.purge(); } + /** @type {import('offscreen').MessageHandler<'databaseGetMediaOffscreen', true>} */ async _getMediaHandler({targets}) { const media = await this._dictionaryDatabase.getMedia(targets); const serializedMedia = media.map((m) => ({...m, content: ArrayBufferUtil.arrayBufferToBase64(m.content)})); return serializedMedia; } + /** @type {import('offscreen').MessageHandler<'translatorPrepareOffscreen', false>} */ _prepareTranslatorHandler({deinflectionReasons}) { - return this._translator.prepare(deinflectionReasons); + this._translator.prepare(deinflectionReasons); } - _findKanjiHandler({text, findKanjiOptions}) { - findKanjiOptions.enabledDictionaryMap = new Map(findKanjiOptions.enabledDictionaryMap); - return this._translator.findKanji(text, findKanjiOptions); + /** @type {import('offscreen').MessageHandler<'findKanjiOffscreen', true>} */ + async _findKanjiHandler({text, options}) { + /** @type {import('translation').FindKanjiOptions} */ + const modifiedOptions = { + ...options, + enabledDictionaryMap: new Map(options.enabledDictionaryMap) + }; + return await this._translator.findKanji(text, modifiedOptions); } - _findTermsHandler({mode, text, findTermsOptions}) { - findTermsOptions.enabledDictionaryMap = new Map(findTermsOptions.enabledDictionaryMap); - if (findTermsOptions.excludeDictionaryDefinitions) { - findTermsOptions.excludeDictionaryDefinitions = new Set(findTermsOptions.excludeDictionaryDefinitions); - } - findTermsOptions.textReplacements = findTermsOptions.textReplacements.map((group) => { - if (!group) { - return group; - } + /** @type {import('offscreen').MessageHandler<'findTermsOffscreen', true>} */ + _findTermsHandler({mode, text, options}) { + const enabledDictionaryMap = new Map(options.enabledDictionaryMap); + const excludeDictionaryDefinitions = ( + options.excludeDictionaryDefinitions !== null ? + new Set(options.excludeDictionaryDefinitions) : + null + ); + const textReplacements = options.textReplacements.map((group) => { + if (group === null) { return null; } return group.map((opt) => { - const [, pattern, flags] = opt.pattern.match(/\/(.*?)\/([a-z]*)?$/i); // https://stackoverflow.com/a/33642463 + // https://stackoverflow.com/a/33642463 + const match = opt.pattern.match(/\/(.*?)\/([a-z]*)?$/i); + const [, pattern, flags] = match !== null ? match : ['', '', '']; return {...opt, pattern: new RegExp(pattern, flags ?? '')}; }); }); - return this._translator.findTerms(mode, text, findTermsOptions); + /** @type {import('translation').FindTermsOptions} */ + const modifiedOptions = { + ...options, + enabledDictionaryMap, + excludeDictionaryDefinitions, + textReplacements + }; + return this._translator.findTerms(mode, text, modifiedOptions); } + /** @type {import('offscreen').MessageHandler<'getTermFrequenciesOffscreen', true>} */ _getTermFrequenciesHandler({termReadingList, dictionaries}) { return this._translator.getTermFrequencies(termReadingList, dictionaries); } + /** @type {import('offscreen').MessageHandler<'clearDatabaseCachesOffscreen', false>} */ _clearDatabaseCachesHandler() { - return this._translator.clearDatabaseCaches(); + this._translator.clearDatabaseCaches(); } + /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ _onMessage({action, params}, sender, callback) { const messageHandler = this._messageHandlers.get(action); if (typeof messageHandler === 'undefined') { return false; } - this._validatePrivilegedMessageSender(sender); - return invokeMessageHandler(messageHandler, params, callback, sender); } - - _validatePrivilegedMessageSender(sender) { - let {url} = sender; - if (typeof url === 'string' && yomitan.isExtensionUrl(url)) { return; } - const {tab} = url; - if (typeof tab === 'object' && tab !== null) { - ({url} = tab); - if (typeof url === 'string' && yomitan.isExtensionUrl(url)) { return; } - } - throw new Error('Invalid message sender'); - } } diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index c21b16b1..aa1b71dd 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -157,7 +157,7 @@ export class Translator { /** * Gets a list of frequency information for a given list of term-reading pairs * and a list of dictionaries. - * @param {{term: string, reading: string|null}[]} termReadingList An array of `{term, reading}` pairs. If reading is null, + * @param {import('translator').TermReadingList} termReadingList An array of `{term, reading}` pairs. If reading is null, * the reading won't be compared. * @param {string[]} dictionaries An array of dictionary names. * @returns {Promise} An array of term frequencies. @@ -203,7 +203,7 @@ export class Translator { * @param {Map} enabledDictionaryMap * @param {import('translation').FindTermsOptions} options * @param {TranslatorTagAggregator} tagAggregator - * @returns {Promise<{dictionaryEntries: import('dictionary').TermDictionaryEntry[], originalTextLength: number}>} + * @returns {Promise} */ async _findTermsInternal(text, enabledDictionaryMap, options, tagAggregator) { if (options.removeNonJapaneseCharacters) { diff --git a/types/ext/dictionary-database.d.ts b/types/ext/dictionary-database.d.ts index 2e0f854b..06a246e8 100644 --- a/types/ext/dictionary-database.d.ts +++ b/types/ext/dictionary-database.d.ts @@ -35,7 +35,7 @@ export interface MediaDataBase { export interface MediaDataArrayBufferContent extends MediaDataBase {} export interface MediaDataStringContent extends MediaDataBase {} -export type Media = {index: number} & MediaDataBase; +export type Media = {index: number} & MediaDataBase; export type DatabaseTermEntry = { expression: string; diff --git a/types/ext/offscreen.d.ts b/types/ext/offscreen.d.ts new file mode 100644 index 00000000..7dd64d1e --- /dev/null +++ b/types/ext/offscreen.d.ts @@ -0,0 +1,115 @@ +/* + * 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 . + */ + +import type * as Core from './core'; +import type * as Deinflector from './deinflector'; +import type * as Dictionary from './dictionary'; +import type * as DictionaryDatabase from './dictionary-database'; +import type * as DictionaryImporter from './dictionary-importer'; +import type * as Environment from './environment'; +import type * as Translation from './translation'; +import type * as Translator from './translator'; + +export type MessageAny2 = Message; + +export type Message = ( + MessageDetailsMap[T] extends undefined ? + {action: T} : + {action: T, params: MessageDetailsMap[T]} +); + +export type MessageReturn = MessageReturnMap[T]; + +type MessageDetailsMap = { + databasePrepareOffscreen: undefined; + getDictionaryInfoOffscreen: undefined; + databasePurgeOffscreen: undefined; + databaseGetMediaOffscreen: { + targets: DictionaryDatabase.MediaRequest[]; + }; + translatorPrepareOffscreen: { + deinflectionReasons: Deinflector.ReasonsRaw; + }; + findKanjiOffscreen: { + text: string; + options: FindKanjiOptionsOffscreen; + }; + findTermsOffscreen: { + mode: Translator.FindTermsMode; + text: string; + options: FindTermsOptionsOffscreen; + }; + getTermFrequenciesOffscreen: { + termReadingList: Translator.TermReadingList; + dictionaries: string[]; + }; + clearDatabaseCachesOffscreen: undefined; + clipboardSetBrowserOffscreen: { + value: Environment.Browser | null; + }; + clipboardGetTextOffscreen: { + useRichText: boolean; + }; + clipboardGetImageOffscreen: undefined; +}; + +type MessageReturnMap = { + databasePrepareOffscreen: void; + getDictionaryInfoOffscreen: DictionaryImporter.Summary[]; + databasePurgeOffscreen: boolean; + databaseGetMediaOffscreen: DictionaryDatabase.Media[]; + translatorPrepareOffscreen: void; + findKanjiOffscreen: Dictionary.KanjiDictionaryEntry[]; + findTermsOffscreen: Translator.FindTermsResult; + getTermFrequenciesOffscreen: Translator.TermFrequencySimple[]; + clearDatabaseCachesOffscreen: void; + clipboardSetBrowserOffscreen: void; + clipboardGetTextOffscreen: string; + clipboardGetImageOffscreen: string | null; +}; + +export type MessageType = keyof MessageDetailsMap; + +export type FindKanjiOptionsOffscreen = Omit & { + enabledDictionaryMap: [ + key: string, + options: Translation.FindKanjiDictionary, + ][]; +}; + + +export type FindTermsOptionsOffscreen = Omit & { + enabledDictionaryMap: [ + key: string, + options: Translation.FindTermDictionary, + ][]; + excludeDictionaryDefinitions: string[] | null; + textReplacements: (FindTermsTextReplacementOffscreen[] | null)[]; +}; + +export type FindTermsTextReplacementOffscreen = Omit & { + pattern: string; +}; + +export type MessageHandler< + TMessage extends MessageType, + TIsAsync extends boolean, +> = ( + details: MessageDetailsMap[TMessage], +) => (TIsAsync extends true ? Promise> : MessageReturn); + +export type MessageHandlerMap = Map; diff --git a/types/ext/translator.d.ts b/types/ext/translator.d.ts index f17b3bf6..e7d45295 100644 --- a/types/ext/translator.d.ts +++ b/types/ext/translator.d.ts @@ -81,3 +81,15 @@ export type SequenceQuery = { }; export type FindTermsMode = 'simple' | 'group' | 'merge' | 'split'; + +export type TermReadingItem = { + term: string; + reading: string | null; +}; + +export type TermReadingList = TermReadingItem[]; + +export type FindTermsResult = { + dictionaryEntries: Dictionary.TermDictionaryEntry[]; + originalTextLength: number; +}; -- cgit v1.2.3 From d5b1217df3fe7480fc5f58fe194f5bbf73281094 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 19:23:17 -0500 Subject: Use import map --- dev/jsconfig.json | 1 + ext/action-popup.html | 8 ++++++++ ext/background.html | 8 ++++++++ ext/info.html | 8 ++++++++ ext/issues.html | 8 ++++++++ ext/js/background/backend.js | 2 +- ext/js/background/offscreen.js | 2 +- ext/js/display/search-display-controller.js | 2 +- ext/js/display/search-main.js | 2 +- ext/js/dom/simple-dom-parser.js | 2 +- ext/js/language/dictionary-importer.js | 4 ++-- ext/js/pages/settings/backup-controller.js | 2 +- ext/js/pages/settings/popup-preview-frame.js | 2 +- ext/js/templates/sandbox/anki-template-renderer.js | 2 +- ext/js/templates/sandbox/template-renderer-media-provider.js | 2 +- ext/js/templates/sandbox/template-renderer.js | 2 +- ext/legal.html | 8 ++++++++ ext/offscreen.html | 8 ++++++++ ext/permissions.html | 8 ++++++++ ext/popup-preview.html | 8 ++++++++ ext/popup.html | 8 ++++++++ ext/search.html | 8 ++++++++ ext/settings.html | 8 ++++++++ ext/template-renderer.html | 8 ++++++++ ext/welcome.html | 8 ++++++++ jsconfig.json | 3 ++- test/japanese-util.test.js | 2 +- test/jsconfig.json | 1 + 28 files changed, 121 insertions(+), 14 deletions(-) (limited to 'ext/js/background') diff --git a/dev/jsconfig.json b/dev/jsconfig.json index a012f32f..518f97ad 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -12,6 +12,7 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { + "zip.js": ["@zip.js/zip.js"], "anki-templates": ["../types/ext/anki-templates"], "anki-templates-internal": ["../types/ext/anki-templates-internal"], "cache-map": ["../types/ext/cache-map"], diff --git a/ext/action-popup.html b/ext/action-popup.html index b60e7055..5c6bfce4 100644 --- a/ext/action-popup.html +++ b/ext/action-popup.html @@ -12,6 +12,14 @@ + diff --git a/ext/background.html b/ext/background.html index dc88f397..6f9ee5f6 100644 --- a/ext/background.html +++ b/ext/background.html @@ -12,6 +12,14 @@ + diff --git a/ext/info.html b/ext/info.html index cb80053d..9e71ffd4 100644 --- a/ext/info.html +++ b/ext/info.html @@ -13,6 +13,14 @@ + diff --git a/ext/issues.html b/ext/issues.html index 904fbf16..c75683dd 100644 --- a/ext/issues.html +++ b/ext/issues.html @@ -13,6 +13,14 @@ + diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index be68ecf4..44f5a42d 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {AccessibilityController} from '../accessibility/accessibility-controller.js'; import {AnkiConnect} from '../comm/anki-connect.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 1b68887b..45345c01 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {ClipboardReader} from '../comm/clipboard-reader.js'; import {invokeMessageHandler} from '../core.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index a9bf2217..b93757c2 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; import {EventListenerCollection, invokeMessageHandler} from '../core.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/display/search-main.js b/ext/js/display/search-main.js index c20cc135..c1445e37 100644 --- a/ext/js/display/search-main.js +++ b/ext/js/display/search-main.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {log} from '../core.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index a1f63890..7ee28d51 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as parse5 from '../../lib/parse5.js'; +import * as parse5 from 'parse5'; /** * @augments import('simple-dom-parser').ISimpleDomParser diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 115e0726..5a4d7257 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -16,14 +16,14 @@ * along with this program. If not, see . */ -import * as ajvSchemas0 from '../../lib/validate-schemas.js'; +import * as ajvSchemas0 from 'validate-schemas'; import { BlobWriter as BlobWriter0, TextWriter as TextWriter0, Uint8ArrayReader as Uint8ArrayReader0, ZipReader as ZipReader0, configure -} from '../../lib/zip.js'; +} from 'zip.js'; import {stringReverse} from '../core.js'; import {MediaUtil} from '../media/media-util.js'; import {ExtensionError} from '../core/extension-error.js'; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 52c5f418..aeff2a97 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Dexie} from '../../../lib/dexie.js'; +import {Dexie} from 'dexie'; import {isObject, log} from '../../core.js'; import {OptionsUtil} from '../../data/options-util.js'; import {ArrayBufferUtil} from '../../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index c1a0d706..bb00829f 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {Frontend} from '../../app/frontend.js'; import {TextSourceRange} from '../../dom/text-source-range.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index 9f4bf6ff..b0dc8223 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from '../../../lib/handlebars.js'; +import {Handlebars} from 'handlebars'; import {AnkiNoteDataCreator} from '../../data/sandbox/anki-note-data-creator.js'; import {PronunciationGenerator} from '../../display/sandbox/pronunciation-generator.js'; import {StructuredContentGenerator} from '../../display/sandbox/structured-content-generator.js'; diff --git a/ext/js/templates/sandbox/template-renderer-media-provider.js b/ext/js/templates/sandbox/template-renderer-media-provider.js index d8a0a16d..23f334e1 100644 --- a/ext/js/templates/sandbox/template-renderer-media-provider.js +++ b/ext/js/templates/sandbox/template-renderer-media-provider.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from '../../../lib/handlebars.js'; +import {Handlebars} from 'handlebars'; export class TemplateRendererMediaProvider { constructor() { diff --git a/ext/js/templates/sandbox/template-renderer.js b/ext/js/templates/sandbox/template-renderer.js index fe240b5f..d4aebd64 100644 --- a/ext/js/templates/sandbox/template-renderer.js +++ b/ext/js/templates/sandbox/template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from '../../../lib/handlebars.js'; +import {Handlebars} from 'handlebars'; import {ExtensionError} from '../../core/extension-error.js'; export class TemplateRenderer { diff --git a/ext/legal.html b/ext/legal.html index 94912c7e..b853f3e8 100644 --- a/ext/legal.html +++ b/ext/legal.html @@ -14,6 +14,14 @@ + diff --git a/ext/offscreen.html b/ext/offscreen.html index afb7eb44..cfab53ee 100644 --- a/ext/offscreen.html +++ b/ext/offscreen.html @@ -12,6 +12,14 @@ + diff --git a/ext/permissions.html b/ext/permissions.html index 61b0d363..7baff14e 100644 --- a/ext/permissions.html +++ b/ext/permissions.html @@ -14,6 +14,14 @@ + diff --git a/ext/popup-preview.html b/ext/popup-preview.html index 15810242..7bd54470 100644 --- a/ext/popup-preview.html +++ b/ext/popup-preview.html @@ -13,6 +13,14 @@ + diff --git a/ext/popup.html b/ext/popup.html index 30e8a8c0..6bc8d690 100644 --- a/ext/popup.html +++ b/ext/popup.html @@ -15,6 +15,14 @@ + diff --git a/ext/search.html b/ext/search.html index 8c595cc4..377a966a 100644 --- a/ext/search.html +++ b/ext/search.html @@ -16,6 +16,14 @@ + diff --git a/ext/settings.html b/ext/settings.html index 346cc1d7..276a7222 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -14,6 +14,14 @@ + diff --git a/ext/template-renderer.html b/ext/template-renderer.html index 116f1a0c..d1747e99 100644 --- a/ext/template-renderer.html +++ b/ext/template-renderer.html @@ -11,6 +11,14 @@ + diff --git a/ext/welcome.html b/ext/welcome.html index 40639881..355bbc5f 100644 --- a/ext/welcome.html +++ b/ext/welcome.html @@ -13,6 +13,14 @@ + diff --git a/jsconfig.json b/jsconfig.json index ace0c2aa..4f316174 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -12,6 +12,7 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { + "zip.js": ["@zip.js/zip.js"], "*": ["./types/ext/*"] }, "types": [ @@ -29,7 +30,7 @@ "include": [ "ext/**/*.js", "types/ext/**/*.ts", - "types/other/web-set-timeout.d.ts" + "types/other/globals.d.ts" ], "exclude": [ "node_modules", diff --git a/test/japanese-util.test.js b/test/japanese-util.test.js index 47da4ccb..ae5b1a16 100644 --- a/test/japanese-util.test.js +++ b/test/japanese-util.test.js @@ -16,10 +16,10 @@ * along with this program. If not, see . */ +import * as wanakana from 'wanakana'; import {expect, test} from 'vitest'; import {TextSourceMap} from '../ext/js/general/text-source-map.js'; import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; -import * as wanakana from '../ext/lib/wanakana.js'; const jp = new JapaneseUtil(wanakana); diff --git a/test/jsconfig.json b/test/jsconfig.json index b025918c..261ec345 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -12,6 +12,7 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { + "zip.js": ["@zip.js/zip.js"], "*": ["../types/ext/*"], "dev/*": ["../types/dev/*"], "test/*": ["../types/test/*"] -- 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/background') 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 From 58ae2ab871591eea82895b1ab2a18753521eab1f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 22:07:28 -0500 Subject: Revert "Use import map" --- dev/jsconfig.json | 1 - ext/action-popup.html | 8 -------- ext/background.html | 8 -------- ext/info.html | 8 -------- ext/issues.html | 8 -------- ext/js/background/backend.js | 2 +- ext/js/background/offscreen.js | 2 +- ext/js/display/search-display-controller.js | 2 +- ext/js/display/search-main.js | 2 +- ext/js/dom/simple-dom-parser.js | 2 +- ext/js/language/dictionary-importer.js | 4 ++-- ext/js/pages/settings/backup-controller.js | 2 +- ext/js/pages/settings/popup-preview-frame.js | 2 +- ext/js/templates/sandbox/anki-template-renderer.js | 2 +- ext/js/templates/sandbox/template-renderer-media-provider.js | 2 +- ext/js/templates/sandbox/template-renderer.js | 2 +- ext/legal.html | 8 -------- ext/offscreen.html | 8 -------- ext/permissions.html | 8 -------- ext/popup-preview.html | 8 -------- ext/popup.html | 8 -------- ext/search.html | 8 -------- ext/settings.html | 8 -------- ext/template-renderer.html | 8 -------- ext/welcome.html | 8 -------- jsconfig.json | 1 - test/japanese-util.test.js | 2 +- test/jsconfig.json | 1 - 28 files changed, 13 insertions(+), 120 deletions(-) (limited to 'ext/js/background') diff --git a/dev/jsconfig.json b/dev/jsconfig.json index d4efe694..5b1c450c 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -12,7 +12,6 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { - "zip.js": ["@zip.js/zip.js"], "anki-templates": ["../types/ext/anki-templates"], "anki-templates-internal": ["../types/ext/anki-templates-internal"], "cache-map": ["../types/ext/cache-map"], diff --git a/ext/action-popup.html b/ext/action-popup.html index 5c6bfce4..b60e7055 100644 --- a/ext/action-popup.html +++ b/ext/action-popup.html @@ -12,14 +12,6 @@ - diff --git a/ext/background.html b/ext/background.html index 6f9ee5f6..dc88f397 100644 --- a/ext/background.html +++ b/ext/background.html @@ -12,14 +12,6 @@ - diff --git a/ext/info.html b/ext/info.html index 9e71ffd4..cb80053d 100644 --- a/ext/info.html +++ b/ext/info.html @@ -13,14 +13,6 @@ - diff --git a/ext/issues.html b/ext/issues.html index c75683dd..904fbf16 100644 --- a/ext/issues.html +++ b/ext/issues.html @@ -13,14 +13,6 @@ - diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index f1a47e7f..3eefed53 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {AccessibilityController} from '../accessibility/accessibility-controller.js'; import {AnkiConnect} from '../comm/anki-connect.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 8da661ad..4b57514d 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {ClipboardReader} from '../comm/clipboard-reader.js'; import {invokeMessageHandler} from '../core.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index 98a4666b..2778c4cd 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; import {EventListenerCollection, invokeMessageHandler} from '../core.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/display/search-main.js b/ext/js/display/search-main.js index c1445e37..c20cc135 100644 --- a/ext/js/display/search-main.js +++ b/ext/js/display/search-main.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {log} from '../core.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index 7ee28d51..a1f63890 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as parse5 from 'parse5'; +import * as parse5 from '../../lib/parse5.js'; /** * @augments import('simple-dom-parser').ISimpleDomParser diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 5a4d7257..115e0726 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -16,14 +16,14 @@ * along with this program. If not, see . */ -import * as ajvSchemas0 from 'validate-schemas'; +import * as ajvSchemas0 from '../../lib/validate-schemas.js'; import { BlobWriter as BlobWriter0, TextWriter as TextWriter0, Uint8ArrayReader as Uint8ArrayReader0, ZipReader as ZipReader0, configure -} from 'zip.js'; +} from '../../lib/zip.js'; import {stringReverse} from '../core.js'; import {MediaUtil} from '../media/media-util.js'; import {ExtensionError} from '../core/extension-error.js'; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index aeff2a97..52c5f418 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Dexie} from 'dexie'; +import {Dexie} from '../../../lib/dexie.js'; import {isObject, log} from '../../core.js'; import {OptionsUtil} from '../../data/options-util.js'; import {ArrayBufferUtil} from '../../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index dab21f4d..60d264fa 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../../lib/wanakana.js'; import {Frontend} from '../../app/frontend.js'; import {TextSourceRange} from '../../dom/text-source-range.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index b0dc8223..9f4bf6ff 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from 'handlebars'; +import {Handlebars} from '../../../lib/handlebars.js'; import {AnkiNoteDataCreator} from '../../data/sandbox/anki-note-data-creator.js'; import {PronunciationGenerator} from '../../display/sandbox/pronunciation-generator.js'; import {StructuredContentGenerator} from '../../display/sandbox/structured-content-generator.js'; diff --git a/ext/js/templates/sandbox/template-renderer-media-provider.js b/ext/js/templates/sandbox/template-renderer-media-provider.js index 23f334e1..d8a0a16d 100644 --- a/ext/js/templates/sandbox/template-renderer-media-provider.js +++ b/ext/js/templates/sandbox/template-renderer-media-provider.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from 'handlebars'; +import {Handlebars} from '../../../lib/handlebars.js'; export class TemplateRendererMediaProvider { constructor() { diff --git a/ext/js/templates/sandbox/template-renderer.js b/ext/js/templates/sandbox/template-renderer.js index d4aebd64..fe240b5f 100644 --- a/ext/js/templates/sandbox/template-renderer.js +++ b/ext/js/templates/sandbox/template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from 'handlebars'; +import {Handlebars} from '../../../lib/handlebars.js'; import {ExtensionError} from '../../core/extension-error.js'; export class TemplateRenderer { diff --git a/ext/legal.html b/ext/legal.html index b853f3e8..94912c7e 100644 --- a/ext/legal.html +++ b/ext/legal.html @@ -14,14 +14,6 @@ - diff --git a/ext/offscreen.html b/ext/offscreen.html index cfab53ee..afb7eb44 100644 --- a/ext/offscreen.html +++ b/ext/offscreen.html @@ -12,14 +12,6 @@ - diff --git a/ext/permissions.html b/ext/permissions.html index 7baff14e..61b0d363 100644 --- a/ext/permissions.html +++ b/ext/permissions.html @@ -14,14 +14,6 @@ - diff --git a/ext/popup-preview.html b/ext/popup-preview.html index 7bd54470..15810242 100644 --- a/ext/popup-preview.html +++ b/ext/popup-preview.html @@ -13,14 +13,6 @@ - diff --git a/ext/popup.html b/ext/popup.html index 6bc8d690..30e8a8c0 100644 --- a/ext/popup.html +++ b/ext/popup.html @@ -15,14 +15,6 @@ - diff --git a/ext/search.html b/ext/search.html index 377a966a..8c595cc4 100644 --- a/ext/search.html +++ b/ext/search.html @@ -16,14 +16,6 @@ - diff --git a/ext/settings.html b/ext/settings.html index 276a7222..346cc1d7 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -14,14 +14,6 @@ - diff --git a/ext/template-renderer.html b/ext/template-renderer.html index d1747e99..116f1a0c 100644 --- a/ext/template-renderer.html +++ b/ext/template-renderer.html @@ -11,14 +11,6 @@ - diff --git a/ext/welcome.html b/ext/welcome.html index 355bbc5f..40639881 100644 --- a/ext/welcome.html +++ b/ext/welcome.html @@ -13,14 +13,6 @@ - diff --git a/jsconfig.json b/jsconfig.json index 4f316174..0f780ead 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -12,7 +12,6 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { - "zip.js": ["@zip.js/zip.js"], "*": ["./types/ext/*"] }, "types": [ diff --git a/test/japanese-util.test.js b/test/japanese-util.test.js index ae5b1a16..47da4ccb 100644 --- a/test/japanese-util.test.js +++ b/test/japanese-util.test.js @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; import {expect, test} from 'vitest'; import {TextSourceMap} from '../ext/js/general/text-source-map.js'; import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; +import * as wanakana from '../ext/lib/wanakana.js'; const jp = new JapaneseUtil(wanakana); diff --git a/test/jsconfig.json b/test/jsconfig.json index 934aab81..2461fda9 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -12,7 +12,6 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { - "zip.js": ["@zip.js/zip.js"], "*": ["../types/ext/*"], "dev/*": ["../types/dev/*"], "test/*": ["../types/test/*"] -- cgit v1.2.3 From 083b4749139213c6fefe80166d73f54604a85267 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 3 Dec 2023 10:45:08 -0500 Subject: Fix some import orderings --- dev/bin/build.js | 4 ++-- ext/js/background/offscreen-proxy.js | 2 +- ext/js/language/dictionary-importer.js | 2 +- test/database.test.js | 2 +- test/deinflector.test.js | 2 +- test/dom-text-scanner.test.js | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) (limited to 'ext/js/background') diff --git a/dev/bin/build.js b/dev/bin/build.js index 47c08f3c..deb82618 100644 --- a/dev/bin/build.js +++ b/dev/bin/build.js @@ -19,10 +19,10 @@ import assert from 'assert'; import childProcess from 'child_process'; import fs from 'fs'; -import path from 'path'; -import readline from 'readline'; import JSZip from 'jszip'; import {fileURLToPath} from 'node:url'; +import path from 'path'; +import readline from 'readline'; import {buildLibs} from '../build-libs.js'; import {ManifestUtil} from '../manifest-util.js'; import {getAllFiles, getArgs, testMain} from '../util.js'; diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index 757d78d5..7b504855 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -17,8 +17,8 @@ */ import {isObject} from '../core.js'; -import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; import {ExtensionError} from '../core/extension-error.js'; +import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; export class OffscreenProxy { constructor() { diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 115e0726..08fcf86b 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -25,8 +25,8 @@ import { configure } from '../../lib/zip.js'; import {stringReverse} from '../core.js'; -import {MediaUtil} from '../media/media-util.js'; import {ExtensionError} from '../core/extension-error.js'; +import {MediaUtil} from '../media/media-util.js'; const ajvSchemas = /** @type {import('dictionary-importer').CompiledSchemaValidators} */ (/** @type {unknown} */ (ajvSchemas0)); const BlobWriter = /** @type {typeof import('@zip.js/zip.js').BlobWriter} */ (/** @type {unknown} */ (BlobWriter0)); diff --git a/test/database.test.js b/test/database.test.js index 30854d55..80871f95 100644 --- a/test/database.test.js +++ b/test/database.test.js @@ -17,8 +17,8 @@ */ import {IDBFactory, IDBKeyRange} from 'fake-indexeddb'; -import path from 'path'; import {fileURLToPath} from 'node:url'; +import path from 'path'; import {beforeEach, describe, expect, test, vi} from 'vitest'; import {createDictionaryArchive} from '../dev/util.js'; import {DictionaryDatabase} from '../ext/js/language/dictionary-database.js'; diff --git a/test/deinflector.test.js b/test/deinflector.test.js index a69f8e56..bd538428 100644 --- a/test/deinflector.test.js +++ b/test/deinflector.test.js @@ -17,10 +17,10 @@ */ import fs from 'fs'; +import {fileURLToPath} from 'node:url'; import path from 'path'; import {describe, expect, test} from 'vitest'; import {Deinflector} from '../ext/js/language/deinflector.js'; -import {fileURLToPath} from 'node:url'; const dirname = path.dirname(fileURLToPath(import.meta.url)); diff --git a/test/dom-text-scanner.test.js b/test/dom-text-scanner.test.js index 30aec33e..f6a7410a 100644 --- a/test/dom-text-scanner.test.js +++ b/test/dom-text-scanner.test.js @@ -18,8 +18,8 @@ import fs from 'fs'; import {JSDOM} from 'jsdom'; -import path from 'path'; import {fileURLToPath} from 'node:url'; +import path from 'path'; import {expect, test} from 'vitest'; import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js'; -- cgit v1.2.3 From dcddbee07e20163ae167dd67fe58f0776f9acb64 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 4 Dec 2023 18:20:05 -0500 Subject: Update how ts comments are handled --- .eslintrc.json | 9 ++++++++- dev/translator-vm.js | 2 +- ext/js/accessibility/google-docs.js | 6 +++--- ext/js/app/popup.js | 2 +- ext/js/background/offscreen-proxy.js | 8 ++++---- ext/js/comm/frame-ancestry-handler.js | 4 ++-- ext/js/dom/document-util.js | 14 ++++++------- ext/js/dom/dom-text-scanner.js | 5 ++--- ext/js/dom/simple-dom-parser.js | 3 +-- ext/js/pages/settings/backup-controller.js | 4 ++-- ext/js/yomitan.js | 4 ++-- test/cache-map.test.js | 2 +- test/data/html/test-document2-script.js | 30 ++++++++++++++-------------- test/object-property-accessor.test.js | 6 +++--- test/profile-conditions-util.test.js | 32 +++++++++++++++--------------- vitest.config.js | 2 +- 16 files changed, 69 insertions(+), 64 deletions(-) (limited to 'ext/js/background') diff --git a/.eslintrc.json b/.eslintrc.json index a46aff4b..83e7ffe6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -350,7 +350,14 @@ "allowAllPropertiesOnSameLine": true } ], - "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-ts-comment": [ + "error", + { + "ts-expect-error": { + "descriptionFormat": "^ - .+$" + } + } + ], "@typescript-eslint/ban-types": [ "error", { diff --git a/dev/translator-vm.js b/dev/translator-vm.js index 4022d465..7fdda879 100644 --- a/dev/translator-vm.js +++ b/dev/translator-vm.js @@ -42,7 +42,7 @@ export class TranslatorVM { } } }; - // @ts-ignore - Overwriting a global + // @ts-expect-error - Overwriting a global global.chrome = chrome; /** @type {?JapaneseUtil} */ diff --git a/ext/js/accessibility/google-docs.js b/ext/js/accessibility/google-docs.js index da6ab994..27841b6d 100644 --- a/ext/js/accessibility/google-docs.js +++ b/ext/js/accessibility/google-docs.js @@ -18,9 +18,9 @@ (async () => { // Reentrant check - // @ts-ignore : Checking a property to the global object + // @ts-expect-error - Checking a property to the global object if (self.googleDocsAccessibilitySetup) { return; } - // @ts-ignore : Adding a property to the global object + // @ts-expect-error - Adding a property to the global object self.googleDocsAccessibilitySetup = true; /** @@ -57,7 +57,7 @@ // The extension ID below is on an allow-list that is used on the Google Docs webpage. /* eslint-disable */ - // @ts-ignore : Adding a property to the global object + // @ts-expect-error : Adding a property to the global object const inject = () => { window._docs_annotate_canvas_by_ext = 'ogmnaimimemjmbakcfefmnahgdfhfami'; }; /* eslint-enable */ diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index 4f201fc3..7419785b 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -745,7 +745,7 @@ export class Popup extends EventDispatcher { if ( fullscreenElement === null || fullscreenElement.shadowRoot || - // @ts-ignore - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions + // @ts-expect-error - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions fullscreenElement.openOrClosedShadowRoot ) { return defaultParent; diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index 7b504855..63f619fa 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -54,16 +54,16 @@ export class OffscreenProxy { */ async _hasOffscreenDocument() { const offscreenUrl = chrome.runtime.getURL('offscreen.html'); - // @ts-ignore - API not defined yet + // @ts-expect-error - API not defined yet if (!chrome.runtime.getContexts) { // chrome version below 116 // Clients: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/clients - // @ts-ignore - Types not set up for service workers yet + // @ts-expect-error - Types not set up for service workers yet const matchedClients = await clients.matchAll(); - // @ts-ignore - Types not set up for service workers yet + // @ts-expect-error - Types not set up for service workers yet return await matchedClients.some((client) => client.url === offscreenUrl); } - // @ts-ignore - API not defined yet + // @ts-expect-error - API not defined yet const contexts = await chrome.runtime.getContexts({ contextTypes: ['OFFSCREEN_DOCUMENT'], documentUrls: [offscreenUrl] diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index 687ec368..e4d08f28 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -295,7 +295,7 @@ export class FrameAncestryHandler { while (walker.nextNode()) { const element = /** @type {Element} */ (walker.currentNode); - // @ts-ignore - this is more simple to elide any type checks or casting + // @ts-expect-error - this is more simple to elide any type checks or casting if (element.contentWindow === contentWindow) { return element; } @@ -303,7 +303,7 @@ export class FrameAncestryHandler { /** @type {?ShadowRoot|undefined} */ const shadowRoot = ( element.shadowRoot || - // @ts-ignore - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions + // @ts-expect-error - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions element.openOrClosedShadowRoot ); if (shadowRoot) { diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index 549a8195..b2aa8f81 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -210,7 +210,7 @@ export class DocumentUtil { */ static computeZoomScale(node) { if (this._cssZoomSupported === null) { - // @ts-ignore - zoom is a non-standard property that exists in Chromium-based browsers + // @ts-expect-error - zoom is a non-standard property that exists in Chromium-based browsers this._cssZoomSupported = (typeof document.createElement('div').style.zoom === 'string'); } if (!this._cssZoomSupported) { return 1; } @@ -387,11 +387,11 @@ export class DocumentUtil { static getFullscreenElement() { return ( document.fullscreenElement || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix document.msFullscreenElement || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix document.mozFullScreenElement || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix document.webkitFullscreenElement || null ); @@ -808,7 +808,7 @@ export class DocumentUtil { return document.caretRangeFromPoint(x, y); } - // @ts-ignore - caretPositionFromPoint is non-standard + // @ts-expect-error - caretPositionFromPoint is non-standard if (typeof document.caretPositionFromPoint === 'function') { // Firefox return this._caretPositionFromPoint(x, y); @@ -824,7 +824,7 @@ export class DocumentUtil { * @returns {?Range} */ static _caretPositionFromPoint(x, y) { - // @ts-ignore - caretPositionFromPoint is non-standard + // @ts-expect-error - caretPositionFromPoint is non-standard const position = /** @type {(x: number, y: number) => ?{offsetNode: Node, offset: number}} */ (document.caretPositionFromPoint)(x, y); if (position === null) { return null; @@ -876,7 +876,7 @@ export class DocumentUtil { nextElement.style.setProperty('user-select', 'text', 'important'); } - // @ts-ignore - caretPositionFromPoint is non-standard + // @ts-expect-error - caretPositionFromPoint is non-standard const position = /** @type {(x: number, y: number) => ?{offsetNode: Node, offset: number}} */ (document.caretPositionFromPoint)(x, y); if (position === null) { return null; diff --git a/ext/js/dom/dom-text-scanner.js b/ext/js/dom/dom-text-scanner.js index 3a785680..42e0acc9 100644 --- a/ext/js/dom/dom-text-scanner.js +++ b/ext/js/dom/dom-text-scanner.js @@ -520,11 +520,10 @@ export class DOMTextScanner { static isStyleSelectable(style) { return !( style.userSelect === 'none' || - // @ts-ignore - vendor prefix style.webkitUserSelect === 'none' || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix style.MozUserSelect === 'none' || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix style.msUserSelect === 'none' ); } diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index a1f63890..bca1cd88 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -27,7 +27,7 @@ export class SimpleDOMParser { */ constructor(content) { /** @type {import('parse5')} */ - // @ts-ignore - parse5 global is not defined in typescript declaration + // @ts-expect-error - parse5 global is not defined in typescript declaration this._parse5Lib = /** @type {import('parse5')} */ (parse5); /** @type {import('parse5').TreeAdapter} */ this._treeAdapter = this._parse5Lib.defaultTreeAdapter; @@ -131,7 +131,6 @@ export class SimpleDOMParser { * @returns {boolean} */ static isSupported() { - // @ts-ignore - parse5 global is not defined in typescript declaration return typeof parse5 !== 'undefined'; } diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 52c5f418..bf44bb90 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -165,9 +165,9 @@ export class BackupController { _saveBlob(blob, fileName) { if ( typeof navigator === 'object' && navigator !== null && - // @ts-ignore - call for legacy Edge + // @ts-expect-error - call for legacy Edge typeof navigator.msSaveBlob === 'function' && - // @ts-ignore - call for legacy Edge + // @ts-expect-error - call for legacy Edge navigator.msSaveBlob(blob) ) { return; diff --git a/ext/js/yomitan.js b/ext/js/yomitan.js index 3c0f7cb9..7cf67aec 100644 --- a/ext/js/yomitan.js +++ b/ext/js/yomitan.js @@ -37,7 +37,7 @@ if ((() => { } return (hasBrowser && !hasChrome); })()) { - // @ts-ignore - objects should have roughly the same interface + // @ts-expect-error - objects should have roughly the same interface chrome = browser; } @@ -182,7 +182,7 @@ export class Yomitan extends EventDispatcher { */ sendMessage(...args) { try { - // @ts-ignore - issue with type conversion, somewhat difficult to resolve in pure JS + // @ts-expect-error - issue with type conversion, somewhat difficult to resolve in pure JS chrome.runtime.sendMessage(...args); } catch (e) { this.triggerExtensionUnloaded(); diff --git a/test/cache-map.test.js b/test/cache-map.test.js index 3e7def1f..df891188 100644 --- a/test/cache-map.test.js +++ b/test/cache-map.test.js @@ -30,7 +30,7 @@ function testConstructor() { [true, () => new CacheMap(1.5)], [true, () => new CacheMap(Number.NaN)], [true, () => new CacheMap(Number.POSITIVE_INFINITY)], - // @ts-ignore - Ignore because it should throw an error + // @ts-expect-error - Ignore because it should throw an error [true, () => new CacheMap('a')] ]; diff --git a/test/data/html/test-document2-script.js b/test/data/html/test-document2-script.js index 5a6ad4d1..f6082802 100644 --- a/test/data/html/test-document2-script.js +++ b/test/data/html/test-document2-script.js @@ -22,17 +22,17 @@ function requestFullscreen(element) { if (element.requestFullscreen) { element.requestFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (element.mozRequestFullScreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility element.mozRequestFullScreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (element.webkitRequestFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility element.webkitRequestFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (element.msRequestFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility element.msRequestFullscreen(); } } @@ -41,17 +41,17 @@ function requestFullscreen(element) { function exitFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (document.mozCancelFullScreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.mozCancelFullScreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (document.webkitExitFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.webkitExitFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (document.msExitFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.msExitFullscreen(); } } @@ -62,11 +62,11 @@ function exitFullscreen() { function getFullscreenElement() { return ( document.fullscreenElement || - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.msFullscreenElement || - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.mozFullScreenElement || - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.webkitFullscreenElement || null ); diff --git a/test/object-property-accessor.test.js b/test/object-property-accessor.test.js index 4f1fa87b..4d50b1e9 100644 --- a/test/object-property-accessor.test.js +++ b/test/object-property-accessor.test.js @@ -334,7 +334,7 @@ function testGetPathString2() { ]; for (const [pathArray, message] of data) { - // @ts-ignore - Throwing is expected + // @ts-expect-error - Throwing is expected expect(() => ObjectPropertyAccessor.getPathString(pathArray)).toThrow(message); } }); @@ -424,7 +424,7 @@ function testHasProperty() { ]; for (const [object, property, expected] of data) { - // @ts-ignore - Ignore potentially property types + // @ts-expect-error - Ignore potentially property types expect(ObjectPropertyAccessor.hasProperty(object, property)).toStrictEqual(expected); } }); @@ -449,7 +449,7 @@ function testIsValidPropertyType() { ]; for (const [object, property, expected] of data) { - // @ts-ignore - Ignore potentially property types + // @ts-expect-error - Ignore potentially property types expect(ObjectPropertyAccessor.isValidPropertyType(object, property)).toStrictEqual(expected); } }); diff --git a/test/profile-conditions-util.test.js b/test/profile-conditions-util.test.js index 30052b34..62b21555 100644 --- a/test/profile-conditions-util.test.js +++ b/test/profile-conditions-util.test.js @@ -637,9 +637,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -673,9 +673,9 @@ function testSchemas() { {expected: false, context: {depth: 0, url: ''}}, {expected: false, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -711,9 +711,9 @@ function testSchemas() { {expected: false, context: {depth: 0, url: ''}}, {expected: false, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -753,9 +753,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -784,9 +784,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -819,9 +819,9 @@ function testSchemas() { {expected: false, context: {depth: 0, url: ''}}, {expected: false, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -849,9 +849,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -885,9 +885,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, diff --git a/vitest.config.js b/vitest.config.js index 3b6cdde0..025eec17 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -24,7 +24,7 @@ export default defineConfig({ 'test/playwright/**' ], environment: 'jsdom', - // @ts-ignore - Appears to not be defined in the type definitions (https://vitest.dev/advanced/pool) + // @ts-expect-error - Appears to not be defined in the type definitions (https://vitest.dev/advanced/pool) poolOptions: { threads: { useAtomics: true -- cgit v1.2.3