Skip to content
This repository has been archived by the owner on Feb 25, 2023. It is now read-only.

Cleanup yomichan api #1394

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"deepEqual": "readonly",
"generateId": "readonly",
"promiseAnimationFrame": "readonly",
"invokeMessageHandler": "readonly",
"log": "readonly",
"DynamicProperty": "readonly",
"EventDispatcher": "readonly",
Expand Down
51 changes: 35 additions & 16 deletions ext/js/app/frontend.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class Frontend {
_onRuntimeMessage({action, params}, sender, callback) {
const messageHandler = this._runtimeMessageHandlers.get(action);
if (typeof messageHandler === 'undefined') { return false; }
return yomichan.invokeMessageHandler(messageHandler, params, callback, sender);
return invokeMessageHandler(messageHandler, params, callback, sender);
}

_onZoomChanged({newZoomFactor}) {
Expand Down Expand Up @@ -455,7 +455,7 @@ class Frontend {
async _getIframeProxyPopup() {
const targetFrameId = 0; // Root frameId
try {
await this._waitForFrontendReady(targetFrameId);
await this._waitForFrontendReady(targetFrameId, 10000);
} catch (e) {
// Root frame not available
return await this._getDefaultPopup();
Expand Down Expand Up @@ -613,21 +613,40 @@ class Frontend {
}
}

async _waitForFrontendReady(frameId) {
const promise = yomichan.getTemporaryListenerResult(
chrome.runtime.onMessage,
({action, params}, {resolve}) => {
if (
action === 'frontendReady' &&
params.frameId === frameId
) {
resolve();
async _waitForFrontendReady(frameId, timeout) {
return new Promise((resolve, reject) => {
let timeoutId = null;

const cleanup = () => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
},
10000
);
yomichan.api.broadcastTab('requestFrontendReadyBroadcast', {frameId: this._frameId});
await promise;
chrome.runtime.onMessage.removeListener(onMessage);
};
const onMessage = (message, sender, sendResponse) => {
try {
const {action, params} = message;
if (action === 'frontendReady' && params.frameId === frameId) {
cleanup();
resolve();
sendResponse();
}
} catch (e) {
// NOP
}
};

if (timeout !== null) {
timeoutId = setTimeout(() => {
timeoutId = null;
cleanup();
reject(new Error(`Wait for frontend ready timed out after ${timeout}ms`));
}, timeout);
}

chrome.runtime.onMessage.addListener(onMessage);
});
}

_getPreventMiddleMouseValueForPageType(preventMiddleMouseOptions) {
Expand Down
19 changes: 17 additions & 2 deletions ext/js/background/backend.js
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ class Backend {
}
}

return yomichan.invokeMessageHandler(messageHandler, params, callback, sender);
return invokeMessageHandler(messageHandler, params, callback, sender);
}

_onConnect(port) {
Expand Down Expand Up @@ -1563,7 +1563,7 @@ class Backend {
return new Promise((resolve, reject) => {
const callback = (response) => {
try {
resolve(yomichan.getMessageResponseResult(response));
resolve(this._getMessageResponseResult(response));
} catch (error) {
reject(error);
}
Expand All @@ -1573,6 +1573,21 @@ class Backend {
});
}

_getMessageResponseResult(response) {
let error = chrome.runtime.lastError;
if (error) {
throw new Error(error.message);
}
if (!isObject(response)) {
throw new Error('Tab did not respond');
}
error = response.error;
if (error) {
throw deserializeError(error);
}
return response.result;
}

async _checkTabUrl(tabId, urlPredicate) {
let tab;
try {
Expand Down
2 changes: 1 addition & 1 deletion ext/js/comm/cross-frame-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ class CrossFrameAPIPort extends EventDispatcher {
}

const callback = (data) => this._sendResult(id, data);
return yomichan.invokeMessageHandler(messageHandler, params, callback);
return invokeMessageHandler(messageHandler, params, callback);
}

_sendResponse(data) {
Expand Down
36 changes: 36 additions & 0 deletions ext/js/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,42 @@ function promiseAnimationFrame(timeout=null) {
});
}

/**
* Invokes a standard message handler. This function is used to react and respond
* to communication messages within the extension.
* @param handler A handler function which is passed `params` and `...extraArgs` as arguments.
* @param async Whether or not the handler is async or not. Values include `false`, `true`, or `'dynamic'`.
* When the value is `'dynamic'`, the handler should return an object of the format `{async: boolean, result: any}`.
* @param params Information which was passed with the original message.
* @param callback A callback function which is invoked after the handler has completed. The value passed
* to the function is in the format:
* * `{result: any}` 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 `true` if the function is invoked asynchronously, `false` otherwise.
*/
function invokeMessageHandler({handler, async}, params, callback, ...extraArgs) {
try {
let promiseOrResult = handler(params, ...extraArgs);
if (async === 'dynamic') {
({async, result: promiseOrResult} = promiseOrResult);
}
if (async) {
promiseOrResult.then(
(result) => { callback({result}); },
(error) => { callback({error: serializeError(error)}); }
);
return true;
} else {
callback({result: promiseOrResult});
return false;
}
} catch (error) {
callback({error: serializeError(error)});
return false;
}
}

/**
* Base class controls basic event dispatching.
*/
Expand Down
2 changes: 1 addition & 1 deletion ext/js/display/display.js
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,7 @@ class Display extends EventDispatcher {
if (typeof messageHandler === 'undefined') { return; }

const callback = () => {}; // NOP
yomichan.invokeMessageHandler(messageHandler, params, callback);
invokeMessageHandler(messageHandler, params, callback);
}

_onMessageSetOptionsContext({optionsContext}) {
Expand Down
2 changes: 1 addition & 1 deletion ext/js/display/search-display-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ class SearchDisplayController {
_onMessage({action, params}, sender, callback) {
const messageHandler = this._messageHandlers.get(action);
if (typeof messageHandler === 'undefined') { return false; }
return yomichan.invokeMessageHandler(messageHandler, params, callback, sender);
return invokeMessageHandler(messageHandler, params, callback, sender);
}

_onKeyDown(e) {
Expand Down
75 changes: 1 addition & 74 deletions ext/js/yomichan.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,42 +120,6 @@ class Yomichan extends EventDispatcher {
}
}

getTemporaryListenerResult(eventHandler, userCallback, timeout=null) {
if (!(
typeof eventHandler.addListener === 'function' &&
typeof eventHandler.removeListener === 'function'
)) {
throw new Error('Event handler type not supported');
}

return new Promise((resolve, reject) => {
const runtimeMessageCallback = ({action, params}, sender, sendResponse) => {
let timeoutId = null;
if (timeout !== null) {
timeoutId = setTimeout(() => {
timeoutId = null;
eventHandler.removeListener(runtimeMessageCallback);
reject(new Error(`Listener timed out in ${timeout} ms`));
}, timeout);
}

const cleanupResolve = (value) => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
eventHandler.removeListener(runtimeMessageCallback);
sendResponse();
resolve(value);
};

userCallback({action, params}, {resolve: cleanupResolve, sender});
};

eventHandler.addListener(runtimeMessageCallback);
});
}

sendMessage(...args) {
try {
return chrome.runtime.sendMessage(...args);
Expand All @@ -174,43 +138,6 @@ class Yomichan extends EventDispatcher {
}
}

getMessageResponseResult(response) {
let error = chrome.runtime.lastError;
if (error) {
throw new Error(error.message);
}
if (!isObject(response)) {
throw new Error('Tab did not respond');
}
error = response.error;
if (error) {
throw deserializeError(error);
}
return response.result;
}

invokeMessageHandler({handler, async}, params, callback, ...extraArgs) {
try {
let promiseOrResult = handler(params, ...extraArgs);
if (async === 'dynamic') {
({async, result: promiseOrResult} = promiseOrResult);
}
if (async) {
promiseOrResult.then(
(result) => { callback({result}); },
(error) => { callback({error: serializeError(error)}); }
);
return true;
} else {
callback({result: promiseOrResult});
return false;
}
} catch (error) {
callback({error: serializeError(error)});
return false;
}
}

triggerExtensionUnloaded() {
this._isExtensionUnloaded = true;
if (this._isTriggeringExtensionUnloaded) { return; }
Expand All @@ -235,7 +162,7 @@ class Yomichan extends EventDispatcher {
_onMessage({action, params}, sender, callback) {
const messageHandler = this._messageHandlers.get(action);
if (typeof messageHandler === 'undefined') { return false; }
return this.invokeMessageHandler(messageHandler, params, callback, sender);
return invokeMessageHandler(messageHandler, params, callback, sender);
}

_onMessageIsReady() {
Expand Down