Skip to content

Commit

Permalink
API type safety updates (#457)
Browse files Browse the repository at this point in the history
* Update message handlers in SearchDisplayController

* Update types

* Updates

* Updates

* Simplify

* Updates

* Updates

* Rename

* Improve types

* Improve types

* Resolve TODOs
  • Loading branch information
toasted-nutbread authored Dec 28, 2023
1 parent fc2123a commit 76805bc
Show file tree
Hide file tree
Showing 11 changed files with 260 additions and 175 deletions.
53 changes: 22 additions & 31 deletions ext/js/app/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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)],
Expand Down Expand Up @@ -239,9 +240,7 @@ export class Frontend {

// Message handlers

/**
* @param {import('frontend').FrontendRequestReadyBroadcastParams} params
*/
/** @type {import('application').ApiHandler<'frontendRequestReadyBroadcast'>} */
_onMessageRequestFrontendReadyBroadcast({frameId}) {
this._signalFrontendReady(frameId);
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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);
}
Expand All @@ -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);
}

/**
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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();
Expand All @@ -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}});
});
}

Expand Down
74 changes: 38 additions & 36 deletions ext/js/background/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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}}, {});
}

/**
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -1094,7 +1095,7 @@ export class Backend {

await this._sendMessageTabPromise(
id,
{action: 'SearchDisplayController.setMode', params: {mode: 'popup'}},
{action: 'searchDisplayControllerSetMode', params: {mode: 'popup'}},
{frameId: 0}
);

Expand All @@ -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';
Expand Down Expand Up @@ -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}
);
}
Expand Down Expand Up @@ -1225,7 +1226,7 @@ export class Backend {

this._accessibilityController.update(this._getOptionsFull(false));

this._sendMessageAllTabsIgnoreResponse('Yomitan.optionsUpdated', {source});
this._sendMessageAllTabsIgnoreResponse({action: 'applicationOptionsUpdated', params: {source}});
}

/**
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand All @@ -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; }
Expand Down Expand Up @@ -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);
Expand All @@ -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) {
Expand All @@ -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) => {
Expand All @@ -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);
}
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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});
}
Expand All @@ -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});
Expand Down Expand Up @@ -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}});
}

/**
Expand Down
14 changes: 6 additions & 8 deletions ext/js/comm/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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});
}

/**
Expand Down
Loading

0 comments on commit 76805bc

Please sign in to comment.