Move api to yomichan object (#1392)

* Move cross frame API from API to Yomichan

* Add API instance to Yomichan

* Move api global to yomichan.api

* Pass yomichan to API

* Remove IIFE
This commit is contained in:
toasted-nutbread 2021-02-14 15:53:35 -05:00 committed by GitHub
parent efe8140f10
commit 286534e648
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 672 additions and 744 deletions

View File

@ -19,15 +19,13 @@
* Frontend * Frontend
* HotkeyHandler * HotkeyHandler
* PopupFactory * PopupFactory
* api
*/ */
(async () => { (async () => {
try { try {
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
const {tabId, frameId} = await api.frameInformationGet(); const {tabId, frameId} = await yomichan.api.frameInformationGet();
if (typeof frameId !== 'number') { if (typeof frameId !== 'number') {
throw new Error('Failed to get frameId'); throw new Error('Failed to get frameId');
} }

View File

@ -20,7 +20,6 @@
* TextScanner * TextScanner
* TextSourceElement * TextSourceElement
* TextSourceRange * TextSourceRange
* api
*/ */
class Frontend { class Frontend {
@ -99,7 +98,7 @@ class Frontend {
async prepare() { async prepare() {
await this.updateOptions(); await this.updateOptions();
try { try {
const {zoomFactor} = await api.getZoom(); const {zoomFactor} = await yomichan.api.getZoom();
this._pageZoomFactor = zoomFactor; this._pageZoomFactor = zoomFactor;
} catch (e) { } catch (e) {
// Ignore exceptions which may occur due to being on an unsupported page (e.g. about:blank) // Ignore exceptions which may occur due to being on an unsupported page (e.g. about:blank)
@ -124,7 +123,7 @@ class Frontend {
this._textScanner.on('clearSelection', this._onClearSelection.bind(this)); this._textScanner.on('clearSelection', this._onClearSelection.bind(this));
this._textScanner.on('searched', this._onSearched.bind(this)); this._textScanner.on('searched', this._onSearched.bind(this));
api.crossFrame.registerHandlers([ yomichan.crossFrame.registerHandlers([
['closePopup', {async: false, handler: this._onApiClosePopup.bind(this)}], ['closePopup', {async: false, handler: this._onApiClosePopup.bind(this)}],
['copySelection', {async: false, handler: this._onApiCopySelection.bind(this)}], ['copySelection', {async: false, handler: this._onApiCopySelection.bind(this)}],
['getSelectionText', {async: false, handler: this._onApiGetSelectionText.bind(this)}], ['getSelectionText', {async: false, handler: this._onApiGetSelectionText.bind(this)}],
@ -332,7 +331,7 @@ class Frontend {
async _updateOptionsInternal() { async _updateOptionsInternal() {
const optionsContext = await this._getOptionsContext(); const optionsContext = await this._getOptionsContext();
const options = await api.optionsGet(optionsContext); const options = await yomichan.api.optionsGet(optionsContext);
const {scanning: scanningOptions, sentenceParsing: sentenceParsingOptions} = options; const {scanning: scanningOptions, sentenceParsing: sentenceParsingOptions} = options;
this._options = options; this._options = options;
@ -462,7 +461,7 @@ class Frontend {
return await this._getDefaultPopup(); return await this._getDefaultPopup();
} }
const {popupId} = await api.crossFrame.invoke(targetFrameId, 'getPopupInfo'); const {popupId} = await yomichan.crossFrame.invoke(targetFrameId, 'getPopupInfo');
if (popupId === null) { if (popupId === null) {
return null; return null;
} }
@ -608,9 +607,9 @@ class Frontend {
_signalFrontendReady(targetFrameId=null) { _signalFrontendReady(targetFrameId=null) {
const params = {frameId: this._frameId}; const params = {frameId: this._frameId};
if (targetFrameId === null) { if (targetFrameId === null) {
api.broadcastTab('frontendReady', params); yomichan.api.broadcastTab('frontendReady', params);
} else { } else {
api.sendMessageToFrame(targetFrameId, 'frontendReady', params); yomichan.api.sendMessageToFrame(targetFrameId, 'frontendReady', params);
} }
} }
@ -627,7 +626,7 @@ class Frontend {
}, },
10000 10000
); );
api.broadcastTab('requestFrontendReadyBroadcast', {frameId: this._frameId}); yomichan.api.broadcastTab('requestFrontendReadyBroadcast', {frameId: this._frameId});
await promise; await promise;
} }
@ -653,7 +652,7 @@ class Frontend {
let documentTitle = document.title; let documentTitle = document.title;
if (this._useProxyPopup) { if (this._useProxyPopup) {
try { try {
({url, documentTitle} = await api.crossFrame.invoke(this._parentFrameId, 'getPageInfo', {})); ({url, documentTitle} = await yomichan.crossFrame.invoke(this._parentFrameId, 'getPageInfo', {}));
} catch (e) { } catch (e) {
// NOP // NOP
} }

View File

@ -20,7 +20,6 @@
* Popup * Popup
* PopupProxy * PopupProxy
* PopupWindow * PopupWindow
* api
*/ */
class PopupFactory { class PopupFactory {
@ -35,7 +34,7 @@ class PopupFactory {
prepare() { prepare() {
this._frameOffsetForwarder.prepare(); this._frameOffsetForwarder.prepare();
api.crossFrame.registerHandlers([ yomichan.crossFrame.registerHandlers([
['getOrCreatePopup', {async: true, handler: this._onApiGetOrCreatePopup.bind(this)}], ['getOrCreatePopup', {async: true, handler: this._onApiGetOrCreatePopup.bind(this)}],
['setOptionsContext', {async: true, handler: this._onApiSetOptionsContext.bind(this)}], ['setOptionsContext', {async: true, handler: this._onApiSetOptionsContext.bind(this)}],
['hide', {async: false, handler: this._onApiHide.bind(this)}], ['hide', {async: false, handler: this._onApiHide.bind(this)}],
@ -132,7 +131,7 @@ class PopupFactory {
throw new Error('Invalid frameId'); throw new Error('Invalid frameId');
} }
const useFrameOffsetForwarder = (parentPopupId === null); const useFrameOffsetForwarder = (parentPopupId === null);
({id, depth, frameId} = await api.crossFrame.invoke(frameId, 'getOrCreatePopup', { ({id, depth, frameId} = await yomichan.crossFrame.invoke(frameId, 'getOrCreatePopup', {
id, id,
parentPopupId, parentPopupId,
frameId, frameId,

View File

@ -15,10 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* global
* api
*/
class PopupProxy extends EventDispatcher { class PopupProxy extends EventDispatcher {
constructor({ constructor({
id, id,
@ -158,7 +154,7 @@ class PopupProxy extends EventDispatcher {
// Private // Private
_invoke(action, params={}) { _invoke(action, params={}) {
return api.crossFrame.invoke(this._frameId, action, params); return yomichan.crossFrame.invoke(this._frameId, action, params);
} }
async _invokeSafe(action, params={}, defaultReturnValue) { async _invokeSafe(action, params={}, defaultReturnValue) {

View File

@ -15,10 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* global
* api
*/
class PopupWindow extends EventDispatcher { class PopupWindow extends EventDispatcher {
constructor({ constructor({
id, id,
@ -82,7 +78,7 @@ class PopupWindow extends EventDispatcher {
} }
async isVisible() { async isVisible() {
return (this._popupTabId !== null && await api.isTabSearchPopup(this._popupTabId)); return (this._popupTabId !== null && await yomichan.api.isTabSearchPopup(this._popupTabId));
} }
async setVisibleOverride(_value, _priority) { async setVisibleOverride(_value, _priority) {
@ -148,7 +144,7 @@ class PopupWindow extends EventDispatcher {
const frameId = 0; const frameId = 0;
if (this._popupTabId !== null) { if (this._popupTabId !== null) {
try { try {
return await api.crossFrame.invokeTab(this._popupTabId, frameId, 'popupMessage', {action, params}); return await yomichan.crossFrame.invokeTab(this._popupTabId, frameId, 'popupMessage', {action, params});
} catch (e) { } catch (e) {
if (yomichan.isExtensionUnloaded) { if (yomichan.isExtensionUnloaded) {
open = false; open = false;
@ -161,9 +157,9 @@ class PopupWindow extends EventDispatcher {
return defaultReturnValue; return defaultReturnValue;
} }
const {tabId} = await api.getOrCreateSearchPopup({focus: 'ifCreated'}); const {tabId} = await yomichan.api.getOrCreateSearchPopup({focus: 'ifCreated'});
this._popupTabId = tabId; this._popupTabId = tabId;
return await api.crossFrame.invokeTab(this._popupTabId, frameId, 'popupMessage', {action, params}); return await yomichan.crossFrame.invokeTab(this._popupTabId, frameId, 'popupMessage', {action, params});
} }
} }

View File

@ -18,7 +18,6 @@
/* global /* global
* DocumentUtil * DocumentUtil
* FrameClient * FrameClient
* api
* dynamicLoader * dynamicLoader
*/ */
@ -460,7 +459,7 @@ class Popup extends EventDispatcher {
if (this._frameClient === null || !this._frameClient.isConnected() || contentWindow === null) { return; } if (this._frameClient === null || !this._frameClient.isConnected() || contentWindow === null) { return; }
const message = this._frameClient.createMessage({action, params}); const message = this._frameClient.createMessage({action, params});
return await api.crossFrame.invoke(this._frameClient.frameId, 'popupMessage', message); return await yomichan.crossFrame.invoke(this._frameClient.frameId, 'popupMessage', message);
} }
async _invokeSafe(action, params={}, defaultReturnValue) { async _invokeSafe(action, params={}, defaultReturnValue) {
@ -676,7 +675,7 @@ class Popup extends EventDispatcher {
async _setOptionsContext(optionsContext) { async _setOptionsContext(optionsContext) {
this._optionsContext = optionsContext; this._optionsContext = optionsContext;
this._options = await api.optionsGet(optionsContext); this._options = await yomichan.api.optionsGet(optionsContext);
this.updateTheme(); this.updateTheme();
} }

View File

@ -15,319 +15,291 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* global class API {
* CrossFrameAPI constructor(yomichan) {
*/ this._yomichan = yomichan;
const api = (() => {
class API {
constructor() {
this._prepared = false;
this._crossFrame = null;
}
get crossFrame() {
return this._crossFrame;
}
prepare() {
if (this._prepared) { return; }
this._crossFrame = new CrossFrameAPI();
this._crossFrame.prepare();
yomichan.on('log', this._onLog.bind(this));
this._prepared = true;
}
// Invoke functions
optionsGet(optionsContext) {
return this._invoke('optionsGet', {optionsContext});
}
optionsGetFull() {
return this._invoke('optionsGetFull');
}
termsFind(text, details, optionsContext) {
return this._invoke('termsFind', {text, details, optionsContext});
}
textParse(text, optionsContext) {
return this._invoke('textParse', {text, optionsContext});
}
kanjiFind(text, optionsContext) {
return this._invoke('kanjiFind', {text, optionsContext});
}
isAnkiConnected() {
return this._invoke('isAnkiConnected');
}
getAnkiConnectVersion() {
return this._invoke('getAnkiConnectVersion');
}
addAnkiNote(note) {
return this._invoke('addAnkiNote', {note});
}
getAnkiNoteInfo(notes) {
return this._invoke('getAnkiNoteInfo', {notes});
}
injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails) {
return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails});
}
noteView(noteId) {
return this._invoke('noteView', {noteId});
}
suspendAnkiCardsForNote(noteId) {
return this._invoke('suspendAnkiCardsForNote', {noteId});
}
getExpressionAudioInfoList(source, expression, reading, details) {
return this._invoke('getExpressionAudioInfoList', {source, expression, reading, details});
}
commandExec(command, params) {
return this._invoke('commandExec', {command, params});
}
sendMessageToFrame(frameId, action, params) {
return this._invoke('sendMessageToFrame', {frameId, action, params});
}
broadcastTab(action, params) {
return this._invoke('broadcastTab', {action, params});
}
frameInformationGet() {
return this._invoke('frameInformationGet');
}
injectStylesheet(type, value) {
return this._invoke('injectStylesheet', {type, value});
}
getStylesheetContent(url) {
return this._invoke('getStylesheetContent', {url});
}
getEnvironmentInfo() {
return this._invoke('getEnvironmentInfo');
}
clipboardGet() {
return this._invoke('clipboardGet');
}
getDisplayTemplatesHtml() {
return this._invoke('getDisplayTemplatesHtml');
}
getZoom() {
return this._invoke('getZoom');
}
getDefaultAnkiFieldTemplates() {
return this._invoke('getDefaultAnkiFieldTemplates');
}
getDictionaryInfo() {
return this._invoke('getDictionaryInfo');
}
getDictionaryCounts(dictionaryNames, getTotal) {
return this._invoke('getDictionaryCounts', {dictionaryNames, getTotal});
}
purgeDatabase() {
return this._invoke('purgeDatabase');
}
getMedia(targets) {
return this._invoke('getMedia', {targets});
}
logIndicatorClear() {
return this._invoke('logIndicatorClear');
}
modifySettings(targets, source) {
return this._invoke('modifySettings', {targets, source});
}
getSettings(targets) {
return this._invoke('getSettings', {targets});
}
setAllSettings(value, source) {
return this._invoke('setAllSettings', {value, source});
}
getOrCreateSearchPopup(details) {
return this._invoke('getOrCreateSearchPopup', isObject(details) ? details : {});
}
isTabSearchPopup(tabId) {
return this._invoke('isTabSearchPopup', {tabId});
}
triggerDatabaseUpdated(type, cause) {
return this._invoke('triggerDatabaseUpdated', {type, cause});
}
testMecab() {
return this._invoke('testMecab', {});
}
// Utilities
_createActionPort(timeout=5000) {
return new Promise((resolve, reject) => {
let timer = null;
const portDetails = deferPromise();
const onConnect = async (port) => {
try {
const {name: expectedName, id: expectedId} = await portDetails.promise;
const {name, id} = JSON.parse(port.name);
if (name !== expectedName || id !== expectedId || timer === null) { return; }
} catch (e) {
return;
}
clearTimeout(timer);
timer = null;
chrome.runtime.onConnect.removeListener(onConnect);
resolve(port);
};
const onError = (e) => {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
chrome.runtime.onConnect.removeListener(onConnect);
portDetails.reject(e);
reject(e);
};
timer = setTimeout(() => onError(new Error('Timeout')), timeout);
chrome.runtime.onConnect.addListener(onConnect);
this._invoke('createActionPort').then(portDetails.resolve, onError);
});
}
_invokeWithProgress(action, params, onProgress, timeout=5000) {
return new Promise((resolve, reject) => {
let port = null;
if (typeof onProgress !== 'function') {
onProgress = () => {};
}
const onMessage = (message) => {
switch (message.type) {
case 'progress':
try {
onProgress(...message.data);
} catch (e) {
// NOP
}
break;
case 'complete':
cleanup();
resolve(message.data);
break;
case 'error':
cleanup();
reject(deserializeError(message.data));
break;
}
};
const onDisconnect = () => {
cleanup();
reject(new Error('Disconnected'));
};
const cleanup = () => {
if (port !== null) {
port.onMessage.removeListener(onMessage);
port.onDisconnect.removeListener(onDisconnect);
port.disconnect();
port = null;
}
onProgress = null;
};
(async () => {
try {
port = await this._createActionPort(timeout);
port.onMessage.addListener(onMessage);
port.onDisconnect.addListener(onDisconnect);
// Chrome has a maximum message size that can be sent, so longer messages must be fragmented.
const messageString = JSON.stringify({action, params});
const fragmentSize = 1e7; // 10 MB
for (let i = 0, ii = messageString.length; i < ii; i += fragmentSize) {
const data = messageString.substring(i, i + fragmentSize);
port.postMessage({action: 'fragment', data});
}
port.postMessage({action: 'invoke'});
} catch (e) {
cleanup();
reject(e);
} finally {
action = null;
params = null;
}
})();
});
}
_invoke(action, params={}) {
const data = {action, params};
return new Promise((resolve, reject) => {
try {
yomichan.sendMessage(data, (response) => {
this._checkLastError(chrome.runtime.lastError);
if (response !== null && typeof response === 'object') {
if (typeof response.error !== 'undefined') {
reject(deserializeError(response.error));
} else {
resolve(response.result);
}
} else {
const message = response === null ? 'Unexpected null response' : `Unexpected response of type ${typeof response}`;
reject(new Error(`${message} (${JSON.stringify(data)})`));
}
});
} catch (e) {
reject(e);
}
});
}
_checkLastError() {
// NOP
}
async _onLog({error, level, context}) {
try {
error = serializeError(error);
await this._invoke('log', {error, level, context});
} catch (e) {
// NOP
}
}
} }
return new API(); optionsGet(optionsContext) {
})(); return this._invoke('optionsGet', {optionsContext});
}
optionsGetFull() {
return this._invoke('optionsGetFull');
}
termsFind(text, details, optionsContext) {
return this._invoke('termsFind', {text, details, optionsContext});
}
textParse(text, optionsContext) {
return this._invoke('textParse', {text, optionsContext});
}
kanjiFind(text, optionsContext) {
return this._invoke('kanjiFind', {text, optionsContext});
}
isAnkiConnected() {
return this._invoke('isAnkiConnected');
}
getAnkiConnectVersion() {
return this._invoke('getAnkiConnectVersion');
}
addAnkiNote(note) {
return this._invoke('addAnkiNote', {note});
}
getAnkiNoteInfo(notes) {
return this._invoke('getAnkiNoteInfo', {notes});
}
injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails) {
return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails});
}
noteView(noteId) {
return this._invoke('noteView', {noteId});
}
suspendAnkiCardsForNote(noteId) {
return this._invoke('suspendAnkiCardsForNote', {noteId});
}
getExpressionAudioInfoList(source, expression, reading, details) {
return this._invoke('getExpressionAudioInfoList', {source, expression, reading, details});
}
commandExec(command, params) {
return this._invoke('commandExec', {command, params});
}
sendMessageToFrame(frameId, action, params) {
return this._invoke('sendMessageToFrame', {frameId, action, params});
}
broadcastTab(action, params) {
return this._invoke('broadcastTab', {action, params});
}
frameInformationGet() {
return this._invoke('frameInformationGet');
}
injectStylesheet(type, value) {
return this._invoke('injectStylesheet', {type, value});
}
getStylesheetContent(url) {
return this._invoke('getStylesheetContent', {url});
}
getEnvironmentInfo() {
return this._invoke('getEnvironmentInfo');
}
clipboardGet() {
return this._invoke('clipboardGet');
}
getDisplayTemplatesHtml() {
return this._invoke('getDisplayTemplatesHtml');
}
getZoom() {
return this._invoke('getZoom');
}
getDefaultAnkiFieldTemplates() {
return this._invoke('getDefaultAnkiFieldTemplates');
}
getDictionaryInfo() {
return this._invoke('getDictionaryInfo');
}
getDictionaryCounts(dictionaryNames, getTotal) {
return this._invoke('getDictionaryCounts', {dictionaryNames, getTotal});
}
purgeDatabase() {
return this._invoke('purgeDatabase');
}
getMedia(targets) {
return this._invoke('getMedia', {targets});
}
log(error, level, context) {
return this._invoke('log', {error, level, context});
}
logIndicatorClear() {
return this._invoke('logIndicatorClear');
}
modifySettings(targets, source) {
return this._invoke('modifySettings', {targets, source});
}
getSettings(targets) {
return this._invoke('getSettings', {targets});
}
setAllSettings(value, source) {
return this._invoke('setAllSettings', {value, source});
}
getOrCreateSearchPopup(details) {
return this._invoke('getOrCreateSearchPopup', isObject(details) ? details : {});
}
isTabSearchPopup(tabId) {
return this._invoke('isTabSearchPopup', {tabId});
}
triggerDatabaseUpdated(type, cause) {
return this._invoke('triggerDatabaseUpdated', {type, cause});
}
testMecab() {
return this._invoke('testMecab', {});
}
// Utilities
_createActionPort(timeout=5000) {
return new Promise((resolve, reject) => {
let timer = null;
const portDetails = deferPromise();
const onConnect = async (port) => {
try {
const {name: expectedName, id: expectedId} = await portDetails.promise;
const {name, id} = JSON.parse(port.name);
if (name !== expectedName || id !== expectedId || timer === null) { return; }
} catch (e) {
return;
}
clearTimeout(timer);
timer = null;
chrome.runtime.onConnect.removeListener(onConnect);
resolve(port);
};
const onError = (e) => {
if (timer !== null) {
clearTimeout(timer);
timer = null;
}
chrome.runtime.onConnect.removeListener(onConnect);
portDetails.reject(e);
reject(e);
};
timer = setTimeout(() => onError(new Error('Timeout')), timeout);
chrome.runtime.onConnect.addListener(onConnect);
this._invoke('createActionPort').then(portDetails.resolve, onError);
});
}
_invokeWithProgress(action, params, onProgress, timeout=5000) {
return new Promise((resolve, reject) => {
let port = null;
if (typeof onProgress !== 'function') {
onProgress = () => {};
}
const onMessage = (message) => {
switch (message.type) {
case 'progress':
try {
onProgress(...message.data);
} catch (e) {
// NOP
}
break;
case 'complete':
cleanup();
resolve(message.data);
break;
case 'error':
cleanup();
reject(deserializeError(message.data));
break;
}
};
const onDisconnect = () => {
cleanup();
reject(new Error('Disconnected'));
};
const cleanup = () => {
if (port !== null) {
port.onMessage.removeListener(onMessage);
port.onDisconnect.removeListener(onDisconnect);
port.disconnect();
port = null;
}
onProgress = null;
};
(async () => {
try {
port = await this._createActionPort(timeout);
port.onMessage.addListener(onMessage);
port.onDisconnect.addListener(onDisconnect);
// Chrome has a maximum message size that can be sent, so longer messages must be fragmented.
const messageString = JSON.stringify({action, params});
const fragmentSize = 1e7; // 10 MB
for (let i = 0, ii = messageString.length; i < ii; i += fragmentSize) {
const data = messageString.substring(i, i + fragmentSize);
port.postMessage({action: 'fragment', data});
}
port.postMessage({action: 'invoke'});
} catch (e) {
cleanup();
reject(e);
} finally {
action = null;
params = null;
}
})();
});
}
_invoke(action, params={}) {
const data = {action, params};
return new Promise((resolve, reject) => {
try {
this._yomichan.sendMessage(data, (response) => {
this._checkLastError(chrome.runtime.lastError);
if (response !== null && typeof response === 'object') {
if (typeof response.error !== 'undefined') {
reject(deserializeError(response.error));
} else {
resolve(response.result);
}
} else {
const message = response === null ? 'Unexpected null response' : `Unexpected response of type ${typeof response}`;
reject(new Error(`${message} (${JSON.stringify(data)})`));
}
});
} catch (e) {
reject(e);
}
});
}
_checkLastError() {
// NOP
}
}

View File

@ -15,10 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* global
* api
*/
/** /**
* This class is used to return the ancestor frame IDs for the current frame. * This class is used to return the ancestor frame IDs for the current frame.
* This is a workaround to using the `webNavigation.getAllFrames` API, which * This is a workaround to using the `webNavigation.getAllFrames` API, which
@ -118,7 +114,7 @@ class FrameAncestryHandler {
clearTimeout(timer); clearTimeout(timer);
timer = null; timer = null;
} }
api.crossFrame.unregisterHandler(responseMessageId); yomichan.crossFrame.unregisterHandler(responseMessageId);
}; };
const onMessage = (params) => { const onMessage = (params) => {
if (params.nonce !== nonce) { return null; } if (params.nonce !== nonce) { return null; }
@ -148,7 +144,7 @@ class FrameAncestryHandler {
}; };
// Start // Start
api.crossFrame.registerHandlers([[responseMessageId, {async: false, handler: onMessage}]]); yomichan.crossFrame.registerHandlers([[responseMessageId, {async: false, handler: onMessage}]]);
resetTimeout(); resetTimeout();
const frameId = this._frameId; const frameId = this._frameId;
this._requestFrameInfo(targetWindow, frameId, frameId, uniqueId, nonce); this._requestFrameInfo(targetWindow, frameId, frameId, uniqueId, nonce);
@ -187,7 +183,7 @@ class FrameAncestryHandler {
const responseMessageId = `${this._responseMessageIdBase}${uniqueId}`; const responseMessageId = `${this._responseMessageIdBase}${uniqueId}`;
try { try {
const response = await api.crossFrame.invoke(originFrameId, responseMessageId, responseParams); const response = await yomichan.crossFrame.invoke(originFrameId, responseMessageId, responseParams);
if (response === null) { return; } if (response === null) { return; }
nonce = response.nonce; nonce = response.nonce;
} catch (e) { } catch (e) {

View File

@ -15,10 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* global
* api
*/
class FrameEndpoint { class FrameEndpoint {
constructor() { constructor() {
this._secret = generateId(16); this._secret = generateId(16);
@ -32,7 +28,7 @@ class FrameEndpoint {
this._eventListeners.addEventListener(window, 'message', this._onMessage.bind(this), false); this._eventListeners.addEventListener(window, 'message', this._onMessage.bind(this), false);
this._eventListenersSetup = true; this._eventListenersSetup = true;
} }
api.broadcastTab('frameEndpointReady', {secret: this._secret}); yomichan.api.broadcastTab('frameEndpointReady', {secret: this._secret});
} }
authenticate(message) { authenticate(message) {
@ -60,6 +56,6 @@ class FrameEndpoint {
this._token = token; this._token = token;
this._eventListeners.removeAllEventListeners(); this._eventListeners.removeAllEventListeners();
api.sendMessageToFrame(hostFrameId, 'frameEndpointConnected', {secret, token}); yomichan.api.sendMessageToFrame(hostFrameId, 'frameEndpointConnected', {secret, token});
} }
} }

View File

@ -17,7 +17,6 @@
/* global /* global
* FrameAncestryHandler * FrameAncestryHandler
* api
*/ */
class FrameOffsetForwarder { class FrameOffsetForwarder {
@ -28,7 +27,7 @@ class FrameOffsetForwarder {
prepare() { prepare() {
this._frameAncestryHandler.prepare(); this._frameAncestryHandler.prepare();
api.crossFrame.registerHandlers([ yomichan.crossFrame.registerHandlers([
['FrameOffsetForwarder.getChildFrameRect', {async: false, handler: this._onMessageGetChildFrameRect.bind(this)}] ['FrameOffsetForwarder.getChildFrameRect', {async: false, handler: this._onMessageGetChildFrameRect.bind(this)}]
]); ]);
} }
@ -43,7 +42,7 @@ class FrameOffsetForwarder {
let childFrameId = this._frameId; let childFrameId = this._frameId;
const promises = []; const promises = [];
for (const frameId of ancestorFrameIds) { for (const frameId of ancestorFrameIds) {
promises.push(api.crossFrame.invoke(frameId, 'FrameOffsetForwarder.getChildFrameRect', {frameId: childFrameId})); promises.push(yomichan.crossFrame.invoke(frameId, 'FrameOffsetForwarder.getChildFrameRect', {frameId: childFrameId}));
childFrameId = frameId; childFrameId = frameId;
} }

View File

@ -18,7 +18,6 @@
/* global /* global
* AudioSystem * AudioSystem
* PopupMenu * PopupMenu
* api
*/ */
class DisplayAudio { class DisplayAudio {
@ -314,7 +313,7 @@ class DisplayAudio {
} }
async _getExpressionAudioInfoList(source, expression, reading, details) { async _getExpressionAudioInfoList(source, expression, reading, details) {
const infoList = await api.getExpressionAudioInfoList(source, expression, reading, details); const infoList = await yomichan.api.getExpressionAudioInfoList(source, expression, reading, details);
return infoList.map((info) => ({info, audioPromise: null, audioResolved: false, audio: null})); return infoList.map((info) => ({info, audioPromise: null, audioResolved: false, audio: null}));
} }

View File

@ -18,7 +18,6 @@
/* global /* global
* DictionaryDataUtil * DictionaryDataUtil
* HtmlTemplateCollection * HtmlTemplateCollection
* api
*/ */
class DisplayGenerator { class DisplayGenerator {
@ -31,7 +30,7 @@ class DisplayGenerator {
} }
async prepare() { async prepare() {
const html = await api.getDisplayTemplatesHtml(); const html = await yomichan.api.getDisplayTemplatesHtml();
this._templates = new HtmlTemplateCollection(html); this._templates = new HtmlTemplateCollection(html);
this.updateHotkeys(); this.updateHotkeys();
} }

View File

@ -17,7 +17,6 @@
/* global /* global
* PanelElement * PanelElement
* api
*/ */
class DisplayProfileSelection { class DisplayProfileSelection {
@ -67,7 +66,7 @@ class DisplayProfileSelection {
async _updateProfileList() { async _updateProfileList() {
this._profileListNeedsUpdate = false; this._profileListNeedsUpdate = false;
const options = await api.optionsGetFull(); const options = await yomichan.api.optionsGetFull();
this._eventListeners.removeAllEventListeners(); this._eventListeners.removeAllEventListeners();
const displayGenerator = this._display.displayGenerator; const displayGenerator = this._display.displayGenerator;
@ -95,7 +94,7 @@ class DisplayProfileSelection {
} }
async _setProfileCurrent(index) { async _setProfileCurrent(index) {
await api.modifySettings([{ await yomichan.api.modifySettings([{
action: 'set', action: 'set',
path: 'profileCurrent', path: 'profileCurrent',
value: index, value: index,

View File

@ -31,7 +31,6 @@
* QueryParser * QueryParser
* ScrollElement * ScrollElement
* TextScanner * TextScanner
* api
* dynamicLoader * dynamicLoader
*/ */
@ -206,7 +205,7 @@ class Display extends EventDispatcher {
async prepare() { async prepare() {
// State setup // State setup
const {documentElement} = document; const {documentElement} = document;
const {browser} = await api.getEnvironmentInfo(); const {browser} = await yomichan.api.getEnvironmentInfo();
this._browser = browser; this._browser = browser;
// Prepare // Prepare
@ -221,7 +220,7 @@ class Display extends EventDispatcher {
this._queryParser.on('searched', this._onQueryParserSearch.bind(this)); this._queryParser.on('searched', this._onQueryParserSearch.bind(this));
this._progressIndicatorVisible.on('change', this._onProgressIndicatorVisibleChanged.bind(this)); this._progressIndicatorVisible.on('change', this._onProgressIndicatorVisibleChanged.bind(this));
yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this));
api.crossFrame.registerHandlers([ yomichan.crossFrame.registerHandlers([
['popupMessage', {async: 'dynamic', handler: this._onDirectMessage.bind(this)}] ['popupMessage', {async: 'dynamic', handler: this._onDirectMessage.bind(this)}]
]); ]);
window.addEventListener('message', this._onWindowMessage.bind(this), false); window.addEventListener('message', this._onWindowMessage.bind(this), false);
@ -290,7 +289,7 @@ class Display extends EventDispatcher {
} }
async updateOptions() { async updateOptions() {
const options = await api.optionsGet(this.getOptionsContext()); const options = await yomichan.api.optionsGet(this.getOptionsContext());
const templates = await this._getAnkiFieldTemplates(options); const templates = await this._getAnkiFieldTemplates(options);
const {scanning: scanningOptions, sentenceParsing: sentenceParsingOptions} = options; const {scanning: scanningOptions, sentenceParsing: sentenceParsingOptions} = options;
this._options = options; this._options = options;
@ -674,7 +673,7 @@ class Display extends EventDispatcher {
if (typeof documentTitle !== 'string') { documentTitle = document.title; } if (typeof documentTitle !== 'string') { documentTitle = document.title; }
const optionsContext = this.getOptionsContext(); const optionsContext = this.getOptionsContext();
const query = e.currentTarget.textContent; const query = e.currentTarget.textContent;
const definitions = await api.kanjiFind(query, optionsContext); const definitions = await yomichan.api.kanjiFind(query, optionsContext);
const details = { const details = {
focus: false, focus: false,
history: true, history: true,
@ -707,7 +706,7 @@ class Display extends EventDispatcher {
_onNoteView(e) { _onNoteView(e) {
e.preventDefault(); e.preventDefault();
const link = e.currentTarget; const link = e.currentTarget;
api.noteView(link.dataset.noteId); yomichan.api.noteView(link.dataset.noteId);
} }
_onWheel(e) { _onWheel(e) {
@ -839,10 +838,10 @@ class Display extends EventDispatcher {
} }
} }
const {definitions} = await api.termsFind(source, findDetails, optionsContext); const {definitions} = await yomichan.api.termsFind(source, findDetails, optionsContext);
return definitions; return definitions;
} else { } else {
const definitions = await api.kanjiFind(source, optionsContext); const definitions = await yomichan.api.kanjiFind(source, optionsContext);
return definitions; return definitions;
} }
} }
@ -1059,7 +1058,7 @@ class Display extends EventDispatcher {
const noteContext = this._getNoteContext(); const noteContext = this._getNoteContext();
states = await this._areDefinitionsAddable(definitions, modes, noteContext); states = await this._areDefinitionsAddable(definitions, modes, noteContext);
} else { } else {
if (!await api.isAnkiConnected()) { if (!await yomichan.api.isAnkiConnected()) {
throw new Error('Anki not connected'); throw new Error('Anki not connected');
} }
states = this._areDefinitionsAddableForcedValue(definitions, modes, true); states = this._areDefinitionsAddableForcedValue(definitions, modes, true);
@ -1183,7 +1182,7 @@ class Display extends EventDispatcher {
_tryViewAnkiNoteForSelectedDefinition() { _tryViewAnkiNoteForSelectedDefinition() {
const button = this._viewerButtonFind(this._index); const button = this._viewerButtonFind(this._index);
if (button !== null && !button.disabled) { if (button !== null && !button.disabled) {
api.noteView(button.dataset.noteId); yomichan.api.noteView(button.dataset.noteId);
} }
} }
@ -1206,7 +1205,7 @@ class Display extends EventDispatcher {
let noteId = null; let noteId = null;
let addNoteOkay = false; let addNoteOkay = false;
try { try {
noteId = await api.addAnkiNote(note); noteId = await yomichan.api.addAnkiNote(note);
addNoteOkay = true; addNoteOkay = true;
} catch (e) { } catch (e) {
errors.length = 0; errors.length = 0;
@ -1219,7 +1218,7 @@ class Display extends EventDispatcher {
} else { } else {
if (suspendNewCards) { if (suspendNewCards) {
try { try {
await api.suspendAnkiCardsForNote(noteId); await yomichan.api.suspendAnkiCardsForNote(noteId);
} catch (e) { } catch (e) {
errors.push(e); errors.push(e);
} }
@ -1400,7 +1399,7 @@ class Display extends EventDispatcher {
templates = this._ankiFieldTemplatesDefault; templates = this._ankiFieldTemplatesDefault;
if (typeof templates === 'string') { return templates; } if (typeof templates === 'string') { return templates; }
templates = await api.getDefaultAnkiFieldTemplates(); templates = await yomichan.api.getDefaultAnkiFieldTemplates();
this._ankiFieldTemplatesDefault = templates; this._ankiFieldTemplatesDefault = templates;
return templates; return templates;
} }
@ -1416,7 +1415,7 @@ class Display extends EventDispatcher {
} }
const notes = await Promise.all(notePromises); const notes = await Promise.all(notePromises);
const infos = await api.getAnkiNoteInfo(notes); const infos = await yomichan.api.getAnkiNoteInfo(notes);
const results = []; const results = [];
for (let i = 0, ii = infos.length; i < ii; i += modeCount) { for (let i = 0, ii = infos.length; i < ii; i += modeCount) {
results.push(infos.slice(i, i + modeCount)); results.push(infos.slice(i, i + modeCount));
@ -1494,7 +1493,7 @@ class Display extends EventDispatcher {
image: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-image'), image: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-image'),
text: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-text') text: this._ankiNoteBuilder.containsMarker(fields, 'clipboard-text')
}; };
return await api.injectAnkiNoteMedia( return await yomichan.api.injectAnkiNoteMedia(
timestamp, timestamp,
definitionDetails, definitionDetails,
audioDetails, audioDetails,
@ -1605,7 +1604,7 @@ class Display extends EventDispatcher {
if (this._contentOriginTabId === this._tabId && this._contentOriginFrameId === this._frameId) { if (this._contentOriginTabId === this._tabId && this._contentOriginFrameId === this._frameId) {
throw new Error('Content origin is same page'); throw new Error('Content origin is same page');
} }
return await api.crossFrame.invokeTab(this._contentOriginTabId, this._contentOriginFrameId, action, params); return await yomichan.crossFrame.invokeTab(this._contentOriginTabId, this._contentOriginFrameId, action, params);
} }
_copyHostSelection() { _copyHostSelection() {

View File

@ -21,7 +21,6 @@
* DocumentFocusController * DocumentFocusController
* HotkeyHandler * HotkeyHandler
* JapaneseUtil * JapaneseUtil
* api
*/ */
(async () => { (async () => {
@ -29,10 +28,9 @@
const documentFocusController = new DocumentFocusController(); const documentFocusController = new DocumentFocusController();
documentFocusController.prepare(); documentFocusController.prepare();
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
const {tabId, frameId} = await api.frameInformationGet(); const {tabId, frameId} = await yomichan.api.frameInformationGet();
const japaneseUtil = new JapaneseUtil(null); const japaneseUtil = new JapaneseUtil(null);

View File

@ -17,7 +17,6 @@
/* global /* global
* TextScanner * TextScanner
* api
*/ */
class QueryParser extends EventDispatcher { class QueryParser extends EventDispatcher {
@ -76,7 +75,7 @@ class QueryParser extends EventDispatcher {
const token = {}; const token = {};
this._setTextToken = token; this._setTextToken = token;
this._parseResults = await api.textParse(text, this._getOptionsContext()); this._parseResults = await yomichan.api.textParse(text, this._getOptionsContext());
if (this._setTextToken !== token) { return; } if (this._setTextToken !== token) { return; }
this._refreshSelectedParser(); this._refreshSelectedParser();
@ -116,7 +115,7 @@ class QueryParser extends EventDispatcher {
_setSelectedParser(value) { _setSelectedParser(value) {
const optionsContext = this._getOptionsContext(); const optionsContext = this._getOptionsContext();
api.modifySettings([{ yomichan.api.modifySettings([{
action: 'set', action: 'set',
path: 'parsing.selectedParser', path: 'parsing.selectedParser',
value, value,

View File

@ -17,7 +17,6 @@
/* global /* global
* ClipboardMonitor * ClipboardMonitor
* api
* wanakana * wanakana
*/ */
@ -40,7 +39,7 @@ class SearchDisplayController {
this._clipboardMonitor = new ClipboardMonitor({ this._clipboardMonitor = new ClipboardMonitor({
japaneseUtil, japaneseUtil,
clipboardReader: { clipboardReader: {
getText: async () => (await api.clipboardGet()) getText: async () => (await yomichan.api.clipboardGet())
} }
}); });
this._messageHandlers = new Map(); this._messageHandlers = new Map();
@ -201,7 +200,7 @@ class SearchDisplayController {
_onWanakanaEnableChange(e) { _onWanakanaEnableChange(e) {
const value = e.target.checked; const value = e.target.checked;
this._setWanakanaEnabled(value); this._setWanakanaEnabled(value);
api.modifySettings([{ yomichan.api.modifySettings([{
action: 'set', action: 'set',
path: 'general.enableWanakana', path: 'general.enableWanakana',
value, value,
@ -301,7 +300,7 @@ class SearchDisplayController {
if (!modify) { return; } if (!modify) { return; }
await api.modifySettings([{ await yomichan.api.modifySettings([{
action: 'set', action: 'set',
path: 'clipboard.enableSearchPageMonitor', path: 'clipboard.enableSearchPageMonitor',
value, value,

View File

@ -21,7 +21,6 @@
* HotkeyHandler * HotkeyHandler
* JapaneseUtil * JapaneseUtil
* SearchDisplayController * SearchDisplayController
* api
* wanakana * wanakana
*/ */
@ -30,10 +29,9 @@
const documentFocusController = new DocumentFocusController(); const documentFocusController = new DocumentFocusController();
documentFocusController.prepare(); documentFocusController.prepare();
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
const {tabId, frameId} = await api.frameInformationGet(); const {tabId, frameId} = await yomichan.api.frameInformationGet();
const japaneseUtil = new JapaneseUtil(wanakana); const japaneseUtil = new JapaneseUtil(wanakana);

View File

@ -17,7 +17,6 @@
/* global /* global
* DocumentUtil * DocumentUtil
* api
*/ */
/** /**
@ -59,7 +58,7 @@ class HotkeyHandler extends EventDispatcher {
prepare() { prepare() {
this._isPrepared = true; this._isPrepared = true;
this._updateEventHandlers(); this._updateEventHandlers();
api.crossFrame.registerHandlers([ yomichan.crossFrame.registerHandlers([
['hotkeyHandler.forwardHotkey', {async: false, handler: this._onMessageForwardHotkey.bind(this)}] ['hotkeyHandler.forwardHotkey', {async: false, handler: this._onMessageForwardHotkey.bind(this)}]
]); ]);
} }
@ -259,7 +258,7 @@ class HotkeyHandler extends EventDispatcher {
const frameId = this._forwardFrameId; const frameId = this._forwardFrameId;
if (frameId === null) { throw new Error('No forwarding target'); } if (frameId === null) { throw new Error('No forwarding target'); }
try { try {
await api.crossFrame.invoke(frameId, 'hotkeyHandler.forwardHotkey', {key, modifiers}); await yomichan.crossFrame.invoke(frameId, 'hotkeyHandler.forwardHotkey', {key, modifiers});
} catch (e) { } catch (e) {
// NOP // NOP
} }

View File

@ -17,7 +17,6 @@
/* global /* global
* HotkeyUtil * HotkeyUtil
* api
*/ */
class HotkeyHelpController { class HotkeyHelpController {
@ -29,7 +28,7 @@ class HotkeyHelpController {
} }
async prepare() { async prepare() {
const {platform: {os}} = await api.getEnvironmentInfo(); const {platform: {os}} = await yomichan.api.getEnvironmentInfo();
this._hotkeyUtil.os = os; this._hotkeyUtil.os = os;
await this._setupGlobalCommands(this._globalActionHotkeys); await this._setupGlobalCommands(this._globalActionHotkeys);
} }

View File

@ -17,7 +17,6 @@
/* global /* global
* DocumentUtil * DocumentUtil
* api
*/ */
class TextScanner extends EventDispatcher { class TextScanner extends EventDispatcher {
@ -762,7 +761,7 @@ class TextScanner extends EventDispatcher {
const searchText = this.getTextSourceContent(textSource, scanLength, layoutAwareScan); const searchText = this.getTextSourceContent(textSource, scanLength, layoutAwareScan);
if (searchText.length === 0) { return null; } if (searchText.length === 0) { return null; }
const {definitions, length} = await api.termsFind(searchText, {}, optionsContext); const {definitions, length} = await yomichan.api.termsFind(searchText, {}, optionsContext);
if (definitions.length === 0) { return null; } if (definitions.length === 0) { return null; }
textSource.setEndOffset(length, layoutAwareScan); textSource.setEndOffset(length, layoutAwareScan);
@ -787,7 +786,7 @@ class TextScanner extends EventDispatcher {
const searchText = this.getTextSourceContent(textSource, 1, layoutAwareScan); const searchText = this.getTextSourceContent(textSource, 1, layoutAwareScan);
if (searchText.length === 0) { return null; } if (searchText.length === 0) { return null; }
const definitions = await api.kanjiFind(searchText, optionsContext); const definitions = await yomichan.api.kanjiFind(searchText, optionsContext);
if (definitions.length === 0) { return null; } if (definitions.length === 0) { return null; }
textSource.setEndOffset(1, layoutAwareScan); textSource.setEndOffset(1, layoutAwareScan);

View File

@ -15,10 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* global
* api
*/
class MediaLoader { class MediaLoader {
constructor() { constructor() {
this._token = {}; this._token = {};
@ -84,7 +80,7 @@ class MediaLoader {
async _getMediaData(path, dictionaryName, cachedData) { async _getMediaData(path, dictionaryName, cachedData) {
const token = this._token; const token = this._token;
const data = (await api.getMedia([{path, dictionaryName}]))[0]; const data = (await yomichan.api.getMedia([{path, dictionaryName}]))[0];
if (token === this._token && data !== null) { if (token === this._token && data !== null) {
const contentArrayBuffer = this._base64ToArrayBuffer(data.content); const contentArrayBuffer = this._base64ToArrayBuffer(data.content);
const blob = new Blob([contentArrayBuffer], {type: data.mediaType}); const blob = new Blob([contentArrayBuffer], {type: data.mediaType});

View File

@ -18,7 +18,6 @@
/* global /* global
* HotkeyHelpController * HotkeyHelpController
* PermissionsUtil * PermissionsUtil
* api
*/ */
class DisplayController { class DisplayController {
@ -35,7 +34,7 @@ class DisplayController {
this._setupButtonEvents('.action-open-search', 'openSearchPage', chrome.runtime.getURL('/search.html')); this._setupButtonEvents('.action-open-search', 'openSearchPage', chrome.runtime.getURL('/search.html'));
this._setupButtonEvents('.action-open-info', 'openInfoPage', chrome.runtime.getURL('/info.html')); this._setupButtonEvents('.action-open-info', 'openInfoPage', chrome.runtime.getURL('/info.html'));
const optionsFull = await api.optionsGetFull(); const optionsFull = await yomichan.api.optionsGetFull();
this._optionsFull = optionsFull; this._optionsFull = optionsFull;
this._setupHotkeys(); this._setupHotkeys();
@ -74,12 +73,12 @@ class DisplayController {
if (typeof command === 'string') { if (typeof command === 'string') {
node.addEventListener('click', (e) => { node.addEventListener('click', (e) => {
if (e.button !== 0) { return; } if (e.button !== 0) { return; }
api.commandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'}); yomichan.api.commandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'});
e.preventDefault(); e.preventDefault();
}, false); }, false);
node.addEventListener('auxclick', (e) => { node.addEventListener('auxclick', (e) => {
if (e.button !== 1) { return; } if (e.button !== 1) { return; }
api.commandExec(command, {mode: 'newTab'}); yomichan.api.commandExec(command, {mode: 'newTab'});
e.preventDefault(); e.preventDefault();
}, false); }, false);
} }
@ -130,7 +129,7 @@ class DisplayController {
_setupOptions({options}) { _setupOptions({options}) {
const extensionEnabled = options.general.enable; const extensionEnabled = options.general.enable;
const onToggleChanged = () => api.commandExec('toggleTextScanning'); const onToggleChanged = () => yomichan.api.commandExec('toggleTextScanning');
for (const toggle of document.querySelectorAll('#enable-search,#enable-search2')) { for (const toggle of document.querySelectorAll('#enable-search,#enable-search2')) {
toggle.checked = extensionEnabled; toggle.checked = extensionEnabled;
toggle.addEventListener('change', onToggleChanged, false); toggle.addEventListener('change', onToggleChanged, false);
@ -178,7 +177,7 @@ class DisplayController {
} }
async _setPrimaryProfileIndex(value) { async _setPrimaryProfileIndex(value) {
return await api.modifySettings( return await yomichan.api.modifySettings(
[{ [{
action: 'set', action: 'set',
path: 'profileCurrent', path: 'profileCurrent',
@ -190,7 +189,7 @@ class DisplayController {
async _updateDictionariesEnabledWarnings(options) { async _updateDictionariesEnabledWarnings(options) {
const noDictionariesEnabledWarnings = document.querySelectorAll('.no-dictionaries-enabled-warning'); const noDictionariesEnabledWarnings = document.querySelectorAll('.no-dictionaries-enabled-warning');
const dictionaries = await api.getDictionaryInfo(); const dictionaries = await yomichan.api.getDictionaryInfo();
let enabledCount = 0; let enabledCount = 0;
for (const {title} of dictionaries) { for (const {title} of dictionaries) {
@ -221,10 +220,9 @@ class DisplayController {
} }
(async () => { (async () => {
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
api.logIndicatorClear(); yomichan.api.logIndicatorClear();
const displayController = new DisplayController(); const displayController = new DisplayController();
displayController.prepare(); displayController.prepare();

View File

@ -19,7 +19,6 @@
* BackupController * BackupController
* DocumentFocusController * DocumentFocusController
* SettingsController * SettingsController
* api
*/ */
function getBrowserDisplayName(browser) { function getBrowserDisplayName(browser) {
@ -54,12 +53,11 @@ function getOperatingSystemDisplayName(os) {
const manifest = chrome.runtime.getManifest(); const manifest = chrome.runtime.getManifest();
const language = chrome.i18n.getUILanguage(); const language = chrome.i18n.getUILanguage();
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
const {userAgent} = navigator; const {userAgent} = navigator;
const {name, version} = manifest; const {name, version} = manifest;
const {browser, platform: {os}} = await api.getEnvironmentInfo(); const {browser, platform: {os}} = await yomichan.api.getEnvironmentInfo();
const thisVersionLink = document.querySelector('#release-notes-this-version-link'); const thisVersionLink = document.querySelector('#release-notes-this-version-link');
thisVersionLink.href = thisVersionLink.dataset.hrefFormat.replace(/\{version\}/g, version); thisVersionLink.href = thisVersionLink.dataset.hrefFormat.replace(/\{version\}/g, version);
@ -73,7 +71,7 @@ function getOperatingSystemDisplayName(os) {
(async () => { (async () => {
let ankiConnectVersion = null; let ankiConnectVersion = null;
try { try {
ankiConnectVersion = await api.getAnkiConnectVersion(); ankiConnectVersion = await yomichan.api.getAnkiConnectVersion();
} catch (e) { } catch (e) {
// NOP // NOP
} }
@ -86,7 +84,7 @@ function getOperatingSystemDisplayName(os) {
(async () => { (async () => {
let dictionaryInfos; let dictionaryInfos;
try { try {
dictionaryInfos = await api.getDictionaryInfo(); dictionaryInfos = await yomichan.api.getDictionaryInfo();
} catch (e) { } catch (e) {
return; return;
} }

View File

@ -19,12 +19,11 @@
* DocumentFocusController * DocumentFocusController
* PermissionsToggleController * PermissionsToggleController
* SettingsController * SettingsController
* api
*/ */
async function setupEnvironmentInfo() { async function setupEnvironmentInfo() {
const {manifest_version: manifestVersion} = chrome.runtime.getManifest(); const {manifest_version: manifestVersion} = chrome.runtime.getManifest();
const {browser, platform} = await api.getEnvironmentInfo(); const {browser, platform} = await yomichan.api.getEnvironmentInfo();
document.documentElement.dataset.browser = browser; document.documentElement.dataset.browser = browser;
document.documentElement.dataset.os = platform.os; document.documentElement.dataset.os = platform.os;
document.documentElement.dataset.manifestVersion = `${manifestVersion}`; document.documentElement.dataset.manifestVersion = `${manifestVersion}`;
@ -69,7 +68,6 @@ function setupPermissionsToggles() {
node.textContent = chrome.runtime.getURL('/'); node.textContent = chrome.runtime.getURL('/');
} }
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
setupEnvironmentInfo(); setupEnvironmentInfo();

View File

@ -25,12 +25,11 @@
* SettingsController * SettingsController
* SettingsDisplayController * SettingsDisplayController
* StatusFooter * StatusFooter
* api
*/ */
async function setupEnvironmentInfo() { async function setupEnvironmentInfo() {
const {manifest_version: manifestVersion} = chrome.runtime.getManifest(); const {manifest_version: manifestVersion} = chrome.runtime.getManifest();
const {browser, platform} = await api.getEnvironmentInfo(); const {browser, platform} = await yomichan.api.getEnvironmentInfo();
document.documentElement.dataset.browser = browser; document.documentElement.dataset.browser = browser;
document.documentElement.dataset.os = platform.os; document.documentElement.dataset.os = platform.os;
document.documentElement.dataset.manifestVersion = `${manifestVersion}`; document.documentElement.dataset.manifestVersion = `${manifestVersion}`;
@ -49,12 +48,11 @@ async function setupGenericSettingsController(genericSettingController) {
const statusFooter = new StatusFooter(document.querySelector('.status-footer-container')); const statusFooter = new StatusFooter(document.querySelector('.status-footer-container'));
statusFooter.prepare(); statusFooter.prepare();
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
setupEnvironmentInfo(); setupEnvironmentInfo();
const optionsFull = await api.optionsGetFull(); const optionsFull = await yomichan.api.optionsGetFull();
const preparePromises = []; const preparePromises = [];

View File

@ -15,10 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* global
* api
*/
const dynamicLoader = (() => { const dynamicLoader = (() => {
const injectedStylesheets = new Map(); const injectedStylesheets = new Map();
const injectedStylesheetsWithParent = new WeakMap(); const injectedStylesheetsWithParent = new WeakMap();
@ -61,7 +57,7 @@ const dynamicLoader = (() => {
} }
if (type === 'file-content') { if (type === 'file-content') {
value = await api.getStylesheetContent(value); value = await yomichan.api.getStylesheetContent(value);
type = 'code'; type = 'code';
useWebExtensionApi = false; useWebExtensionApi = false;
} }
@ -73,7 +69,7 @@ const dynamicLoader = (() => {
} }
setInjectedStylesheet(id, parentNode, null); setInjectedStylesheet(id, parentNode, null);
await api.injectStylesheet(type, value); await yomichan.api.injectStylesheet(type, value);
return null; return null;
} }

View File

@ -17,7 +17,6 @@
/* global /* global
* AnkiNoteBuilder * AnkiNoteBuilder
* api
*/ */
class AnkiTemplatesController { class AnkiTemplatesController {
@ -37,7 +36,7 @@ class AnkiTemplatesController {
} }
async prepare() { async prepare() {
this._defaultFieldTemplates = await api.getDefaultAnkiFieldTemplates(); this._defaultFieldTemplates = await yomichan.api.getDefaultAnkiFieldTemplates();
this._fieldTemplatesTextarea = document.querySelector('#anki-card-templates-textarea'); this._fieldTemplatesTextarea = document.querySelector('#anki-card-templates-textarea');
this._compileResultInfo = document.querySelector('#anki-card-templates-compile-result'); this._compileResultInfo = document.querySelector('#anki-card-templates-compile-result');
@ -154,7 +153,7 @@ class AnkiTemplatesController {
async _getDefinition(text, optionsContext) { async _getDefinition(text, optionsContext) {
if (this._cachedDefinitionText !== text) { if (this._cachedDefinitionText !== text) {
const {definitions} = await api.termsFind(text, {}, optionsContext); const {definitions} = await yomichan.api.termsFind(text, {}, optionsContext);
if (definitions.length === 0) { return null; } if (definitions.length === 0) { return null; }
this._cachedDefinitionValue = definitions[0]; this._cachedDefinitionValue = definitions[0];

View File

@ -18,7 +18,6 @@
/* global /* global
* DictionaryController * DictionaryController
* OptionsUtil * OptionsUtil
* api
*/ */
class BackupController { class BackupController {
@ -85,8 +84,8 @@ class BackupController {
async _getSettingsExportData(date) { async _getSettingsExportData(date) {
const optionsFull = await this._settingsController.getOptionsFull(); const optionsFull = await this._settingsController.getOptionsFull();
const environment = await api.getEnvironmentInfo(); const environment = await yomichan.api.getEnvironmentInfo();
const fieldTemplatesDefault = await api.getDefaultAnkiFieldTemplates(); const fieldTemplatesDefault = await yomichan.api.getDefaultAnkiFieldTemplates();
const permissions = await this._settingsController.permissionsUtil.getAllPermissions(); const permissions = await this._settingsController.permissionsUtil.getAllPermissions();
// Format options // Format options

View File

@ -18,7 +18,6 @@
/* global /* global
* DictionaryDatabase * DictionaryDatabase
* ObjectPropertyAccessor * ObjectPropertyAccessor
* api
*/ */
class DictionaryEntry { class DictionaryEntry {
@ -362,7 +361,7 @@ class DictionaryController {
const token = this._databaseStateToken; const token = this._databaseStateToken;
const dictionaryTitles = this._dictionaries.map(({title}) => title); const dictionaryTitles = this._dictionaries.map(({title}) => title);
const {counts, total} = await api.getDictionaryCounts(dictionaryTitles, true); const {counts, total} = await yomichan.api.getDictionaryCounts(dictionaryTitles, true);
if (this._databaseStateToken !== token) { return; } if (this._databaseStateToken !== token) { return; }
for (let i = 0, ii = Math.min(counts.length, this._dictionaryEntries.length); i < ii; ++i) { for (let i = 0, ii = Math.min(counts.length, this._dictionaryEntries.length); i < ii; ++i) {
@ -499,7 +498,7 @@ class DictionaryController {
const dictionaryDatabase = await this._getPreparedDictionaryDatabase(); const dictionaryDatabase = await this._getPreparedDictionaryDatabase();
try { try {
await dictionaryDatabase.deleteDictionary(dictionaryTitle, {rate: 1000}, onProgress); await dictionaryDatabase.deleteDictionary(dictionaryTitle, {rate: 1000}, onProgress);
api.triggerDatabaseUpdated('dictionary', 'delete'); yomichan.api.triggerDatabaseUpdated('dictionary', 'delete');
} finally { } finally {
dictionaryDatabase.close(); dictionaryDatabase.close();
} }

View File

@ -19,7 +19,6 @@
* DictionaryDatabase * DictionaryDatabase
* DictionaryImporter * DictionaryImporter
* ObjectPropertyAccessor * ObjectPropertyAccessor
* api
*/ */
class DictionaryImportController { class DictionaryImportController {
@ -102,7 +101,7 @@ class DictionaryImportController {
this._setSpinnerVisible(true); this._setSpinnerVisible(true);
if (purgeNotification !== null) { purgeNotification.hidden = false; } if (purgeNotification !== null) { purgeNotification.hidden = false; }
await api.purgeDatabase(); await yomichan.api.purgeDatabase();
const errors = await this._clearDictionarySettings(); const errors = await this._clearDictionarySettings();
if (errors.length > 0) { if (errors.length > 0) {
@ -197,7 +196,7 @@ class DictionaryImportController {
const dictionaryImporter = new DictionaryImporter(); const dictionaryImporter = new DictionaryImporter();
const archiveContent = await this._readFile(file); const archiveContent = await this._readFile(file);
const {result, errors} = await dictionaryImporter.importDictionary(dictionaryDatabase, archiveContent, importDetails, onProgress); const {result, errors} = await dictionaryImporter.importDictionary(dictionaryDatabase, archiveContent, importDetails, onProgress);
api.triggerDatabaseUpdated('dictionary', 'import'); yomichan.api.triggerDatabaseUpdated('dictionary', 'import');
const errors2 = await this._addDictionarySettings(result.sequenced, result.title); const errors2 = await this._addDictionarySettings(result.sequenced, result.title);
if (errors.length > 0) { if (errors.length > 0) {

View File

@ -18,7 +18,6 @@
/* global /* global
* HotkeyUtil * HotkeyUtil
* KeyboardMouseInputField * KeyboardMouseInputField
* api
*/ */
class ExtensionKeyboardShortcutController { class ExtensionKeyboardShortcutController {
@ -53,7 +52,7 @@ class ExtensionKeyboardShortcutController {
this._clearButton.addEventListener('click', this._onClearClick.bind(this)); this._clearButton.addEventListener('click', this._onClearClick.bind(this));
} }
const {platform: {os}} = await api.getEnvironmentInfo(); const {platform: {os}} = await yomichan.api.getEnvironmentInfo();
this._os = os; this._os = os;
this._hotkeyUtil.os = os; this._hotkeyUtil.os = os;

View File

@ -17,7 +17,6 @@
/* global /* global
* KeyboardMouseInputField * KeyboardMouseInputField
* api
*/ */
class KeyboardShortcutController { class KeyboardShortcutController {
@ -38,7 +37,7 @@ class KeyboardShortcutController {
} }
async prepare() { async prepare() {
const {platform: {os}} = await api.getEnvironmentInfo(); const {platform: {os}} = await yomichan.api.getEnvironmentInfo();
this._os = os; this._os = os;
this._addButton = document.querySelector('#hotkey-list-add'); this._addButton = document.querySelector('#hotkey-list-add');

View File

@ -31,7 +31,6 @@
* ScanInputsSimpleController * ScanInputsSimpleController
* SettingsController * SettingsController
* StorageController * StorageController
* api
*/ */
function showExtensionInformation() { function showExtensionInformation() {
@ -43,7 +42,7 @@ function showExtensionInformation() {
} }
async function setupEnvironmentInfo() { async function setupEnvironmentInfo() {
const {browser, platform} = await api.getEnvironmentInfo(); const {browser, platform} = await yomichan.api.getEnvironmentInfo();
document.documentElement.dataset.browser = browser; document.documentElement.dataset.browser = browser;
document.documentElement.dataset.operatingSystem = platform.os; document.documentElement.dataset.operatingSystem = platform.os;
} }
@ -51,13 +50,12 @@ async function setupEnvironmentInfo() {
(async () => { (async () => {
try { try {
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
setupEnvironmentInfo(); setupEnvironmentInfo();
showExtensionInformation(); showExtensionInformation();
const optionsFull = await api.optionsGetFull(); const optionsFull = await yomichan.api.optionsGetFull();
const modalController = new ModalController(); const modalController = new ModalController();
modalController.prepare(); modalController.prepare();

View File

@ -15,10 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* global
* api
*/
class MecabController { class MecabController {
constructor(settingsController) { constructor(settingsController) {
this._settingsController = settingsController; this._settingsController = settingsController;
@ -49,7 +45,7 @@ class MecabController {
this._testButton.disabled = true; this._testButton.disabled = true;
this._resultsContainer.textContent = ''; this._resultsContainer.textContent = '';
this._resultsContainer.hidden = true; this._resultsContainer.hidden = true;
await api.testMecab(); await yomichan.api.testMecab();
this._setStatus('Connection was successful', false); this._setStatus('Connection was successful', false);
} catch (e) { } catch (e) {
this._setStatus(e.message, true); this._setStatus(e.message, true);

View File

@ -17,12 +17,10 @@
/* global /* global
* DisplayGenerator * DisplayGenerator
* api
*/ */
(async () => { (async () => {
try { try {
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
const displayGenerator = new DisplayGenerator({ const displayGenerator = new DisplayGenerator({

View File

@ -19,15 +19,13 @@
* HotkeyHandler * HotkeyHandler
* PopupFactory * PopupFactory
* PopupPreviewFrame * PopupPreviewFrame
* api
*/ */
(async () => { (async () => {
try { try {
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
const {tabId, frameId} = await api.frameInformationGet(); const {tabId, frameId} = await yomichan.api.frameInformationGet();
const hotkeyHandler = new HotkeyHandler(); const hotkeyHandler = new HotkeyHandler();
hotkeyHandler.prepare(); hotkeyHandler.prepare();

View File

@ -18,7 +18,6 @@
/* global /* global
* Frontend * Frontend
* TextSourceRange * TextSourceRange
* api
* wanakana * wanakana
*/ */
@ -63,8 +62,8 @@ class PopupPreviewFrame {
this._exampleTextInput.addEventListener('input', this._onExampleTextInputInput.bind(this), false); this._exampleTextInput.addEventListener('input', this._onExampleTextInputInput.bind(this), false);
// Overwrite API functions // Overwrite API functions
this._apiOptionsGetOld = api.optionsGet.bind(api); this._apiOptionsGetOld = yomichan.api.optionsGet.bind(yomichan.api);
api.optionsGet = this._apiOptionsGet.bind(this); yomichan.api.optionsGet = this._apiOptionsGet.bind(this);
// Overwrite frontend // Overwrite frontend
this._frontend = new Frontend({ this._frontend = new Frontend({

View File

@ -15,10 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* global
* api
*/
class PopupWindowController { class PopupWindowController {
prepare() { prepare() {
const testLink = document.querySelector('#test-window-open-link'); const testLink = document.querySelector('#test-window-open-link');
@ -33,6 +29,6 @@ class PopupWindowController {
} }
async _testWindowOpen() { async _testWindowOpen() {
await api.getOrCreateSearchPopup({focus: true}); await yomichan.api.getOrCreateSearchPopup({focus: true});
} }
} }

View File

@ -17,7 +17,6 @@
/* global /* global
* ProfileConditionsUI * ProfileConditionsUI
* api
*/ */
class ProfileController { class ProfileController {
@ -58,7 +57,7 @@ class ProfileController {
} }
async prepare() { async prepare() {
const {platform: {os}} = await api.getEnvironmentInfo(); const {platform: {os}} = await yomichan.api.getEnvironmentInfo();
this._profileConditionsUI.os = os; this._profileConditionsUI.os = os;
this._profileActiveSelect = document.querySelector('#profile-active-select'); this._profileActiveSelect = document.querySelector('#profile-active-select');

View File

@ -17,7 +17,6 @@
/* global /* global
* KeyboardMouseInputField * KeyboardMouseInputField
* api
*/ */
class ScanInputsController { class ScanInputsController {
@ -31,7 +30,7 @@ class ScanInputsController {
} }
async prepare() { async prepare() {
const {platform: {os}} = await api.getEnvironmentInfo(); const {platform: {os}} = await yomichan.api.getEnvironmentInfo();
this._os = os; this._os = os;
this._container = document.querySelector('#scan-input-list'); this._container = document.querySelector('#scan-input-list');

View File

@ -18,7 +18,6 @@
/* global /* global
* HotkeyUtil * HotkeyUtil
* ScanInputsController * ScanInputsController
* api
*/ */
class ScanInputsSimpleController { class ScanInputsSimpleController {
@ -34,7 +33,7 @@ class ScanInputsSimpleController {
this._middleMouseButtonScan = document.querySelector('#middle-mouse-button-scan'); this._middleMouseButtonScan = document.querySelector('#middle-mouse-button-scan');
this._mainScanModifierKeyInput = document.querySelector('#main-scan-modifier-key'); this._mainScanModifierKeyInput = document.querySelector('#main-scan-modifier-key');
const {platform: {os}} = await api.getEnvironmentInfo(); const {platform: {os}} = await yomichan.api.getEnvironmentInfo();
this._hotkeyUtil.os = os; this._hotkeyUtil.os = os;
this._mainScanModifierKeyInputHasOther = false; this._mainScanModifierKeyInputHasOther = false;

View File

@ -19,7 +19,6 @@
* HtmlTemplateCollection * HtmlTemplateCollection
* OptionsUtil * OptionsUtil
* PermissionsUtil * PermissionsUtil
* api
*/ */
class SettingsController extends EventDispatcher { class SettingsController extends EventDispatcher {
@ -62,16 +61,16 @@ class SettingsController extends EventDispatcher {
async getOptions() { async getOptions() {
const optionsContext = this.getOptionsContext(); const optionsContext = this.getOptionsContext();
return await api.optionsGet(optionsContext); return await yomichan.api.optionsGet(optionsContext);
} }
async getOptionsFull() { async getOptionsFull() {
return await api.optionsGetFull(); return await yomichan.api.optionsGetFull();
} }
async setAllSettings(value) { async setAllSettings(value) {
const profileIndex = value.profileCurrent; const profileIndex = value.profileCurrent;
await api.setAllSettings(value, this._source); await yomichan.api.setAllSettings(value, this._source);
this._setProfileIndex(profileIndex); this._setProfileIndex(profileIndex);
} }
@ -108,7 +107,7 @@ class SettingsController extends EventDispatcher {
} }
async getDictionaryInfo() { async getDictionaryInfo() {
return await api.getDictionaryInfo(); return await yomichan.api.getDictionaryInfo();
} }
getOptionsContext() { getOptionsContext() {
@ -171,12 +170,12 @@ class SettingsController extends EventDispatcher {
async _getSettings(targets, extraFields) { async _getSettings(targets, extraFields) {
targets = this._setupTargets(targets, extraFields); targets = this._setupTargets(targets, extraFields);
return await api.getSettings(targets); return await yomichan.api.getSettings(targets);
} }
async _modifySettings(targets, extraFields) { async _modifySettings(targets, extraFields) {
targets = this._setupTargets(targets, extraFields); targets = this._setupTargets(targets, extraFields);
return await api.modifySettings(targets, this._source); return await yomichan.api.modifySettings(targets, this._source);
} }
_onBeforeUnload(e) { _onBeforeUnload(e) {

View File

@ -42,12 +42,11 @@
* StatusFooter * StatusFooter
* StorageController * StorageController
* TranslationTextReplacementsController * TranslationTextReplacementsController
* api
*/ */
async function setupEnvironmentInfo() { async function setupEnvironmentInfo() {
const {manifest_version: manifestVersion} = chrome.runtime.getManifest(); const {manifest_version: manifestVersion} = chrome.runtime.getManifest();
const {browser, platform} = await api.getEnvironmentInfo(); const {browser, platform} = await yomichan.api.getEnvironmentInfo();
document.documentElement.dataset.browser = browser; document.documentElement.dataset.browser = browser;
document.documentElement.dataset.os = platform.os; document.documentElement.dataset.os = platform.os;
document.documentElement.dataset.manifestVersion = `${manifestVersion}`; document.documentElement.dataset.manifestVersion = `${manifestVersion}`;
@ -66,12 +65,11 @@ async function setupGenericSettingsController(genericSettingController) {
const statusFooter = new StatusFooter(document.querySelector('.status-footer-container')); const statusFooter = new StatusFooter(document.querySelector('.status-footer-container'));
statusFooter.prepare(); statusFooter.prepare();
api.prepare();
await yomichan.prepare(); await yomichan.prepare();
setupEnvironmentInfo(); setupEnvironmentInfo();
const optionsFull = await api.optionsGetFull(); const optionsFull = await yomichan.api.optionsGetFull();
const preparePromises = []; const preparePromises = [];

View File

@ -28,6 +28,6 @@
templateRenderer.registerDataType('ankiNote', { templateRenderer.registerDataType('ankiNote', {
modifier: ({data, marker}) => new AnkiNoteData(data, marker).createPublic() modifier: ({data, marker}) => new AnkiNoteData(data, marker).createPublic()
}); });
const api = new TemplateRendererFrameApi(templateRenderer); const templateRendererFrameApi = new TemplateRendererFrameApi(templateRenderer);
api.prepare(); templateRendererFrameApi.prepare();
})(); })();

View File

@ -15,6 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
/* global
* API
* CrossFrameAPI
*/
// Set up chrome alias if it's not available (Edge Legacy) // Set up chrome alias if it's not available (Edge Legacy)
if ((() => { if ((() => {
let hasChrome = false; let hasChrome = false;
@ -34,271 +39,300 @@ if ((() => {
chrome = browser; chrome = browser;
} }
const yomichan = (() => { class Yomichan extends EventDispatcher {
class Yomichan extends EventDispatcher { constructor() {
constructor() { super();
super();
this._extensionName = 'Yomichan'; this._extensionName = 'Yomichan';
try { try {
const manifest = chrome.runtime.getManifest(); const manifest = chrome.runtime.getManifest();
this._extensionName = `${manifest.name} v${manifest.version}`; this._extensionName = `${manifest.name} v${manifest.version}`;
} catch (e) { } catch (e) {
// NOP // NOP
}
this._isExtensionUnloaded = false;
this._isTriggeringExtensionUnloaded = false;
this._isReady = false;
const {promise, resolve} = deferPromise();
this._isBackendReadyPromise = promise;
this._isBackendReadyPromiseResolve = resolve;
this._messageHandlers = new Map([
['isReady', {async: false, handler: this._onMessageIsReady.bind(this)}],
['backendReady', {async: false, handler: this._onMessageBackendReady.bind(this)}],
['getUrl', {async: false, handler: this._onMessageGetUrl.bind(this)}],
['optionsUpdated', {async: false, handler: this._onMessageOptionsUpdated.bind(this)}],
['databaseUpdated', {async: false, handler: this._onMessageDatabaseUpdated.bind(this)}],
['zoomChanged', {async: false, handler: this._onMessageZoomChanged.bind(this)}]
]);
} }
// Public this._isBackground = null;
this._api = null;
this._crossFrame = null;
this._isExtensionUnloaded = false;
this._isTriggeringExtensionUnloaded = false;
this._isReady = false;
get isExtensionUnloaded() { const {promise, resolve} = deferPromise();
return this._isExtensionUnloaded; this._isBackendReadyPromise = promise;
} this._isBackendReadyPromiseResolve = resolve;
async prepare(isBackground=false) { this._messageHandlers = new Map([
chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); ['isReady', {async: false, handler: this._onMessageIsReady.bind(this)}],
['backendReady', {async: false, handler: this._onMessageBackendReady.bind(this)}],
['getUrl', {async: false, handler: this._onMessageGetUrl.bind(this)}],
['optionsUpdated', {async: false, handler: this._onMessageOptionsUpdated.bind(this)}],
['databaseUpdated', {async: false, handler: this._onMessageDatabaseUpdated.bind(this)}],
['zoomChanged', {async: false, handler: this._onMessageZoomChanged.bind(this)}]
]);
}
if (!isBackground) { // Public
this.sendMessage({action: 'requestBackendReadySignal'});
await this._isBackendReadyPromise;
}
}
ready() { get isBackground() {
this._isReady = true; return this._isBackground;
this.sendMessage({action: 'yomichanReady'}); }
}
isExtensionUrl(url) { get isExtensionUnloaded() {
try { return this._isExtensionUnloaded;
return url.startsWith(chrome.runtime.getURL('/')); }
} catch (e) {
return false;
}
}
getTemporaryListenerResult(eventHandler, userCallback, timeout=null) { get api() {
if (!( return this._api;
typeof eventHandler.addListener === 'function' && }
typeof eventHandler.removeListener === 'function'
)) {
throw new Error('Event handler type not supported');
}
return new Promise((resolve, reject) => { get crossFrame() {
const runtimeMessageCallback = ({action, params}, sender, sendResponse) => { return this._crossFrame;
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) => { async prepare(isBackground=false) {
if (timeoutId !== null) { this._isBackground = isBackground;
clearTimeout(timeoutId); chrome.runtime.onMessage.addListener(this._onMessage.bind(this));
timeoutId = null;
}
eventHandler.removeListener(runtimeMessageCallback);
sendResponse();
resolve(value);
};
userCallback({action, params}, {resolve: cleanupResolve, sender}); if (!isBackground) {
}; this._api = new API(this);
eventHandler.addListener(runtimeMessageCallback); this._crossFrame = new CrossFrameAPI();
}); this._crossFrame.prepare();
}
logWarning(error) { this.sendMessage({action: 'requestBackendReadySignal'});
this.log(error, 'warn'); await this._isBackendReadyPromise;
}
logError(error) { this.on('log', this._onForwardLog.bind(this));
this.log(error, 'error');
}
log(error, level, context=null) {
if (!isObject(context)) {
context = this._getLogContext();
}
let errorString;
try {
errorString = error.toString();
if (/^\[object \w+\]$/.test(errorString)) {
errorString = JSON.stringify(error);
}
} catch (e) {
errorString = `${error}`;
}
let errorStack;
try {
errorStack = (typeof error.stack === 'string' ? error.stack.trimRight() : '');
} catch (e) {
errorStack = '';
}
let errorData;
try {
errorData = error.data;
} catch (e) {
// NOP
}
if (errorStack.startsWith(errorString)) {
errorString = errorStack;
} else if (errorStack.length > 0) {
errorString += `\n${errorStack}`;
}
let message = `${this._extensionName} has encountered a problem.`;
message += `\nOriginating URL: ${context.url}\n`;
message += errorString;
if (typeof errorData !== 'undefined') {
message += `\nData: ${JSON.stringify(errorData, null, 4)}`;
}
message += '\n\nIssues can be reported at https://github.com/FooSoft/yomichan/issues';
switch (level) {
case 'info': console.info(message); break;
case 'debug': console.debug(message); break;
case 'warn': console.warn(message); break;
case 'error': console.error(message); break;
default: console.log(message); break;
}
this.trigger('log', {error, level, context});
}
sendMessage(...args) {
try {
return chrome.runtime.sendMessage(...args);
} catch (e) {
this.triggerExtensionUnloaded();
throw e;
}
}
connect(...args) {
try {
return chrome.runtime.connect(...args);
} catch (e) {
this.triggerExtensionUnloaded();
throw e;
}
}
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; }
try {
this._isTriggeringExtensionUnloaded = true;
this.trigger('extensionUnloaded');
} finally {
this._isTriggeringExtensionUnloaded = false;
}
}
// Private
_getUrl() {
return location.href;
}
_getLogContext() {
return {url: this._getUrl()};
}
_onMessage({action, params}, sender, callback) {
const messageHandler = this._messageHandlers.get(action);
if (typeof messageHandler === 'undefined') { return false; }
return this.invokeMessageHandler(messageHandler, params, callback, sender);
}
_onMessageIsReady() {
return this._isReady;
}
_onMessageBackendReady() {
if (this._isBackendReadyPromiseResolve === null) { return; }
this._isBackendReadyPromiseResolve();
this._isBackendReadyPromiseResolve = null;
}
_onMessageGetUrl() {
return {url: this._getUrl()};
}
_onMessageOptionsUpdated({source}) {
this.trigger('optionsUpdated', {source});
}
_onMessageDatabaseUpdated({type, cause}) {
this.trigger('databaseUpdated', {type, cause});
}
_onMessageZoomChanged({oldZoomFactor, newZoomFactor}) {
this.trigger('zoomChanged', {oldZoomFactor, newZoomFactor});
} }
} }
return new Yomichan(); ready() {
})(); this._isReady = true;
this.sendMessage({action: 'yomichanReady'});
}
isExtensionUrl(url) {
try {
return url.startsWith(chrome.runtime.getURL('/'));
} catch (e) {
return false;
}
}
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);
});
}
logWarning(error) {
this.log(error, 'warn');
}
logError(error) {
this.log(error, 'error');
}
log(error, level, context=null) {
if (!isObject(context)) {
context = this._getLogContext();
}
let errorString;
try {
errorString = error.toString();
if (/^\[object \w+\]$/.test(errorString)) {
errorString = JSON.stringify(error);
}
} catch (e) {
errorString = `${error}`;
}
let errorStack;
try {
errorStack = (typeof error.stack === 'string' ? error.stack.trimRight() : '');
} catch (e) {
errorStack = '';
}
let errorData;
try {
errorData = error.data;
} catch (e) {
// NOP
}
if (errorStack.startsWith(errorString)) {
errorString = errorStack;
} else if (errorStack.length > 0) {
errorString += `\n${errorStack}`;
}
let message = `${this._extensionName} has encountered a problem.`;
message += `\nOriginating URL: ${context.url}\n`;
message += errorString;
if (typeof errorData !== 'undefined') {
message += `\nData: ${JSON.stringify(errorData, null, 4)}`;
}
message += '\n\nIssues can be reported at https://github.com/FooSoft/yomichan/issues';
switch (level) {
case 'info': console.info(message); break;
case 'debug': console.debug(message); break;
case 'warn': console.warn(message); break;
case 'error': console.error(message); break;
default: console.log(message); break;
}
this.trigger('log', {error, level, context});
}
sendMessage(...args) {
try {
return chrome.runtime.sendMessage(...args);
} catch (e) {
this.triggerExtensionUnloaded();
throw e;
}
}
connect(...args) {
try {
return chrome.runtime.connect(...args);
} catch (e) {
this.triggerExtensionUnloaded();
throw e;
}
}
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; }
try {
this._isTriggeringExtensionUnloaded = true;
this.trigger('extensionUnloaded');
} finally {
this._isTriggeringExtensionUnloaded = false;
}
}
// Private
_getUrl() {
return location.href;
}
_getLogContext() {
return {url: this._getUrl()};
}
_onMessage({action, params}, sender, callback) {
const messageHandler = this._messageHandlers.get(action);
if (typeof messageHandler === 'undefined') { return false; }
return this.invokeMessageHandler(messageHandler, params, callback, sender);
}
_onMessageIsReady() {
return this._isReady;
}
_onMessageBackendReady() {
if (this._isBackendReadyPromiseResolve === null) { return; }
this._isBackendReadyPromiseResolve();
this._isBackendReadyPromiseResolve = null;
}
_onMessageGetUrl() {
return {url: this._getUrl()};
}
_onMessageOptionsUpdated({source}) {
this.trigger('optionsUpdated', {source});
}
_onMessageDatabaseUpdated({type, cause}) {
this.trigger('databaseUpdated', {type, cause});
}
_onMessageZoomChanged({oldZoomFactor, newZoomFactor}) {
this.trigger('zoomChanged', {oldZoomFactor, newZoomFactor});
}
async _onForwardLog({error, level, context}) {
try {
await this._api.log(serializeError(error), level, context);
} catch (e) {
// NOP
}
}
}
const yomichan = new Yomichan();