diff options
Diffstat (limited to 'ext/js/app')
| -rw-r--r-- | ext/js/app/frontend.js | 42 | ||||
| -rw-r--r-- | ext/js/app/popup-factory.js | 105 | ||||
| -rw-r--r-- | ext/js/app/popup-proxy.js | 48 | ||||
| -rw-r--r-- | ext/js/app/popup-window.js | 34 | ||||
| -rw-r--r-- | ext/js/app/popup.js | 32 | 
5 files changed, 104 insertions, 157 deletions
| diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index e386bf64..9dafde7a 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -178,11 +178,11 @@ export class Frontend {          /* eslint-disable no-multi-spaces */          yomitan.crossFrame.registerHandlers([ -            ['Frontend.closePopup',       this._onApiClosePopup.bind(this)], -            ['Frontend.copySelection',    this._onApiCopySelection.bind(this)], -            ['Frontend.getSelectionText', this._onApiGetSelectionText.bind(this)], -            ['Frontend.getPopupInfo',     this._onApiGetPopupInfo.bind(this)], -            ['Frontend.getPageInfo',      this._onApiGetPageInfo.bind(this)] +            ['frontendClosePopup',       this._onApiClosePopup.bind(this)], +            ['frontendCopySelection',    this._onApiCopySelection.bind(this)], +            ['frontendGetSelectionText', this._onApiGetSelectionText.bind(this)], +            ['frontendGetPopupInfo',     this._onApiGetPopupInfo.bind(this)], +            ['frontendGetPageInfo',      this._onApiGetPageInfo.bind(this)]          ]);          /* eslint-enable no-multi-spaces */ @@ -263,48 +263,31 @@ export class Frontend {      // API message handlers -    /** -     * @returns {string} -     */ -    _onApiGetUrl() { -        return window.location.href; -    } - -    /** -     * @returns {void} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'frontendClosePopup'>} */      _onApiClosePopup() {          this._clearSelection(false);      } -    /** -     * @returns {void} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'frontendCopySelection'>} */      _onApiCopySelection() {          // This will not work on Firefox if a popup has focus, which is usually the case when this function is called.          document.execCommand('copy');      } -    /** -     * @returns {string} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'frontendGetSelectionText'>} */      _onApiGetSelectionText() {          const selection = document.getSelection();          return selection !== null ? selection.toString() : '';      } -    /** -     * @returns {import('frontend').GetPopupInfoResult} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'frontendGetPopupInfo'>} */      _onApiGetPopupInfo() {          return {              popupId: (this._popup !== null ? this._popup.id : null)          };      } -    /** -     * @returns {{url: string, documentTitle: string}} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'frontendGetPageInfo'>} */      _onApiGetPageInfo() {          return {              url: window.location.href, @@ -620,8 +603,7 @@ export class Frontend {              return await this._getDefaultPopup();          } -        /** @type {import('frontend').GetPopupInfoResult} */ -        const {popupId} = await yomitan.crossFrame.invoke(targetFrameId, 'Frontend.getPopupInfo', {}); +        const {popupId} = await yomitan.crossFrame.invoke(targetFrameId, 'frontendGetPopupInfo', void 0);          if (popupId === null) {              return null;          } @@ -905,7 +887,7 @@ export class Frontend {          let documentTitle = document.title;          if (this._useProxyPopup && this._parentFrameId !== null) {              try { -                ({url, documentTitle} = await yomitan.crossFrame.invoke(this._parentFrameId, 'Frontend.getPageInfo', {})); +                ({url, documentTitle} = await yomitan.crossFrame.invoke(this._parentFrameId, 'frontendGetPageInfo', void 0));              } catch (e) {                  // NOP              } diff --git a/ext/js/app/popup-factory.js b/ext/js/app/popup-factory.js index 184a55ca..0d8cabd4 100644 --- a/ext/js/app/popup-factory.js +++ b/ext/js/app/popup-factory.js @@ -49,21 +49,21 @@ export class PopupFactory {          this._frameOffsetForwarder.prepare();          /* eslint-disable no-multi-spaces */          yomitan.crossFrame.registerHandlers([ -            ['PopupFactory.getOrCreatePopup',     this._onApiGetOrCreatePopup.bind(this)], -            ['PopupFactory.setOptionsContext',    this._onApiSetOptionsContext.bind(this)], -            ['PopupFactory.hide',                 this._onApiHide.bind(this)], -            ['PopupFactory.isVisible',            this._onApiIsVisibleAsync.bind(this)], -            ['PopupFactory.setVisibleOverride',   this._onApiSetVisibleOverride.bind(this)], -            ['PopupFactory.clearVisibleOverride', this._onApiClearVisibleOverride.bind(this)], -            ['PopupFactory.containsPoint',        this._onApiContainsPoint.bind(this)], -            ['PopupFactory.showContent',          this._onApiShowContent.bind(this)], -            ['PopupFactory.setCustomCss',         this._onApiSetCustomCss.bind(this)], -            ['PopupFactory.clearAutoPlayTimer',   this._onApiClearAutoPlayTimer.bind(this)], -            ['PopupFactory.setContentScale',      this._onApiSetContentScale.bind(this)], -            ['PopupFactory.updateTheme',          this._onApiUpdateTheme.bind(this)], -            ['PopupFactory.setCustomOuterCss',    this._onApiSetCustomOuterCss.bind(this)], -            ['PopupFactory.getFrameSize',         this._onApiGetFrameSize.bind(this)], -            ['PopupFactory.setFrameSize',         this._onApiSetFrameSize.bind(this)] +            ['popupFactoryGetOrCreatePopup',     this._onApiGetOrCreatePopup.bind(this)], +            ['popupFactorySetOptionsContext',    this._onApiSetOptionsContext.bind(this)], +            ['popupFactoryHide',                 this._onApiHide.bind(this)], +            ['popupFactoryIsVisible',            this._onApiIsVisibleAsync.bind(this)], +            ['popupFactorySetVisibleOverride',   this._onApiSetVisibleOverride.bind(this)], +            ['popupFactoryClearVisibleOverride', this._onApiClearVisibleOverride.bind(this)], +            ['popupFactoryContainsPoint',        this._onApiContainsPoint.bind(this)], +            ['popupFactoryShowContent',          this._onApiShowContent.bind(this)], +            ['popupFactorySetCustomCss',         this._onApiSetCustomCss.bind(this)], +            ['popupFactoryClearAutoPlayTimer',   this._onApiClearAutoPlayTimer.bind(this)], +            ['popupFactorySetContentScale',      this._onApiSetContentScale.bind(this)], +            ['popupFactoryUpdateTheme',          this._onApiUpdateTheme.bind(this)], +            ['popupFactorySetCustomOuterCss',    this._onApiSetCustomOuterCss.bind(this)], +            ['popupFactoryGetFrameSize',         this._onApiGetFrameSize.bind(this)], +            ['popupFactorySetFrameSize',         this._onApiSetFrameSize.bind(this)]          ]);          /* eslint-enable no-multi-spaces */      } @@ -152,7 +152,7 @@ export class PopupFactory {              }              const useFrameOffsetForwarder = (parentPopupId === null);              /** @type {{id: string, depth: number, frameId: number}} */ -            const info = await yomitan.crossFrame.invoke(frameId, 'PopupFactory.getOrCreatePopup', /** @type {import('popup-factory').GetOrCreatePopupDetails} */ ({ +            const info = await yomitan.crossFrame.invoke(frameId, 'popupFactoryGetOrCreatePopup', /** @type {import('popup-factory').GetOrCreatePopupDetails} */ ({                  id,                  parentPopupId,                  frameId, @@ -239,10 +239,7 @@ export class PopupFactory {      // API message handlers -    /** -     * @param {import('popup-factory').GetOrCreatePopupDetails} details -     * @returns {Promise<{id: string, depth: number, frameId: number}>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactoryGetOrCreatePopup'>} */      async _onApiGetOrCreatePopup(details) {          const popup = await this.getOrCreatePopup(details);          return { @@ -252,53 +249,37 @@ export class PopupFactory {          };      } -    /** -     * @param {{id: string, optionsContext: import('settings').OptionsContext}} params -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactorySetOptionsContext'>} */      async _onApiSetOptionsContext({id, optionsContext}) {          const popup = this._getPopup(id);          await popup.setOptionsContext(optionsContext);      } -    /** -     * @param {{id: string, changeFocus: boolean}} params -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactoryHide'>} */      async _onApiHide({id, changeFocus}) {          const popup = this._getPopup(id);          await popup.hide(changeFocus);      } -    /** -     * @param {{id: string}} params -     * @returns {Promise<boolean>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactoryIsVisible'>} */      async _onApiIsVisibleAsync({id}) {          const popup = this._getPopup(id);          return await popup.isVisible();      } -    /** -     * @param {{id: string, value: boolean, priority: number}} params -     * @returns {Promise<?import('core').TokenString>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactorySetVisibleOverride'>} */      async _onApiSetVisibleOverride({id, value, priority}) {          const popup = this._getPopup(id);          return await popup.setVisibleOverride(value, priority);      } -    /** -     * @param {{id: string, token: import('core').TokenString}} params -     * @returns {Promise<boolean>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactoryClearVisibleOverride'>} */      async _onApiClearVisibleOverride({id, token}) {          const popup = this._getPopup(id);          return await popup.clearVisibleOverride(token);      } -    /** -     * @param {{id: string, x: number, y: number}} params -     * @returns {Promise<boolean>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactoryContainsPoint'>} */      async _onApiContainsPoint({id, x, y}) {          const popup = this._getPopup(id);          const offset = this._getPopupOffset(popup); @@ -307,10 +288,7 @@ export class PopupFactory {          return await popup.containsPoint(x, y);      } -    /** -     * @param {{id: string, details: import('popup').ContentDetails, displayDetails: ?import('display').ContentDetails}} params -     * @returns {Promise<void>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactoryShowContent'>} */      async _onApiShowContent({id, details, displayDetails}) {          const popup = this._getPopup(id);          if (!this._popupCanShow(popup)) { return; } @@ -327,64 +305,43 @@ export class PopupFactory {          return await popup.showContent(details, displayDetails);      } -    /** -     * @param {{id: string, css: string}} params -     * @returns {Promise<void>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactorySetCustomCss'>} */      async _onApiSetCustomCss({id, css}) {          const popup = this._getPopup(id);          await popup.setCustomCss(css);      } -    /** -     * @param {{id: string}} params -     * @returns {Promise<void>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactoryClearAutoPlayTimer'>} */      async _onApiClearAutoPlayTimer({id}) {          const popup = this._getPopup(id);          await popup.clearAutoPlayTimer();      } -    /** -     * @param {{id: string, scale: number}} params -     * @returns {Promise<void>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactorySetContentScale'>} */      async _onApiSetContentScale({id, scale}) {          const popup = this._getPopup(id);          await popup.setContentScale(scale);      } -    /** -     * @param {{id: string}} params -     * @returns {Promise<void>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactoryUpdateTheme'>} */      async _onApiUpdateTheme({id}) {          const popup = this._getPopup(id);          await popup.updateTheme();      } -    /** -     * @param {{id: string, css: string, useWebExtensionApi: boolean}} params -     * @returns {Promise<void>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactorySetCustomOuterCss'>} */      async _onApiSetCustomOuterCss({id, css, useWebExtensionApi}) {          const popup = this._getPopup(id);          await popup.setCustomOuterCss(css, useWebExtensionApi);      } -    /** -     * @param {{id: string}} params -     * @returns {Promise<import('popup').ValidSize>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactoryGetFrameSize'>} */      async _onApiGetFrameSize({id}) {          const popup = this._getPopup(id);          return await popup.getFrameSize();      } -    /** -     * @param {{id: string, width: number, height: number}} params -     * @returns {Promise<boolean>} -     */ +    /** @type {import('cross-frame-api').ApiHandler<'popupFactorySetFrameSize'>} */      async _onApiSetFrameSize({id, width, height}) {          const popup = this._getPopup(id);          return await popup.setFrameSize(width, height); diff --git a/ext/js/app/popup-proxy.js b/ext/js/app/popup-proxy.js index 2821d774..e581be82 100644 --- a/ext/js/app/popup-proxy.js +++ b/ext/js/app/popup-proxy.js @@ -140,7 +140,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<void>}       */      async setOptionsContext(optionsContext) { -        await this._invokeSafe('PopupFactory.setOptionsContext', {id: this._id, optionsContext}, void 0); +        await this._invokeSafe('popupFactorySetOptionsContext', {id: this._id, optionsContext}, void 0);      }      /** @@ -149,7 +149,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<void>}       */      async hide(changeFocus) { -        await this._invokeSafe('PopupFactory.hide', {id: this._id, changeFocus}, void 0); +        await this._invokeSafe('popupFactoryHide', {id: this._id, changeFocus}, void 0);      }      /** @@ -157,7 +157,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<boolean>} `true` if the popup is visible, `false` otherwise.       */      isVisible() { -        return this._invokeSafe('PopupFactory.isVisible', {id: this._id}, false); +        return this._invokeSafe('popupFactoryIsVisible', {id: this._id}, false);      }      /** @@ -168,7 +168,7 @@ export class PopupProxy extends EventDispatcher {       *   or null if the override wasn't assigned.       */      setVisibleOverride(value, priority) { -        return this._invokeSafe('PopupFactory.setVisibleOverride', {id: this._id, value, priority}, null); +        return this._invokeSafe('popupFactorySetVisibleOverride', {id: this._id, value, priority}, null);      }      /** @@ -177,7 +177,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<boolean>} `true` if the override existed and was removed, `false` otherwise.       */      clearVisibleOverride(token) { -        return this._invokeSafe('PopupFactory.clearVisibleOverride', {id: this._id, token}, false); +        return this._invokeSafe('popupFactoryClearVisibleOverride', {id: this._id, token}, false);      }      /** @@ -192,7 +192,7 @@ export class PopupProxy extends EventDispatcher {              x += this._frameOffsetX;              y += this._frameOffsetY;          } -        return await this._invokeSafe('PopupFactory.containsPoint', {id: this._id, x, y}, false); +        return await this._invokeSafe('popupFactoryContainsPoint', {id: this._id, x, y}, false);      }      /** @@ -212,7 +212,7 @@ export class PopupProxy extends EventDispatcher {                  sourceRect.bottom += this._frameOffsetY;              }          } -        await this._invokeSafe('PopupFactory.showContent', {id: this._id, details, displayDetails}, void 0); +        await this._invokeSafe('popupFactoryShowContent', {id: this._id, details, displayDetails}, void 0);      }      /** @@ -221,7 +221,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<void>}       */      async setCustomCss(css) { -        await this._invokeSafe('PopupFactory.setCustomCss', {id: this._id, css}, void 0); +        await this._invokeSafe('popupFactorySetCustomCss', {id: this._id, css}, void 0);      }      /** @@ -229,7 +229,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<void>}       */      async clearAutoPlayTimer() { -        await this._invokeSafe('PopupFactory.clearAutoPlayTimer', {id: this._id}, void 0); +        await this._invokeSafe('popupFactoryClearAutoPlayTimer', {id: this._id}, void 0);      }      /** @@ -238,7 +238,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<void>}       */      async setContentScale(scale) { -        await this._invokeSafe('PopupFactory.setContentScale', {id: this._id, scale}, void 0); +        await this._invokeSafe('popupFactorySetContentScale', {id: this._id, scale}, void 0);      }      /** @@ -254,7 +254,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<void>}       */      async updateTheme() { -        await this._invokeSafe('PopupFactory.updateTheme', {id: this._id}, void 0); +        await this._invokeSafe('popupFactoryUpdateTheme', {id: this._id}, void 0);      }      /** @@ -265,7 +265,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<void>}       */      async setCustomOuterCss(css, useWebExtensionApi) { -        await this._invokeSafe('PopupFactory.setCustomOuterCss', {id: this._id, css, useWebExtensionApi}, void 0); +        await this._invokeSafe('popupFactorySetCustomOuterCss', {id: this._id, css, useWebExtensionApi}, void 0);      }      /** @@ -282,7 +282,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<import('popup').ValidSize>} The size and whether or not it is valid.       */      getFrameSize() { -        return this._invokeSafe('PopupFactory.getFrameSize', {id: this._id}, {width: 0, height: 0, valid: false}); +        return this._invokeSafe('popupFactoryGetFrameSize', {id: this._id}, {width: 0, height: 0, valid: false});      }      /** @@ -292,32 +292,28 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<boolean>} `true` if the size assignment was successful, `false` otherwise.       */      setFrameSize(width, height) { -        return this._invokeSafe('PopupFactory.setFrameSize', {id: this._id, width, height}, false); +        return this._invokeSafe('popupFactorySetFrameSize', {id: this._id, width, height}, false);      }      // Private -    // TODO : Type safety      /** -     * @template {import('core').SerializableObject} TParams -     * @template [TReturn=unknown] -     * @param {string} action -     * @param {TParams} params -     * @returns {Promise<TReturn>} +     * @template {import('cross-frame-api').ApiNames} TName +     * @param {TName} action +     * @param {import('cross-frame-api').ApiParams<TName>} params +     * @returns {Promise<import('cross-frame-api').ApiReturn<TName>>}       */      _invoke(action, params) {          return yomitan.crossFrame.invoke(this._frameId, action, params);      } -    // TODO : Type safety      /** -     * @template {import('core').SerializableObject} TParams -     * @template [TReturn=unknown] +     * @template {import('cross-frame-api').ApiNames} TName       * @template [TReturnDefault=unknown] -     * @param {string} action -     * @param {TParams} params +     * @param {TName} action +     * @param {import('cross-frame-api').ApiParams<TName>} params       * @param {TReturnDefault} defaultReturnValue -     * @returns {Promise<TReturn|TReturnDefault>} +     * @returns {Promise<import('cross-frame-api').ApiReturn<TName>|TReturnDefault>}       */      async _invokeSafe(action, params, defaultReturnValue) {          try { diff --git a/ext/js/app/popup-window.js b/ext/js/app/popup-window.js index 801afb3f..a696885a 100644 --- a/ext/js/app/popup-window.js +++ b/ext/js/app/popup-window.js @@ -126,7 +126,7 @@ export class PopupWindow extends EventDispatcher {       * @returns {Promise<void>}       */      async setOptionsContext(optionsContext) { -        await this._invoke(false, 'displaySetOptionsContext', {id: this._id, optionsContext}); +        await this._invoke(false, 'displaySetOptionsContext', {optionsContext});      }      /** @@ -183,7 +183,7 @@ export class PopupWindow extends EventDispatcher {       */      async showContent(_details, displayDetails) {          if (displayDetails === null) { return; } -        await this._invoke(true, 'displaySetContent', {id: this._id, details: displayDetails}); +        await this._invoke(true, 'displaySetContent', {details: displayDetails});      }      /** @@ -192,7 +192,7 @@ export class PopupWindow extends EventDispatcher {       * @returns {Promise<void>}       */      async setCustomCss(css) { -        await this._invoke(false, 'displaySetCustomCss', {id: this._id, css}); +        await this._invoke(false, 'displaySetCustomCss', {css});      }      /** @@ -200,7 +200,7 @@ export class PopupWindow extends EventDispatcher {       * @returns {Promise<void>}       */      async clearAutoPlayTimer() { -        await this._invoke(false, 'displayAudioClearAutoPlayTimer', {id: this._id}); +        await this._invoke(false, 'displayAudioClearAutoPlayTimer', void 0);      }      /** @@ -266,24 +266,29 @@ export class PopupWindow extends EventDispatcher {      // Private -    // TODO : Type safety      /** -     * @template {import('core').SerializableObject} TParams -     * @template [TReturn=unknown] +     * @template {import('display').DirectApiNames} TName       * @param {boolean} open -     * @param {string} action -     * @param {TParams} params -     * @returns {Promise<TReturn|undefined>} +     * @param {TName} action +     * @param {import('display').DirectApiParams<TName>} params +     * @returns {Promise<import('display').DirectApiReturn<TName>|undefined>}       */      async _invoke(open, action, params) {          if (yomitan.isExtensionUnloaded) {              return void 0;          } +        const message = /** @type {import('display').DirectApiMessageAny} */ ({action, params}); +          const frameId = 0;          if (this._popupTabId !== null) {              try { -                return await yomitan.crossFrame.invokeTab(this._popupTabId, frameId, 'popupMessage', {action, params}); +                return /** @type {import('display').DirectApiReturn<TName>} */ (await yomitan.crossFrame.invokeTab( +                    this._popupTabId, +                    frameId, +                    'displayPopupMessage2', +                    message +                ));              } catch (e) {                  if (yomitan.isExtensionUnloaded) {                      open = false; @@ -299,6 +304,11 @@ export class PopupWindow extends EventDispatcher {          const {tabId} = await yomitan.api.getOrCreateSearchPopup({focus: 'ifCreated'});          this._popupTabId = tabId; -        return await yomitan.crossFrame.invokeTab(this._popupTabId, frameId, 'popupMessage', {action, params}); +        return /** @type {import('display').DirectApiReturn<TName>} */ (await yomitan.crossFrame.invokeTab( +            this._popupTabId, +            frameId, +            'displayPopupMessage2', +            message +        ));      }  } diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index a8cdf1a6..0f7fbd87 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -316,7 +316,7 @@ export class Popup extends EventDispatcher {       */      async clearAutoPlayTimer() {          if (this._frameConnected) { -            await this._invokeSafe('displayAudioClearAutoPlayTimer', {}); +            await this._invokeSafe('displayAudioClearAutoPlayTimer', void 0);          }      } @@ -679,13 +679,11 @@ export class Popup extends EventDispatcher {          }      } -    // TODO : Type safety      /** -     * @template {import('core').SerializableObject} TParams -     * @template [TReturn=unknown] -     * @param {string} action -     * @param {TParams} params -     * @returns {Promise<TReturn>} +     * @template {import('display').DirectApiNames} TName +     * @param {TName} action +     * @param {import('display').DirectApiParams<TName>} params +     * @returns {Promise<import('display').DirectApiReturn<TName>>}       */      async _invoke(action, params) {          const contentWindow = this._frame.contentWindow; @@ -693,17 +691,21 @@ export class Popup extends EventDispatcher {              throw new Error(`Failed to invoke action ${action}: frame state invalid`);          } -        const message = this._frameClient.createMessage({action, params}); -        return await yomitan.crossFrame.invoke(this._frameClient.frameId, 'popupMessage', message); +        /** @type {import('display').DirectApiMessage<TName>} */ +        const message = {action, params}; +        const wrappedMessage = this._frameClient.createMessage(message); +        return /** @type {import('display').DirectApiReturn<TName>} */ (await yomitan.crossFrame.invoke( +            this._frameClient.frameId, +            'displayPopupMessage1', +            /** @type {import('display').DirectApiFrameClientMessageAny} */ (wrappedMessage) +        ));      } -    // TODO : Type safety      /** -     * @template {import('core').SerializableObject} TParams -     * @template [TReturn=unknown] -     * @param {string} action -     * @param {TParams} params -     * @returns {Promise<TReturn|undefined>} +     * @template {import('display').DirectApiNames} TName +     * @param {TName} action +     * @param {import('display').DirectApiParams<TName>} params +     * @returns {Promise<import('display').DirectApiReturn<TName>|undefined>}       */      async _invokeSafe(action, params) {          try { |