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
* HotkeyHandler
* PopupFactory
* api
*/
(async () => {
try {
api.prepare();
await yomichan.prepare();
const {tabId, frameId} = await api.frameInformationGet();
const {tabId, frameId} = await yomichan.api.frameInformationGet();
if (typeof frameId !== 'number') {
throw new Error('Failed to get frameId');
}

View File

@ -20,7 +20,6 @@
* TextScanner
* TextSourceElement
* TextSourceRange
* api
*/
class Frontend {
@ -99,7 +98,7 @@ class Frontend {
async prepare() {
await this.updateOptions();
try {
const {zoomFactor} = await api.getZoom();
const {zoomFactor} = await yomichan.api.getZoom();
this._pageZoomFactor = zoomFactor;
} catch (e) {
// 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('searched', this._onSearched.bind(this));
api.crossFrame.registerHandlers([
yomichan.crossFrame.registerHandlers([
['closePopup', {async: false, handler: this._onApiClosePopup.bind(this)}],
['copySelection', {async: false, handler: this._onApiCopySelection.bind(this)}],
['getSelectionText', {async: false, handler: this._onApiGetSelectionText.bind(this)}],
@ -332,7 +331,7 @@ class Frontend {
async _updateOptionsInternal() {
const optionsContext = await this._getOptionsContext();
const options = await api.optionsGet(optionsContext);
const options = await yomichan.api.optionsGet(optionsContext);
const {scanning: scanningOptions, sentenceParsing: sentenceParsingOptions} = options;
this._options = options;
@ -462,7 +461,7 @@ class Frontend {
return await this._getDefaultPopup();
}
const {popupId} = await api.crossFrame.invoke(targetFrameId, 'getPopupInfo');
const {popupId} = await yomichan.crossFrame.invoke(targetFrameId, 'getPopupInfo');
if (popupId === null) {
return null;
}
@ -608,9 +607,9 @@ class Frontend {
_signalFrontendReady(targetFrameId=null) {
const params = {frameId: this._frameId};
if (targetFrameId === null) {
api.broadcastTab('frontendReady', params);
yomichan.api.broadcastTab('frontendReady', params);
} else {
api.sendMessageToFrame(targetFrameId, 'frontendReady', params);
yomichan.api.sendMessageToFrame(targetFrameId, 'frontendReady', params);
}
}
@ -627,7 +626,7 @@ class Frontend {
},
10000
);
api.broadcastTab('requestFrontendReadyBroadcast', {frameId: this._frameId});
yomichan.api.broadcastTab('requestFrontendReadyBroadcast', {frameId: this._frameId});
await promise;
}
@ -653,7 +652,7 @@ class Frontend {
let documentTitle = document.title;
if (this._useProxyPopup) {
try {
({url, documentTitle} = await api.crossFrame.invoke(this._parentFrameId, 'getPageInfo', {}));
({url, documentTitle} = await yomichan.crossFrame.invoke(this._parentFrameId, 'getPageInfo', {}));
} catch (e) {
// NOP
}

View File

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

View File

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

View File

@ -15,10 +15,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* global
* api
*/
class PopupWindow extends EventDispatcher {
constructor({
id,
@ -82,7 +78,7 @@ class PopupWindow extends EventDispatcher {
}
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) {
@ -148,7 +144,7 @@ class PopupWindow extends EventDispatcher {
const frameId = 0;
if (this._popupTabId !== null) {
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) {
if (yomichan.isExtensionUnloaded) {
open = false;
@ -161,9 +157,9 @@ class PopupWindow extends EventDispatcher {
return defaultReturnValue;
}
const {tabId} = await api.getOrCreateSearchPopup({focus: 'ifCreated'});
const {tabId} = await yomichan.api.getOrCreateSearchPopup({focus: 'ifCreated'});
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
* DocumentUtil
* FrameClient
* api
* dynamicLoader
*/
@ -460,7 +459,7 @@ class Popup extends EventDispatcher {
if (this._frameClient === null || !this._frameClient.isConnected() || contentWindow === null) { return; }
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) {
@ -676,7 +675,7 @@ class Popup extends EventDispatcher {
async _setOptionsContext(optionsContext) {
this._optionsContext = optionsContext;
this._options = await api.optionsGet(optionsContext);
this._options = await yomichan.api.optionsGet(optionsContext);
this.updateTheme();
}

View File

@ -15,319 +15,291 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/* global
* CrossFrameAPI
*/
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
}
}
class API {
constructor(yomichan) {
this._yomichan = yomichan;
}
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/>.
*/
/* global
* api
*/
/**
* 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
@ -118,7 +114,7 @@ class FrameAncestryHandler {
clearTimeout(timer);
timer = null;
}
api.crossFrame.unregisterHandler(responseMessageId);
yomichan.crossFrame.unregisterHandler(responseMessageId);
};
const onMessage = (params) => {
if (params.nonce !== nonce) { return null; }
@ -148,7 +144,7 @@ class FrameAncestryHandler {
};
// Start
api.crossFrame.registerHandlers([[responseMessageId, {async: false, handler: onMessage}]]);
yomichan.crossFrame.registerHandlers([[responseMessageId, {async: false, handler: onMessage}]]);
resetTimeout();
const frameId = this._frameId;
this._requestFrameInfo(targetWindow, frameId, frameId, uniqueId, nonce);
@ -187,7 +183,7 @@ class FrameAncestryHandler {
const responseMessageId = `${this._responseMessageIdBase}${uniqueId}`;
try {
const response = await api.crossFrame.invoke(originFrameId, responseMessageId, responseParams);
const response = await yomichan.crossFrame.invoke(originFrameId, responseMessageId, responseParams);
if (response === null) { return; }
nonce = response.nonce;
} catch (e) {

View File

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

View File

@ -17,7 +17,6 @@
/* global
* FrameAncestryHandler
* api
*/
class FrameOffsetForwarder {
@ -28,7 +27,7 @@ class FrameOffsetForwarder {
prepare() {
this._frameAncestryHandler.prepare();
api.crossFrame.registerHandlers([
yomichan.crossFrame.registerHandlers([
['FrameOffsetForwarder.getChildFrameRect', {async: false, handler: this._onMessageGetChildFrameRect.bind(this)}]
]);
}
@ -43,7 +42,7 @@ class FrameOffsetForwarder {
let childFrameId = this._frameId;
const promises = [];
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;
}

View File

@ -18,7 +18,6 @@
/* global
* AudioSystem
* PopupMenu
* api
*/
class DisplayAudio {
@ -314,7 +313,7 @@ class DisplayAudio {
}
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}));
}

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,6 @@
/* global
* TextScanner
* api
*/
class QueryParser extends EventDispatcher {
@ -76,7 +75,7 @@ class QueryParser extends EventDispatcher {
const 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; }
this._refreshSelectedParser();
@ -116,7 +115,7 @@ class QueryParser extends EventDispatcher {
_setSelectedParser(value) {
const optionsContext = this._getOptionsContext();
api.modifySettings([{
yomichan.api.modifySettings([{
action: 'set',
path: 'parsing.selectedParser',
value,

View File

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

View File

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

View File

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

View File

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

View File

@ -17,7 +17,6 @@
/* global
* DocumentUtil
* api
*/
class TextScanner extends EventDispatcher {
@ -762,7 +761,7 @@ class TextScanner extends EventDispatcher {
const searchText = this.getTextSourceContent(textSource, scanLength, layoutAwareScan);
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; }
textSource.setEndOffset(length, layoutAwareScan);
@ -787,7 +786,7 @@ class TextScanner extends EventDispatcher {
const searchText = this.getTextSourceContent(textSource, 1, layoutAwareScan);
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; }
textSource.setEndOffset(1, layoutAwareScan);

View File

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

View File

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

View File

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

View File

@ -19,12 +19,11 @@
* DocumentFocusController
* PermissionsToggleController
* SettingsController
* api
*/
async function setupEnvironmentInfo() {
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.os = platform.os;
document.documentElement.dataset.manifestVersion = `${manifestVersion}`;
@ -69,7 +68,6 @@ function setupPermissionsToggles() {
node.textContent = chrome.runtime.getURL('/');
}
api.prepare();
await yomichan.prepare();
setupEnvironmentInfo();

View File

@ -25,12 +25,11 @@
* SettingsController
* SettingsDisplayController
* StatusFooter
* api
*/
async function setupEnvironmentInfo() {
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.os = platform.os;
document.documentElement.dataset.manifestVersion = `${manifestVersion}`;
@ -49,12 +48,11 @@ async function setupGenericSettingsController(genericSettingController) {
const statusFooter = new StatusFooter(document.querySelector('.status-footer-container'));
statusFooter.prepare();
api.prepare();
await yomichan.prepare();
setupEnvironmentInfo();
const optionsFull = await api.optionsGetFull();
const optionsFull = await yomichan.api.optionsGetFull();
const preparePromises = [];

View File

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

View File

@ -17,7 +17,6 @@
/* global
* AnkiNoteBuilder
* api
*/
class AnkiTemplatesController {
@ -37,7 +36,7 @@ class AnkiTemplatesController {
}
async prepare() {
this._defaultFieldTemplates = await api.getDefaultAnkiFieldTemplates();
this._defaultFieldTemplates = await yomichan.api.getDefaultAnkiFieldTemplates();
this._fieldTemplatesTextarea = document.querySelector('#anki-card-templates-textarea');
this._compileResultInfo = document.querySelector('#anki-card-templates-compile-result');
@ -154,7 +153,7 @@ class AnkiTemplatesController {
async _getDefinition(text, optionsContext) {
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; }
this._cachedDefinitionValue = definitions[0];

View File

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

View File

@ -18,7 +18,6 @@
/* global
* DictionaryDatabase
* ObjectPropertyAccessor
* api
*/
class DictionaryEntry {
@ -362,7 +361,7 @@ class DictionaryController {
const token = this._databaseStateToken;
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; }
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();
try {
await dictionaryDatabase.deleteDictionary(dictionaryTitle, {rate: 1000}, onProgress);
api.triggerDatabaseUpdated('dictionary', 'delete');
yomichan.api.triggerDatabaseUpdated('dictionary', 'delete');
} finally {
dictionaryDatabase.close();
}

View File

@ -19,7 +19,6 @@
* DictionaryDatabase
* DictionaryImporter
* ObjectPropertyAccessor
* api
*/
class DictionaryImportController {
@ -102,7 +101,7 @@ class DictionaryImportController {
this._setSpinnerVisible(true);
if (purgeNotification !== null) { purgeNotification.hidden = false; }
await api.purgeDatabase();
await yomichan.api.purgeDatabase();
const errors = await this._clearDictionarySettings();
if (errors.length > 0) {
@ -197,7 +196,7 @@ class DictionaryImportController {
const dictionaryImporter = new DictionaryImporter();
const archiveContent = await this._readFile(file);
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);
if (errors.length > 0) {

View File

@ -18,7 +18,6 @@
/* global
* HotkeyUtil
* KeyboardMouseInputField
* api
*/
class ExtensionKeyboardShortcutController {
@ -53,7 +52,7 @@ class ExtensionKeyboardShortcutController {
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._hotkeyUtil.os = os;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,7 +18,6 @@
/* global
* HotkeyUtil
* ScanInputsController
* api
*/
class ScanInputsSimpleController {
@ -34,7 +33,7 @@ class ScanInputsSimpleController {
this._middleMouseButtonScan = document.querySelector('#middle-mouse-button-scan');
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._mainScanModifierKeyInputHasOther = false;

View File

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

View File

@ -42,12 +42,11 @@
* StatusFooter
* StorageController
* TranslationTextReplacementsController
* api
*/
async function setupEnvironmentInfo() {
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.os = platform.os;
document.documentElement.dataset.manifestVersion = `${manifestVersion}`;
@ -66,12 +65,11 @@ async function setupGenericSettingsController(genericSettingController) {
const statusFooter = new StatusFooter(document.querySelector('.status-footer-container'));
statusFooter.prepare();
api.prepare();
await yomichan.prepare();
setupEnvironmentInfo();
const optionsFull = await api.optionsGetFull();
const optionsFull = await yomichan.api.optionsGetFull();
const preparePromises = [];

View File

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

View File

@ -15,6 +15,11 @@
* 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)
if ((() => {
let hasChrome = false;
@ -34,271 +39,300 @@ if ((() => {
chrome = browser;
}
const yomichan = (() => {
class Yomichan extends EventDispatcher {
constructor() {
super();
class Yomichan extends EventDispatcher {
constructor() {
super();
this._extensionName = 'Yomichan';
try {
const manifest = chrome.runtime.getManifest();
this._extensionName = `${manifest.name} v${manifest.version}`;
} catch (e) {
// 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)}]
]);
this._extensionName = 'Yomichan';
try {
const manifest = chrome.runtime.getManifest();
this._extensionName = `${manifest.name} v${manifest.version}`;
} catch (e) {
// NOP
}
// Public
this._isBackground = null;
this._api = null;
this._crossFrame = null;
this._isExtensionUnloaded = false;
this._isTriggeringExtensionUnloaded = false;
this._isReady = false;
get isExtensionUnloaded() {
return this._isExtensionUnloaded;
}
const {promise, resolve} = deferPromise();
this._isBackendReadyPromise = promise;
this._isBackendReadyPromiseResolve = resolve;
async prepare(isBackground=false) {
chrome.runtime.onMessage.addListener(this._onMessage.bind(this));
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)}]
]);
}
if (!isBackground) {
this.sendMessage({action: 'requestBackendReadySignal'});
await this._isBackendReadyPromise;
}
}
// Public
ready() {
this._isReady = true;
this.sendMessage({action: 'yomichanReady'});
}
get isBackground() {
return this._isBackground;
}
isExtensionUrl(url) {
try {
return url.startsWith(chrome.runtime.getURL('/'));
} catch (e) {
return false;
}
}
get isExtensionUnloaded() {
return this._isExtensionUnloaded;
}
getTemporaryListenerResult(eventHandler, userCallback, timeout=null) {
if (!(
typeof eventHandler.addListener === 'function' &&
typeof eventHandler.removeListener === 'function'
)) {
throw new Error('Event handler type not supported');
}
get api() {
return this._api;
}
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);
}
get crossFrame() {
return this._crossFrame;
}
const cleanupResolve = (value) => {
if (timeoutId !== null) {
clearTimeout(timeoutId);
timeoutId = null;
}
eventHandler.removeListener(runtimeMessageCallback);
sendResponse();
resolve(value);
};
async prepare(isBackground=false) {
this._isBackground = isBackground;
chrome.runtime.onMessage.addListener(this._onMessage.bind(this));
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.log(error, 'warn');
}
this.sendMessage({action: 'requestBackendReadySignal'});
await this._isBackendReadyPromise;
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});
this.on('log', this._onForwardLog.bind(this));
}
}
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();