diff options
| author | toasted-nutbread <toasted-nutbread@users.noreply.github.com> | 2021-02-14 15:53:35 -0500 | 
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-02-14 15:53:35 -0500 | 
| commit | 286534e648af350d24fbf3c7892a7ec81aaeb4bd (patch) | |
| tree | 89d88e961c5a0a6f508c66789e30b9ba4a968e73 | |
| parent | efe8140f103179f50b610f182148b9427af99010 (diff) | |
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
46 files changed, 586 insertions, 658 deletions
| diff --git a/ext/js/app/content-script-main.js b/ext/js/app/content-script-main.js index ee05034e..a09e52ea 100644 --- a/ext/js/app/content-script-main.js +++ b/ext/js/app/content-script-main.js @@ -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');          } diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index a62b06bf..74cc63d2 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -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              } diff --git a/ext/js/app/popup-factory.js b/ext/js/app/popup-factory.js index 7571d7ab..8f0c2a6e 100644 --- a/ext/js/app/popup-factory.js +++ b/ext/js/app/popup-factory.js @@ -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, diff --git a/ext/js/app/popup-proxy.js b/ext/js/app/popup-proxy.js index b2e81824..19856e3f 100644 --- a/ext/js/app/popup-proxy.js +++ b/ext/js/app/popup-proxy.js @@ -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) { diff --git a/ext/js/app/popup-window.js b/ext/js/app/popup-window.js index 5fa0c647..d0826775 100644 --- a/ext/js/app/popup-window.js +++ b/ext/js/app/popup-window.js @@ -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});      }  } diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index 75b74257..44cca14a 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -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();      } diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 26397d1f..472e464b 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -15,319 +15,291 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -/* global - * CrossFrameAPI - */ +class API { +    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}); +    } -        optionsGet(optionsContext) { -            return this._invoke('optionsGet', {optionsContext}); -        } +    optionsGetFull() { +        return this._invoke('optionsGetFull'); +    } -        optionsGetFull() { -            return this._invoke('optionsGetFull'); -        } +    termsFind(text, details, optionsContext) { +        return this._invoke('termsFind', {text, details, optionsContext}); +    } -        termsFind(text, details, optionsContext) { -            return this._invoke('termsFind', {text, details, optionsContext}); -        } +    textParse(text, optionsContext) { +        return this._invoke('textParse', {text, optionsContext}); +    } -        textParse(text, optionsContext) { -            return this._invoke('textParse', {text, optionsContext}); -        } +    kanjiFind(text, optionsContext) { +        return this._invoke('kanjiFind', {text, optionsContext}); +    } -        kanjiFind(text, optionsContext) { -            return this._invoke('kanjiFind', {text, optionsContext}); -        } +    isAnkiConnected() { +        return this._invoke('isAnkiConnected'); +    } -        isAnkiConnected() { -            return this._invoke('isAnkiConnected'); -        } +    getAnkiConnectVersion() { +        return this._invoke('getAnkiConnectVersion'); +    } -        getAnkiConnectVersion() { -            return this._invoke('getAnkiConnectVersion'); -        } +    addAnkiNote(note) { +        return this._invoke('addAnkiNote', {note}); +    } -        addAnkiNote(note) { -            return this._invoke('addAnkiNote', {note}); -        } +    getAnkiNoteInfo(notes) { +        return this._invoke('getAnkiNoteInfo', {notes}); +    } -        getAnkiNoteInfo(notes) { -            return this._invoke('getAnkiNoteInfo', {notes}); -        } +    injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails) { +        return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails}); +    } -        injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails) { -            return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails}); -        } +    noteView(noteId) { +        return this._invoke('noteView', {noteId}); +    } -        noteView(noteId) { -            return this._invoke('noteView', {noteId}); -        } +    suspendAnkiCardsForNote(noteId) { +        return this._invoke('suspendAnkiCardsForNote', {noteId}); +    } -        suspendAnkiCardsForNote(noteId) { -            return this._invoke('suspendAnkiCardsForNote', {noteId}); -        } +    getExpressionAudioInfoList(source, expression, reading, details) { +        return this._invoke('getExpressionAudioInfoList', {source, expression, reading, details}); +    } -        getExpressionAudioInfoList(source, expression, reading, details) { -            return this._invoke('getExpressionAudioInfoList', {source, expression, reading, details}); -        } +    commandExec(command, params) { +        return this._invoke('commandExec', {command, params}); +    } -        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'); -        } +    sendMessageToFrame(frameId, action, params) { +        return this._invoke('sendMessageToFrame', {frameId, action, params}); +    } -        getDictionaryInfo() { -            return this._invoke('getDictionaryInfo'); -        } +    broadcastTab(action, params) { +        return this._invoke('broadcastTab', {action, params}); +    } -        getDictionaryCounts(dictionaryNames, getTotal) { -            return this._invoke('getDictionaryCounts', {dictionaryNames, getTotal}); -        } +    frameInformationGet() { +        return this._invoke('frameInformationGet'); +    } -        purgeDatabase() { -            return this._invoke('purgeDatabase'); -        } +    injectStylesheet(type, value) { +        return this._invoke('injectStylesheet', {type, value}); +    } -        getMedia(targets) { -            return this._invoke('getMedia', {targets}); -        } +    getStylesheetContent(url) { +        return this._invoke('getStylesheetContent', {url}); +    } -        logIndicatorClear() { -            return this._invoke('logIndicatorClear'); -        } +    getEnvironmentInfo() { +        return this._invoke('getEnvironmentInfo'); +    } -        modifySettings(targets, source) { -            return this._invoke('modifySettings', {targets, source}); -        } +    clipboardGet() { +        return this._invoke('clipboardGet'); +    } -        getSettings(targets) { -            return this._invoke('getSettings', {targets}); -        } +    getDisplayTemplatesHtml() { +        return this._invoke('getDisplayTemplatesHtml'); +    } -        setAllSettings(value, source) { -            return this._invoke('setAllSettings', {value, source}); -        } +    getZoom() { +        return this._invoke('getZoom'); +    } -        getOrCreateSearchPopup(details) { -            return this._invoke('getOrCreateSearchPopup', isObject(details) ? details : {}); -        } +    getDefaultAnkiFieldTemplates() { +        return this._invoke('getDefaultAnkiFieldTemplates'); +    } -        isTabSearchPopup(tabId) { -            return this._invoke('isTabSearchPopup', {tabId}); -        } +    getDictionaryInfo() { +        return this._invoke('getDictionaryInfo'); +    } -        triggerDatabaseUpdated(type, cause) { -            return this._invoke('triggerDatabaseUpdated', {type, cause}); -        } +    getDictionaryCounts(dictionaryNames, getTotal) { +        return this._invoke('getDictionaryCounts', {dictionaryNames, getTotal}); +    } -        testMecab() { -            return this._invoke('testMecab', {}); -        } +    purgeDatabase() { +        return this._invoke('purgeDatabase'); +    } -        // 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; -                    } +    getMedia(targets) { +        return this._invoke('getMedia', {targets}); +    } -                    clearTimeout(timer); -                    timer = null; +    log(error, level, context) { +        return this._invoke('log', {error, level, context}); +    } -                    chrome.runtime.onConnect.removeListener(onConnect); -                    resolve(port); -                }; +    logIndicatorClear() { +        return this._invoke('logIndicatorClear'); +    } -                const onError = (e) => { -                    if (timer !== null) { -                        clearTimeout(timer); -                        timer = null; -                    } -                    chrome.runtime.onConnect.removeListener(onConnect); -                    portDetails.reject(e); -                    reject(e); -                }; +    modifySettings(targets, source) { +        return this._invoke('modifySettings', {targets, source}); +    } -                timer = setTimeout(() => onError(new Error('Timeout')), timeout); +    getSettings(targets) { +        return this._invoke('getSettings', {targets}); +    } -                chrome.runtime.onConnect.addListener(onConnect); -                this._invoke('createActionPort').then(portDetails.resolve, onError); -            }); -        } +    setAllSettings(value, source) { +        return this._invoke('setAllSettings', {value, source}); +    } -        _invokeWithProgress(action, params, onProgress, timeout=5000) { -            return new Promise((resolve, reject) => { -                let port = null; +    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); +            }; -                if (typeof onProgress !== 'function') { -                    onProgress = () => {}; +            const onError = (e) => { +                if (timer !== null) { +                    clearTimeout(timer); +                    timer = null;                  } +                chrome.runtime.onConnect.removeListener(onConnect); +                portDetails.reject(e); +                reject(e); +            }; -                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; -                    } -                }; +            timer = setTimeout(() => onError(new Error('Timeout')), timeout); -                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}); +            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                          } -                        port.postMessage({action: 'invoke'}); -                    } catch (e) { +                        break; +                    case 'complete':                          cleanup(); -                        reject(e); -                    } finally { -                        action = null; -                        params = null; -                    } -                })(); -            }); -        } +                        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; +            }; -        _invoke(action, params={}) { -            const data = {action, params}; -            return new Promise((resolve, reject) => { +            (async () => {                  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)})`)); -                        } -                    }); +                    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;                  } -            }); -        } - -        _checkLastError() { -            // NOP -        } +            })(); +        }); +    } -        async _onLog({error, level, context}) { +    _invoke(action, params={}) { +        const data = {action, params}; +        return new Promise((resolve, reject) => {              try { -                error = serializeError(error); -                await this._invoke('log', {error, level, context}); +                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) { -                // NOP +                reject(e);              } -        } +        });      } -    return new API(); -})(); +    _checkLastError() { +        // NOP +    } +} diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index b1ed7114..d53334e1 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -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) { diff --git a/ext/js/comm/frame-endpoint.js b/ext/js/comm/frame-endpoint.js index 27af9cf3..bc3c50f8 100644 --- a/ext/js/comm/frame-endpoint.js +++ b/ext/js/comm/frame-endpoint.js @@ -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});      }  } diff --git a/ext/js/comm/frame-offset-forwarder.js b/ext/js/comm/frame-offset-forwarder.js index 0a0b4a18..2382a9fa 100644 --- a/ext/js/comm/frame-offset-forwarder.js +++ b/ext/js/comm/frame-offset-forwarder.js @@ -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;          } diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index f624d85b..24f2dd7b 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -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}));      } diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js index 05376ee5..18159f68 100644 --- a/ext/js/display/display-generator.js +++ b/ext/js/display/display-generator.js @@ -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();      } diff --git a/ext/js/display/display-profile-selection.js b/ext/js/display/display-profile-selection.js index 0a44392e..d858da28 100644 --- a/ext/js/display/display-profile-selection.js +++ b/ext/js/display/display-profile-selection.js @@ -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, diff --git a/ext/js/display/display.js b/ext/js/display/display.js index fe4160a6..553c17e7 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -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() { diff --git a/ext/js/display/popup-main.js b/ext/js/display/popup-main.js index f1228aa6..24be6c01 100644 --- a/ext/js/display/popup-main.js +++ b/ext/js/display/popup-main.js @@ -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); diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index 05ebfa27..29401657 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -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, diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index a295346d..3b48af44 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -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, diff --git a/ext/js/display/search-main.js b/ext/js/display/search-main.js index d3e8af0b..08645833 100644 --- a/ext/js/display/search-main.js +++ b/ext/js/display/search-main.js @@ -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); diff --git a/ext/js/input/hotkey-handler.js b/ext/js/input/hotkey-handler.js index 423410b7..6db3e0f0 100644 --- a/ext/js/input/hotkey-handler.js +++ b/ext/js/input/hotkey-handler.js @@ -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          } diff --git a/ext/js/input/hotkey-help-controller.js b/ext/js/input/hotkey-help-controller.js index 8137b50b..9bf95c77 100644 --- a/ext/js/input/hotkey-help-controller.js +++ b/ext/js/input/hotkey-help-controller.js @@ -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);      } diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index 7672b69d..e91498ba 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -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); diff --git a/ext/js/media/media-loader.js b/ext/js/media/media-loader.js index 5974e31a..d9d40a36 100644 --- a/ext/js/media/media-loader.js +++ b/ext/js/media/media-loader.js @@ -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}); diff --git a/ext/js/pages/action-popup-main.js b/ext/js/pages/action-popup-main.js index 99a76bdf..75dfb641 100644 --- a/ext/js/pages/action-popup-main.js +++ b/ext/js/pages/action-popup-main.js @@ -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(); diff --git a/ext/js/pages/info-main.js b/ext/js/pages/info-main.js index 7d34d47a..45c28d25 100644 --- a/ext/js/pages/info-main.js +++ b/ext/js/pages/info-main.js @@ -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;              } diff --git a/ext/js/pages/permissions-main.js b/ext/js/pages/permissions-main.js index 7c0427b9..0cb37e93 100644 --- a/ext/js/pages/permissions-main.js +++ b/ext/js/pages/permissions-main.js @@ -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(); diff --git a/ext/js/pages/welcome-main.js b/ext/js/pages/welcome-main.js index 626cbd3a..5a6bb2a5 100644 --- a/ext/js/pages/welcome-main.js +++ b/ext/js/pages/welcome-main.js @@ -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 = []; diff --git a/ext/js/script/dynamic-loader.js b/ext/js/script/dynamic-loader.js index 0464f151..a2cfb77a 100644 --- a/ext/js/script/dynamic-loader.js +++ b/ext/js/script/dynamic-loader.js @@ -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;          } diff --git a/ext/js/settings/anki-templates-controller.js b/ext/js/settings/anki-templates-controller.js index 31bd1e92..8e3a1a70 100644 --- a/ext/js/settings/anki-templates-controller.js +++ b/ext/js/settings/anki-templates-controller.js @@ -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]; diff --git a/ext/js/settings/backup-controller.js b/ext/js/settings/backup-controller.js index 8837b927..117f2422 100644 --- a/ext/js/settings/backup-controller.js +++ b/ext/js/settings/backup-controller.js @@ -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 diff --git a/ext/js/settings/dictionary-controller.js b/ext/js/settings/dictionary-controller.js index ea9f7503..78173202 100644 --- a/ext/js/settings/dictionary-controller.js +++ b/ext/js/settings/dictionary-controller.js @@ -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();          } diff --git a/ext/js/settings/dictionary-import-controller.js b/ext/js/settings/dictionary-import-controller.js index c4ad9e59..00eb7b95 100644 --- a/ext/js/settings/dictionary-import-controller.js +++ b/ext/js/settings/dictionary-import-controller.js @@ -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) { diff --git a/ext/js/settings/extension-keyboard-shortcuts-controller.js b/ext/js/settings/extension-keyboard-shortcuts-controller.js index 9c930703..032f9dcc 100644 --- a/ext/js/settings/extension-keyboard-shortcuts-controller.js +++ b/ext/js/settings/extension-keyboard-shortcuts-controller.js @@ -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; diff --git a/ext/js/settings/keyboard-shortcuts-controller.js b/ext/js/settings/keyboard-shortcuts-controller.js index 0dcfa2ee..99b16f06 100644 --- a/ext/js/settings/keyboard-shortcuts-controller.js +++ b/ext/js/settings/keyboard-shortcuts-controller.js @@ -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'); diff --git a/ext/js/settings/main.js b/ext/js/settings/main.js index 0707ea3c..51cd5f27 100644 --- a/ext/js/settings/main.js +++ b/ext/js/settings/main.js @@ -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(); diff --git a/ext/js/settings/mecab-controller.js b/ext/js/settings/mecab-controller.js index ff2a4a66..122f82f9 100644 --- a/ext/js/settings/mecab-controller.js +++ b/ext/js/settings/mecab-controller.js @@ -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); diff --git a/ext/js/settings/pitch-accents-preview-main.js b/ext/js/settings/pitch-accents-preview-main.js index 7bc995a2..fe3bd36d 100644 --- a/ext/js/settings/pitch-accents-preview-main.js +++ b/ext/js/settings/pitch-accents-preview-main.js @@ -17,12 +17,10 @@  /* global   * DisplayGenerator - * api   */  (async () => {      try { -        api.prepare();          await yomichan.prepare();          const displayGenerator = new DisplayGenerator({ diff --git a/ext/js/settings/popup-preview-frame-main.js b/ext/js/settings/popup-preview-frame-main.js index f61b26dc..a62e8d28 100644 --- a/ext/js/settings/popup-preview-frame-main.js +++ b/ext/js/settings/popup-preview-frame-main.js @@ -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(); diff --git a/ext/js/settings/popup-preview-frame.js b/ext/js/settings/popup-preview-frame.js index 56100fb3..638dd414 100644 --- a/ext/js/settings/popup-preview-frame.js +++ b/ext/js/settings/popup-preview-frame.js @@ -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({ diff --git a/ext/js/settings/popup-window-controller.js b/ext/js/settings/popup-window-controller.js index cc83db68..403c060c 100644 --- a/ext/js/settings/popup-window-controller.js +++ b/ext/js/settings/popup-window-controller.js @@ -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});      }  } diff --git a/ext/js/settings/profile-controller.js b/ext/js/settings/profile-controller.js index 914fc679..3883e80a 100644 --- a/ext/js/settings/profile-controller.js +++ b/ext/js/settings/profile-controller.js @@ -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'); diff --git a/ext/js/settings/scan-inputs-controller.js b/ext/js/settings/scan-inputs-controller.js index eb179c6a..79b2bdf4 100644 --- a/ext/js/settings/scan-inputs-controller.js +++ b/ext/js/settings/scan-inputs-controller.js @@ -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'); diff --git a/ext/js/settings/scan-inputs-simple-controller.js b/ext/js/settings/scan-inputs-simple-controller.js index 01f044c2..b011af5d 100644 --- a/ext/js/settings/scan-inputs-simple-controller.js +++ b/ext/js/settings/scan-inputs-simple-controller.js @@ -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; diff --git a/ext/js/settings/settings-controller.js b/ext/js/settings/settings-controller.js index 11a9435c..4a86470d 100644 --- a/ext/js/settings/settings-controller.js +++ b/ext/js/settings/settings-controller.js @@ -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) { diff --git a/ext/js/settings/settings-main.js b/ext/js/settings/settings-main.js index a7e6b7b0..a5bb642c 100644 --- a/ext/js/settings/settings-main.js +++ b/ext/js/settings/settings-main.js @@ -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 = []; diff --git a/ext/js/templates/template-renderer-frame-main.js b/ext/js/templates/template-renderer-frame-main.js index d25eb56d..a3892002 100644 --- a/ext/js/templates/template-renderer-frame-main.js +++ b/ext/js/templates/template-renderer-frame-main.js @@ -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();  })(); diff --git a/ext/js/yomichan.js b/ext/js/yomichan.js index 7d101a4c..73deeab9 100644 --- a/ext/js/yomichan.js +++ b/ext/js/yomichan.js @@ -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(); - -            this._extensionName = 'Yomichan'; -            try { -                const manifest = chrome.runtime.getManifest(); -                this._extensionName = `${manifest.name} v${manifest.version}`; -            } catch (e) { -                // NOP -            } +class Yomichan extends EventDispatcher { +    constructor() { +        super(); -            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; + +        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)}] +        ]); +    } -        get isExtensionUnloaded() { -            return this._isExtensionUnloaded; -        } +    // Public -        async prepare(isBackground=false) { -            chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); +    get isBackground() { +        return this._isBackground; +    } -            if (!isBackground) { -                this.sendMessage({action: 'requestBackendReadySignal'}); -                await this._isBackendReadyPromise; -            } -        } +    get isExtensionUnloaded() { +        return this._isExtensionUnloaded; +    } -        ready() { -            this._isReady = true; -            this.sendMessage({action: 'yomichanReady'}); -        } +    get api() { +        return this._api; +    } -        isExtensionUrl(url) { -            try { -                return url.startsWith(chrome.runtime.getURL('/')); -            } catch (e) { -                return false; -            } -        } +    get crossFrame() { +        return this._crossFrame; +    } -        getTemporaryListenerResult(eventHandler, userCallback, timeout=null) { -            if (!( -                typeof eventHandler.addListener === 'function' && -                typeof eventHandler.removeListener === 'function' -            )) { -                throw new Error('Event handler type not supported'); -            } +    async prepare(isBackground=false) { +        this._isBackground = isBackground; +        chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); -            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); -                    } +        if (!isBackground) { +            this._api = new API(this); -                    const cleanupResolve = (value) => { -                        if (timeoutId !== null) { -                            clearTimeout(timeoutId); -                            timeoutId = null; -                        } -                        eventHandler.removeListener(runtimeMessageCallback); -                        sendResponse(); -                        resolve(value); -                    }; +            this._crossFrame = new CrossFrameAPI(); +            this._crossFrame.prepare(); -                    userCallback({action, params}, {resolve: cleanupResolve, sender}); -                }; +            this.sendMessage({action: 'requestBackendReadySignal'}); +            await this._isBackendReadyPromise; -                eventHandler.addListener(runtimeMessageCallback); -            }); +            this.on('log', this._onForwardLog.bind(this));          } +    } -        logWarning(error) { -            this.log(error, 'warn'); -        } +    ready() { +        this._isReady = true; +        this.sendMessage({action: 'yomichanReady'}); +    } -        logError(error) { -            this.log(error, 'error'); +    isExtensionUrl(url) { +        try { +            return url.startsWith(chrome.runtime.getURL('/')); +        } catch (e) { +            return false;          } +    } -        log(error, level, context=null) { -            if (!isObject(context)) { -                context = this._getLogContext(); -            } +    getTemporaryListenerResult(eventHandler, userCallback, timeout=null) { +        if (!( +            typeof eventHandler.addListener === 'function' && +            typeof eventHandler.removeListener === 'function' +        )) { +            throw new Error('Event handler type not supported'); +        } -            let errorString; -            try { -                errorString = error.toString(); -                if (/^\[object \w+\]$/.test(errorString)) { -                    errorString = JSON.stringify(error); +        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);                  } -            } catch (e) { -                errorString = `${error}`; -            } -            let errorStack; -            try { -                errorStack = (typeof error.stack === 'string' ? error.stack.trimRight() : ''); -            } catch (e) { -                errorStack = ''; -            } +                const cleanupResolve = (value) => { +                    if (timeoutId !== null) { +                        clearTimeout(timeoutId); +                        timeoutId = null; +                    } +                    eventHandler.removeListener(runtimeMessageCallback); +                    sendResponse(); +                    resolve(value); +                }; -            let errorData; -            try { -                errorData = error.data; -            } catch (e) { -                // NOP -            } +                userCallback({action, params}, {resolve: cleanupResolve, sender}); +            }; -            if (errorStack.startsWith(errorString)) { -                errorString = errorStack; -            } else if (errorStack.length > 0) { -                errorString += `\n${errorStack}`; -            } +            eventHandler.addListener(runtimeMessageCallback); +        }); +    } -            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; -            } +    logWarning(error) { +        this.log(error, 'warn'); +    } + +    logError(error) { +        this.log(error, 'error'); +    } -            this.trigger('log', {error, level, context}); +    log(error, level, context=null) { +        if (!isObject(context)) { +            context = this._getLogContext();          } -        sendMessage(...args) { -            try { -                return chrome.runtime.sendMessage(...args); -            } catch (e) { -                this.triggerExtensionUnloaded(); -                throw e; +        let errorString; +        try { +            errorString = error.toString(); +            if (/^\[object \w+\]$/.test(errorString)) { +                errorString = JSON.stringify(error);              } +        } catch (e) { +            errorString = `${error}`;          } -        connect(...args) { -            try { -                return chrome.runtime.connect(...args); -            } catch (e) { -                this.triggerExtensionUnloaded(); -                throw e; -            } +        let errorStack; +        try { +            errorStack = (typeof error.stack === 'string' ? error.stack.trimRight() : ''); +        } catch (e) { +            errorStack = '';          } -        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; +        let errorData; +        try { +            errorData = error.data; +        } catch (e) { +            // NOP          } -        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; -            } +        if (errorStack.startsWith(errorString)) { +            errorString = errorStack; +        } else if (errorStack.length > 0) { +            errorString += `\n${errorStack}`;          } -        triggerExtensionUnloaded() { -            this._isExtensionUnloaded = true; -            if (this._isTriggeringExtensionUnloaded) { return; } -            try { -                this._isTriggeringExtensionUnloaded = true; -                this.trigger('extensionUnloaded'); -            } finally { -                this._isTriggeringExtensionUnloaded = false; -            } +        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;          } -        // Private +        this.trigger('log', {error, level, context}); +    } -        _getUrl() { -            return location.href; +    sendMessage(...args) { +        try { +            return chrome.runtime.sendMessage(...args); +        } catch (e) { +            this.triggerExtensionUnloaded(); +            throw e;          } +    } -        _getLogContext() { -            return {url: this._getUrl()}; +    connect(...args) { +        try { +            return chrome.runtime.connect(...args); +        } catch (e) { +            this.triggerExtensionUnloaded(); +            throw e;          } +    } -        _onMessage({action, params}, sender, callback) { -            const messageHandler = this._messageHandlers.get(action); -            if (typeof messageHandler === 'undefined') { return false; } -            return this.invokeMessageHandler(messageHandler, params, callback, sender); +    getMessageResponseResult(response) { +        let error = chrome.runtime.lastError; +        if (error) { +            throw new Error(error.message);          } - -        _onMessageIsReady() { -            return this._isReady; +        if (!isObject(response)) { +            throw new Error('Tab did not respond');          } - -        _onMessageBackendReady() { -            if (this._isBackendReadyPromiseResolve === null) { return; } -            this._isBackendReadyPromiseResolve(); -            this._isBackendReadyPromiseResolve = null; +        error = response.error; +        if (error) { +            throw deserializeError(error);          } +        return response.result; +    } -        _onMessageGetUrl() { -            return {url: this._getUrl()}; +    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;          } +    } -        _onMessageOptionsUpdated({source}) { -            this.trigger('optionsUpdated', {source}); +    triggerExtensionUnloaded() { +        this._isExtensionUnloaded = true; +        if (this._isTriggeringExtensionUnloaded) { return; } +        try { +            this._isTriggeringExtensionUnloaded = true; +            this.trigger('extensionUnloaded'); +        } finally { +            this._isTriggeringExtensionUnloaded = false;          } +    } -        _onMessageDatabaseUpdated({type, cause}) { -            this.trigger('databaseUpdated', {type, cause}); -        } +    // 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}); +    } -        _onMessageZoomChanged({oldZoomFactor, newZoomFactor}) { -            this.trigger('zoomChanged', {oldZoomFactor, newZoomFactor}); +    _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          }      } +} -    return new Yomichan(); -})(); +const yomichan = new Yomichan(); |