aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2023-12-29 19:17:46 -0500
committerGitHub <noreply@github.com>2023-12-30 00:17:46 +0000
commit7303da3991814a0ce220bf2fff3e51b968913b86 (patch)
tree809c289d824ec2a08c5ff54579766b7f5c5e09e1
parent1b0e0c551d1505ed4242c04ebac224e5fff81f04 (diff)
Cross frame API safety (#491)
* Require error type * Add TODOs * Fix init * Updates * More type safety * Fix incorrect API map * Update type safety * Updates * Add API * Update types * Update types * Updates * Remove unused * Restore types * Update frame ancestry handler * Simplify names * Fix * Remove old message handlers
-rw-r--r--ext/js/app/frontend.js42
-rw-r--r--ext/js/app/popup-factory.js105
-rw-r--r--ext/js/app/popup-proxy.js48
-rw-r--r--ext/js/app/popup-window.js34
-rw-r--r--ext/js/app/popup.js32
-rw-r--r--ext/js/comm/cross-frame-api.js99
-rw-r--r--ext/js/comm/frame-ancestry-handler.js48
-rw-r--r--ext/js/comm/frame-offset-forwarder.js9
-rw-r--r--ext/js/core.js32
-rw-r--r--ext/js/display/display-resizer.js4
-rw-r--r--ext/js/display/display.js74
-rw-r--r--ext/js/input/hotkey-handler.js7
-rw-r--r--types/ext/core.d.ts13
-rw-r--r--types/ext/cross-frame-api.d.ts215
-rw-r--r--types/ext/display.d.ts14
-rw-r--r--types/ext/frame-ancestry-handler.d.ts7
-rw-r--r--types/ext/frontend.d.ts4
17 files changed, 436 insertions, 351 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 {
diff --git a/ext/js/comm/cross-frame-api.js b/ext/js/comm/cross-frame-api.js
index 14e410a8..3569c037 100644
--- a/ext/js/comm/cross-frame-api.js
+++ b/ext/js/comm/cross-frame-api.js
@@ -16,7 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-import {EventDispatcher, EventListenerCollection, invokeMessageHandler, log} from '../core.js';
+import {EventDispatcher, EventListenerCollection, log} from '../core.js';
+import {extendApiMap, invokeApiMapHandler} from '../core/api-map.js';
import {ExtensionError} from '../core/extension-error.js';
import {parseJson} from '../core/json.js';
import {yomitan} from '../yomitan.js';
@@ -29,9 +30,9 @@ export class CrossFrameAPIPort extends EventDispatcher {
* @param {number} otherTabId
* @param {number} otherFrameId
* @param {chrome.runtime.Port} port
- * @param {import('core').MessageHandlerMap} messageHandlers
+ * @param {import('cross-frame-api').ApiMap} apiMap
*/
- constructor(otherTabId, otherFrameId, port, messageHandlers) {
+ constructor(otherTabId, otherFrameId, port, apiMap) {
super();
/** @type {number} */
this._otherTabId = otherTabId;
@@ -39,8 +40,8 @@ export class CrossFrameAPIPort extends EventDispatcher {
this._otherFrameId = otherFrameId;
/** @type {?chrome.runtime.Port} */
this._port = port;
- /** @type {import('core').MessageHandlerMap} */
- this._messageHandlers = messageHandlers;
+ /** @type {import('cross-frame-api').ApiMap} */
+ this._apiMap = apiMap;
/** @type {Map<number, import('cross-frame-api').Invocation>} */
this._activeInvocations = new Map();
/** @type {number} */
@@ -69,13 +70,12 @@ export class CrossFrameAPIPort extends EventDispatcher {
}
/**
- * @template [TParams=import('core').SerializableObject]
- * @template [TReturn=unknown]
- * @param {string} action
- * @param {TParams} params
+ * @template {import('cross-frame-api').ApiNames} TName
+ * @param {TName} action
+ * @param {import('cross-frame-api').ApiParams<TName>} params
* @param {number} ackTimeout
* @param {number} responseTimeout
- * @returns {Promise<TReturn>}
+ * @returns {Promise<import('cross-frame-api').ApiReturn<TName>>}
*/
invoke(action, params, ackTimeout, responseTimeout) {
return new Promise((resolve, reject) => {
@@ -186,7 +186,7 @@ export class CrossFrameAPIPort extends EventDispatcher {
/**
* @param {number} id
- * @param {import('core').Response<unknown>} data
+ * @param {import('core').Response<import('cross-frame-api').ApiReturnAny>} data
*/
_onResult(id, data) {
const invocation = this._activeInvocations.get(id);
@@ -217,15 +217,13 @@ export class CrossFrameAPIPort extends EventDispatcher {
/**
* @param {number} id
- * @param {unknown} error
+ * @param {unknown} errorOrMessage
*/
- _onError(id, error) {
+ _onError(id, errorOrMessage) {
const invocation = this._activeInvocations.get(id);
if (typeof invocation === 'undefined') { return; }
- if (!(error instanceof Error)) {
- error = new Error(`${error} (${invocation.action})`);
- }
+ const error = errorOrMessage instanceof Error ? errorOrMessage : new Error(`${errorOrMessage} (${invocation.action})`);
this._activeInvocations.delete(id);
if (invocation.timer !== null) {
@@ -239,23 +237,18 @@ export class CrossFrameAPIPort extends EventDispatcher {
/**
* @param {number} id
- * @param {import('cross-frame-api').InvocationData} details
- * @returns {boolean}
+ * @param {import('cross-frame-api').ApiMessageAny} details
*/
_onInvoke(id, {action, params}) {
- const messageHandler = this._messageHandlers.get(action);
this._sendAck(id);
- if (typeof messageHandler === 'undefined') {
- this._sendError(id, new Error(`Unknown action: ${action}`));
- return false;
- }
-
- /**
- * @param {import('core').Response<unknown>} data
- * @returns {void}
- */
- const callback = (data) => this._sendResult(id, data);
- return invokeMessageHandler(messageHandler, params, callback);
+ invokeApiMapHandler(
+ this._apiMap,
+ action,
+ params,
+ [],
+ (data) => this._sendResult(id, data),
+ () => this._sendError(id, new Error(`Unknown action: ${action}`))
+ );
}
/**
@@ -279,7 +272,7 @@ export class CrossFrameAPIPort extends EventDispatcher {
/**
* @param {number} id
- * @param {import('core').Response<unknown>} data
+ * @param {import('core').Response<import('cross-frame-api').ApiReturnAny>} data
*/
_sendResult(id, data) {
this._sendResponse({type: 'result', id, data});
@@ -302,8 +295,8 @@ export class CrossFrameAPI {
this._responseTimeout = 10000; // 10 seconds
/** @type {Map<number, Map<number, CrossFrameAPIPort>>} */
this._commPorts = new Map();
- /** @type {import('core').MessageHandlerMap} */
- this._messageHandlers = new Map();
+ /** @type {import('cross-frame-api').ApiMap} */
+ this._apiMap = new Map();
/** @type {(port: CrossFrameAPIPort) => void} */
this._onDisconnectBind = this._onDisconnect.bind(this);
/** @type {?number} */
@@ -319,25 +312,23 @@ export class CrossFrameAPI {
}
/**
- * @template [TParams=import('core').SerializableObject]
- * @template [TReturn=unknown]
+ * @template {import('cross-frame-api').ApiNames} TName
* @param {number} targetFrameId
- * @param {string} action
- * @param {TParams} params
- * @returns {Promise<TReturn>}
+ * @param {TName} action
+ * @param {import('cross-frame-api').ApiParams<TName>} params
+ * @returns {Promise<import('cross-frame-api').ApiReturn<TName>>}
*/
invoke(targetFrameId, action, params) {
return this.invokeTab(null, targetFrameId, action, params);
}
/**
- * @template [TParams=import('core').SerializableObject]
- * @template [TReturn=unknown]
+ * @template {import('cross-frame-api').ApiNames} TName
* @param {?number} targetTabId
* @param {number} targetFrameId
- * @param {string} action
- * @param {TParams} params
- * @returns {Promise<TReturn>}
+ * @param {TName} action
+ * @param {import('cross-frame-api').ApiParams<TName>} params
+ * @returns {Promise<import('cross-frame-api').ApiReturn<TName>>}
*/
async invokeTab(targetTabId, targetFrameId, action, params) {
if (typeof targetTabId !== 'number') {
@@ -351,24 +342,10 @@ export class CrossFrameAPI {
}
/**
- * @param {import('core').MessageHandlerMapInit} messageHandlers
- * @throws {Error}
- */
- registerHandlers(messageHandlers) {
- for (const [key, value] of messageHandlers) {
- if (this._messageHandlers.has(key)) {
- throw new Error(`Handler ${key} is already registered`);
- }
- this._messageHandlers.set(key, value);
- }
- }
-
- /**
- * @param {string} key
- * @returns {boolean}
+ * @param {import('cross-frame-api').ApiMapInit} handlers
*/
- unregisterHandler(key) {
- return this._messageHandlers.delete(key);
+ registerHandlers(handlers) {
+ extendApiMap(this._apiMap, handlers);
}
// Private
@@ -451,7 +428,7 @@ export class CrossFrameAPI {
* @returns {CrossFrameAPIPort}
*/
_setupCommPort(otherTabId, otherFrameId, port) {
- const commPort = new CrossFrameAPIPort(otherTabId, otherFrameId, port, this._messageHandlers);
+ const commPort = new CrossFrameAPIPort(otherTabId, otherFrameId, port, this._apiMap);
let tabPorts = this._commPorts.get(otherTabId);
if (typeof tabPorts === 'undefined') {
tabPorts = new Map();
diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js
index 261ea943..e715cedc 100644
--- a/ext/js/comm/frame-ancestry-handler.js
+++ b/ext/js/comm/frame-ancestry-handler.js
@@ -37,12 +37,12 @@ export class FrameAncestryHandler {
this._isPrepared = false;
/** @type {string} */
this._requestMessageId = 'FrameAncestryHandler.requestFrameInfo';
- /** @type {string} */
- this._responseMessageIdBase = `${this._requestMessageId}.response.`;
/** @type {?Promise<number[]>} */
this._getFrameAncestryInfoPromise = null;
/** @type {Map<number, {window: Window, frameElement: ?(undefined|Element)}>} */
this._childFrameMap = new Map();
+ /** @type {Map<string, import('frame-ancestry-handler').ResponseHandler>} */
+ this._responseHandlers = new Map();
}
/**
@@ -59,6 +59,9 @@ export class FrameAncestryHandler {
prepare() {
if (this._isPrepared) { return; }
window.addEventListener('message', this._onWindowMessage.bind(this), false);
+ yomitan.crossFrame.registerHandlers([
+ ['frameAncestryHandlerRequestFrameInfoResponse', this._onFrameAncestryHandlerRequestFrameInfoResponse.bind(this)]
+ ]);
this._isPrepared = true;
}
@@ -119,7 +122,6 @@ export class FrameAncestryHandler {
const uniqueId = generateId(16);
let nonce = generateId(16);
- const responseMessageId = `${this._responseMessageIdBase}${uniqueId}`;
/** @type {number[]} */
const results = [];
/** @type {?import('core').Timeout} */
@@ -130,12 +132,9 @@ export class FrameAncestryHandler {
clearTimeout(timer);
timer = null;
}
- yomitan.crossFrame.unregisterHandler(responseMessageId);
+ this._removeResponseHandler(uniqueId);
};
- /**
- * @param {import('frame-ancestry-handler').RequestFrameInfoResponseParams} params
- * @returns {?import('frame-ancestry-handler').RequestFrameInfoResponseReturn}
- */
+ /** @type {import('frame-ancestry-handler').ResponseHandler} */
const onMessage = (params) => {
if (params.nonce !== nonce) { return null; }
@@ -164,9 +163,7 @@ export class FrameAncestryHandler {
};
// Start
- yomitan.crossFrame.registerHandlers([
- [responseMessageId, onMessage]
- ]);
+ this._addResponseHandler(uniqueId, onMessage);
resetTimeout();
const frameId = this._frameId;
this._requestFrameInfo(targetWindow, frameId, frameId, uniqueId, nonce);
@@ -212,13 +209,9 @@ export class FrameAncestryHandler {
const frameId = this._frameId;
const {parent} = window;
const more = (window !== parent);
- /** @type {import('frame-ancestry-handler').RequestFrameInfoResponseParams} */
- const responseParams = {frameId, nonce, more};
- const responseMessageId = `${this._responseMessageIdBase}${uniqueId}`;
try {
- /** @type {?import('frame-ancestry-handler').RequestFrameInfoResponseReturn} */
- const response = await yomitan.crossFrame.invoke(originFrameId, responseMessageId, responseParams);
+ const response = await yomitan.crossFrame.invoke(originFrameId, 'frameAncestryHandlerRequestFrameInfoResponse', {uniqueId, frameId, nonce, more});
if (response === null) { return; }
const nonce2 = response.nonce;
if (typeof nonce2 !== 'string') { return; }
@@ -317,4 +310,27 @@ export class FrameAncestryHandler {
// Not found
return null;
}
+
+ /**
+ * @param {string} id
+ * @param {import('frame-ancestry-handler').ResponseHandler} handler
+ * @throws {Error}
+ */
+ _addResponseHandler(id, handler) {
+ if (this._responseHandlers.has(id)) { throw new Error('Identifier already used'); }
+ this._responseHandlers.set(id, handler);
+ }
+
+ /**
+ * @param {string} id
+ */
+ _removeResponseHandler(id) {
+ this._responseHandlers.delete(id);
+ }
+
+ /** @type {import('cross-frame-api').ApiHandler<'frameAncestryHandlerRequestFrameInfoResponse'>} */
+ _onFrameAncestryHandlerRequestFrameInfoResponse(params) {
+ const handler = this._responseHandlers.get(params.uniqueId);
+ return typeof handler !== 'undefined' ? handler(params) : null;
+ }
}
diff --git a/ext/js/comm/frame-offset-forwarder.js b/ext/js/comm/frame-offset-forwarder.js
index afa6a5e6..570f3e88 100644
--- a/ext/js/comm/frame-offset-forwarder.js
+++ b/ext/js/comm/frame-offset-forwarder.js
@@ -36,7 +36,7 @@ export class FrameOffsetForwarder {
prepare() {
this._frameAncestryHandler.prepare();
yomitan.crossFrame.registerHandlers([
- ['FrameOffsetForwarder.getChildFrameRect', this._onMessageGetChildFrameRect.bind(this)]
+ ['frameOffsetForwarderGetChildFrameRect', this._onMessageGetChildFrameRect.bind(this)]
]);
}
@@ -55,7 +55,7 @@ export class FrameOffsetForwarder {
/** @type {Promise<?import('frame-offset-forwarder').ChildFrameRect>[]} */
const promises = [];
for (const frameId of ancestorFrameIds) {
- promises.push(yomitan.crossFrame.invoke(frameId, 'FrameOffsetForwarder.getChildFrameRect', {frameId: childFrameId}));
+ promises.push(yomitan.crossFrame.invoke(frameId, 'frameOffsetForwarderGetChildFrameRect', {frameId: childFrameId}));
childFrameId = frameId;
}
@@ -76,10 +76,7 @@ export class FrameOffsetForwarder {
// Private
- /**
- * @param {{frameId: number}} event
- * @returns {?import('frame-offset-forwarder').ChildFrameRect}
- */
+ /** @type {import('cross-frame-api').ApiHandler<'frameOffsetForwarderGetChildFrameRect'>} */
_onMessageGetChildFrameRect({frameId}) {
const frameElement = this._frameAncestryHandler.getChildFrameElement(frameId);
if (frameElement === null) { return null; }
diff --git a/ext/js/core.js b/ext/js/core.js
index c9c989ac..726b037c 100644
--- a/ext/js/core.js
+++ b/ext/js/core.js
@@ -321,38 +321,6 @@ export function promiseAnimationFrame(timeout) {
}
/**
- * Invokes a standard message handler. This function is used to react and respond
- * to communication messages within the extension.
- * @template {import('core').SafeAny} TParams
- * @param {import('core').MessageHandler} handler The message handler function.
- * @param {TParams} params Information which was passed with the original message.
- * @param {(response: import('core').Response) => void} callback A callback function which is invoked after the handler has completed. The value passed
- * to the function is in the format:
- * - `{result: unknown}` if the handler invoked successfully.
- * - `{error: object}` if the handler thew an error. The error is serialized.
- * @param {...*} extraArgs Additional arguments which are passed to the `handler` function.
- * @returns {boolean} `true` if the function is invoked asynchronously, `false` otherwise.
- */
-export function invokeMessageHandler(handler, params, callback, ...extraArgs) {
- try {
- const promiseOrResult = handler(params, ...extraArgs);
- if (promiseOrResult instanceof Promise) {
- /** @type {Promise<any>} */ (promiseOrResult).then(
- (result) => { callback({result}); },
- (error) => { callback({error: ExtensionError.serialize(error)}); }
- );
- return true;
- } else {
- callback({result: promiseOrResult});
- return false;
- }
- } catch (error) {
- callback({error: ExtensionError.serialize(error)});
- return false;
- }
-}
-
-/**
* The following typedef is required because the JSDoc `implements` tag doesn't work with `import()`.
* https://github.com/microsoft/TypeScript/issues/49905
* @typedef {import('core').EventDispatcherOffGeneric} EventDispatcherOffGeneric
diff --git a/ext/js/display/display-resizer.js b/ext/js/display/display-resizer.js
index 794398b8..8245e0bb 100644
--- a/ext/js/display/display-resizer.js
+++ b/ext/js/display/display-resizer.js
@@ -174,7 +174,7 @@ export class DisplayResizer {
if (parentPopupId === null) { return; }
/** @type {import('popup').ValidSize} */
- const size = await this._display.invokeParentFrame('PopupFactory.getFrameSize', {id: parentPopupId});
+ const size = await this._display.invokeParentFrame('popupFactoryGetFrameSize', {id: parentPopupId});
if (this._token !== token) { return; }
const {width, height} = size;
this._startSize = {width, height};
@@ -210,7 +210,7 @@ export class DisplayResizer {
height += y - this._startOffset.y;
width = Math.max(Math.max(0, handleSize.width), width);
height = Math.max(Math.max(0, handleSize.height), height);
- await this._display.invokeParentFrame('PopupFactory.setFrameSize', {id: parentPopupId, width, height});
+ await this._display.invokeParentFrame('popupFactorySetFrameSize', {id: parentPopupId, width, height});
}
/**
diff --git a/ext/js/display/display.js b/ext/js/display/display.js
index e3c92ee2..cae394f8 100644
--- a/ext/js/display/display.js
+++ b/ext/js/display/display.js
@@ -19,7 +19,7 @@
import {ThemeController} from '../app/theme-controller.js';
import {FrameEndpoint} from '../comm/frame-endpoint.js';
import {DynamicProperty, EventDispatcher, EventListenerCollection, clone, deepEqual, log, promiseTimeout} from '../core.js';
-import {invokeApiMapHandler} from '../core/api-map.js';
+import {extendApiMap, invokeApiMapHandler} from '../core/api-map.js';
import {ExtensionError} from '../core/extension-error.js';
import {PopupMenu} from '../dom/popup-menu.js';
import {querySelectorNotNull} from '../dom/query-selector.js';
@@ -91,7 +91,7 @@ export class Display extends EventDispatcher {
});
/** @type {import('display').DirectApiMap} */
this._directApiMap = new Map();
- /** @type {import('display').WindowApiMap} */
+ /** @type {import('api-map').ApiMap<import('display').WindowApiSurface>} */ // import('display').WindowApiMap
this._windowApiMap = new Map();
/** @type {DisplayHistory} */
this._history = new DisplayHistory({clearable: true, useBrowserHistory: false});
@@ -328,7 +328,8 @@ export class Display extends EventDispatcher {
this._progressIndicatorVisible.on('change', this._onProgressIndicatorVisibleChanged.bind(this));
yomitan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this));
yomitan.crossFrame.registerHandlers([
- ['popupMessage', this._onDirectMessage.bind(this)]
+ ['displayPopupMessage1', this._onDisplayPopupMessage1.bind(this)],
+ ['displayPopupMessage2', this._onDisplayPopupMessage2.bind(this)]
]);
window.addEventListener('message', this._onWindowMessage.bind(this), false);
@@ -507,25 +508,21 @@ export class Display extends EventDispatcher {
* @param {import('display').DirectApiMapInit} handlers
*/
registerDirectMessageHandlers(handlers) {
- for (const [name, handlerInfo] of handlers) {
- this._directApiMap.set(name, handlerInfo);
- }
+ extendApiMap(this._directApiMap, handlers);
}
/**
* @param {import('display').WindowApiMapInit} handlers
*/
registerWindowMessageHandlers(handlers) {
- for (const [name, handlerInfo] of handlers) {
- this._windowApiMap.set(name, handlerInfo);
- }
+ extendApiMap(this._windowApiMap, handlers);
}
/** */
close() {
switch (this._pageType) {
case 'popup':
- this.invokeContentOrigin('Frontend.closePopup');
+ this.invokeContentOrigin('frontendClosePopup', void 0);
break;
case 'search':
this._closeTab();
@@ -578,12 +575,12 @@ export class Display extends EventDispatcher {
}
/**
- * @template [TReturn=unknown]
- * @param {string} action
- * @param {import('core').SerializableObject} [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>>}
*/
- async invokeContentOrigin(action, params = {}) {
+ async invokeContentOrigin(action, params) {
if (this._contentOriginTabId === this._tabId && this._contentOriginFrameId === this._frameId) {
throw new Error('Content origin is same page');
}
@@ -594,12 +591,12 @@ export class Display extends EventDispatcher {
}
/**
- * @template [TReturn=unknown]
- * @param {string} action
- * @param {import('core').SerializableObject} [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>>}
*/
- async invokeParentFrame(action, params = {}) {
+ async invokeParentFrame(action, params) {
if (this._parentFrameId === null || this._parentFrameId === this._frameId) {
throw new Error('Invalid parent frame');
}
@@ -634,14 +631,17 @@ export class Display extends EventDispatcher {
// Message handlers
- /**
- * @param {import('frame-client').Message<import('display').DirectApiMessageAny>} data
- * @returns {Promise<import('display').DirectApiReturnAny>}
- * @throws {Error}
- */
- _onDirectMessage(data) {
+ /** @type {import('cross-frame-api').ApiHandler<'displayPopupMessage1'>} */
+ async _onDisplayPopupMessage1(message) {
+ /** @type {import('display').DirectApiMessageAny} */
+ const messageInner = this._authenticateMessageData(message);
+ return await this._onDisplayPopupMessage2(messageInner);
+ }
+
+ /** @type {import('cross-frame-api').ApiHandler<'displayPopupMessage2'>} */
+ _onDisplayPopupMessage2(message) {
return new Promise((resolve, reject) => {
- const {action, params} = this._authenticateMessageData(data);
+ const {action, params} = message;
invokeApiMapHandler(
this._directApiMap,
action,
@@ -663,9 +663,10 @@ export class Display extends EventDispatcher {
}
/**
- * @param {MessageEvent<import('frame-client').Message<import('display').WindowApiMessageAny>>} details
+ * @param {MessageEvent<import('display').WindowApiFrameClientMessageAny>} details
*/
_onWindowMessage({data}) {
+ /** @type {import('display').WindowApiMessageAny} */
let data2;
try {
data2 = this._authenticateMessageData(data);
@@ -676,7 +677,7 @@ export class Display extends EventDispatcher {
try {
const {action, params} = data2;
const callback = () => {}; // NOP
- invokeApiMapHandler(this._directApiMap, action, params, [], callback);
+ invokeApiMapHandler(this._windowApiMap, action, params, [], callback);
} catch (e) {
// NOP
}
@@ -729,18 +730,15 @@ export class Display extends EventDispatcher {
/**
* @template [T=unknown]
- * @param {T|import('frame-client').Message<T>} data
+ * @param {import('frame-client').Message<unknown>} message
* @returns {T}
* @throws {Error}
*/
- _authenticateMessageData(data) {
- if (this._frameEndpoint === null) {
- return /** @type {T} */ (data);
- }
- if (!this._frameEndpoint.authenticate(data)) {
+ _authenticateMessageData(message) {
+ if (this._frameEndpoint !== null && !this._frameEndpoint.authenticate(message)) {
throw new Error('Invalid authentication');
}
- return /** @type {import('frame-client').Message<T>} */ (data).data;
+ return /** @type {import('frame-client').Message<T>} */ (message).data;
}
/** */
@@ -1767,7 +1765,7 @@ export class Display extends EventDispatcher {
/** @type {string} */
let text;
try {
- text = await this.invokeContentOrigin('Frontend.getSelectionText');
+ text = await this.invokeContentOrigin('frontendGetSelectionText', void 0);
} catch (e) {
break;
}
@@ -1775,7 +1773,7 @@ export class Display extends EventDispatcher {
}
break;
default:
- await this.invokeContentOrigin('Frontend.copySelection');
+ await this.invokeContentOrigin('frontendCopySelection', void 0);
break;
}
}
diff --git a/ext/js/input/hotkey-handler.js b/ext/js/input/hotkey-handler.js
index 48c2de57..5969af05 100644
--- a/ext/js/input/hotkey-handler.js
+++ b/ext/js/input/hotkey-handler.js
@@ -51,7 +51,7 @@ export class HotkeyHandler extends EventDispatcher {
this._isPrepared = true;
this._updateEventHandlers();
yomitan.crossFrame.registerHandlers([
- ['HotkeyHandler.forwardHotkey', this._onMessageForwardHotkey.bind(this)]
+ ['hotkeyHandlerForwardHotkey', this._onMessageForwardHotkey.bind(this)]
]);
}
@@ -159,10 +159,7 @@ export class HotkeyHandler extends EventDispatcher {
// Message handlers
- /**
- * @param {{key: string, modifiers: import('input').ModifierKey[]}} details
- * @returns {boolean}
- */
+ /** @type {import('cross-frame-api').ApiHandler<'hotkeyHandlerForwardHotkey'>} */
_onMessageForwardHotkey({key, modifiers}) {
return this.simulate(key, modifiers);
}
diff --git a/types/ext/core.d.ts b/types/ext/core.d.ts
index b94b649b..48cb68d3 100644
--- a/types/ext/core.d.ts
+++ b/types/ext/core.d.ts
@@ -74,19 +74,6 @@ export type ResponseError = {
export type Response<T = unknown> = ResponseSuccess<T> | ResponseError;
-export type MessageHandler = (params: SafeAny, ...extraArgs: SafeAny[]) => (
- MessageHandlerResult |
- Promise<MessageHandlerResult>
-);
-
-export type MessageHandlerResult = SafeAny;
-
-export type MessageHandlerMap = Map<string, MessageHandler>;
-
-export type MessageHandlerMapInit = MessageHandlerMapInitItem[];
-
-export type MessageHandlerMapInitItem = [key: string, handlerDetails: MessageHandler];
-
export type Timeout = number | NodeJS.Timeout;
export type EventSurface = {[name: string]: unknown};
diff --git a/types/ext/cross-frame-api.d.ts b/types/ext/cross-frame-api.d.ts
index 8ddca6e7..148bfe00 100644
--- a/types/ext/cross-frame-api.d.ts
+++ b/types/ext/cross-frame-api.d.ts
@@ -16,7 +16,28 @@
*/
import type {CrossFrameAPIPort} from '../../ext/js/comm/cross-frame-api.js';
-import type * as Core from './core';
+import type {Response, Timeout, TokenString} from './core';
+import type {ModifierKey} from './input';
+import type {ContentDetails as PopupContentDetails, ValidSize} from './popup';
+import type {GetOrCreatePopupDetails} from './popup-factory';
+import type {OptionsContext} from './settings';
+import type {
+ ApiMap as BaseApiMap,
+ ApiParams as BaseApiParams,
+ ApiNames as BaseApiNames,
+ ApiMapInit as BaseApiMapInit,
+ ApiHandler as BaseApiHandler,
+ ApiReturn as BaseApiReturn,
+ ApiReturnAny as BaseApiReturnAny,
+} from './api-map';
+import type {
+ DirectApiFrameClientMessageAny as DisplayDirectApiFrameClientMessageAny,
+ DirectApiMessageAny as DisplayDirectApiMessageAny,
+ DirectApiReturnAny as DisplayDirectApiReturnAny,
+ ContentDetails as DisplayContentDetails,
+} from './display';
+import type {ChildFrameRect} from 'frame-offset-forwarder';
+import type {RequestFrameInfoResponseParams, RequestFrameInfoResponseReturn} from './frame-ancestry-handler';
export type CrossFrameAPIPortEvents = {
disconnect: CrossFrameAPIPort;
@@ -30,30 +51,25 @@ export type AcknowledgeMessage = {
export type ResultMessage = {
type: 'result';
id: number;
- data: Core.Response<unknown>;
+ data: Response<ApiReturnAny>;
};
export type InvokeMessage = {
type: 'invoke';
id: number;
- data: InvocationData;
-};
-
-export type InvocationData = {
- action: string;
- params: Core.SerializableObject;
+ data: ApiMessageAny;
};
export type Message = AcknowledgeMessage | ResultMessage | InvokeMessage;
export type Invocation = {
id: number;
- resolve: (value: Core.SafeAny) => void;
- reject: (reason?: unknown) => void;
+ resolve: (value: ApiReturnAny) => void;
+ reject: (reason: Error) => void;
responseTimeout: number;
action: string;
ack: boolean;
- timer: Core.Timeout | null;
+ timer: Timeout | null;
};
export type PortDetails = CrossFrameCommunicationPortDetails;
@@ -63,3 +79,180 @@ export type CrossFrameCommunicationPortDetails = {
otherTabId: number;
otherFrameId: number;
};
+
+type ApiSurface = {
+ displayPopupMessage1: {
+ params: DisplayDirectApiFrameClientMessageAny;
+ return: DisplayDirectApiReturnAny;
+ };
+ displayPopupMessage2: {
+ params: DisplayDirectApiMessageAny;
+ return: DisplayDirectApiReturnAny;
+ };
+ frontendClosePopup: {
+ params: void;
+ return: void;
+ };
+ frontendCopySelection: {
+ params: void;
+ return: void;
+ };
+ frontendGetSelectionText: {
+ params: void;
+ return: string;
+ };
+ frontendGetPopupInfo: {
+ params: void;
+ return: {
+ popupId: string | null;
+ };
+ };
+ frontendGetPageInfo: {
+ params: void;
+ return: {
+ url: string;
+ documentTitle: string;
+ };
+ };
+ frameOffsetForwarderGetChildFrameRect: {
+ params: {
+ frameId: number;
+ };
+ return: ChildFrameRect | null;
+ };
+ hotkeyHandlerForwardHotkey: {
+ params: {
+ key: string;
+ modifiers: ModifierKey[];
+ };
+ return: boolean;
+ };
+ popupFactoryGetOrCreatePopup: {
+ params: GetOrCreatePopupDetails;
+ return: {id: string, depth: number, frameId: number};
+ };
+ popupFactorySetOptionsContext: {
+ params: {
+ id: string;
+ optionsContext: OptionsContext;
+ };
+ return: void;
+ };
+ popupFactoryHide: {
+ params: {
+ id: string;
+ changeFocus: boolean;
+ };
+ return: void;
+ };
+ popupFactoryIsVisible: {
+ params: {
+ id: string;
+ };
+ return: boolean;
+ };
+ popupFactorySetVisibleOverride: {
+ params: {
+ id: string;
+ value: boolean;
+ priority: number;
+ };
+ return: TokenString | null;
+ };
+ popupFactoryClearVisibleOverride: {
+ params: {
+ id: string;
+ token: TokenString;
+ };
+ return: boolean;
+ };
+ popupFactoryContainsPoint: {
+ params: {
+ id: string;
+ x: number;
+ y: number;
+ };
+ return: boolean;
+ };
+ popupFactoryShowContent: {
+ params: {
+ id: string;
+ details: PopupContentDetails;
+ displayDetails: DisplayContentDetails | null;
+ };
+ return: void;
+ };
+ popupFactorySetCustomCss: {
+ params: {
+ id: string;
+ css: string;
+ };
+ return: void;
+ };
+ popupFactoryClearAutoPlayTimer: {
+ params: {
+ id: string;
+ };
+ return: void;
+ };
+ popupFactorySetContentScale: {
+ params: {
+ id: string;
+ scale: number;
+ };
+ return: void;
+ };
+ popupFactoryUpdateTheme: {
+ params: {
+ id: string;
+ };
+ return: void;
+ };
+ popupFactorySetCustomOuterCss: {
+ params: {
+ id: string;
+ css: string;
+ useWebExtensionApi: boolean;
+ };
+ return: void;
+ };
+ popupFactoryGetFrameSize: {
+ params: {
+ id: string;
+ };
+ return: ValidSize;
+ };
+ popupFactorySetFrameSize: {
+ params: {
+ id: string;
+ width: number;
+ height: number;
+ };
+ return: boolean;
+ };
+ frameAncestryHandlerRequestFrameInfoResponse: {
+ params: RequestFrameInfoResponseParams;
+ return: RequestFrameInfoResponseReturn;
+ };
+};
+
+export type ApiNames = BaseApiNames<ApiSurface>;
+
+export type ApiMapInit = BaseApiMapInit<ApiSurface>;
+
+export type ApiMap = BaseApiMap<ApiSurface, []>;
+
+export type ApiHandler<TName extends ApiNames> = BaseApiHandler<ApiSurface[TName]>;
+
+export type ApiParams<TName extends ApiNames> = BaseApiParams<ApiSurface[TName]>;
+
+export type ApiReturn<TName extends ApiNames> = BaseApiReturn<ApiSurface[TName]>;
+
+export type ApiReturnAny = BaseApiReturnAny<ApiSurface>;
+
+export type ApiMessageAny = {[name in ApiNames]: ApiMessage<name>}[ApiNames];
+
+type ApiMessage<TName extends ApiNames> = {
+ action: TName;
+ params: ApiParams<TName>;
+};
diff --git a/types/ext/display.d.ts b/types/ext/display.d.ts
index f7c45b02..127823d2 100644
--- a/types/ext/display.d.ts
+++ b/types/ext/display.d.ts
@@ -23,6 +23,7 @@ import type * as Extension from './extension';
import type * as Settings from './settings';
import type * as TextScannerTypes from './text-scanner';
import type {EventNames, EventArgument as BaseEventArgument} from './core';
+import type {Message as FrameClientMessage} from './frame-client';
import type {
ApiMap as BaseApiMap,
ApiParams as BaseApiParams,
@@ -30,6 +31,7 @@ import type {
ApiMapInit as BaseApiMapInit,
ApiParamsAny as BaseApiParamsAny,
ApiHandler as BaseApiHandler,
+ ApiReturn as BaseApiReturn,
} from './api-map';
export type HistoryMode = 'clear' | 'overwrite' | 'new';
@@ -222,7 +224,7 @@ export type DirectApiSurface = {
};
};
-type DirectApiNames = BaseApiNames<DirectApiSurface>;
+export type DirectApiNames = BaseApiNames<DirectApiSurface>;
export type DirectApiMapInit = BaseApiMapInit<DirectApiSurface>;
@@ -230,7 +232,9 @@ export type DirectApiMap = BaseApiMap<DirectApiSurface, []>;
export type DirectApiHandler<TName extends DirectApiNames> = BaseApiHandler<DirectApiSurface[TName]>;
-type DirectApiParams<TName extends DirectApiNames> = BaseApiParams<DirectApiSurface[TName]>;
+export type DirectApiParams<TName extends DirectApiNames> = BaseApiParams<DirectApiSurface[TName]>;
+
+export type DirectApiReturn<TName extends DirectApiNames> = BaseApiReturn<DirectApiSurface[TName]>;
export type DirectApiMessageAny = {[name in DirectApiNames]: DirectApiMessage<name>}[DirectApiNames];
@@ -241,10 +245,12 @@ type DirectApiMessage<TName extends DirectApiNames> = {
params: DirectApiParams<TName>;
};
+export type DirectApiFrameClientMessageAny = {[name in DirectApiNames]: FrameClientMessage<DirectApiMessage<name>>}[DirectApiNames];
+
// Window API
export type WindowApiSurface = {
- 'displayExtensionUnloaded': {
+ displayExtensionUnloaded: {
params: void;
return: void;
};
@@ -266,3 +272,5 @@ type WindowApiMessage<TName extends WindowApiNames> = {
action: TName;
params: WindowApiParams<TName>;
};
+
+export type WindowApiFrameClientMessageAny = {[name in WindowApiNames]: FrameClientMessage<WindowApiMessage<name>>}[WindowApiNames]; \ No newline at end of file
diff --git a/types/ext/frame-ancestry-handler.d.ts b/types/ext/frame-ancestry-handler.d.ts
index 3c9e32bf..4eb7f861 100644
--- a/types/ext/frame-ancestry-handler.d.ts
+++ b/types/ext/frame-ancestry-handler.d.ts
@@ -16,11 +16,12 @@
*/
export type RequestFrameInfoResponseParams = {
+ uniqueId: string;
frameId: number;
nonce: string;
more: boolean;
};
-export type RequestFrameInfoResponseReturn = {
- nonce: string;
-};
+export type RequestFrameInfoResponseReturn = {nonce: string} | null;
+
+export type ResponseHandler = (params: RequestFrameInfoResponseParams) => RequestFrameInfoResponseReturn;
diff --git a/types/ext/frontend.d.ts b/types/ext/frontend.d.ts
index 4cc8d03b..b06e6040 100644
--- a/types/ext/frontend.d.ts
+++ b/types/ext/frontend.d.ts
@@ -47,7 +47,3 @@ export type ConstructorDetails = {
};
export type PageType = 'web' | 'popup' | 'search';
-
-export type GetPopupInfoResult = {
- popupId: string | null;
-};