summaryrefslogtreecommitdiff
path: root/ext
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2023-12-21 09:46:50 -0500
committerGitHub <noreply@github.com>2023-12-21 14:46:50 +0000
commitab847b124d418b13037b59f446b288ff435e66a4 (patch)
treee481d9abb8a3a5b7887cfc570b3d3df85f124d14 /ext
parentb83ca2f37d6bb1007f62216cebf96a1177e556dc (diff)
API maps (#413)
* Add API map type descriptions * Remove unused ApiMapInitLax * Add createApiMap function * Add extendApiMap * Support promises * Update Offscreen to use API map * Add ApiNames<> template * Add getApiMapHandler * Use getApiMapHandler in offscreen
Diffstat (limited to 'ext')
-rw-r--r--ext/js/background/offscreen-proxy.js2
-rw-r--r--ext/js/background/offscreen.js38
-rw-r--r--ext/js/core/api-map.js48
3 files changed, 70 insertions, 18 deletions
diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js
index dfd342b4..99dc0741 100644
--- a/ext/js/background/offscreen-proxy.js
+++ b/ext/js/background/offscreen-proxy.js
@@ -74,7 +74,7 @@ export class OffscreenProxy {
/**
* @template {import('offscreen').MessageType} TMessageType
* @param {import('offscreen').Message<TMessageType>} message
- * @returns {Promise<import('offscreen').MessageReturn<TMessageType>>}
+ * @returns {Promise<import('offscreen').OffscreenApiReturn<TMessageType>>}
*/
sendMessagePromise(message) {
return new Promise((resolve, reject) => {
diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js
index d1cf3384..44b0af77 100644
--- a/ext/js/background/offscreen.js
+++ b/ext/js/background/offscreen.js
@@ -19,6 +19,7 @@
import * as wanakana from '../../lib/wanakana.js';
import {ClipboardReader} from '../comm/clipboard-reader.js';
import {invokeMessageHandler} from '../core.js';
+import {createApiMap, getApiMapHandler} from '../core/api-map.js';
import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js';
import {DictionaryDatabase} from '../language/dictionary-database.js';
import {JapaneseUtil} from '../language/sandbox/japanese-util.js';
@@ -50,9 +51,10 @@ export class Offscreen {
richContentPasteTargetSelector: '#clipboard-rich-content-paste-target'
});
+
/* eslint-disable no-multi-spaces */
- /** @type {import('offscreen').MessageHandlerMap} */
- this._messageHandlers = new Map(/** @type {import('offscreen').MessageHandlerMapInit} */ ([
+ /** @type {import('offscreen').OffscreenApiMapInit} */
+ const messageHandlersInit = [
['clipboardGetTextOffscreen', this._getTextHandler.bind(this)],
['clipboardGetImageOffscreen', this._getImageHandler.bind(this)],
['clipboardSetBrowserOffscreen', this._setClipboardBrowser.bind(this)],
@@ -65,8 +67,10 @@ export class Offscreen {
['findTermsOffscreen', this._findTermsHandler.bind(this)],
['getTermFrequenciesOffscreen', this._getTermFrequenciesHandler.bind(this)],
['clearDatabaseCachesOffscreen', this._clearDatabaseCachesHandler.bind(this)]
- ]));
- /* eslint-enable no-multi-spaces */
+ ];
+
+ /** @type {import('offscreen').OffscreenApiMap} */
+ this._messageHandlers = createApiMap(messageHandlersInit);
/** @type {?Promise<void>} */
this._prepareDatabasePromise = null;
@@ -77,22 +81,22 @@ export class Offscreen {
chrome.runtime.onMessage.addListener(this._onMessage.bind(this));
}
- /** @type {import('offscreen').MessageHandler<'clipboardGetTextOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'clipboardGetTextOffscreen'>} */
async _getTextHandler({useRichText}) {
return await this._clipboardReader.getText(useRichText);
}
- /** @type {import('offscreen').MessageHandler<'clipboardGetImageOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'clipboardGetImageOffscreen'>} */
async _getImageHandler() {
return await this._clipboardReader.getImage();
}
- /** @type {import('offscreen').MessageHandler<'clipboardSetBrowserOffscreen', false>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'clipboardSetBrowserOffscreen'>} */
_setClipboardBrowser({value}) {
this._clipboardReader.browser = value;
}
- /** @type {import('offscreen').MessageHandler<'databasePrepareOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'databasePrepareOffscreen'>} */
_prepareDatabaseHandler() {
if (this._prepareDatabasePromise !== null) {
return this._prepareDatabasePromise;
@@ -101,29 +105,29 @@ export class Offscreen {
return this._prepareDatabasePromise;
}
- /** @type {import('offscreen').MessageHandler<'getDictionaryInfoOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'getDictionaryInfoOffscreen'>} */
async _getDictionaryInfoHandler() {
return await this._dictionaryDatabase.getDictionaryInfo();
}
- /** @type {import('offscreen').MessageHandler<'databasePurgeOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'databasePurgeOffscreen'>} */
async _purgeDatabaseHandler() {
return await this._dictionaryDatabase.purge();
}
- /** @type {import('offscreen').MessageHandler<'databaseGetMediaOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'databaseGetMediaOffscreen'>} */
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>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'translatorPrepareOffscreen'>} */
_prepareTranslatorHandler({deinflectionReasons}) {
this._translator.prepare(deinflectionReasons);
}
- /** @type {import('offscreen').MessageHandler<'findKanjiOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'findKanjiOffscreen'>} */
async _findKanjiHandler({text, options}) {
/** @type {import('translation').FindKanjiOptions} */
const modifiedOptions = {
@@ -133,7 +137,7 @@ export class Offscreen {
return await this._translator.findKanji(text, modifiedOptions);
}
- /** @type {import('offscreen').MessageHandler<'findTermsOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'findTermsOffscreen'>} */
_findTermsHandler({mode, text, options}) {
const enabledDictionaryMap = new Map(options.enabledDictionaryMap);
const excludeDictionaryDefinitions = (
@@ -160,19 +164,19 @@ export class Offscreen {
return this._translator.findTerms(mode, text, modifiedOptions);
}
- /** @type {import('offscreen').MessageHandler<'getTermFrequenciesOffscreen', true>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'getTermFrequenciesOffscreen'>} */
_getTermFrequenciesHandler({termReadingList, dictionaries}) {
return this._translator.getTermFrequencies(termReadingList, dictionaries);
}
- /** @type {import('offscreen').MessageHandler<'clearDatabaseCachesOffscreen', false>} */
+ /** @type {import('offscreen').OffscreenApiHandler<'clearDatabaseCachesOffscreen'>} */
_clearDatabaseCachesHandler() {
this._translator.clearDatabaseCaches();
}
/** @type {import('extension').ChromeRuntimeOnMessageCallback} */
_onMessage({action, params}, sender, callback) {
- const messageHandler = this._messageHandlers.get(/** @type {import('offscreen').MessageType} */ (action));
+ const messageHandler = getApiMapHandler(this._messageHandlers, action);
if (typeof messageHandler === 'undefined') { return false; }
return invokeMessageHandler(messageHandler, params, callback, sender);
}
diff --git a/ext/js/core/api-map.js b/ext/js/core/api-map.js
new file mode 100644
index 00000000..eb4abeea
--- /dev/null
+++ b/ext/js/core/api-map.js
@@ -0,0 +1,48 @@
+/*
+ * 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 <https://www.gnu.org/licenses/>.
+ */
+
+/**
+ * @template {import('api-map').ApiSurface} [TApiSurface=never]
+ * @param {import('api-map').ApiMapInit<TApiSurface>} init
+ * @returns {import('api-map').ApiMap<TApiSurface>}
+ */
+export function createApiMap(init) {
+ return new Map(init);
+}
+
+/**
+ * @template {import('api-map').ApiSurface} [TApiSurface=never]
+ * @param {import('api-map').ApiMap<TApiSurface>} map
+ * @param {import('api-map').ApiMapInit<TApiSurface>} init
+ * @throws {Error}
+ */
+export function extendApiMap(map, init) {
+ for (const [key, value] of init) {
+ if (map.has(key)) { throw new Error(`The handler for ${String(key)} has already been registered`); }
+ map.set(key, value);
+ }
+}
+
+/**
+ * @template {import('api-map').ApiSurface} [TApiSurface=never]
+ * @param {import('api-map').ApiMap<TApiSurface>} map
+ * @param {string} name
+ * @returns {import('api-map').ApiHandlerAny<TApiSurface>|undefined}
+ */
+export function getApiMapHandler(map, name) {
+ return map.get(/** @type {import('api-map').ApiNames<TApiSurface>} */ (name));
+}