diff options
Diffstat (limited to 'ext/js')
| -rw-r--r-- | ext/js/app/frontend.js | 53 | ||||
| -rw-r--r-- | ext/js/background/backend.js | 74 | ||||
| -rw-r--r-- | ext/js/comm/api.js | 14 | ||||
| -rw-r--r-- | ext/js/comm/frame-client.js | 8 | ||||
| -rw-r--r-- | ext/js/comm/frame-endpoint.js | 4 | ||||
| -rw-r--r-- | ext/js/display/search-display-controller.js | 52 | ||||
| -rw-r--r-- | ext/js/yomitan.js | 56 | 
7 files changed, 108 insertions, 153 deletions
| diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index b68b55f3..b093ec33 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -16,7 +16,8 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {EventListenerCollection, invokeMessageHandler, log, promiseAnimationFrame} from '../core.js'; +import {EventListenerCollection, log, promiseAnimationFrame} from '../core.js'; +import {createApiMap, invokeApiMapHandler} from '../core/api-map.js';  import {DocumentUtil} from '../dom/document-util.js';  import {TextSourceElement} from '../dom/text-source-element.js';  import {TextSourceRange} from '../dom/text-source-range.js'; @@ -106,12 +107,12 @@ export class Frontend {          this._optionsContextOverride = null;          /* eslint-disable no-multi-spaces */ -        /** @type {import('core').MessageHandlerMap} */ -        this._runtimeMessageHandlers = new Map(/** @type {import('core').MessageHandlerMapInit} */ ([ -            ['Frontend.requestReadyBroadcast',   this._onMessageRequestFrontendReadyBroadcast.bind(this)], -            ['Frontend.setAllVisibleOverride',   this._onApiSetAllVisibleOverride.bind(this)], -            ['Frontend.clearAllVisibleOverride', this._onApiClearAllVisibleOverride.bind(this)] -        ])); +        /** @type {import('application').ApiMap} */ +        this._runtimeApiMap = createApiMap([ +            ['frontendRequestReadyBroadcast',   this._onMessageRequestFrontendReadyBroadcast.bind(this)], +            ['frontendSetAllVisibleOverride',   this._onApiSetAllVisibleOverride.bind(this)], +            ['frontendClearAllVisibleOverride', this._onApiClearAllVisibleOverride.bind(this)] +        ]);          this._hotkeyHandler.registerActions([              ['scanSelectedText', this._onActionScanSelectedText.bind(this)], @@ -239,9 +240,7 @@ export class Frontend {      // Message handlers -    /** -     * @param {import('frontend').FrontendRequestReadyBroadcastParams} params -     */ +    /** @type {import('application').ApiHandler<'frontendRequestReadyBroadcast'>} */      _onMessageRequestFrontendReadyBroadcast({frameId}) {          this._signalFrontendReady(frameId);      } @@ -313,10 +312,7 @@ export class Frontend {          };      } -    /** -     * @param {{value: boolean, priority: number, awaitFrame: boolean}} params -     * @returns {Promise<import('core').TokenString>} -     */ +    /** @type {import('application').ApiHandler<'frontendSetAllVisibleOverride'>} */      async _onApiSetAllVisibleOverride({value, priority, awaitFrame}) {          const result = await this._popupFactory.setAllVisibleOverride(value, priority);          if (awaitFrame) { @@ -325,10 +321,7 @@ export class Frontend {          return result;      } -    /** -     * @param {{token: import('core').TokenString}} params -     * @returns {Promise<boolean>} -     */ +    /** @type {import('application').ApiHandler<'frontendClearAllVisibleOverride'>} */      async _onApiClearAllVisibleOverride({token}) {          return await this._popupFactory.clearAllVisibleOverride(token);      } @@ -342,11 +335,9 @@ export class Frontend {          this._updatePopupPosition();      } -    /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ -    _onRuntimeMessage({action, params}, sender, callback) { -        const messageHandler = this._runtimeMessageHandlers.get(action); -        if (typeof messageHandler === 'undefined') { return false; } -        return invokeMessageHandler(messageHandler, params, callback, sender); +    /** @type {import('extension').ChromeRuntimeOnMessageCallback<import('application').ApiMessageAny>} */ +    _onRuntimeMessage({action, params}, _sender, callback) { +        return invokeApiMapHandler(this._runtimeApiMap, action, params, [], callback);      }      /** @@ -827,12 +818,12 @@ export class Frontend {       * @param {?number} targetFrameId       */      _signalFrontendReady(targetFrameId) { -        /** @type {import('frontend').FrontendReadyDetails} */ -        const params = {frameId: this._frameId}; +        /** @type {import('application').ApiMessageNoFrameId<'frontendReady'>} */ +        const message = {action: 'frontendReady', params: {frameId: this._frameId}};          if (targetFrameId === null) { -            yomitan.api.broadcastTab('frontendReady', params); +            yomitan.api.broadcastTab(message);          } else { -            yomitan.api.sendMessageToFrame(targetFrameId, 'frontendReady', params); +            yomitan.api.sendMessageToFrame(targetFrameId, message);          }      } @@ -853,11 +844,11 @@ export class Frontend {                  }                  chrome.runtime.onMessage.removeListener(onMessage);              }; -            /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ +            /** @type {import('extension').ChromeRuntimeOnMessageCallback<import('application').ApiMessageAny>} */              const onMessage = (message, _sender, sendResponse) => {                  try { -                    const {action, params} = message; -                    if (action === 'frontendReady' && /** @type {import('frontend').FrontendReadyDetails} */ (params).frameId === frameId) { +                    const {action} = message; +                    if (action === 'frontendReady' && message.params.frameId === frameId) {                          cleanup();                          resolve();                          sendResponse(); @@ -876,7 +867,7 @@ export class Frontend {              }              chrome.runtime.onMessage.addListener(onMessage); -            yomitan.api.broadcastTab('Frontend.requestReadyBroadcast', {frameId: this._frameId}); +            yomitan.api.broadcastTab({action: 'frontendRequestReadyBroadcast', params: {frameId: this._frameId}});          });      } diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 0604fe8b..ae78a97b 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -299,8 +299,8 @@ export class Backend {              this._clipboardMonitor.on('change', this._onClipboardTextChange.bind(this)); -            this._sendMessageAllTabsIgnoreResponse('Yomitan.backendReady', {}); -            this._sendMessageIgnoreResponse({action: 'Yomitan.backendReady', params: {}}); +            this._sendMessageAllTabsIgnoreResponse({action: 'applicationBackendReady'}); +            this._sendMessageIgnoreResponse({action: 'applicationBackendReady'});          } catch (e) {              log.error(e);              throw e; @@ -404,7 +404,7 @@ export class Backend {       * @param {chrome.tabs.ZoomChangeInfo} event       */      _onZoomChange({tabId, oldZoomFactor, newZoomFactor}) { -        this._sendMessageTabIgnoreResponse(tabId, {action: 'Yomitan.zoomChanged', params: {oldZoomFactor, newZoomFactor}}, {}); +        this._sendMessageTabIgnoreResponse(tabId, {action: 'applicationZoomChanged', params: {oldZoomFactor, newZoomFactor}}, {});      }      /** @@ -427,7 +427,8 @@ export class Backend {      /** @type {import('api').ApiHandler<'requestBackendReadySignal'>} */      _onApiRequestBackendReadySignal(_params, sender) {          // tab ID isn't set in background (e.g. browser_action) -        const data = {action: 'Yomitan.backendReady', params: {}}; +        /** @type {import('application').ApiMessage<'applicationBackendReady'>} */ +        const data = {action: 'applicationBackendReady'};          if (typeof sender.tab === 'undefined') {              this._sendMessageIgnoreResponse(data);              return false; @@ -609,30 +610,30 @@ export class Backend {      }      /** @type {import('api').ApiHandler<'sendMessageToFrame'>} */ -    _onApiSendMessageToFrame({frameId: targetFrameId, action, params}, sender) { +    _onApiSendMessageToFrame({frameId: targetFrameId, message}, sender) {          if (!sender) { return false; }          const {tab} = sender;          if (!tab) { return false; }          const {id} = tab;          if (typeof id !== 'number') { return false; } -        const frameId = sender.frameId; -        /** @type {import('extension').ChromeRuntimeMessageWithFrameId} */ -        const message = {action, params, frameId}; -        this._sendMessageTabIgnoreResponse(id, message, {frameId: targetFrameId}); +        const {frameId} = sender; +        /** @type {import('application').ApiMessageAny} */ +        const message2 = {...message, frameId}; +        this._sendMessageTabIgnoreResponse(id, message2, {frameId: targetFrameId});          return true;      }      /** @type {import('api').ApiHandler<'broadcastTab'>} */ -    _onApiBroadcastTab({action, params}, sender) { +    _onApiBroadcastTab({message}, sender) {          if (!sender) { return false; }          const {tab} = sender;          if (!tab) { return false; }          const {id} = tab;          if (typeof id !== 'number') { return false; } -        const frameId = sender.frameId; -        /** @type {import('extension').ChromeRuntimeMessageWithFrameId} */ -        const message = {action, params, frameId}; -        this._sendMessageTabIgnoreResponse(id, message, {}); +        const {frameId} = sender; +        /** @type {import('application').ApiMessageAny} */ +        const message2 = {...message, frameId}; +        this._sendMessageTabIgnoreResponse(id, message2, {});          return true;      } @@ -1094,7 +1095,7 @@ export class Backend {          await this._sendMessageTabPromise(              id, -            {action: 'SearchDisplayController.setMode', params: {mode: 'popup'}}, +            {action: 'searchDisplayControllerSetMode', params: {mode: 'popup'}},              {frameId: 0}          ); @@ -1114,7 +1115,7 @@ export class Backend {              try {                  const mode = await this._sendMessageTabPromise(                      id, -                    {action: 'SearchDisplayController.getMode', params: {}}, +                    {action: 'searchDisplayControllerGetMode'},                      {frameId: 0}                  );                  return mode === 'popup'; @@ -1194,7 +1195,7 @@ export class Backend {      async _updateSearchQuery(tabId, text, animate) {          await this._sendMessageTabPromise(              tabId, -            {action: 'SearchDisplayController.updateSearchQuery', params: {text, animate}}, +            {action: 'searchDisplayControllerUpdateSearchQuery', params: {text, animate}},              {frameId: 0}          );      } @@ -1225,7 +1226,7 @@ export class Backend {          this._accessibilityController.update(this._getOptionsFull(false)); -        this._sendMessageAllTabsIgnoreResponse('Yomitan.optionsUpdated', {source}); +        this._sendMessageAllTabsIgnoreResponse({action: 'applicationOptionsUpdated', params: {source}});      }      /** @@ -1633,7 +1634,7 @@ export class Backend {          try {              const response = await this._sendMessageTabPromise(                  tabId, -                {action: 'Yomitan.getUrl', params: {}}, +                {action: 'applicationGetUrl'},                  {frameId: 0}              );              const url = typeof response === 'object' && response !== null ? /** @type {import('core').SerializableObject} */ (response).url : void 0; @@ -1804,14 +1805,14 @@ export class Backend {          return new Promise((resolve, reject) => {              /** @type {?import('core').Timeout} */              let timer = null; -            /** @type {?import('extension').ChromeRuntimeOnMessageCallback} */ +            /** @type {?import('extension').ChromeRuntimeOnMessageCallback<import('application').ApiMessageAny>} */              let onMessage = (message, sender) => {                  if (                      !sender.tab ||                      sender.tab.id !== tabId ||                      sender.frameId !== frameId ||                      !(typeof message === 'object' && message !== null) || -                    /** @type {import('core').SerializableObject} */ (message).action !== 'yomitanReady' +                    message.action !== 'applicationReady'                  ) {                      return;                  } @@ -1832,7 +1833,7 @@ export class Backend {              chrome.runtime.onMessage.addListener(onMessage); -            this._sendMessageTabPromise(tabId, {action: 'Yomitan.isReady'}, {frameId}) +            this._sendMessageTabPromise(tabId, {action: 'applicationIsReady'}, {frameId})                  .then(                      (value) => {                          if (!value) { return; } @@ -1891,7 +1892,8 @@ export class Backend {      }      /** -     * @param {{action: string, params: import('core').SerializableObject}} message +     * @template {import('application').ApiNames} TName +     * @param {import('application').ApiMessage<TName>} message       */      _sendMessageIgnoreResponse(message) {          const callback = () => this._checkLastError(chrome.runtime.lastError); @@ -1900,7 +1902,7 @@ export class Backend {      /**       * @param {number} tabId -     * @param {{action: string, params?: import('core').SerializableObject, frameId?: number}} message +     * @param {import('application').ApiMessageAny} message       * @param {chrome.tabs.MessageSendOptions} options       */      _sendMessageTabIgnoreResponse(tabId, message, options) { @@ -1909,25 +1911,25 @@ export class Backend {      }      /** -     * @param {string} action -     * @param {import('core').SerializableObject} params +     * @param {import('application').ApiMessageAny} message       */ -    _sendMessageAllTabsIgnoreResponse(action, params) { +    _sendMessageAllTabsIgnoreResponse(message) {          const callback = () => this._checkLastError(chrome.runtime.lastError);          chrome.tabs.query({}, (tabs) => {              for (const tab of tabs) {                  const {id} = tab;                  if (typeof id !== 'number') { continue; } -                chrome.tabs.sendMessage(id, {action, params}, callback); +                chrome.tabs.sendMessage(id, message, callback);              }          });      }      /** +     * @template {import('application').ApiNames} TName       * @param {number} tabId -     * @param {{action: string, params?: import('core').SerializableObject}} message +     * @param {import('application').ApiMessage<TName>} message       * @param {chrome.tabs.MessageSendOptions} options -     * @returns {Promise<unknown>} +     * @returns {Promise<import('application').ApiReturn<TName>>}       */      _sendMessageTabPromise(tabId, message, options) {          return new Promise((resolve, reject) => { @@ -1936,7 +1938,7 @@ export class Backend {               */              const callback = (response) => {                  try { -                    resolve(this._getMessageResponseResult(response)); +                    resolve(/** @type {import('application').ApiReturn<TName>} */ (this._getMessageResponseResult(response)));                  } catch (error) {                      reject(error);                  } @@ -1959,11 +1961,11 @@ export class Backend {          if (typeof response !== 'object' || response === null) {              throw new Error('Tab did not respond');          } -        const responseError = /** @type {import('core').SerializedError|undefined} */ (/** @type {import('core').SerializableObject} */ (response).error); +        const responseError = /** @type {import('core').Response<unknown>} */ (response).error;          if (typeof responseError === 'object' && responseError !== null) {              throw ExtensionError.deserialize(responseError);          } -        return /** @type {import('core').SerializableObject} */ (response).result; +        return /** @type {import('core').Response<unknown>} */ (response).result;      }      /** @@ -1998,7 +2000,7 @@ export class Backend {          let token = null;          try {              if (typeof tabId === 'number' && typeof frameId === 'number') { -                const action = 'Frontend.setAllVisibleOverride'; +                const action = 'frontendSetAllVisibleOverride';                  const params = {value: false, priority: 0, awaitFrame: true};                  token = await this._sendMessageTabPromise(tabId, {action, params}, {frameId});              } @@ -2015,7 +2017,7 @@ export class Backend {              });          } finally {              if (token !== null) { -                const action = 'Frontend.clearAllVisibleOverride'; +                const action = 'frontendClearAllVisibleOverride';                  const params = {token};                  try {                      await this._sendMessageTabPromise(tabId, {action, params}, {frameId}); @@ -2380,7 +2382,7 @@ export class Backend {       */      _triggerDatabaseUpdated(type, cause) {          this._translator.clearDatabaseCaches(); -        this._sendMessageAllTabsIgnoreResponse('Yomitan.databaseUpdated', {type, cause}); +        this._sendMessageAllTabsIgnoreResponse({action: 'applicationDatabaseUpdated', params: {type, cause}});      }      /** diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index c2351538..423115f1 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -156,21 +156,19 @@ export class API {      /**       * @param {import('api').ApiParam<'sendMessageToFrame', 'frameId'>} frameId -     * @param {import('api').ApiParam<'sendMessageToFrame', 'action'>} action -     * @param {import('api').ApiParam<'sendMessageToFrame', 'params'>} [params] +     * @param {import('api').ApiParam<'sendMessageToFrame', 'message'>} message       * @returns {Promise<import('api').ApiReturn<'sendMessageToFrame'>>}       */ -    sendMessageToFrame(frameId, action, params) { -        return this._invoke('sendMessageToFrame', {frameId, action, params}); +    sendMessageToFrame(frameId, message) { +        return this._invoke('sendMessageToFrame', {frameId, message});      }      /** -     * @param {import('api').ApiParam<'broadcastTab', 'action'>} action -     * @param {import('api').ApiParam<'broadcastTab', 'params'>} params +     * @param {import('api').ApiParam<'broadcastTab', 'message'>} message       * @returns {Promise<import('api').ApiReturn<'broadcastTab'>>}       */ -    broadcastTab(action, params) { -        return this._invoke('broadcastTab', {action, params}); +    broadcastTab(message) { +        return this._invoke('broadcastTab', {message});      }      /** diff --git a/ext/js/comm/frame-client.js b/ext/js/comm/frame-client.js index 5e997622..cb591ca9 100644 --- a/ext/js/comm/frame-client.js +++ b/ext/js/comm/frame-client.js @@ -110,14 +110,14 @@ export class FrameClient {                  contentWindow.postMessage({action, params}, targetOrigin);              }; -            /** @type {import('extension').ChromeRuntimeOnMessageCallback<import('extension').ChromeRuntimeMessageWithFrameId>} */ +            /** @type {import('extension').ChromeRuntimeOnMessageCallback<import('application').ApiMessageAny>} */              const onMessage = (message) => {                  onMessageInner(message);                  return false;              };              /** -             * @param {import('extension').ChromeRuntimeMessageWithFrameId} message +             * @param {import('application').ApiMessageAny} message               */              const onMessageInner = async (message) => {                  try { @@ -130,7 +130,7 @@ export class FrameClient {                      switch (action) {                          case 'frameEndpointReady':                              { -                                const {secret} = /** @type {import('frame-client').FrameEndpointReadyDetails} */ (params); +                                const {secret} = params;                                  const token = generateId(16);                                  tokenMap.set(secret, token);                                  postMessage('frameEndpointConnect', {secret, token, hostFrameId}); @@ -138,7 +138,7 @@ export class FrameClient {                              break;                          case 'frameEndpointConnected':                              { -                                const {secret, token} = /** @type {import('frame-client').FrameEndpointConnectedDetails} */ (params); +                                const {secret, token} = params;                                  const frameId = message.frameId;                                  const token2 = tokenMap.get(secret);                                  if (typeof token2 !== 'undefined' && token === token2 && typeof frameId === 'number') { diff --git a/ext/js/comm/frame-endpoint.js b/ext/js/comm/frame-endpoint.js index c338e143..4c5f58c1 100644 --- a/ext/js/comm/frame-endpoint.js +++ b/ext/js/comm/frame-endpoint.js @@ -41,7 +41,7 @@ export class FrameEndpoint {          }          /** @type {import('frame-client').FrameEndpointReadyDetails} */          const details = {secret: this._secret}; -        yomitan.api.broadcastTab('frameEndpointReady', details); +        yomitan.api.broadcastTab({action: 'frameEndpointReady', params: details});      }      /** @@ -83,6 +83,6 @@ export class FrameEndpoint {          this._eventListeners.removeAllEventListeners();          /** @type {import('frame-client').FrameEndpointConnectedDetails} */          const details = {secret, token}; -        yomitan.api.sendMessageToFrame(hostFrameId, 'frameEndpointConnected', details); +        yomitan.api.sendMessageToFrame(hostFrameId, {action: 'frameEndpointConnected', params: details});      }  } diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index 482afd56..6767d201 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -18,7 +18,8 @@  import * as wanakana from '../../lib/wanakana.js';  import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; -import {EventListenerCollection, invokeMessageHandler} from '../core.js'; +import {EventListenerCollection} from '../core.js'; +import {createApiMap, invokeApiMapHandler} from '../core/api-map.js';  import {querySelectorNotNull} from '../dom/query-selector.js';  import {yomitan} from '../yomitan.js'; @@ -75,8 +76,12 @@ export class SearchDisplayController {                  getText: yomitan.api.clipboardGet.bind(yomitan.api)              }          }); -        /** @type {import('core').MessageHandlerMap} */ -        this._messageHandlers = new Map(); +        /** @type {import('application').ApiMap} */ +        this._apiMap = createApiMap([ +            ['searchDisplayControllerGetMode', this._onMessageGetMode.bind(this)], +            ['searchDisplayControllerSetMode', this._onMessageSetMode.bind(this)], +            ['searchDisplayControllerUpdateSearchQuery', this._onExternalSearchUpdate.bind(this)] +        ]);      }      /** */ @@ -94,13 +99,6 @@ export class SearchDisplayController {          this._display.hotkeyHandler.registerActions([              ['focusSearchBox', this._onActionFocusSearchBox.bind(this)]          ]); -        /* eslint-disable no-multi-spaces */ -        this._registerMessageHandlers([ -            ['SearchDisplayController.getMode',           this._onMessageGetMode.bind(this)], -            ['SearchDisplayController.setMode',           this._onMessageSetMode.bind(this)], -            ['SearchDisplayController.updateSearchQuery', this._onExternalSearchUpdate.bind(this)] -        ]); -        /* eslint-enable no-multi-spaces */          this._updateClipboardMonitorEnabled(); @@ -140,32 +138,21 @@ export class SearchDisplayController {      // Messages -    /** -     * @param {{mode: import('display').SearchMode}} details -     */ +    /** @type {import('application').ApiHandler<'searchDisplayControllerSetMode'>} */      _onMessageSetMode({mode}) {          this.setMode(mode);      } -    /** -     * @returns {import('display').SearchMode} -     */ +    /** @type {import('application').ApiHandler<'searchDisplayControllerGetMode'>} */      _onMessageGetMode() {          return this._searchPersistentStateController.mode;      }      // Private -    /** -     * @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; } -        return invokeMessageHandler(messageHandler, params, callback, sender); +    /** @type {import('extension').ChromeRuntimeOnMessageCallback<import('application').ApiMessageAny>} */ +    _onMessage({action, params}, _sender, callback) { +        return invokeApiMapHandler(this._apiMap, action, params, [], callback);      }      /** @@ -284,9 +271,7 @@ export class SearchDisplayController {          this._clipboardMonitor.setPreviousText(selection !== null ? selection.toString().trim() : '');      } -    /** -     * @param {{text: string, animate?: boolean}} details -     */ +    /** @type {import('application').ApiHandler<'searchDisplayControllerUpdateSearchQuery'>} */      _onExternalSearchUpdate({text, animate = true}) {          const options = this._display.getOptions();          if (options === null) { return; } @@ -549,15 +534,6 @@ export class SearchDisplayController {      }      /** -     * @param {import('core').MessageHandlerMapInit} handlers -     */ -    _registerMessageHandlers(handlers) { -        for (const [name, handlerInfo] of handlers) { -            this._messageHandlers.set(name, handlerInfo); -        } -    } - -    /**       * @param {?Element} element       * @returns {boolean}       */ diff --git a/ext/js/yomitan.js b/ext/js/yomitan.js index 7505c0ca..621e9cf0 100644 --- a/ext/js/yomitan.js +++ b/ext/js/yomitan.js @@ -18,7 +18,8 @@  import {API} from './comm/api.js';  import {CrossFrameAPI} from './comm/cross-frame-api.js'; -import {EventDispatcher, deferPromise, invokeMessageHandler, log} from './core.js'; +import {EventDispatcher, deferPromise, log} from './core.js'; +import {createApiMap, invokeApiMapHandler} from './core/api-map.js';  import {ExtensionError} from './core/extension-error.js';  /** @@ -95,15 +96,15 @@ export class Yomitan extends EventDispatcher {          this._isBackendReadyPromiseResolve = resolve;          /* eslint-disable no-multi-spaces */ -        /** @type {import('core').MessageHandlerMap} */ -        this._messageHandlers = new Map(/** @type {import('core').MessageHandlerMapInit} */ ([ -            ['Yomitan.isReady',         this._onMessageIsReady.bind(this)], -            ['Yomitan.backendReady',    this._onMessageBackendReady.bind(this)], -            ['Yomitan.getUrl',          this._onMessageGetUrl.bind(this)], -            ['Yomitan.optionsUpdated',  this._onMessageOptionsUpdated.bind(this)], -            ['Yomitan.databaseUpdated', this._onMessageDatabaseUpdated.bind(this)], -            ['Yomitan.zoomChanged',     this._onMessageZoomChanged.bind(this)] -        ])); +        /** @type {import('application').ApiMap} */ +        this._apiMap = createApiMap([ +            ['applicationIsReady',         this._onMessageIsReady.bind(this)], +            ['applicationBackendReady',    this._onMessageBackendReady.bind(this)], +            ['applicationGetUrl',          this._onMessageGetUrl.bind(this)], +            ['applicationOptionsUpdated',  this._onMessageOptionsUpdated.bind(this)], +            ['applicationDatabaseUpdated', this._onMessageDatabaseUpdated.bind(this)], +            ['applicationZoomChanged',     this._onMessageZoomChanged.bind(this)] +        ]);          /* eslint-enable no-multi-spaces */      } @@ -171,7 +172,7 @@ export class Yomitan extends EventDispatcher {       */      ready() {          this._isReady = true; -        this.sendMessage({action: 'yomitanReady'}); +        this.sendMessage({action: 'applicationReady'});      }      /** @@ -183,6 +184,7 @@ export class Yomitan extends EventDispatcher {          return this._extensionUrlBase !== null && url.startsWith(this._extensionUrlBase);      } +    // TODO : this function needs type safety      /**       * Runs `chrome.runtime.sendMessage()` with additional exception handling events.       * @param {import('extension').ChromeRuntimeSendMessageArgs} args The arguments to be passed to `chrome.runtime.sendMessage()`. @@ -221,55 +223,41 @@ export class Yomitan extends EventDispatcher {          return location.href;      } -    /** @type {import('extension').ChromeRuntimeOnMessageCallback} */ -    _onMessage({action, params}, sender, callback) { -        const messageHandler = this._messageHandlers.get(action); -        if (typeof messageHandler === 'undefined') { return false; } -        return invokeMessageHandler(messageHandler, params, callback, sender); +    /** @type {import('extension').ChromeRuntimeOnMessageCallback<import('application').ApiMessageAny>} */ +    _onMessage({action, params}, _sender, callback) { +        return invokeApiMapHandler(this._apiMap, action, params, [], callback);      } -    /** -     * @returns {boolean} -     */ +    /** @type {import('application').ApiHandler<'applicationIsReady'>} */      _onMessageIsReady() {          return this._isReady;      } -    /** -     * @returns {void} -     */ +    /** @type {import('application').ApiHandler<'applicationBackendReady'>} */      _onMessageBackendReady() {          if (this._isBackendReadyPromiseResolve === null) { return; }          this._isBackendReadyPromiseResolve();          this._isBackendReadyPromiseResolve = null;      } -    /** -     * @returns {{url: string}} -     */ +    /** @type {import('application').ApiHandler<'applicationGetUrl'>} */      _onMessageGetUrl() {          return {url: this._getUrl()};      } -    /** -     * @param {{source: string}} params -     */ +    /** @type {import('application').ApiHandler<'applicationOptionsUpdated'>} */      _onMessageOptionsUpdated({source}) {          if (source !== 'background') {              this.trigger('optionsUpdated', {source});          }      } -    /** -     * @param {{type: string, cause: string}} params -     */ +    /** @type {import('application').ApiHandler<'applicationDatabaseUpdated'>} */      _onMessageDatabaseUpdated({type, cause}) {          this.trigger('databaseUpdated', {type, cause});      } -    /** -     * @param {{oldZoomFactor: number, newZoomFactor: number}} params -     */ +    /** @type {import('application').ApiHandler<'applicationZoomChanged'>} */      _onMessageZoomChanged({oldZoomFactor, newZoomFactor}) {          this.trigger('zoomChanged', {oldZoomFactor, newZoomFactor});      } |