diff options
Diffstat (limited to 'ext/js')
| -rw-r--r-- | ext/js/background/backend.js | 22 | ||||
| -rw-r--r-- | ext/js/background/offscreen-proxy.js | 27 | ||||
| -rw-r--r-- | ext/js/background/offscreen.js | 9 | ||||
| -rw-r--r-- | ext/js/background/request-builder.js | 13 | ||||
| -rw-r--r-- | ext/js/comm/api.js | 6 | ||||
| -rw-r--r-- | ext/js/comm/clipboard-reader.js | 4 | ||||
| -rw-r--r-- | ext/js/display/search-action-popup-controller.js | 4 | ||||
| -rw-r--r-- | ext/js/dom/sandbox/css-style-applier.js | 2 | ||||
| -rw-r--r-- | ext/js/dom/text-source-element.js | 2 | ||||
| -rw-r--r-- | ext/js/dom/text-source-range.js | 2 | ||||
| -rw-r--r-- | ext/js/general/regex-util.js | 2 | ||||
| -rw-r--r-- | ext/js/language/__mocks__/dictionary-importer-media-loader.js | 1 | ||||
| -rw-r--r-- | ext/js/language/dictionary-importer.js | 2 | ||||
| -rw-r--r-- | ext/js/language/dictionary-worker.js | 2 | ||||
| -rw-r--r-- | ext/js/language/sandbox/japanese-util.js | 8 | ||||
| -rw-r--r-- | ext/js/language/text-scanner.js | 1 | ||||
| -rw-r--r-- | ext/js/language/translator.js | 4 | ||||
| -rw-r--r-- | ext/js/media/audio-downloader.js | 6 | ||||
| -rw-r--r-- | ext/js/pages/settings/backup-controller.js | 54 | ||||
| -rw-r--r-- | ext/js/pages/settings/recommended-permissions-controller.js | 36 | 
20 files changed, 125 insertions, 82 deletions
| diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 14877cf1..be68ecf4 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -96,7 +96,7 @@ export class Backend {          });          /** @type {?import('settings').Options} */          this._options = null; -        /** @type {JsonSchema[]} */ +        /** @type {import('../data/json-schema.js').JsonSchema[]} */          this._profileConditionsSchemaCache = [];          /** @type {ProfileConditionsUtil} */          this._profileConditionsUtil = new ProfileConditionsUtil(); @@ -665,7 +665,7 @@ export class Backend {      async _onApiInjectStylesheet({type, value}, sender) {          const {frameId, tab} = sender;          if (typeof tab !== 'object' || tab === null || typeof tab.id !== 'number') { throw new Error('Invalid tab'); } -        return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false, true, 'document_start'); +        return await this._scriptManager.injectStylesheet(type, value, tab.id, frameId, false);      }      /** @type {import('api').Handler<import('api').GetStylesheetContentDetails, import('api').GetStylesheetContentResult>} */ @@ -895,13 +895,7 @@ export class Backend {          }      } -    /** -     * -     * @param root0 -     * @param root0.targetTabId -     * @param root0.targetFrameId -     * @param sender -     */ +    /** @type {import('api').Handler<import('api').OpenCrossFramePortDetails, import('api').OpenCrossFramePortResult, true>} */      _onApiOpenCrossFramePort({targetTabId, targetFrameId}, sender) {          const sourceTabId = (sender && sender.tab ? sender.tab.id : null);          if (typeof sourceTabId !== 'number') { @@ -922,7 +916,9 @@ export class Backend {              otherTabId: sourceTabId,              otherFrameId: sourceFrameId          }; +        /** @type {?chrome.runtime.Port} */          let sourcePort = chrome.tabs.connect(sourceTabId, {frameId: sourceFrameId, name: JSON.stringify(sourceDetails)}); +        /** @type {?chrome.runtime.Port} */          let targetPort = chrome.tabs.connect(targetTabId, {frameId: targetFrameId, name: JSON.stringify(targetDetails)});          const cleanup = () => { @@ -937,8 +933,12 @@ export class Backend {              }          }; -        sourcePort.onMessage.addListener((message) => { targetPort.postMessage(message); }); -        targetPort.onMessage.addListener((message) => { sourcePort.postMessage(message); }); +        sourcePort.onMessage.addListener((message) => { +            if (targetPort !== null) { targetPort.postMessage(message); } +        }); +        targetPort.onMessage.addListener((message) => { +            if (sourcePort !== null) { sourcePort.postMessage(message); } +        });          sourcePort.onDisconnect.addListener(cleanup);          targetPort.onDisconnect.addListener(cleanup); diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index c01f523d..0fb2f269 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -16,7 +16,7 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {deserializeError, isObject} from '../core.js'; +import {isObject} from '../core.js';  import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js';  export class OffscreenProxy { @@ -158,15 +158,36 @@ export class TranslatorProxy {  }  export class ClipboardReaderProxy { +    /** +     * @param {OffscreenProxy} offscreen +     */      constructor(offscreen) { +        /** @type {?import('environment').Browser} */ +        this._browser = null; +        /** @type {OffscreenProxy} */          this._offscreen = offscreen;      } +    /** @type {?import('environment').Browser} */ +    get browser() { return this._browser; } +    set browser(value) { +        if (this._browser === value) { return; } +        this._browser = value; +        this._offscreen.sendMessagePromise({action: 'clipboardSetBrowserOffsecreen', params: {value}}); +    } + +    /** +     * @param {boolean} useRichText +     * @returns {Promise<string>} +     */      async getText(useRichText) { -        return this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}}); +        return await this._offscreen.sendMessagePromise({action: 'clipboardGetTextOffscreen', params: {useRichText}});      } +    /** +     * @returns {Promise<?string>} +     */      async getImage() { -        return this._offscreen.sendMessagePromise({action: 'clipboardGetImageOffscreen'}); +        return await this._offscreen.sendMessagePromise({action: 'clipboardGetImageOffscreen'});      }  } diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 27cee8c4..6302aa84 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -50,6 +50,7 @@ export class Offscreen {          this._messageHandlers = new Map([              ['clipboardGetTextOffscreen',    {async: true,  contentScript: true,  handler: this._getTextHandler.bind(this)}],              ['clipboardGetImageOffscreen',   {async: true,  contentScript: true,  handler: this._getImageHandler.bind(this)}], +            ['clipboardSetBrowserOffsecreen', {async: false, contentScript: true, handler: this._setClipboardBrowser.bind(this)}],              ['databasePrepareOffscreen',     {async: true,  contentScript: true,  handler: this._prepareDatabaseHandler.bind(this)}],              ['getDictionaryInfoOffscreen',   {async: true,  contentScript: true,  handler: this._getDictionaryInfoHandler.bind(this)}],              ['databasePurgeOffscreen',       {async: true,  contentScript: true,  handler: this._purgeDatabaseHandler.bind(this)}], @@ -59,7 +60,6 @@ export class Offscreen {              ['findTermsOffscreen',           {async: true,  contentScript: true,  handler: this._findTermsHandler.bind(this)}],              ['getTermFrequenciesOffscreen',  {async: true,  contentScript: true,  handler: this._getTermFrequenciesHandler.bind(this)}],              ['clearDatabaseCachesOffscreen', {async: false,  contentScript: true,  handler: this._clearDatabaseCachesHandler.bind(this)}] -          ]);          const onMessage = this._onMessage.bind(this); @@ -76,6 +76,13 @@ export class Offscreen {          return this._clipboardReader.getImage();      } +    /** +     * @param {{value: import('environment').Browser}} details +     */ +    _setClipboardBrowser({value}) { +        this._clipboardReader.browser = value; +    } +      _prepareDatabaseHandler() {          if (this._prepareDatabasePromise !== null) {              return this._prepareDatabasePromise; diff --git a/ext/js/background/request-builder.js b/ext/js/background/request-builder.js index 48fe2dd9..5ae7fbf5 100644 --- a/ext/js/background/request-builder.js +++ b/ext/js/background/request-builder.js @@ -22,12 +22,6 @@   */  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.       */      constructor() { @@ -109,14 +103,17 @@ export class RequestBuilder {      /**       * Reads the array buffer body of a fetch response, with an optional `onProgress` callback.       * @param {Response} response The response of a `fetch` call. -     * @param {ProgressCallback} onProgress The progress callback +     * @param {?import('request-builder.js').ProgressCallback} onProgress The progress callback       * @returns {Promise<Uint8Array>} The resulting binary data.       */      static async readFetchResponseArrayBuffer(response, onProgress) {          let reader;          try {              if (typeof onProgress === 'function') { -                reader = response.body.getReader(); +                const {body} = response; +                if (body !== null) { +                    reader = body.getReader(); +                }              }          } catch (e) {              // Not supported diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 62dc98b1..0cfdba59 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -415,9 +415,9 @@ export class API {      }      /** -     * -     * @param targetTabId -     * @param targetFrameId +     * @param {import('api').OpenCrossFramePortDetails['targetTabId']} targetTabId +     * @param {import('api').OpenCrossFramePortDetails['targetFrameId']} targetFrameId +     * @returns {Promise<import('api').OpenCrossFramePortResult>}       */      openCrossFramePort(targetTabId, targetFrameId) {          return this._invoke('openCrossFramePort', {targetTabId, targetFrameId}); diff --git a/ext/js/comm/clipboard-reader.js b/ext/js/comm/clipboard-reader.js index c7b45a7c..364e31a3 100644 --- a/ext/js/comm/clipboard-reader.js +++ b/ext/js/comm/clipboard-reader.js @@ -29,7 +29,7 @@ export class ClipboardReader {      constructor({document=null, pasteTargetSelector=null, richContentPasteTargetSelector=null}) {          /** @type {?Document} */          this._document = document; -        /** @type {?string} */ +        /** @type {?import('environment').Browser} */          this._browser = null;          /** @type {?HTMLTextAreaElement} */          this._pasteTarget = null; @@ -43,7 +43,7 @@ export class ClipboardReader {      /**       * Gets the browser being used. -     * @type {?string} +     * @type {?import('environment').Browser}       */      get browser() {          return this._browser; diff --git a/ext/js/display/search-action-popup-controller.js b/ext/js/display/search-action-popup-controller.js index 733fd70a..3a2057a1 100644 --- a/ext/js/display/search-action-popup-controller.js +++ b/ext/js/display/search-action-popup-controller.js @@ -18,10 +18,10 @@  export class SearchActionPopupController {      /** -     * @param {SearchPersistentStateController} searchPersistentStateController +     * @param {import('./search-persistent-state-controller.js').SearchPersistentStateController} searchPersistentStateController       */      constructor(searchPersistentStateController) { -        /** @type {SearchPersistentStateController} */ +        /** @type {import('./search-persistent-state-controller.js').SearchPersistentStateController} */          this._searchPersistentStateController = searchPersistentStateController;      } diff --git a/ext/js/dom/sandbox/css-style-applier.js b/ext/js/dom/sandbox/css-style-applier.js index 332ca4f2..ea36a02d 100644 --- a/ext/js/dom/sandbox/css-style-applier.js +++ b/ext/js/dom/sandbox/css-style-applier.js @@ -24,7 +24,7 @@ export class CssStyleApplier {      /**       * Creates a new instance of the class.       * @param {string} styleDataUrl The local URL to the JSON file continaing the style rules. -     *   The style rules should follow the format of {@link CssStyleApplierRawStyleData}. +     *   The style rules should follow the format of `CssStyleApplierRawStyleData`.       */      constructor(styleDataUrl) {          /** @type {string} */ diff --git a/ext/js/dom/text-source-element.js b/ext/js/dom/text-source-element.js index 47c18e30..40ff5cc9 100644 --- a/ext/js/dom/text-source-element.js +++ b/ext/js/dom/text-source-element.js @@ -173,7 +173,7 @@ export class TextSourceElement {      /**       * Checks whether another text source has the same starting point. -     * @param {TextSourceElement|TextSourceRange} other The other source to test. +     * @param {import('text-source').TextSource} other The other source to test.       * @returns {boolean} `true` if the starting points are equivalent, `false` otherwise.       */      hasSameStart(other) { diff --git a/ext/js/dom/text-source-range.js b/ext/js/dom/text-source-range.js index 5dbbd636..fd09fdda 100644 --- a/ext/js/dom/text-source-range.js +++ b/ext/js/dom/text-source-range.js @@ -206,7 +206,7 @@ export class TextSourceRange {      /**       * Checks whether another text source has the same starting point. -     * @param {TextSourceElement|TextSourceRange} other The other source to test. +     * @param {import('text-source').TextSource} other The other source to test.       * @returns {boolean} `true` if the starting points are equivalent, `false` otherwise.       * @throws {Error} An exception can be thrown if `Range.compareBoundaryPoints` fails,       *   which shouldn't happen, but the handler is kept in case of unexpected errors. diff --git a/ext/js/general/regex-util.js b/ext/js/general/regex-util.js index 726ce9f2..62248968 100644 --- a/ext/js/general/regex-util.js +++ b/ext/js/general/regex-util.js @@ -25,7 +25,7 @@ export class RegexUtil {       * Applies string.replace using a regular expression and replacement string as arguments.       * A source map of the changes is also maintained.       * @param {string} text A string of the text to replace. -     * @param {TextSourceMap} sourceMap An instance of `TextSourceMap` which corresponds to `text`. +     * @param {import('./text-source-map.js').TextSourceMap} sourceMap An instance of `TextSourceMap` which corresponds to `text`.       * @param {RegExp} pattern A regular expression to use as the replacement.       * @param {string} replacement A replacement string that follows the format of the standard       *   JavaScript regular expression replacement string. diff --git a/ext/js/language/__mocks__/dictionary-importer-media-loader.js b/ext/js/language/__mocks__/dictionary-importer-media-loader.js index 96f0f6dd..ffda29b3 100644 --- a/ext/js/language/__mocks__/dictionary-importer-media-loader.js +++ b/ext/js/language/__mocks__/dictionary-importer-media-loader.js @@ -17,6 +17,7 @@   */  export class DictionaryImporterMediaLoader { +    /** @type {import('dictionary-importer-media-loader').GetImageDetailsFunction} */      async getImageDetails(content) {          // Placeholder values          return {content, width: 100, height: 100}; diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index aa6d7ae6..2a2f4063 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -36,7 +36,7 @@ export class DictionaryImporter {      }      /** -     * @param {DictionaryDatabase} dictionaryDatabase +     * @param {import('./dictionary-database.js').DictionaryDatabase} dictionaryDatabase       * @param {ArrayBuffer} archiveContent       * @param {import('dictionary-importer').ImportDetails} details       * @returns {Promise<import('dictionary-importer').ImportResult>} diff --git a/ext/js/language/dictionary-worker.js b/ext/js/language/dictionary-worker.js index 3e78a6ff..3119dd7b 100644 --- a/ext/js/language/dictionary-worker.js +++ b/ext/js/language/dictionary-worker.js @@ -157,6 +157,8 @@ export class DictionaryWorker {                  resolve(result2);              } else {                  // If formatResult is not provided, the response is assumed to be the same type +                // For some reason, eslint thinks the TResponse type is undefined +                // eslint-disable-next-line jsdoc/no-undefined-types                  resolve(/** @type {TResponse} */ (/** @type {unknown} */ (result)));              }          } diff --git a/ext/js/language/sandbox/japanese-util.js b/ext/js/language/sandbox/japanese-util.js index f7f20b3b..4c9c46bd 100644 --- a/ext/js/language/sandbox/japanese-util.js +++ b/ext/js/language/sandbox/japanese-util.js @@ -466,7 +466,7 @@ export class JapaneseUtil {      /**       * @param {string} text -     * @param {?TextSourceMap} [sourceMap] +     * @param {?import('../../general/text-source-map.js').TextSourceMap} [sourceMap]       * @returns {string}       */      convertHalfWidthKanaToFullWidth(text, sourceMap=null) { @@ -513,7 +513,7 @@ export class JapaneseUtil {      /**       * @param {string} text -     * @param {?TextSourceMap} sourceMap +     * @param {?import('../../general/text-source-map.js').TextSourceMap} sourceMap       * @returns {string}       */      convertAlphabeticToKana(text, sourceMap=null) { @@ -676,7 +676,7 @@ export class JapaneseUtil {      /**       * @param {string} text       * @param {boolean} fullCollapse -     * @param {?TextSourceMap} [sourceMap] +     * @param {?import('../../general/text-source-map.js').TextSourceMap} [sourceMap]       * @returns {string}       */      collapseEmphaticSequences(text, fullCollapse, sourceMap=null) { @@ -816,7 +816,7 @@ export class JapaneseUtil {      /**       * @param {string} text -     * @param {?TextSourceMap} sourceMap +     * @param {?import('../../general/text-source-map.js').TextSourceMap} sourceMap       * @param {number} sourceMapStart       * @returns {string}       */ diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index b4d9a642..f6bcde8d 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -18,6 +18,7 @@  import {EventDispatcher, EventListenerCollection, clone, log} from '../core.js';  import {DocumentUtil} from '../dom/document-util.js'; +import {TextSourceElement} from '../dom/text-source-element.js';  import {yomitan} from '../yomitan.js';  /** diff --git a/ext/js/language/translator.js b/ext/js/language/translator.js index 67cc53c6..c21b16b1 100644 --- a/ext/js/language/translator.js +++ b/ext/js/language/translator.js @@ -29,9 +29,9 @@ export class Translator {       * @param {import('translator').ConstructorDetails} details The details for the class.       */      constructor({japaneseUtil, database}) { -        /** @type {JapaneseUtil} */ +        /** @type {import('./sandbox/japanese-util.js').JapaneseUtil} */          this._japaneseUtil = japaneseUtil; -        /** @type {DictionaryDatabase} */ +        /** @type {import('./dictionary-database.js').DictionaryDatabase} */          this._database = database;          /** @type {?Deinflector} */          this._deinflector = null; diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index 7b236790..0847d479 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -25,10 +25,10 @@ import {SimpleDOMParser} from '../dom/simple-dom-parser.js';  export class AudioDownloader {      /** -     * @param {{japaneseUtil: JapaneseUtil, requestBuilder: RequestBuilder}} details +     * @param {{japaneseUtil: import('../language/sandbox/japanese-util.js').JapaneseUtil, requestBuilder: RequestBuilder}} details       */      constructor({japaneseUtil, requestBuilder}) { -        /** @type {JapaneseUtil} */ +        /** @type {import('../language/sandbox/japanese-util.js').JapaneseUtil} */          this._japaneseUtil = japaneseUtil;          /** @type {RequestBuilder} */          this._requestBuilder = requestBuilder; @@ -314,7 +314,7 @@ export class AudioDownloader {       */      async _downloadAudioFromUrl(url, sourceType, idleTimeout) {          let signal; -        /** @type {?(done: boolean) => void} */ +        /** @type {?import('request-builder.js').ProgressCallback} */          let onProgress = null;          /** @type {?number} */          let idleTimer = null; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 50a50b1a..52c5f418 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -534,12 +534,11 @@ export class BackupController {      // Exporting Dictionaries Database      /** -     * -     * @param message -     * @param isWarning +     * @param {string} message +     * @param {boolean} [isWarning]       */      _databaseExportImportErrorMessage(message, isWarning=false) { -        const errorMessageContainer = document.querySelector('#db-ops-error-report'); +        const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report'));          errorMessageContainer.style.display = 'block';          errorMessageContainer.textContent = message; @@ -553,15 +552,11 @@ export class BackupController {      }      /** -     * -     * @param root0 -     * @param root0.totalRows -     * @param root0.completedRows -     * @param root0.done +     * @param {{totalRows: number, completedRows: number, done: boolean}} details       */      _databaseExportProgressCallback({totalRows, completedRows, done}) {          console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); -        const messageContainer = document.querySelector('#db-ops-progress-report'); +        const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report'));          messageContainer.style.display = 'block';          messageContainer.textContent = `Export Progress: ${completedRows} of ${totalRows} rows completed`; @@ -572,8 +567,8 @@ export class BackupController {      }      /** -     * -     * @param databaseName +     * @param {string} databaseName +     * @returns {Promise<Blob>}       */      async _exportDatabase(databaseName) {          const db = await new Dexie(databaseName).open(); @@ -592,7 +587,7 @@ export class BackupController {              return;          } -        const errorMessageContainer = document.querySelector('#db-ops-error-report'); +        const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report'));          errorMessageContainer.style.display = 'none';          const date = new Date(Date.now()); @@ -616,15 +611,11 @@ export class BackupController {      // Importing Dictionaries Database      /** -     * -     * @param root0 -     * @param root0.totalRows -     * @param root0.completedRows -     * @param root0.done +     * @param {{totalRows: number, completedRows: number, done: boolean}} details       */      _databaseImportProgressCallback({totalRows, completedRows, done}) {          console.log(`Progress: ${completedRows} of ${totalRows} rows completed`); -        const messageContainer = document.querySelector('#db-ops-progress-report'); +        const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report'));          messageContainer.style.display = 'block';          messageContainer.style.color = '#4169e1';          messageContainer.textContent = `Import Progress: ${completedRows} of ${totalRows} rows completed`; @@ -637,9 +628,8 @@ export class BackupController {      }      /** -     * -     * @param databaseName -     * @param file +     * @param {string} databaseName +     * @param {File} file       */      async _importDatabase(databaseName, file) {          await yomitan.api.purgeDatabase(); @@ -648,16 +638,13 @@ export class BackupController {          yomitan.trigger('storageChanged');      } -    /** -     * -     */ +    /** */      _onSettingsImportDatabaseClick() { -        document.querySelector('#settings-import-db').click(); +        /** @type {HTMLElement} */ (document.querySelector('#settings-import-db')).click();      }      /** -     * -     * @param e +     * @param {Event} e       */      async _onSettingsImportDatabaseChange(e) {          if (this._settingsExportDatabaseToken !== null) { @@ -666,22 +653,23 @@ export class BackupController {              return;          } -        const errorMessageContainer = document.querySelector('#db-ops-error-report'); +        const errorMessageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-error-report'));          errorMessageContainer.style.display = 'none'; -        const files = e.target.files; -        if (files.length === 0) { return; } +        const element = /** @type {HTMLInputElement} */ (e.currentTarget); +        const files = element.files; +        if (files === null || files.length === 0) { return; }          const pageExitPrevention = this._settingsController.preventPageExit();          const file = files[0]; -        e.target.value = null; +        element.value = '';          try {              const token = {};              this._settingsExportDatabaseToken = token;              await this._importDatabase(this._dictionariesDatabaseName, file);          } catch (error) {              console.log(error); -            const messageContainer = document.querySelector('#db-ops-progress-report'); +            const messageContainer = /** @type {HTMLElement} */ (document.querySelector('#db-ops-progress-report'));              messageContainer.style.color = 'red';              this._databaseExportImportErrorMessage('Encountered errors when importing. Please restart the browser and try again. If it continues to fail, reinstall Yomitan and import dictionaries one-by-one.');          } finally { diff --git a/ext/js/pages/settings/recommended-permissions-controller.js b/ext/js/pages/settings/recommended-permissions-controller.js index e04dbdf7..b19311aa 100644 --- a/ext/js/pages/settings/recommended-permissions-controller.js +++ b/ext/js/pages/settings/recommended-permissions-controller.js @@ -19,13 +19,21 @@  import {EventListenerCollection} from '../../core.js';  export class RecommendedPermissionsController { +    /** +     * @param {import('./settings-controller.js').SettingsController} settingsController +     */      constructor(settingsController) { +        /** @type {import('./settings-controller.js').SettingsController} */          this._settingsController = settingsController; +        /** @type {?NodeListOf<HTMLInputElement>} */          this._originToggleNodes = null; +        /** @type {EventListenerCollection} */          this._eventListeners = new EventListenerCollection(); +        /** @type {?HTMLElement} */          this._errorContainer = null;      } +    /** */      async prepare() {          this._originToggleNodes = document.querySelectorAll('.recommended-permissions-toggle');          this._errorContainer = document.querySelector('#recommended-permissions-error'); @@ -39,35 +47,53 @@ export class RecommendedPermissionsController {      // Private +    /** +     * @param {import('settings-controller').PermissionsChangedEvent} details +     */      _onPermissionsChanged({permissions}) {          this._eventListeners.removeAllEventListeners();          const originsSet = new Set(permissions.origins); -        for (const node of this._originToggleNodes) { -            node.checked = originsSet.has(node.dataset.origin); +        if (this._originToggleNodes !== null) { +            for (const node of this._originToggleNodes) { +                const {origin} = node.dataset; +                node.checked = typeof origin === 'string' && originsSet.has(origin); +            }          }      } +    /** +     * @param {Event} e +     */      _onOriginToggleChange(e) { -        const node = e.currentTarget; +        const node = /** @type {HTMLInputElement} */ (e.currentTarget);          const value = node.checked;          node.checked = !value;          const {origin} = node.dataset; +        if (typeof origin !== 'string') { return; }          this._setOriginPermissionEnabled(origin, value);      } +    /** */      async _updatePermissions() {          const permissions = await this._settingsController.permissionsUtil.getAllPermissions();          this._onPermissionsChanged({permissions});      } +    /** +     * @param {string} origin +     * @param {boolean} enabled +     * @returns {Promise<boolean>} +     */      async _setOriginPermissionEnabled(origin, enabled) {          let added = false;          try {              added = await this._settingsController.permissionsUtil.setPermissionsGranted({origins: [origin]}, enabled);          } catch (e) { -            this._errorContainer.hidden = false; -            this._errorContainer.textContent = e.message; +            if (this._errorContainer !== null) { +                this._errorContainer.hidden = false; +                this._errorContainer.textContent = e instanceof Error ? e.message : `${e}`; +            }          }          if (!added) { return false; }          await this._updatePermissions(); |