diff options
60 files changed, 396 insertions, 274 deletions
| diff --git a/.eslintrc.json b/.eslintrc.json index aea94cde..fbb47626 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -658,9 +658,8 @@          },          {              "files": [ -                "ext/js/core.js",                  "ext/js/core/extension-error.js", -                "ext/js/yomitan.js", +                "ext/js/application.js",                  "ext/js/accessibility/accessibility-controller.js",                  "ext/js/background/backend.js",                  "ext/js/background/offscreen.js", diff --git a/ext/js/app/content-script-main.js b/ext/js/app/content-script-main.js index c0bea73c..d77f1fa0 100644 --- a/ext/js/app/content-script-main.js +++ b/ext/js/app/content-script-main.js @@ -16,29 +16,31 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {Application} from '../application.js';  import {log} from '../core/logger.js';  import {HotkeyHandler} from '../input/hotkey-handler.js'; -import {yomitan} from '../yomitan.js';  import {Frontend} from './frontend.js';  import {PopupFactory} from './popup-factory.js';  /** Entry point. */  async function main() {      try { -        await yomitan.prepare(); +        const application = new Application(); +        await application.prepare(); -        const {tabId, frameId} = await yomitan.api.frameInformationGet(); +        const {tabId, frameId} = await application.api.frameInformationGet();          if (typeof frameId !== 'number') {              throw new Error('Failed to get frameId');          }          const hotkeyHandler = new HotkeyHandler(); -        hotkeyHandler.prepare(); +        hotkeyHandler.prepare(application.crossFrame); -        const popupFactory = new PopupFactory(frameId); +        const popupFactory = new PopupFactory(application, frameId);          popupFactory.prepare();          const frontend = new Frontend({ +            application,              tabId,              frameId,              popupFactory, @@ -54,7 +56,7 @@ async function main() {          });          await frontend.prepare(); -        yomitan.ready(); +        application.ready();      } catch (e) {          log.error(e);      } diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index d1c32b03..de1c5a46 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -25,7 +25,6 @@ import {TextSourceElement} from '../dom/text-source-element.js';  import {TextSourceGenerator} from '../dom/text-source-generator.js';  import {TextSourceRange} from '../dom/text-source-range.js';  import {TextScanner} from '../language/text-scanner.js'; -import {yomitan} from '../yomitan.js';  /**   * This is the main class responsible for scanning and handling webpage content. @@ -36,6 +35,7 @@ export class Frontend {       * @param {import('frontend').ConstructorDetails} details Details about how to set up the instance.       */      constructor({ +        application,          pageType,          popupFactory,          depth, @@ -49,6 +49,8 @@ export class Frontend {          childrenSupported = true,          hotkeyHandler      }) { +        /** @type {import('../application.js').Application} */ +        this._application = application;          /** @type {import('frontend').PageType} */          this._pageType = pageType;          /** @type {import('./popup-factory.js').PopupFactory} */ @@ -89,6 +91,7 @@ export class Frontend {          this._textSourceGenerator = new TextSourceGenerator();          /** @type {TextScanner} */          this._textScanner = new TextScanner({ +            api: application.api,              node: window,              ignoreElements: this._ignoreElements.bind(this),              ignorePoint: this._ignorePoint.bind(this), @@ -157,7 +160,7 @@ export class Frontend {      async prepare() {          await this.updateOptions();          try { -            const {zoomFactor} = await yomitan.api.getZoom(); +            const {zoomFactor} = await this._application.api.getZoom();              this._pageZoomFactor = zoomFactor;          } catch (e) {              // Ignore exceptions which may occur due to being on an unsupported page (e.g. about:blank) @@ -174,16 +177,16 @@ export class Frontend {              visualViewport.addEventListener('resize', this._onVisualViewportResize.bind(this));          } -        yomitan.on('optionsUpdated', this.updateOptions.bind(this)); -        yomitan.on('zoomChanged', this._onZoomChanged.bind(this)); -        yomitan.on('closePopups', this._onClosePopups.bind(this)); +        this._application.on('optionsUpdated', this.updateOptions.bind(this)); +        this._application.on('zoomChanged', this._onZoomChanged.bind(this)); +        this._application.on('closePopups', this._onClosePopups.bind(this));          chrome.runtime.onMessage.addListener(this._onRuntimeMessage.bind(this));          this._textScanner.on('clear', this._onTextScannerClear.bind(this));          this._textScanner.on('searched', this._onSearched.bind(this));          /* eslint-disable no-multi-spaces */ -        yomitan.crossFrame.registerHandlers([ +        this._application.crossFrame.registerHandlers([              ['frontendClosePopup',       this._onApiClosePopup.bind(this)],              ['frontendCopySelection',    this._onApiCopySelection.bind(this)],              ['frontendGetSelectionText', this._onApiGetSelectionText.bind(this)], @@ -230,7 +233,7 @@ export class Frontend {          try {              await this._updateOptionsInternal();          } catch (e) { -            if (!yomitan.webExtension.unloaded) { +            if (!this._application.webExtension.unloaded) {                  throw e;              }          } @@ -372,7 +375,7 @@ export class Frontend {          const scanningOptions = /** @type {import('settings').ProfileOptions} */ (this._options).scanning;          if (error !== null) { -            if (yomitan.webExtension.unloaded) { +            if (this._application.webExtension.unloaded) {                  if (textSource !== null && !passive) {                      this._showExtensionUnloaded(textSource);                  } @@ -461,7 +464,7 @@ export class Frontend {       */      async _updateOptionsInternal() {          const optionsContext = await this._getOptionsContext(); -        const options = await yomitan.api.optionsGet(optionsContext); +        const options = await this._application.api.optionsGet(optionsContext);          const {scanning: scanningOptions, sentenceParsing: sentenceParsingOptions} = options;          this._options = options; @@ -609,7 +612,7 @@ export class Frontend {              return await this._getDefaultPopup();          } -        const {popupId} = await yomitan.crossFrame.invoke(targetFrameId, 'frontendGetPopupInfo', void 0); +        const {popupId} = await this._application.crossFrame.invoke(targetFrameId, 'frontendGetPopupInfo', void 0);          if (popupId === null) {              return null;          } @@ -659,7 +662,7 @@ export class Frontend {          try {              return this._popup !== null && await this._popup.containsPoint(x, y);          } catch (e) { -            if (!yomitan.webExtension.unloaded) { +            if (!this._application.webExtension.unloaded) {                  throw e;              }              return false; @@ -746,7 +749,7 @@ export class Frontend {              Promise.resolve()          );          this._lastShowPromise.catch((error) => { -            if (yomitan.webExtension.unloaded) { return; } +            if (this._application.webExtension.unloaded) { return; }              log.error(error);          });          return this._lastShowPromise; @@ -811,9 +814,9 @@ export class Frontend {          /** @type {import('application').ApiMessageNoFrameId<'frontendReady'>} */          const message = {action: 'frontendReady', params: {frameId: this._frameId}};          if (targetFrameId === null) { -            yomitan.api.broadcastTab(message); +            this._application.api.broadcastTab(message);          } else { -            yomitan.api.sendMessageToFrame(targetFrameId, message); +            this._application.api.sendMessageToFrame(targetFrameId, message);          }      } @@ -857,7 +860,7 @@ export class Frontend {              }              chrome.runtime.onMessage.addListener(onMessage); -            yomitan.api.broadcastTab({action: 'frontendRequestReadyBroadcast', params: {frameId: this._frameId}}); +            this._application.api.broadcastTab({action: 'frontendRequestReadyBroadcast', params: {frameId: this._frameId}});          });      } @@ -892,7 +895,7 @@ export class Frontend {          let documentTitle = document.title;          if (this._useProxyPopup && this._parentFrameId !== null) {              try { -                ({url, documentTitle} = await yomitan.crossFrame.invoke(this._parentFrameId, 'frontendGetPageInfo', void 0)); +                ({url, documentTitle} = await this._application.crossFrame.invoke(this._parentFrameId, 'frontendGetPageInfo', void 0));              } catch (e) {                  // NOP              } diff --git a/ext/js/app/popup-factory.js b/ext/js/app/popup-factory.js index f9eec913..1b7d21db 100644 --- a/ext/js/app/popup-factory.js +++ b/ext/js/app/popup-factory.js @@ -18,7 +18,6 @@  import {FrameOffsetForwarder} from '../comm/frame-offset-forwarder.js';  import {generateId} from '../core/utilities.js'; -import {yomitan} from '../yomitan.js';  import {PopupProxy} from './popup-proxy.js';  import {PopupWindow} from './popup-window.js';  import {Popup} from './popup.js'; @@ -29,13 +28,16 @@ import {Popup} from './popup.js';  export class PopupFactory {      /**       * Creates a new instance. +     * @param {import('../application.js').Application} application       * @param {number} frameId The frame ID of the host frame.       */ -    constructor(frameId) { +    constructor(application, frameId) { +        /** @type {import('../application.js').Application} */ +        this._application = application;          /** @type {number} */          this._frameId = frameId;          /** @type {FrameOffsetForwarder} */ -        this._frameOffsetForwarder = new FrameOffsetForwarder(frameId); +        this._frameOffsetForwarder = new FrameOffsetForwarder(application.crossFrame, frameId);          /** @type {Map<string, import('popup').PopupAny>} */          this._popups = new Map();          /** @type {Map<string, {popup: import('popup').PopupAny, token: string}[]>} */ @@ -48,7 +50,7 @@ export class PopupFactory {      prepare() {          this._frameOffsetForwarder.prepare();          /* eslint-disable no-multi-spaces */ -        yomitan.crossFrame.registerHandlers([ +        this._application.crossFrame.registerHandlers([              ['popupFactoryGetOrCreatePopup',     this._onApiGetOrCreatePopup.bind(this)],              ['popupFactorySetOptionsContext',    this._onApiSetOptionsContext.bind(this)],              ['popupFactoryHide',                 this._onApiHide.bind(this)], @@ -119,6 +121,7 @@ export class PopupFactory {                  id = generateId(16);              }              const popup = new PopupWindow({ +                application: this._application,                  id,                  depth,                  frameId: this._frameId @@ -131,6 +134,7 @@ export class PopupFactory {                  id = generateId(16);              }              const popup = new Popup({ +                application: this._application,                  id,                  depth,                  frameId: this._frameId, @@ -152,7 +156,7 @@ export class PopupFactory {              }              const useFrameOffsetForwarder = (parentPopupId === null);              /** @type {{id: string, depth: number, frameId: number}} */ -            const info = await yomitan.crossFrame.invoke(frameId, 'popupFactoryGetOrCreatePopup', /** @type {import('popup-factory').GetOrCreatePopupDetails} */ ({ +            const info = await this._application.crossFrame.invoke(frameId, 'popupFactoryGetOrCreatePopup', /** @type {import('popup-factory').GetOrCreatePopupDetails} */ ({                  id,                  parentPopupId,                  frameId, @@ -160,6 +164,7 @@ export class PopupFactory {              }));              id = info.id;              const popup = new PopupProxy({ +                application: this._application,                  id,                  depth: info.depth,                  frameId: info.frameId, diff --git a/ext/js/app/popup-proxy.js b/ext/js/app/popup-proxy.js index 856ec086..3632b8cb 100644 --- a/ext/js/app/popup-proxy.js +++ b/ext/js/app/popup-proxy.js @@ -18,7 +18,6 @@  import {EventDispatcher} from '../core/event-dispatcher.js';  import {log} from '../core/logger.js'; -import {yomitan} from '../yomitan.js';  /**   * This class is a proxy for a Popup that is hosted in a different frame. @@ -31,12 +30,15 @@ export class PopupProxy extends EventDispatcher {       * @param {import('popup').PopupProxyConstructorDetails} details Details about how to set up the instance.       */      constructor({ +        application,          id,          depth,          frameId,          frameOffsetForwarder      }) {          super(); +        /** @type {import('../application.js').Application} */ +        this._application = application;          /** @type {string} */          this._id = id;          /** @type {number} */ @@ -305,7 +307,7 @@ export class PopupProxy extends EventDispatcher {       * @returns {Promise<import('cross-frame-api').ApiReturn<TName>>}       */      _invoke(action, params) { -        return yomitan.crossFrame.invoke(this._frameId, action, params); +        return this._application.crossFrame.invoke(this._frameId, action, params);      }      /** @@ -320,7 +322,7 @@ export class PopupProxy extends EventDispatcher {          try {              return await this._invoke(action, params);          } catch (e) { -            if (!yomitan.webExtension.unloaded) { throw e; } +            if (!this._application.webExtension.unloaded) { throw e; }              return defaultReturnValue;          }      } diff --git a/ext/js/app/popup-window.js b/ext/js/app/popup-window.js index 7a0b6af4..32c4d67b 100644 --- a/ext/js/app/popup-window.js +++ b/ext/js/app/popup-window.js @@ -17,7 +17,6 @@   */  import {EventDispatcher} from '../core/event-dispatcher.js'; -import {yomitan} from '../yomitan.js';  /**   * This class represents a popup that is hosted in a new native window. @@ -29,11 +28,14 @@ export class PopupWindow extends EventDispatcher {       * @param {import('popup').PopupWindowConstructorDetails} details Details about how to set up the instance.       */      constructor({ +        application,          id,          depth,          frameId      }) {          super(); +        /** @type {import('../application.js').Application} */ +        this._application = application;          /** @type {string} */          this._id = id;          /** @type {number} */ @@ -142,7 +144,7 @@ export class PopupWindow extends EventDispatcher {       * @returns {Promise<boolean>} `true` if the popup is visible, `false` otherwise.       */      async isVisible() { -        return (this._popupTabId !== null && await yomitan.api.isTabSearchPopup(this._popupTabId)); +        return (this._popupTabId !== null && await this._application.api.isTabSearchPopup(this._popupTabId));      }      /** @@ -274,7 +276,7 @@ export class PopupWindow extends EventDispatcher {       * @returns {Promise<import('display').DirectApiReturn<TName>|undefined>}       */      async _invoke(open, action, params) { -        if (yomitan.webExtension.unloaded) { +        if (this._application.webExtension.unloaded) {              return void 0;          } @@ -283,14 +285,14 @@ export class PopupWindow extends EventDispatcher {          const frameId = 0;          if (this._popupTabId !== null) {              try { -                return /** @type {import('display').DirectApiReturn<TName>} */ (await yomitan.crossFrame.invokeTab( +                return /** @type {import('display').DirectApiReturn<TName>} */ (await this._application.crossFrame.invokeTab(                      this._popupTabId,                      frameId,                      'displayPopupMessage2',                      message                  ));              } catch (e) { -                if (yomitan.webExtension.unloaded) { +                if (this._application.webExtension.unloaded) {                      open = false;                  }              } @@ -301,10 +303,10 @@ export class PopupWindow extends EventDispatcher {              return void 0;          } -        const {tabId} = await yomitan.api.getOrCreateSearchPopup({focus: 'ifCreated'}); +        const {tabId} = await this._application.api.getOrCreateSearchPopup({focus: 'ifCreated'});          this._popupTabId = tabId; -        return /** @type {import('display').DirectApiReturn<TName>} */ (await yomitan.crossFrame.invokeTab( +        return /** @type {import('display').DirectApiReturn<TName>} */ (await this._application.crossFrame.invokeTab(              this._popupTabId,              frameId,              'displayPopupMessage2', diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index c741e8f1..08ff0661 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -24,7 +24,6 @@ import {ExtensionError} from '../core/extension-error.js';  import {deepEqual} from '../core/utilities.js';  import {DocumentUtil} from '../dom/document-util.js';  import {loadStyle} from '../dom/style-util.js'; -import {yomitan} from '../yomitan.js';  import {ThemeController} from './theme-controller.js';  /** @@ -37,12 +36,15 @@ export class Popup extends EventDispatcher {       * @param {import('popup').PopupConstructorDetails} details The details used to construct the new instance.       */      constructor({ +        application,          id,          depth,          frameId,          childrenSupported      }) {          super(); +        /** @type {import('../application.js').Application} */ +        this._application = application;          /** @type {string} */          this._id = id;          /** @type {number} */ @@ -206,7 +208,7 @@ export class Popup extends EventDispatcher {          this._frame.addEventListener('scroll', (e) => e.stopPropagation());          this._frame.addEventListener('load', this._onFrameLoad.bind(this));          this._visible.on('change', this._onVisibleChange.bind(this)); -        yomitan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); +        this._application.on('extensionUnloaded', this._onExtensionUnloaded.bind(this));          this._onVisibleChange({value: this.isVisibleSync()});          this._themeController.prepare();      } @@ -362,7 +364,7 @@ export class Popup extends EventDispatcher {              useWebExtensionApi = false;              parentNode = this._shadow;          } -        const node = await loadStyle('yomitan-popup-outer-user-stylesheet', 'code', css, useWebExtensionApi, parentNode); +        const node = await loadStyle(this._application, 'yomitan-popup-outer-user-stylesheet', 'code', css, useWebExtensionApi, parentNode);          this.trigger('customOuterCssChanged', {node, useWebExtensionApi, inShadow});      } @@ -575,7 +577,7 @@ export class Popup extends EventDispatcher {              useWebExtensionApi = false;              parentNode = this._shadow;          } -        await loadStyle('yomitan-popup-outer-stylesheet', fileType, '/css/popup-outer.css', useWebExtensionApi, parentNode); +        await loadStyle(this._application, 'yomitan-popup-outer-stylesheet', fileType, '/css/popup-outer.css', useWebExtensionApi, parentNode);      }      /** @@ -697,7 +699,7 @@ export class Popup extends EventDispatcher {          /** @type {import('display').DirectApiMessage<TName>} */          const message = {action, params};          const wrappedMessage = this._frameClient.createMessage(message); -        return /** @type {import('display').DirectApiReturn<TName>} */ (await yomitan.crossFrame.invoke( +        return /** @type {import('display').DirectApiReturn<TName>} */ (await this._application.crossFrame.invoke(              this._frameClient.frameId,              'displayPopupMessage1',              /** @type {import('display').DirectApiFrameClientMessageAny} */ (wrappedMessage) @@ -714,7 +716,7 @@ export class Popup extends EventDispatcher {          try {              return await this._invoke(action, params);          } catch (e) { -            if (!yomitan.webExtension.unloaded) { throw e; } +            if (!this._application.webExtension.unloaded) { throw e; }              return void 0;          }      } @@ -1008,7 +1010,7 @@ export class Popup extends EventDispatcher {       */      async _setOptionsContext(optionsContext) {          this._optionsContext = optionsContext; -        const options = await yomitan.api.optionsGet(optionsContext); +        const options = await this._application.api.optionsGet(optionsContext);          const {general} = options;          this._themeController.theme = general.popupTheme;          this._themeController.outerTheme = general.popupOuterTheme; diff --git a/ext/js/yomitan.js b/ext/js/application.js index 33afac27..87bd0e86 100644 --- a/ext/js/yomitan.js +++ b/ext/js/application.js @@ -55,7 +55,7 @@ if (checkChromeNotAvailable()) {   * The Yomitan class is a core component through which various APIs are handled and invoked.   * @augments EventDispatcher<import('application').Events>   */ -export class Yomitan extends EventDispatcher { +export class Application extends EventDispatcher {      /**       * Creates a new instance. The instance should not be used until it has been fully prepare()'d.       */ @@ -158,7 +158,7 @@ export class Yomitan extends EventDispatcher {              await this._webExtension.sendMessagePromise({action: 'requestBackendReadySignal'});              await this._isBackendReadyPromise; -            this._crossFrame = new CrossFrameAPI(); +            this._crossFrame = new CrossFrameAPI(this._api);              await this._crossFrame.prepare();              log.on('log', this._onForwardLog.bind(this)); @@ -253,8 +253,3 @@ export class Yomitan extends EventDispatcher {          }      }  } - -/** - * The default Yomitan class instance. - */ -export const yomitan = new Yomitan(); diff --git a/ext/js/background/background-main.js b/ext/js/background/background-main.js index f5871a14..b63b4396 100644 --- a/ext/js/background/background-main.js +++ b/ext/js/background/background-main.js @@ -16,14 +16,15 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {yomitan} from '../yomitan.js'; +import {Application} from '../application.js';  import {Backend} from './backend.js';  /** Entry point. */  async function main() { -    yomitan.prepare(true); +    const application = new Application(); +    application.prepare(true); -    const backend = new Backend(yomitan.webExtension); +    const backend = new Backend(application.webExtension);      await backend.prepare();  } diff --git a/ext/js/comm/cross-frame-api.js b/ext/js/comm/cross-frame-api.js index fca7c84d..eb9bed38 100644 --- a/ext/js/comm/cross-frame-api.js +++ b/ext/js/comm/cross-frame-api.js @@ -22,7 +22,6 @@ import {EventListenerCollection} from '../core/event-listener-collection.js';  import {ExtensionError} from '../core/extension-error.js';  import {parseJson} from '../core/json.js';  import {log} from '../core/logger.js'; -import {yomitan} from '../yomitan.js';  /**   * @augments EventDispatcher<import('cross-frame-api').CrossFrameAPIPortEvents> @@ -290,7 +289,12 @@ export class CrossFrameAPIPort extends EventDispatcher {  }  export class CrossFrameAPI { -    constructor() { +    /** +     * @param {import('../comm/api.js').API} api +     */ +    constructor(api) { +        /** @type {import('../comm/api.js').API} */ +        this._api = api;          /** @type {number} */          this._ackTimeout = 3000; // 3 seconds          /** @type {number} */ @@ -310,7 +314,7 @@ export class CrossFrameAPI {      /** */      async prepare() {          chrome.runtime.onConnect.addListener(this._onConnect.bind(this)); -        ({tabId: this._tabId = null, frameId: this._frameId = null} = await yomitan.api.frameInformationGet()); +        ({tabId: this._tabId = null, frameId: this._frameId = null} = await this._api.frameInformationGet());      }      /** @@ -411,7 +415,7 @@ export class CrossFrameAPI {       * @returns {Promise<CrossFrameAPIPort>}       */      async _createCommPort(otherTabId, otherFrameId) { -        await yomitan.api.openCrossFramePort(otherTabId, otherFrameId); +        await this._api.openCrossFramePort(otherTabId, otherFrameId);          const tabPorts = this._commPorts.get(otherTabId);          if (typeof tabPorts !== 'undefined') { diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index 31739654..3e58d57b 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -17,7 +17,6 @@   */  import {generateId} from '../core/utilities.js'; -import {yomitan} from '../yomitan.js';  /**   * This class is used to return the ancestor frame IDs for the current frame. @@ -28,9 +27,12 @@ import {yomitan} from '../yomitan.js';  export class FrameAncestryHandler {      /**       * Creates a new instance. +     * @param {import('../comm/cross-frame-api.js').CrossFrameAPI} crossFrameApi       * @param {number} frameId The frame ID of the current frame the instance is instantiated in.       */ -    constructor(frameId) { +    constructor(crossFrameApi, frameId) { +        /** @type {import('../comm/cross-frame-api.js').CrossFrameAPI} */ +        this._crossFrameApi = crossFrameApi;          /** @type {number} */          this._frameId = frameId;          /** @type {boolean} */ @@ -59,7 +61,7 @@ export class FrameAncestryHandler {      prepare() {          if (this._isPrepared) { return; }          window.addEventListener('message', this._onWindowMessage.bind(this), false); -        yomitan.crossFrame.registerHandlers([ +        this._crossFrameApi.registerHandlers([              ['frameAncestryHandlerRequestFrameInfoResponse', this._onFrameAncestryHandlerRequestFrameInfoResponse.bind(this)]          ]);          this._isPrepared = true; @@ -211,7 +213,7 @@ export class FrameAncestryHandler {              const more = (window !== parent);              try { -                const response = await yomitan.crossFrame.invoke(originFrameId, 'frameAncestryHandlerRequestFrameInfoResponse', {uniqueId, frameId, nonce, more}); +                const response = await this._crossFrameApi.invoke(originFrameId, 'frameAncestryHandlerRequestFrameInfoResponse', {uniqueId, frameId, nonce, more});                  if (response === null) { return; }                  const nonce2 = response.nonce;                  if (typeof nonce2 !== 'string') { return; } diff --git a/ext/js/comm/frame-endpoint.js b/ext/js/comm/frame-endpoint.js index 0008417d..d2002d2e 100644 --- a/ext/js/comm/frame-endpoint.js +++ b/ext/js/comm/frame-endpoint.js @@ -18,10 +18,14 @@  import {EventListenerCollection} from '../core/event-listener-collection.js';  import {generateId} from '../core/utilities.js'; -import {yomitan} from '../yomitan.js';  export class FrameEndpoint { -    constructor() { +    /** +     * @param {import('../comm/api.js').API} api +     */ +    constructor(api) { +        /** @type {import('../comm/api.js').API} */ +        this._api = api;          /** @type {string} */          this._secret = generateId(16);          /** @type {?string} */ @@ -42,7 +46,7 @@ export class FrameEndpoint {          }          /** @type {import('frame-client').FrameEndpointReadyDetails} */          const details = {secret: this._secret}; -        yomitan.api.broadcastTab({action: 'frameEndpointReady', params: details}); +        this._api.broadcastTab({action: 'frameEndpointReady', params: details});      }      /** @@ -84,6 +88,6 @@ export class FrameEndpoint {          this._eventListeners.removeAllEventListeners();          /** @type {import('frame-client').FrameEndpointConnectedDetails} */          const details = {secret, token}; -        yomitan.api.sendMessageToFrame(hostFrameId, {action: 'frameEndpointConnected', params: details}); +        this._api.sendMessageToFrame(hostFrameId, {action: 'frameEndpointConnected', params: details});      }  } diff --git a/ext/js/comm/frame-offset-forwarder.js b/ext/js/comm/frame-offset-forwarder.js index a1f249e2..fe1ff98a 100644 --- a/ext/js/comm/frame-offset-forwarder.js +++ b/ext/js/comm/frame-offset-forwarder.js @@ -16,18 +16,20 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {yomitan} from '../yomitan.js';  import {FrameAncestryHandler} from './frame-ancestry-handler.js';  export class FrameOffsetForwarder {      /** +     * @param {import('../comm/cross-frame-api.js').CrossFrameAPI} crossFrameApi       * @param {number} frameId       */ -    constructor(frameId) { +    constructor(crossFrameApi, frameId) { +        /** @type {import('../comm/cross-frame-api.js').CrossFrameAPI} */ +        this._crossFrameApi = crossFrameApi;          /** @type {number} */          this._frameId = frameId;          /** @type {FrameAncestryHandler} */ -        this._frameAncestryHandler = new FrameAncestryHandler(frameId); +        this._frameAncestryHandler = new FrameAncestryHandler(crossFrameApi, frameId);      }      /** @@ -35,7 +37,7 @@ export class FrameOffsetForwarder {       */      prepare() {          this._frameAncestryHandler.prepare(); -        yomitan.crossFrame.registerHandlers([ +        this._crossFrameApi.registerHandlers([              ['frameOffsetForwarderGetChildFrameRect', this._onMessageGetChildFrameRect.bind(this)]          ]);      } @@ -55,7 +57,7 @@ export class FrameOffsetForwarder {              /** @type {Promise<?import('frame-offset-forwarder').ChildFrameRect>[]} */              const promises = [];              for (const frameId of ancestorFrameIds) { -                promises.push(yomitan.crossFrame.invoke(frameId, 'frameOffsetForwarderGetChildFrameRect', {frameId: childFrameId})); +                promises.push(this._crossFrameApi.invoke(frameId, 'frameOffsetForwarderGetChildFrameRect', {frameId: childFrameId}));                  childFrameId = frameId;              } diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js index 5bb943c2..a6e1aafc 100644 --- a/ext/js/data/anki-note-builder.js +++ b/ext/js/data/anki-note-builder.js @@ -19,15 +19,17 @@  import {ExtensionError} from '../core/extension-error.js';  import {deferPromise} from '../core/utilities.js';  import {convertHiraganaToKatakana, convertKatakanaToHiragana} from '../language/japanese.js'; -import {yomitan} from '../yomitan.js';  import {cloneFieldMarkerPattern, getRootDeckName} from './anki-util.js';  export class AnkiNoteBuilder {      /**       * Initiate an instance of AnkiNoteBuilder. +     * @param {import('anki-note-builder').MinimalApi} api       * @param {import('../templates/template-renderer-proxy.js').TemplateRendererProxy|import('../templates/sandbox/template-renderer.js').TemplateRenderer} templateRenderer       */ -    constructor(templateRenderer) { +    constructor(api, templateRenderer) { +        /** @type {import('anki-note-builder').MinimalApi} */ +        this._api = api;          /** @type {RegExp} */          this._markerPattern = cloneFieldMarkerPattern(true);          /** @type {import('../templates/template-renderer-proxy.js').TemplateRendererProxy|import('../templates/sandbox/template-renderer.js').TemplateRenderer} */ @@ -431,7 +433,7 @@ export class AnkiNoteBuilder {          // Inject media          const selectionText = injectSelectionText ? this._getSelectionText() : null; -        const injectedMedia = await yomitan.api.injectAnkiNoteMedia( +        const injectedMedia = await this._api.injectAnkiNoteMedia(              timestamp,              dictionaryEntryDetails,              audioDetails, @@ -483,7 +485,7 @@ export class AnkiNoteBuilder {      async _getTextFurigana(entries, optionsContext, scanLength) {          const results = [];          for (const {text, readingMode} of entries) { -            const parseResults = await yomitan.api.parseText(text, optionsContext, scanLength, true, false); +            const parseResults = await this._api.parseText(text, optionsContext, scanLength, true, false);              let data = null;              for (const {source, content} of parseResults) {                  if (source !== 'scanning-parser') { continue; } diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index 68d28d33..c19cfa22 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -24,7 +24,6 @@ import {isNoteDataValid} from '../data/anki-util.js';  import {PopupMenu} from '../dom/popup-menu.js';  import {querySelectorNotNull} from '../dom/query-selector.js';  import {TemplateRendererProxy} from '../templates/template-renderer-proxy.js'; -import {yomitan} from '../yomitan.js';  export class DisplayAnki {      /** @@ -41,7 +40,7 @@ export class DisplayAnki {          /** @type {?string} */          this._ankiFieldTemplatesDefault = null;          /** @type {AnkiNoteBuilder} */ -        this._ankiNoteBuilder = new AnkiNoteBuilder(new TemplateRendererProxy()); +        this._ankiNoteBuilder = new AnkiNoteBuilder(display.application.api, new TemplateRendererProxy());          /** @type {?import('./display-notification.js').DisplayNotification} */          this._errorNotification = null;          /** @type {?EventListenerCollection} */ @@ -487,7 +486,7 @@ export class DisplayAnki {              let noteId = null;              let addNoteOkay = false;              try { -                noteId = await yomitan.api.addAnkiNote(note); +                noteId = await this._display.application.api.addAnkiNote(note);                  addNoteOkay = true;              } catch (e) {                  allErrors.length = 0; @@ -500,7 +499,7 @@ export class DisplayAnki {                  } else {                      if (this._suspendNewCards) {                          try { -                            await yomitan.api.suspendAnkiCardsForNote(noteId); +                            await this._display.application.api.suspendAnkiCardsForNote(noteId);                          } catch (e) {                              allErrors.push(toError(e));                          } @@ -605,7 +604,7 @@ export class DisplayAnki {          templates = this._ankiFieldTemplatesDefault;          if (typeof templates === 'string') { return templates; } -        templates = await yomitan.api.getDefaultAnkiFieldTemplates(); +        templates = await this._display.application.api.getDefaultAnkiFieldTemplates();          this._ankiFieldTemplatesDefault = templates;          return templates;      } @@ -639,12 +638,12 @@ export class DisplayAnki {          let ankiError = null;          try {              if (forceCanAddValue !== null) { -                if (!await yomitan.api.isAnkiConnected()) { +                if (!await this._display.application.api.isAnkiConnected()) {                      throw new Error('Anki not connected');                  }                  infos = this._getAnkiNoteInfoForceValue(notes, forceCanAddValue);              } else { -                infos = await yomitan.api.getAnkiNoteInfo(notes, fetchAdditionalInfo); +                infos = await this._display.application.api.getAnkiNoteInfo(notes, fetchAdditionalInfo);              }          } catch (e) {              infos = this._getAnkiNoteInfoForceValue(notes, false); @@ -853,7 +852,7 @@ export class DisplayAnki {          const noteIds = this._getNodeNoteIds(node);          if (noteIds.length === 0) { return; }          try { -            await yomitan.api.noteView(noteIds[0], this._noteGuiMode, false); +            await this._display.application.api.noteView(noteIds[0], this._noteGuiMode, false);          } catch (e) {              const displayErrors = (                  toError(e).message === 'Mode not supported' ? diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index 8cbfc83f..4acd6494 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -20,7 +20,6 @@ import {EventListenerCollection} from '../core/event-listener-collection.js';  import {PopupMenu} from '../dom/popup-menu.js';  import {querySelectorNotNull} from '../dom/query-selector.js';  import {AudioSystem} from '../media/audio-system.js'; -import {yomitan} from '../yomitan.js';  export class DisplayAudio {      /** @@ -676,7 +675,7 @@ export class DisplayAudio {       */      async _getTermAudioInfoList(source, term, reading) {          const sourceData = this._getSourceData(source); -        const infoList = await yomitan.api.getTermAudioInfoList(sourceData, term, reading); +        const infoList = await this._display.application.api.getTermAudioInfoList(sourceData, term, reading);          return infoList.map((info) => ({info, audioPromise: null, audioResolved: false, audio: null}));      } diff --git a/ext/js/display/display-content-manager.js b/ext/js/display/display-content-manager.js index 4465ce3e..81742279 100644 --- a/ext/js/display/display-content-manager.js +++ b/ext/js/display/display-content-manager.js @@ -18,7 +18,6 @@  import {EventListenerCollection} from '../core/event-listener-collection.js';  import {base64ToArrayBuffer} from '../data/sandbox/array-buffer-util.js'; -import {yomitan} from '../yomitan.js';  /**   * The content manager which is used when generating HTML display content. @@ -140,7 +139,7 @@ export class DisplayContentManager {       */      async _getMediaData(path, dictionary) {          const token = this._token; -        const datas = await yomitan.api.getMedia([{path, dictionary}]); +        const datas = await this._display.application.api.getMedia([{path, dictionary}]);          if (token === this._token && datas.length > 0) {              const data = datas[0];              const buffer = base64ToArrayBuffer(data.content); diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js index 01f6f38b..fdfe3d4a 100644 --- a/ext/js/display/display-generator.js +++ b/ext/js/display/display-generator.js @@ -21,7 +21,6 @@ import {isObject} from '../core/utilities.js';  import {getDisambiguations, getGroupedPronunciations, getTermFrequency, groupKanjiFrequencies, groupTermFrequencies, groupTermTags, isNonNounVerbOrAdjective} from '../dictionary/dictionary-data-util.js';  import {HtmlTemplateCollection} from '../dom/html-template-collection.js';  import {distributeFurigana, getKanaMorae, getPitchCategory, isCodePointKanji, isStringPartiallyJapanese} from '../language/japanese.js'; -import {yomitan} from '../yomitan.js';  import {createPronunciationDownstepPosition, createPronunciationGraph, createPronunciationText} from './sandbox/pronunciation-generator.js';  import {StructuredContentGenerator} from './sandbox/structured-content-generator.js'; @@ -40,9 +39,11 @@ export class DisplayGenerator {          this._structuredContentGenerator = new StructuredContentGenerator(this._contentManager, document);      } -    /** */ -    async prepare() { -        const html = await yomitan.api.getDisplayTemplatesHtml(); +    /** +     * @param {import('../comm/api.js').API} api +     */ +    async prepare(api) { +        const html = await api.getDisplayTemplatesHtml();          this._templates.load(html);          this.updateHotkeys();      } diff --git a/ext/js/display/display-profile-selection.js b/ext/js/display/display-profile-selection.js index b61b49d5..3df79b74 100644 --- a/ext/js/display/display-profile-selection.js +++ b/ext/js/display/display-profile-selection.js @@ -20,7 +20,6 @@ import {EventListenerCollection} from '../core/event-listener-collection.js';  import {generateId} from '../core/utilities.js';  import {PanelElement} from '../dom/panel-element.js';  import {querySelectorNotNull} from '../dom/query-selector.js'; -import {yomitan} from '../yomitan.js';  export class DisplayProfileSelection {      /** @@ -50,7 +49,7 @@ export class DisplayProfileSelection {      /** */      async prepare() { -        yomitan.on('optionsUpdated', this._onOptionsUpdated.bind(this)); +        this._display.application.on('optionsUpdated', this._onOptionsUpdated.bind(this));          this._profileButton.addEventListener('click', this._onProfileButtonClick.bind(this), false);          this._profileListNeedsUpdate = true;      } @@ -92,7 +91,7 @@ export class DisplayProfileSelection {      /** */      async _updateProfileList() {          this._profileListNeedsUpdate = false; -        const options = await yomitan.api.optionsGetFull(); +        const options = await this._display.application.api.optionsGetFull();          this._eventListeners.removeAllEventListeners();          const displayGenerator = this._display.displayGenerator; @@ -138,7 +137,7 @@ export class DisplayProfileSelection {              scope: 'global',              optionsContext: null          }; -        await yomitan.api.modifySettings([modification], this._source); +        await this._display.application.api.modifySettings([modification], this._source);          this._setProfilePanelVisible(false);      }  } diff --git a/ext/js/display/display.js b/ext/js/display/display.js index c7a2775d..4114cc45 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -32,7 +32,6 @@ import {ScrollElement} from '../dom/scroll-element.js';  import {TextSourceGenerator} from '../dom/text-source-generator.js';  import {HotkeyHelpController} from '../input/hotkey-help-controller.js';  import {TextScanner} from '../language/text-scanner.js'; -import {yomitan} from '../yomitan.js';  import {DisplayContentManager} from './display-content-manager.js';  import {DisplayGenerator} from './display-generator.js';  import {DisplayHistory} from './display-history.js'; @@ -46,14 +45,17 @@ import {QueryParser} from './query-parser.js';   */  export class Display extends EventDispatcher {      /** +     * @param {import('../application.js').Application} application       * @param {number|undefined} tabId       * @param {number|undefined} frameId       * @param {import('display').DisplayPageType} pageType       * @param {import('../dom/document-focus-controller.js').DocumentFocusController} documentFocusController       * @param {import('../input/hotkey-handler.js').HotkeyHandler} hotkeyHandler       */ -    constructor(tabId, frameId, pageType, documentFocusController, hotkeyHandler) { +    constructor(application, tabId, frameId, pageType, documentFocusController, hotkeyHandler) {          super(); +        /** @type {import('../application.js').Application} */ +        this._application = application;          /** @type {number|undefined} */          this._tabId = tabId;          /** @type {number|undefined} */ @@ -131,6 +133,7 @@ export class Display extends EventDispatcher {          this._textSourceGenerator = new TextSourceGenerator();          /** @type {QueryParser} */          this._queryParser = new QueryParser({ +            api: application.api,              getSearchContext: this._getSearchContext.bind(this),              textSourceGenerator: this._textSourceGenerator          }); @@ -163,7 +166,7 @@ export class Display extends EventDispatcher {          /** @type {boolean} */          this._childrenSupported = true;          /** @type {?FrameEndpoint} */ -        this._frameEndpoint = (pageType === 'popup' ? new FrameEndpoint() : null); +        this._frameEndpoint = (pageType === 'popup' ? new FrameEndpoint(this._application.api) : null);          /** @type {?import('environment').Browser} */          this._browser = null;          /** @type {?HTMLTextAreaElement} */ @@ -224,6 +227,11 @@ export class Display extends EventDispatcher {          /* eslint-enable no-multi-spaces */      } +    /** @type {import('../application.js').Application} */ +    get application() { +        return this._application; +    } +      /** @type {DisplayGenerator} */      get displayGenerator() {          return this._displayGenerator; @@ -307,7 +315,7 @@ export class Display extends EventDispatcher {          // State setup          const {documentElement} = document; -        const {browser} = await yomitan.api.getEnvironmentInfo(); +        const {browser} = await this._application.api.getEnvironmentInfo();          this._browser = browser;          if (documentElement !== null) { @@ -315,8 +323,8 @@ export class Display extends EventDispatcher {          }          // Prepare -        await this._hotkeyHelpController.prepare(); -        await this._displayGenerator.prepare(); +        await this._hotkeyHelpController.prepare(this._application.api); +        await this._displayGenerator.prepare(this._application.api);          this._queryParser.prepare();          this._history.prepare();          this._optionToggleHotkeyHandler.prepare(); @@ -325,8 +333,8 @@ export class Display extends EventDispatcher {          this._history.on('stateChanged', this._onStateChanged.bind(this));          this._queryParser.on('searched', this._onQueryParserSearch.bind(this));          this._progressIndicatorVisible.on('change', this._onProgressIndicatorVisibleChanged.bind(this)); -        yomitan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); -        yomitan.crossFrame.registerHandlers([ +        this._application.on('extensionUnloaded', this._onExtensionUnloaded.bind(this)); +        this._application.crossFrame.registerHandlers([              ['displayPopupMessage1', this._onDisplayPopupMessage1.bind(this)],              ['displayPopupMessage2', this._onDisplayPopupMessage2.bind(this)]          ]); @@ -384,7 +392,7 @@ export class Display extends EventDispatcher {       * @param {Error} error       */      onError(error) { -        if (yomitan.webExtension.unloaded) { return; } +        if (this._application.webExtension.unloaded) { return; }          log.error(error);      } @@ -412,7 +420,7 @@ export class Display extends EventDispatcher {      /** */      async updateOptions() { -        const options = await yomitan.api.optionsGet(this.getOptionsContext()); +        const options = await this._application.api.optionsGet(this.getOptionsContext());          const {scanning: scanningOptions, sentenceParsing: sentenceParsingOptions} = options;          this._options = options; @@ -586,7 +594,7 @@ export class Display extends EventDispatcher {          if (typeof this._contentOriginTabId !== 'number' || typeof this._contentOriginFrameId !== 'number') {              throw new Error('No content origin is assigned');          } -        return await yomitan.crossFrame.invokeTab(this._contentOriginTabId, this._contentOriginFrameId, action, params); +        return await this._application.crossFrame.invokeTab(this._contentOriginTabId, this._contentOriginFrameId, action, params);      }      /** @@ -599,7 +607,7 @@ export class Display extends EventDispatcher {          if (this._parentFrameId === null || this._parentFrameId === this._frameId) {              throw new Error('Invalid parent frame');          } -        return await yomitan.crossFrame.invoke(this._parentFrameId, action, params); +        return await this._application.crossFrame.invoke(this._parentFrameId, action, params);      }      /** @@ -721,7 +729,7 @@ export class Display extends EventDispatcher {      /** @type {import('display').WindowApiHandler<'displayExtensionUnloaded'>} */      _onMessageExtensionUnloaded() { -        yomitan.webExtension.triggerUnloaded(); +        this._application.webExtension.triggerUnloaded();      }      // Private @@ -900,7 +908,7 @@ export class Display extends EventDispatcher {              const element = /** @type {Element} */ (e.currentTarget);              let query = element.textContent;              if (query === null) { query = ''; } -            const dictionaryEntries = await yomitan.api.kanjiFind(query, optionsContext); +            const dictionaryEntries = await this._application.api.kanjiFind(query, optionsContext);              /** @type {import('display').ContentDetails} */              const details = {                  focus: false, @@ -1136,7 +1144,7 @@ export class Display extends EventDispatcher {       */      async _findDictionaryEntries(isKanji, source, wildcardsEnabled, optionsContext) {          if (isKanji) { -            const dictionaryEntries = await yomitan.api.kanjiFind(source, optionsContext); +            const dictionaryEntries = await this._application.api.kanjiFind(source, optionsContext);              return dictionaryEntries;          } else {              /** @type {import('api').FindTermsDetails} */ @@ -1155,7 +1163,7 @@ export class Display extends EventDispatcher {                  }              } -            const {dictionaryEntries} = await yomitan.api.termsFind(source, findDetails, optionsContext); +            const {dictionaryEntries} = await this._application.api.termsFind(source, findDetails, optionsContext);              return dictionaryEntries;          }      } @@ -1640,7 +1648,7 @@ export class Display extends EventDispatcher {      /** */      _closePopups() { -        yomitan.triggerClosePopups(); +        this._application.triggerClosePopups();      }      /** @@ -1711,11 +1719,12 @@ export class Display extends EventDispatcher {              import('../app/frontend.js')          ]); -        const popupFactory = new PopupFactory(this._frameId); +        const popupFactory = new PopupFactory(this._application, this._frameId);          popupFactory.prepare();          /** @type {import('frontend').ConstructorDetails} */          const setupNestedPopupsOptions = { +            application: this._application,              useProxyPopup,              parentPopupId,              parentFrameId, @@ -1828,6 +1837,7 @@ export class Display extends EventDispatcher {          if (this._contentTextScanner === null) {              this._contentTextScanner = new TextScanner({ +                api: this._application.api,                  node: window,                  getSearchContext: this._getSearchContext.bind(this),                  searchTerms: true, @@ -1888,7 +1898,7 @@ export class Display extends EventDispatcher {       * @param {import('text-scanner').SearchedEventDetails} details       */      _onContentTextScannerSearched({type, dictionaryEntries, sentence, textSource, optionsContext, error}) { -        if (error !== null && !yomitan.webExtension.unloaded) { +        if (error !== null && !this._application.webExtension.unloaded) {              log.error(error);          } diff --git a/ext/js/display/option-toggle-hotkey-handler.js b/ext/js/display/option-toggle-hotkey-handler.js index d9065e7f..b2f48a3e 100644 --- a/ext/js/display/option-toggle-hotkey-handler.js +++ b/ext/js/display/option-toggle-hotkey-handler.js @@ -16,10 +16,9 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {generateId} from '../core/utilities.js';  import {ExtensionError} from '../core/extension-error.js';  import {toError} from '../core/to-error.js'; -import {yomitan} from '../yomitan.js'; +import {generateId} from '../core/utilities.js';  export class OptionToggleHotkeyHandler {      /** @@ -72,7 +71,7 @@ export class OptionToggleHotkeyHandler {          try {              const optionsContext = this._display.getOptionsContext(); -            const getSettingsResponse = (await yomitan.api.getSettings([{ +            const getSettingsResponse = (await this._display.application.api.getSettings([{                  scope: 'profile',                  path,                  optionsContext @@ -97,7 +96,7 @@ export class OptionToggleHotkeyHandler {                  value,                  optionsContext              }; -            const modifySettingsResponse = (await yomitan.api.modifySettings([modification], this._source))[0]; +            const modifySettingsResponse = (await this._display.application.api.modifySettings([modification], this._source))[0];              const {error: modifySettingsError} = modifySettingsResponse;              if (typeof modifySettingsError !== 'undefined') {                  throw ExtensionError.deserialize(modifySettingsError); diff --git a/ext/js/display/popup-main.js b/ext/js/display/popup-main.js index 870e039e..2ca2fcd3 100644 --- a/ext/js/display/popup-main.js +++ b/ext/js/display/popup-main.js @@ -16,10 +16,10 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {Application} from '../application.js';  import {log} from '../core/logger.js';  import {DocumentFocusController} from '../dom/document-focus-controller.js';  import {HotkeyHandler} from '../input/hotkey-handler.js'; -import {yomitan} from '../yomitan.js';  import {DisplayAnki} from './display-anki.js';  import {DisplayAudio} from './display-audio.js';  import {DisplayProfileSelection} from './display-profile-selection.js'; @@ -32,14 +32,15 @@ async function main() {          const documentFocusController = new DocumentFocusController();          documentFocusController.prepare(); -        await yomitan.prepare(); +        const application = new Application(); +        await application.prepare(); -        const {tabId, frameId} = await yomitan.api.frameInformationGet(); +        const {tabId, frameId} = await application.api.frameInformationGet();          const hotkeyHandler = new HotkeyHandler(); -        hotkeyHandler.prepare(); +        hotkeyHandler.prepare(application.crossFrame); -        const display = new Display(tabId, frameId, 'popup', documentFocusController, hotkeyHandler); +        const display = new Display(application, tabId, frameId, 'popup', documentFocusController, hotkeyHandler);          await display.prepare();          const displayAudio = new DisplayAudio(display); @@ -58,7 +59,7 @@ async function main() {          document.documentElement.dataset.loaded = 'true'; -        yomitan.ready(); +        application.ready();      } catch (e) {          log.error(e);      } diff --git a/ext/js/display/query-parser.js b/ext/js/display/query-parser.js index 178bb110..daa298d2 100644 --- a/ext/js/display/query-parser.js +++ b/ext/js/display/query-parser.js @@ -21,7 +21,6 @@ import {log} from '../core/logger.js';  import {querySelectorNotNull} from '../dom/query-selector.js';  import {convertHiraganaToKatakana, convertKatakanaToHiragana, isStringEntirelyKana} from '../language/japanese.js';  import {TextScanner} from '../language/text-scanner.js'; -import {yomitan} from '../yomitan.js';  /**   * @augments EventDispatcher<import('query-parser').Events> @@ -30,8 +29,10 @@ export class QueryParser extends EventDispatcher {      /**       * @param {import('display').QueryParserConstructorDetails} details       */ -    constructor({getSearchContext, textSourceGenerator}) { +    constructor({api, getSearchContext, textSourceGenerator}) {          super(); +        /** @type {import('../comm/api.js').API} */ +        this._api = api;          /** @type {import('display').GetSearchContextCallback} */          this._getSearchContext = getSearchContext;          /** @type {string} */ @@ -58,6 +59,7 @@ export class QueryParser extends EventDispatcher {          this._queryParserModeSelect = querySelectorNotNull(document, '#query-parser-mode-select');          /** @type {TextScanner} */          this._textScanner = new TextScanner({ +            api,              node: this._queryParser,              getSearchContext,              searchTerms: true, @@ -128,7 +130,7 @@ export class QueryParser extends EventDispatcher {          /** @type {?import('core').TokenObject} */          const token = {};          this._setTextToken = token; -        this._parseResults = await yomitan.api.parseText(text, this._getOptionsContext(), this._scanLength, this._useInternalParser, this._useMecabParser); +        this._parseResults = await this._api.parseText(text, this._getOptionsContext(), this._scanLength, this._useInternalParser, this._useMecabParser);          if (this._setTextToken !== token) { return; }          this._refreshSelectedParser(); @@ -214,7 +216,7 @@ export class QueryParser extends EventDispatcher {              scope: 'profile',              optionsContext          }; -        yomitan.api.modifySettings([modification], 'search'); +        this._api.modifySettings([modification], 'search');      }      /** diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index ff4340c1..49c69520 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -21,7 +21,6 @@ import {ClipboardMonitor} from '../comm/clipboard-monitor.js';  import {createApiMap, invokeApiMapHandler} from '../core/api-map.js';  import {EventListenerCollection} from '../core/event-listener-collection.js';  import {querySelectorNotNull} from '../dom/query-selector.js'; -import {yomitan} from '../yomitan.js';  export class SearchDisplayController {      /** @@ -71,7 +70,7 @@ export class SearchDisplayController {          /** @type {ClipboardMonitor} */          this._clipboardMonitor = new ClipboardMonitor({              clipboardReader: { -                getText: yomitan.api.clipboardGet.bind(yomitan.api) +                getText: this._display.application.api.clipboardGet.bind(this._display.application.api)              }          });          /** @type {import('application').ApiMap} */ @@ -89,7 +88,7 @@ export class SearchDisplayController {          this._searchPersistentStateController.on('modeChange', this._onModeChange.bind(this));          chrome.runtime.onMessage.addListener(this._onMessage.bind(this)); -        yomitan.on('optionsUpdated', this._onOptionsUpdated.bind(this)); +        this._display.application.on('optionsUpdated', this._onOptionsUpdated.bind(this));          this._display.on('optionsUpdated', this._onDisplayOptionsUpdated.bind(this));          this._display.on('contentUpdateStart', this._onContentUpdateStart.bind(this)); @@ -297,7 +296,7 @@ export class SearchDisplayController {              scope: 'profile',              optionsContext: this._display.getOptionsContext()          }; -        yomitan.api.modifySettings([modification], 'search'); +        this._display.application.api.modifySettings([modification], 'search');      }      /** @@ -430,7 +429,7 @@ export class SearchDisplayController {              scope: 'profile',              optionsContext: this._display.getOptionsContext()          }; -        await yomitan.api.modifySettings([modification], 'search'); +        await this._display.application.api.modifySettings([modification], 'search');      }      /** */ diff --git a/ext/js/display/search-main.js b/ext/js/display/search-main.js index dedad163..dc4f1b7e 100644 --- a/ext/js/display/search-main.js +++ b/ext/js/display/search-main.js @@ -16,10 +16,10 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {Application} from '../application.js';  import {log} from '../core/logger.js';  import {DocumentFocusController} from '../dom/document-focus-controller.js';  import {HotkeyHandler} from '../input/hotkey-handler.js'; -import {yomitan} from '../yomitan.js';  import {DisplayAnki} from './display-anki.js';  import {DisplayAudio} from './display-audio.js';  import {Display} from './display.js'; @@ -30,6 +30,8 @@ import {SearchPersistentStateController} from './search-persistent-state-control  /** Entry point. */  async function main() {      try { +        const application = new Application(); +          const documentFocusController = new DocumentFocusController('#search-textbox');          documentFocusController.prepare(); @@ -39,14 +41,14 @@ async function main() {          const searchActionPopupController = new SearchActionPopupController(searchPersistentStateController);          searchActionPopupController.prepare(); -        await yomitan.prepare(); +        await application.prepare(); -        const {tabId, frameId} = await yomitan.api.frameInformationGet(); +        const {tabId, frameId} = await application.api.frameInformationGet();          const hotkeyHandler = new HotkeyHandler(); -        hotkeyHandler.prepare(); +        hotkeyHandler.prepare(application.crossFrame); -        const display = new Display(tabId, frameId, 'search', documentFocusController, hotkeyHandler); +        const display = new Display(application, tabId, frameId, 'search', documentFocusController, hotkeyHandler);          await display.prepare();          const displayAudio = new DisplayAudio(display); @@ -62,7 +64,7 @@ async function main() {          document.documentElement.dataset.loaded = 'true'; -        yomitan.ready(); +        application.ready();      } catch (e) {          log.error(e);      } diff --git a/ext/js/dom/style-util.js b/ext/js/dom/style-util.js index ac20e655..e5046e5c 100644 --- a/ext/js/dom/style-util.js +++ b/ext/js/dom/style-util.js @@ -15,8 +15,6 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {yomitan} from '../yomitan.js'; -  /** @type {Map<string, ?HTMLStyleElement|HTMLLinkElement>} */  const injectedStylesheets = new Map();  /** @type {WeakMap<Node, Map<string, ?HTMLStyleElement|HTMLLinkElement>>} */ @@ -54,6 +52,7 @@ function setInjectedStylesheet(id, parentNode, value) {  }  /** + * @param {import('../application.js').Application} application   * @param {string} id   * @param {'code'|'file'|'file-content'} type   * @param {string} value @@ -62,8 +61,8 @@ function setInjectedStylesheet(id, parentNode, value) {   * @returns {Promise<?HTMLStyleElement|HTMLLinkElement>}   * @throws {Error}   */ -export async function loadStyle(id, type, value, useWebExtensionApi = false, parentNode = null) { -    if (useWebExtensionApi && yomitan.isExtensionUrl(window.location.href)) { +export async function loadStyle(application, id, type, value, useWebExtensionApi = false, parentNode = null) { +    if (useWebExtensionApi && application.isExtensionUrl(window.location.href)) {          // Permissions error will occur if trying to use the WebExtension API to inject into an extension page          useWebExtensionApi = false;      } @@ -79,7 +78,7 @@ export async function loadStyle(id, type, value, useWebExtensionApi = false, par      }      if (type === 'file-content') { -        value = await yomitan.api.getStylesheetContent(value); +        value = await application.api.getStylesheetContent(value);          type = 'code';          useWebExtensionApi = false;      } @@ -91,7 +90,7 @@ export async function loadStyle(id, type, value, useWebExtensionApi = false, par          }          setInjectedStylesheet(id, parentNode, null); -        await yomitan.api.injectStylesheet(type, value); +        await application.api.injectStylesheet(type, value);          return null;      } diff --git a/ext/js/input/hotkey-handler.js b/ext/js/input/hotkey-handler.js index 3b40a86d..9caedcc2 100644 --- a/ext/js/input/hotkey-handler.js +++ b/ext/js/input/hotkey-handler.js @@ -19,7 +19,6 @@  import {EventDispatcher} from '../core/event-dispatcher.js';  import {EventListenerCollection} from '../core/event-listener-collection.js';  import {DocumentUtil} from '../dom/document-util.js'; -import {yomitan} from '../yomitan.js';  /**   * Class which handles hotkey events and actions. @@ -47,11 +46,12 @@ export class HotkeyHandler extends EventDispatcher {      /**       * Begins listening to key press events in order to detect hotkeys. +     * @param {import('../comm/cross-frame-api.js').CrossFrameAPI} crossFrameApi       */ -    prepare() { +    prepare(crossFrameApi) {          this._isPrepared = true;          this._updateEventHandlers(); -        yomitan.crossFrame.registerHandlers([ +        crossFrameApi.registerHandlers([              ['hotkeyHandlerForwardHotkey', this._onMessageForwardHotkey.bind(this)]          ]);      } diff --git a/ext/js/input/hotkey-help-controller.js b/ext/js/input/hotkey-help-controller.js index 4c4f56d5..a75ab9db 100644 --- a/ext/js/input/hotkey-help-controller.js +++ b/ext/js/input/hotkey-help-controller.js @@ -16,9 +16,8 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {isObject} from '../core/utilities.js';  import {parseJson} from '../core/json.js'; -import {yomitan} from '../yomitan.js'; +import {isObject} from '../core/utilities.js';  import {HotkeyUtil} from './hotkey-util.js';  export class HotkeyHelpController { @@ -34,10 +33,10 @@ export class HotkeyHelpController {      }      /** -     * @returns {Promise<void>} +     * @param {import('../comm/api.js').API} api       */ -    async prepare() { -        const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); +    async prepare(api) { +        const {platform: {os}} = await 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 6228a82c..d78c4c74 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -22,7 +22,6 @@ import {log} from '../core/logger.js';  import {clone} from '../core/utilities.js';  import {DocumentUtil} from '../dom/document-util.js';  import {TextSourceElement} from '../dom/text-source-element.js'; -import {yomitan} from '../yomitan.js';  /**   * @augments EventDispatcher<import('text-scanner').Events> @@ -32,6 +31,7 @@ export class TextScanner extends EventDispatcher {       * @param {import('text-scanner').ConstructorDetails} details       */      constructor({ +        api,          node,          getSearchContext,          ignoreElements = null, @@ -43,6 +43,8 @@ export class TextScanner extends EventDispatcher {          textSourceGenerator      }) {          super(); +        /** @type {import('../comm/api.js').API} */ +        this._api = api;          /** @type {HTMLElement|Window} */          this._node = node;          /** @type {import('text-scanner').GetSearchContextCallback} */ @@ -1204,7 +1206,7 @@ export class TextScanner extends EventDispatcher {          /** @type {import('api').FindTermsDetails} */          const details = {};          if (this._matchTypePrefix) { details.matchType = 'prefix'; } -        const {dictionaryEntries, originalTextLength} = await yomitan.api.termsFind(searchText, details, optionsContext); +        const {dictionaryEntries, originalTextLength} = await this._api.termsFind(searchText, details, optionsContext);          if (dictionaryEntries.length === 0) { return null; }          textSource.setEndOffset(originalTextLength, false, layoutAwareScan); @@ -1236,7 +1238,7 @@ export class TextScanner extends EventDispatcher {          const searchText = this.getTextSourceContent(textSource, 1, layoutAwareScan);          if (searchText.length === 0) { return null; } -        const dictionaryEntries = await yomitan.api.kanjiFind(searchText, optionsContext); +        const dictionaryEntries = await this._api.kanjiFind(searchText, optionsContext);          if (dictionaryEntries.length === 0) { return null; }          textSource.setEndOffset(1, false, layoutAwareScan); @@ -1564,7 +1566,7 @@ export class TextScanner extends EventDispatcher {       */      async _hasJapanese(text) {          try { -            return await yomitan.api.textHasJapaneseCharacters(text); +            return await this._api.textHasJapaneseCharacters(text);          } catch (e) {              return false;          } diff --git a/ext/js/pages/action-popup-main.js b/ext/js/pages/action-popup-main.js index 86201e83..6d2c85ab 100644 --- a/ext/js/pages/action-popup-main.js +++ b/ext/js/pages/action-popup-main.js @@ -16,13 +16,18 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {Application} from '../application.js';  import {getAllPermissions, hasRequiredPermissionsForOptions} from '../data/permissions-util.js';  import {querySelectorNotNull} from '../dom/query-selector.js';  import {HotkeyHelpController} from '../input/hotkey-help-controller.js'; -import {yomitan} from '../yomitan.js';  class DisplayController { -    constructor() { +    /** +     * @param {import('../comm/api.js').API} api +     */ +    constructor(api) { +        /** @type {import('../comm/api.js').API} */ +        this._api = api;          /** @type {?import('settings').Options} */          this._optionsFull = null;      } @@ -36,7 +41,7 @@ class DisplayController {          this._setupButtonEvents('.action-open-search', 'openSearchPage', chrome.runtime.getURL('/search.html'), this._onSearchClick.bind(this));          this._setupButtonEvents('.action-open-info', 'openInfoPage', chrome.runtime.getURL('/info.html')); -        const optionsFull = await yomitan.api.optionsGetFull(); +        const optionsFull = await this._api.optionsGetFull();          this._optionsFull = optionsFull;          this._setupHotkeys(); @@ -108,7 +113,7 @@ class DisplayController {                          const result = customHandler(e);                          if (typeof result !== 'undefined') { return; }                      } -                    yomitan.api.commandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'}); +                    this._api.commandExec(command, {mode: e.ctrlKey ? 'newTab' : 'existingOrNewTab'});                      e.preventDefault();                  };                  /** @@ -116,7 +121,7 @@ class DisplayController {                   */                  const onAuxClick = (e) => {                      if (e.button !== 1) { return; } -                    yomitan.api.commandExec(command, {mode: 'newTab'}); +                    this._api.commandExec(command, {mode: 'newTab'});                      e.preventDefault();                  };                  node.addEventListener('click', onClick, false); @@ -180,7 +185,7 @@ class DisplayController {       */      _setupOptions({options}) {          const extensionEnabled = options.general.enable; -        const onToggleChanged = () => yomitan.api.commandExec('toggleTextScanning'); +        const onToggleChanged = () => this._api.commandExec('toggleTextScanning');          for (const toggle of /** @type {NodeListOf<HTMLInputElement>} */ (document.querySelectorAll('#enable-search,#enable-search2'))) {              toggle.checked = extensionEnabled;              toggle.addEventListener('change', onToggleChanged, false); @@ -192,7 +197,7 @@ class DisplayController {      /** */      async _setupHotkeys() {          const hotkeyHelpController = new HotkeyHelpController(); -        await hotkeyHelpController.prepare(); +        await hotkeyHelpController.prepare(this._api);          const {profiles, profileCurrent} = /** @type {import('settings').Options} */ (this._optionsFull);          const primaryProfile = (profileCurrent >= 0 && profileCurrent < profiles.length) ? profiles[profileCurrent] : null; @@ -250,7 +255,7 @@ class DisplayController {              scope: 'global',              optionsContext: null          }; -        await yomitan.api.modifySettings([modification], 'action-popup'); +        await this._api.modifySettings([modification], 'action-popup');      }      /** @@ -258,7 +263,7 @@ class DisplayController {       */      async _updateDictionariesEnabledWarnings(options) {          const noDictionariesEnabledWarnings = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.no-dictionaries-enabled-warning')); -        const dictionaries = await yomitan.api.getDictionaryInfo(); +        const dictionaries = await this._api.getDictionaryInfo();          const enabledDictionaries = new Set();          for (const {name, enabled} of options.dictionaries) { @@ -295,21 +300,22 @@ class DisplayController {      /** @returns {Promise<boolean>} */      async _isSafari() { -        const {browser} = await yomitan.api.getEnvironmentInfo(); +        const {browser} = await this._api.getEnvironmentInfo();          return browser === 'safari';      }  }  /** Entry point. */  async function main() { -    await yomitan.prepare(); +    const application = new Application(); +    await application.prepare(); -    yomitan.api.logIndicatorClear(); +    application.api.logIndicatorClear(); -    const displayController = new DisplayController(); +    const displayController = new DisplayController(application.api);      displayController.prepare(); -    yomitan.ready(); +    application.ready();  }  await main(); diff --git a/ext/js/pages/info-main.js b/ext/js/pages/info-main.js index dd55ab4b..ca5094b1 100644 --- a/ext/js/pages/info-main.js +++ b/ext/js/pages/info-main.js @@ -16,11 +16,11 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {Application} from '../application.js';  import {log} from '../core/logger.js';  import {promiseTimeout} from '../core/utilities.js';  import {DocumentFocusController} from '../dom/document-focus-controller.js';  import {querySelectorNotNull} from '../dom/query-selector.js'; -import {yomitan} from '../yomitan.js';  import {BackupController} from './settings/backup-controller.js';  import {SettingsController} from './settings/settings-controller.js'; @@ -57,11 +57,13 @@ function getOperatingSystemDisplayName(os) {      }  } -/** */ -async function showAnkiConnectInfo() { +/** + * @param {import('../comm/api.js').API} api + */ +async function showAnkiConnectInfo(api) {      let ankiConnectVersion = null;      try { -        ankiConnectVersion = await yomitan.api.getAnkiConnectVersion(); +        ankiConnectVersion = await api.getAnkiConnectVersion();      } catch (e) {          // NOP      } @@ -78,11 +80,13 @@ async function showAnkiConnectInfo() {      ankiVersionUnknownElement.hidden = (ankiConnectVersion !== null);  } -/** */ -async function showDictionaryInfo() { +/** + * @param {import('../comm/api.js').API} api + */ +async function showDictionaryInfo(api) {      let dictionaryInfos;      try { -        dictionaryInfos = await yomitan.api.getDictionaryInfo(); +        dictionaryInfos = await api.getDictionaryInfo();      } catch (e) {          return;      } @@ -121,11 +125,12 @@ async function main() {          const manifest = chrome.runtime.getManifest();          const language = chrome.i18n.getUILanguage(); -        await yomitan.prepare(); +        const application = new Application(); +        await application.prepare();          const {userAgent} = navigator;          const {name, version} = manifest; -        const {browser, platform: {os}} = await yomitan.api.getEnvironmentInfo(); +        const {browser, platform: {os}} = await application.api.getEnvironmentInfo();          /** @type {HTMLLinkElement} */          const thisVersionLink = querySelectorNotNull(document, '#release-notes-this-version-link'); @@ -149,10 +154,10 @@ async function main() {          languageElement.textContent = `${language}`;          userAgentElement.textContent = userAgent; -        showAnkiConnectInfo(); -        showDictionaryInfo(); +        showAnkiConnectInfo(application.api); +        showDictionaryInfo(application.api); -        const settingsController = new SettingsController(); +        const settingsController = new SettingsController(application);          await settingsController.prepare();          const backupController = new BackupController(settingsController, null); diff --git a/ext/js/pages/permissions-main.js b/ext/js/pages/permissions-main.js index 38135689..1659bea5 100644 --- a/ext/js/pages/permissions-main.js +++ b/ext/js/pages/permissions-main.js @@ -16,11 +16,11 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {Application} from '../application.js';  import {log} from '../core/logger.js';  import {promiseTimeout} from '../core/utilities.js';  import {DocumentFocusController} from '../dom/document-focus-controller.js';  import {querySelectorNotNull} from '../dom/query-selector.js'; -import {yomitan} from '../yomitan.js';  import {ExtensionContentController} from './common/extension-content-controller.js';  import {ModalController} from './settings/modal-controller.js';  import {PermissionsOriginController} from './settings/permissions-origin-controller.js'; @@ -30,11 +30,11 @@ import {SettingsController} from './settings/settings-controller.js';  import {SettingsDisplayController} from './settings/settings-display-controller.js';  /** - * @returns {Promise<void>} + * @param {import('../comm/api.js').API} api   */ -async function setupEnvironmentInfo() { +async function setupEnvironmentInfo(api) {      const {manifest_version: manifestVersion} = chrome.runtime.getManifest(); -    const {browser, platform} = await yomitan.api.getEnvironmentInfo(); +    const {browser, platform} = await api.getEnvironmentInfo();      document.documentElement.dataset.browser = browser;      document.documentElement.dataset.os = platform.os;      document.documentElement.dataset.manifestVersion = `${manifestVersion}`; @@ -90,6 +90,8 @@ function setupPermissionsToggles() {  /** Entry point. */  async function main() {      try { +        const application = new Application(); +          const documentFocusController = new DocumentFocusController();          documentFocusController.prepare(); @@ -98,9 +100,9 @@ async function main() {          setupPermissionsToggles(); -        await yomitan.prepare(); +        await application.prepare(); -        setupEnvironmentInfo(); +        setupEnvironmentInfo(application.api);          /** @type {HTMLInputElement} */          const permissionCheckbox1 = querySelectorNotNull(document, '#permission-checkbox-allow-in-private-windows'); @@ -121,7 +123,7 @@ async function main() {          const modalController = new ModalController();          modalController.prepare(); -        const settingsController = new SettingsController(); +        const settingsController = new SettingsController(application);          await settingsController.prepare();          const permissionsToggleController = new PermissionsToggleController(settingsController); @@ -130,7 +132,7 @@ async function main() {          const permissionsOriginController = new PermissionsOriginController(settingsController);          permissionsOriginController.prepare(); -        const persistentStorageController = new PersistentStorageController(); +        const persistentStorageController = new PersistentStorageController(application);          persistentStorageController.prepare();          await promiseTimeout(100); diff --git a/ext/js/pages/settings/anki-controller.js b/ext/js/pages/settings/anki-controller.js index 09ab3c03..ae6a71db 100644 --- a/ext/js/pages/settings/anki-controller.js +++ b/ext/js/pages/settings/anki-controller.js @@ -26,7 +26,6 @@ import {getRequiredPermissionsForAnkiFieldValue, hasPermissions, setPermissionsG  import {querySelectorNotNull} from '../../dom/query-selector.js';  import {SelectorObserver} from '../../dom/selector-observer.js';  import {ObjectPropertyAccessor} from '../../general/object-property-accessor.js'; -import {yomitan} from '../../yomitan.js';  export class AnkiController {      /** @@ -510,7 +509,7 @@ export class AnkiController {          let noteId = null;          for (const query of queries) { -            const notes = await yomitan.api.findAnkiNotes(query); +            const notes = await this._settingsController.application.api.findAnkiNotes(query);              if (notes.length > 0) {                  noteId = notes[0];                  break; @@ -521,7 +520,7 @@ export class AnkiController {              throw new Error('Could not find a note to test with');          } -        await yomitan.api.noteView(noteId, mode, false); +        await this._settingsController.application.api.noteView(noteId, mode, false);      }      /** diff --git a/ext/js/pages/settings/anki-templates-controller.js b/ext/js/pages/settings/anki-templates-controller.js index 869c9e16..332102cf 100644 --- a/ext/js/pages/settings/anki-templates-controller.js +++ b/ext/js/pages/settings/anki-templates-controller.js @@ -21,7 +21,6 @@ import {toError} from '../../core/to-error.js';  import {AnkiNoteBuilder} from '../../data/anki-note-builder.js';  import {querySelectorNotNull} from '../../dom/query-selector.js';  import {TemplateRendererProxy} from '../../templates/template-renderer-proxy.js'; -import {yomitan} from '../../yomitan.js';  export class AnkiTemplatesController {      /** @@ -55,12 +54,12 @@ export class AnkiTemplatesController {          /** @type {?import('./modal.js').Modal} */          this._fieldTemplateResetModal = null;          /** @type {AnkiNoteBuilder} */ -        this._ankiNoteBuilder = new AnkiNoteBuilder(new TemplateRendererProxy()); +        this._ankiNoteBuilder = new AnkiNoteBuilder(settingsController.application.api, new TemplateRendererProxy());      }      /** */      async prepare() { -        this._defaultFieldTemplates = await yomitan.api.getDefaultAnkiFieldTemplates(); +        this._defaultFieldTemplates = await this._settingsController.application.api.getDefaultAnkiFieldTemplates();          /** @type {HTMLButtonElement} */          const menuButton = querySelectorNotNull(document, '#anki-card-templates-test-field-menu-button'); @@ -205,7 +204,7 @@ export class AnkiTemplatesController {       */      async _getDictionaryEntry(text, optionsContext) {          if (this._cachedDictionaryEntryText !== text) { -            const {dictionaryEntries} = await yomitan.api.termsFind(text, {}, optionsContext); +            const {dictionaryEntries} = await this._settingsController.application.api.termsFind(text, {}, optionsContext);              if (dictionaryEntries.length === 0) { return null; }              this._cachedDictionaryEntryValue = dictionaryEntries[0]; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 053cc96b..79733c4d 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -25,7 +25,6 @@ import {OptionsUtil} from '../../data/options-util.js';  import {getAllPermissions} from '../../data/permissions-util.js';  import {arrayBufferUtf8Decode} from '../../data/sandbox/array-buffer-util.js';  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  import {DictionaryController} from './dictionary-controller.js';  export class BackupController { @@ -134,8 +133,8 @@ export class BackupController {       */      async _getSettingsExportData(date) {          const optionsFull = await this._settingsController.getOptionsFull(); -        const environment = await yomitan.api.getEnvironmentInfo(); -        const fieldTemplatesDefault = await yomitan.api.getDefaultAnkiFieldTemplates(); +        const environment = await this._settingsController.application.api.getEnvironmentInfo(); +        const fieldTemplatesDefault = await this._settingsController.application.api.getDefaultAnkiFieldTemplates();          const permissions = await getAllPermissions();          // Format options @@ -644,10 +643,10 @@ export class BackupController {       * @param {File} file       */      async _importDatabase(databaseName, file) { -        await yomitan.api.purgeDatabase(); +        await this._settingsController.application.api.purgeDatabase();          await Dexie.import(file, {progressCallback: this._databaseImportProgressCallback}); -        yomitan.api.triggerDatabaseUpdated('dictionary', 'import'); -        yomitan.triggerStorageChanged(); +        this._settingsController.application.api.triggerDatabaseUpdated('dictionary', 'import'); +        this._settingsController.application.triggerStorageChanged();      }      /** */ diff --git a/ext/js/pages/settings/collapsible-dictionary-controller.js b/ext/js/pages/settings/collapsible-dictionary-controller.js index e6930049..5ba61e0c 100644 --- a/ext/js/pages/settings/collapsible-dictionary-controller.js +++ b/ext/js/pages/settings/collapsible-dictionary-controller.js @@ -18,7 +18,6 @@  import {EventListenerCollection} from '../../core/event-listener-collection.js';  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  export class CollapsibleDictionaryController {      /** @@ -45,7 +44,7 @@ export class CollapsibleDictionaryController {      async prepare() {          await this._onDatabaseUpdated(); -        yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); +        this._settingsController.application.on('databaseUpdated', this._onDatabaseUpdated.bind(this));          this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));          this._settingsController.on('dictionarySettingsReordered', this._onDictionarySettingsReordered.bind(this));      } diff --git a/ext/js/pages/settings/dictionary-controller.js b/ext/js/pages/settings/dictionary-controller.js index 10dfdcdc..1d3c1730 100644 --- a/ext/js/pages/settings/dictionary-controller.js +++ b/ext/js/pages/settings/dictionary-controller.js @@ -20,7 +20,6 @@ import {EventListenerCollection} from '../../core/event-listener-collection.js';  import {log} from '../../core/logger.js';  import {DictionaryWorker} from '../../dictionary/dictionary-worker.js';  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  class DictionaryEntry {      /** @@ -437,7 +436,7 @@ export class DictionaryController {          /** @type {HTMLButtonElement} */          const dictionaryMoveButton = querySelectorNotNull(document, '#dictionary-move-button'); -        yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); +        this._settingsController.application.on('databaseUpdated', this._onDatabaseUpdated.bind(this));          this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));          this._allCheckbox.addEventListener('change', this._onAllCheckboxChange.bind(this), false);          dictionaryDeleteButton.addEventListener('click', this._onDictionaryConfirmDelete.bind(this), false); @@ -917,7 +916,7 @@ export class DictionaryController {       */      async _deleteDictionaryInternal(dictionaryTitle, onProgress) {          await new DictionaryWorker().deleteDictionary(dictionaryTitle, onProgress); -        yomitan.api.triggerDatabaseUpdated('dictionary', 'delete'); +        this._settingsController.application.api.triggerDatabaseUpdated('dictionary', 'delete');      }      /** @@ -947,7 +946,7 @@ export class DictionaryController {      /** */      _triggerStorageChanged() { -        yomitan.triggerStorageChanged(); +        this._settingsController.application.triggerStorageChanged();      }      /** */ diff --git a/ext/js/pages/settings/dictionary-import-controller.js b/ext/js/pages/settings/dictionary-import-controller.js index 183c0ccd..0484001d 100644 --- a/ext/js/pages/settings/dictionary-import-controller.js +++ b/ext/js/pages/settings/dictionary-import-controller.js @@ -21,7 +21,6 @@ import {log} from '../../core/logger.js';  import {toError} from '../../core/to-error.js';  import {DictionaryWorker} from '../../dictionary/dictionary-worker.js';  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  import {DictionaryController} from './dictionary-controller.js';  export class DictionaryImportController { @@ -120,7 +119,7 @@ export class DictionaryImportController {              this._setModifying(true);              this._hideErrors(); -            await yomitan.api.purgeDatabase(); +            await this._settingsController.application.api.purgeDatabase();              const errors = await this._clearDictionarySettings();              if (errors.length > 0) { @@ -236,7 +235,7 @@ export class DictionaryImportController {      async _importDictionary(file, importDetails, onProgress) {          const archiveContent = await this._readFile(file);          const {result, errors} = await new DictionaryWorker().importDictionary(archiveContent, importDetails, onProgress); -        yomitan.api.triggerDatabaseUpdated('dictionary', 'import'); +        this._settingsController.application.api.triggerDatabaseUpdated('dictionary', 'import');          const errors2 = await this._addDictionarySettings(result.sequenced, result.title);          if (errors.length > 0) { @@ -399,6 +398,6 @@ export class DictionaryImportController {      /** */      _triggerStorageChanged() { -        yomitan.triggerStorageChanged(); +        this._settingsController.application.triggerStorageChanged();      }  } diff --git a/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js b/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js index 61330bb8..61eefffa 100644 --- a/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js +++ b/ext/js/pages/settings/extension-keyboard-shortcuts-controller.js @@ -20,7 +20,6 @@ import {EventListenerCollection} from '../../core/event-listener-collection.js';  import {isObject} from '../../core/utilities.js';  import {querySelectorNotNull} from '../../dom/query-selector.js';  import {HotkeyUtil} from '../../input/hotkey-util.js'; -import {yomitan} from '../../yomitan.js';  import {KeyboardMouseInputField} from './keyboard-mouse-input-field.js';  export class ExtensionKeyboardShortcutController { @@ -63,7 +62,7 @@ export class ExtensionKeyboardShortcutController {              this._clearButton.addEventListener('click', this._onClearClick.bind(this));          } -        const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); +        const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo();          this._os = os;          this._hotkeyUtil.os = os; diff --git a/ext/js/pages/settings/keyboard-shortcuts-controller.js b/ext/js/pages/settings/keyboard-shortcuts-controller.js index 396b0cc2..9392f768 100644 --- a/ext/js/pages/settings/keyboard-shortcuts-controller.js +++ b/ext/js/pages/settings/keyboard-shortcuts-controller.js @@ -20,7 +20,6 @@ import {EventListenerCollection} from '../../core/event-listener-collection.js';  import {DocumentUtil} from '../../dom/document-util.js';  import {querySelectorNotNull} from '../../dom/query-selector.js';  import {ObjectPropertyAccessor} from '../../general/object-property-accessor.js'; -import {yomitan} from '../../yomitan.js';  import {KeyboardMouseInputField} from './keyboard-mouse-input-field.js';  export class KeyboardShortcutController { @@ -81,7 +80,7 @@ export class KeyboardShortcutController {      /** */      async prepare() { -        const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); +        const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo();          this._os = os;          this._addButton.addEventListener('click', this._onAddClick.bind(this)); diff --git a/ext/js/pages/settings/mecab-controller.js b/ext/js/pages/settings/mecab-controller.js index dec2be68..ba2f6166 100644 --- a/ext/js/pages/settings/mecab-controller.js +++ b/ext/js/pages/settings/mecab-controller.js @@ -18,10 +18,14 @@  import {toError} from '../../core/to-error.js';  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  export class MecabController { -    constructor() { +    /** +     * @param {import('../../comm/api.js').API} api +     */ +    constructor(api) { +        /** @type {import('../../comm/api.js').API} */ +        this._api = api;          /** @type {HTMLButtonElement} */          this._testButton = querySelectorNotNull(document, '#test-mecab-button');          /** @type {HTMLElement} */ @@ -55,7 +59,7 @@ export class MecabController {              /** @type {HTMLButtonElement} */ (this._testButton).disabled = true;              resultsContainer.textContent = '';              resultsContainer.hidden = true; -            await yomitan.api.testMecab(); +            await this._api.testMecab();              this._setStatus('Connection was successful', false);          } catch (e) {              this._setStatus(toError(e).message, true); diff --git a/ext/js/pages/settings/persistent-storage-controller.js b/ext/js/pages/settings/persistent-storage-controller.js index baffa969..8b7726dd 100644 --- a/ext/js/pages/settings/persistent-storage-controller.js +++ b/ext/js/pages/settings/persistent-storage-controller.js @@ -18,14 +18,23 @@  import {isObject} from '../../core/utilities.js';  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  export class PersistentStorageController { -    constructor() { +    /** +     * @param {import('../../application.js').Application} application +     */ +    constructor(application) { +        /** @type {import('../../application.js').Application} */ +        this._application = application;          /** @type {HTMLInputElement} */          this._persistentStorageCheckbox = querySelectorNotNull(document, '#storage-persistent-checkbox');      } +    /** @type {import('../../application.js').Application} */ +    get application() { +        return this._application; +    } +      /** */      async prepare() {          this._persistentStorageCheckbox.addEventListener('change', this._onPersistentStorageCheckboxChange.bind(this), false); @@ -82,7 +91,7 @@ export class PersistentStorageController {          const node = document.querySelector('#storage-persistent-fail-warning');          if (node !== null) { node.hidden = isStoragePeristent; } -        yomitan.triggerStorageChanged(); +        this._application.triggerStorageChanged();      }      /** diff --git a/ext/js/pages/settings/popup-preview-frame-main.js b/ext/js/pages/settings/popup-preview-frame-main.js index e3d7d0ec..fd08bf1d 100644 --- a/ext/js/pages/settings/popup-preview-frame-main.js +++ b/ext/js/pages/settings/popup-preview-frame-main.js @@ -17,17 +17,18 @@   */  import {PopupFactory} from '../../app/popup-factory.js'; +import {Application} from '../../application.js';  import {log} from '../../core/logger.js';  import {HotkeyHandler} from '../../input/hotkey-handler.js'; -import {yomitan} from '../../yomitan.js';  import {PopupPreviewFrame} from './popup-preview-frame.js';  /** Entry point. */  async function main() {      try { -        await yomitan.prepare(); +        const application = new Application(); +        await application.prepare(); -        const {tabId, frameId} = await yomitan.api.frameInformationGet(); +        const {tabId, frameId} = await application.api.frameInformationGet();          if (typeof tabId === 'undefined') {              throw new Error('Failed to get tabId');          } @@ -36,12 +37,12 @@ async function main() {          }          const hotkeyHandler = new HotkeyHandler(); -        hotkeyHandler.prepare(); +        hotkeyHandler.prepare(application.crossFrame); -        const popupFactory = new PopupFactory(frameId); +        const popupFactory = new PopupFactory(application, frameId);          popupFactory.prepare(); -        const preview = new PopupPreviewFrame(tabId, frameId, popupFactory, hotkeyHandler); +        const preview = new PopupPreviewFrame(application, tabId, frameId, popupFactory, hotkeyHandler);          await preview.prepare();          document.documentElement.dataset.loaded = 'true'; diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index 7a899641..e9cfa541 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -20,16 +20,18 @@ import * as wanakana from '../../../lib/wanakana.js';  import {Frontend} from '../../app/frontend.js';  import {querySelectorNotNull} from '../../dom/query-selector.js';  import {TextSourceRange} from '../../dom/text-source-range.js'; -import {yomitan} from '../../yomitan.js';  export class PopupPreviewFrame {      /** +     * @param {import('../../application.js').Application} application       * @param {number} tabId       * @param {number} frameId       * @param {import('../../app/popup-factory.js').PopupFactory} popupFactory       * @param {import('../../input/hotkey-handler.js').HotkeyHandler} hotkeyHandler       */ -    constructor(tabId, frameId, popupFactory, hotkeyHandler) { +    constructor(application, tabId, frameId, popupFactory, hotkeyHandler) { +        /** @type {import('../../application.js').Application} */ +        this._application = application;          /** @type {number} */          this._tabId = tabId;          /** @type {number} */ @@ -86,11 +88,12 @@ export class PopupPreviewFrame {          // Overwrite API functions          /** @type {?(optionsContext: import('settings').OptionsContext) => Promise<import('settings').ProfileOptions>} */ -        this._apiOptionsGetOld = yomitan.api.optionsGet.bind(yomitan.api); -        yomitan.api.optionsGet = this._apiOptionsGet.bind(this); +        this._apiOptionsGetOld = this._application.api.optionsGet.bind(this._application.api); +        this._application.api.optionsGet = this._apiOptionsGet.bind(this);          // Overwrite frontend          this._frontend = new Frontend({ +            application: this._application,              tabId: this._tabId,              frameId: this._frameId,              popupFactory: this._popupFactory, diff --git a/ext/js/pages/settings/popup-window-controller.js b/ext/js/pages/settings/popup-window-controller.js index 1b767f77..62879f3b 100644 --- a/ext/js/pages/settings/popup-window-controller.js +++ b/ext/js/pages/settings/popup-window-controller.js @@ -17,9 +17,16 @@   */  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  export class PopupWindowController { +    /** +     * @param {import('../../comm/api.js').API} api +     */ +    constructor(api) { +        /** @type {import('../../comm/api.js').API} */ +        this._api = api; +    } +      /** */      prepare() {          /** @type {HTMLElement} */ @@ -39,6 +46,6 @@ export class PopupWindowController {      /** */      async _testWindowOpen() { -        await yomitan.api.getOrCreateSearchPopup({focus: true}); +        await this._api.getOrCreateSearchPopup({focus: true});      }  } diff --git a/ext/js/pages/settings/profile-controller.js b/ext/js/pages/settings/profile-controller.js index 73926a69..c5ccbe7d 100644 --- a/ext/js/pages/settings/profile-controller.js +++ b/ext/js/pages/settings/profile-controller.js @@ -19,7 +19,6 @@  import {EventListenerCollection} from '../../core/event-listener-collection.js';  import {clone} from '../../core/utilities.js';  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  import {ProfileConditionsUI} from './profile-conditions-ui.js';  export class ProfileController { @@ -82,7 +81,7 @@ export class ProfileController {      /** */      async prepare() { -        const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); +        const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo();          this._profileConditionsUI.os = os;          this._profileRemoveModal = this._modalController.getModal('profile-remove'); diff --git a/ext/js/pages/settings/scan-inputs-controller.js b/ext/js/pages/settings/scan-inputs-controller.js index 2dfa3de3..f1547fe4 100644 --- a/ext/js/pages/settings/scan-inputs-controller.js +++ b/ext/js/pages/settings/scan-inputs-controller.js @@ -19,7 +19,6 @@  import {EventListenerCollection} from '../../core/event-listener-collection.js';  import {DocumentUtil} from '../../dom/document-util.js';  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  import {KeyboardMouseInputField} from './keyboard-mouse-input-field.js';  export class ScanInputsController { @@ -43,7 +42,7 @@ export class ScanInputsController {      /** */      async prepare() { -        const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); +        const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo();          this._os = os;          this._scanningInputCountNodes = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.scanning-input-count')); diff --git a/ext/js/pages/settings/scan-inputs-simple-controller.js b/ext/js/pages/settings/scan-inputs-simple-controller.js index e4f34c99..b09a3a76 100644 --- a/ext/js/pages/settings/scan-inputs-simple-controller.js +++ b/ext/js/pages/settings/scan-inputs-simple-controller.js @@ -18,7 +18,6 @@  import {querySelectorNotNull} from '../../dom/query-selector.js';  import {HotkeyUtil} from '../../input/hotkey-util.js'; -import {yomitan} from '../../yomitan.js';  import {ScanInputsController} from './scan-inputs-controller.js';  export class ScanInputsSimpleController { @@ -40,7 +39,7 @@ export class ScanInputsSimpleController {      /** */      async prepare() { -        const {platform: {os}} = await yomitan.api.getEnvironmentInfo(); +        const {platform: {os}} = await this._settingsController.application.api.getEnvironmentInfo();          this._hotkeyUtil.os = os;          this._mainScanModifierKeyInputHasOther = false; diff --git a/ext/js/pages/settings/secondary-search-dictionary-controller.js b/ext/js/pages/settings/secondary-search-dictionary-controller.js index 592f5eeb..c708bf65 100644 --- a/ext/js/pages/settings/secondary-search-dictionary-controller.js +++ b/ext/js/pages/settings/secondary-search-dictionary-controller.js @@ -18,7 +18,6 @@  import {EventListenerCollection} from '../../core/event-listener-collection.js';  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  export class SecondarySearchDictionaryController {      /** @@ -41,7 +40,7 @@ export class SecondarySearchDictionaryController {      async prepare() {          await this._onDatabaseUpdated(); -        yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); +        this._settingsController.application.on('databaseUpdated', this._onDatabaseUpdated.bind(this));          this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));          this._settingsController.on('dictionarySettingsReordered', this._onDictionarySettingsReordered.bind(this));      } diff --git a/ext/js/pages/settings/settings-controller.js b/ext/js/pages/settings/settings-controller.js index 49fa9c9e..3f389271 100644 --- a/ext/js/pages/settings/settings-controller.js +++ b/ext/js/pages/settings/settings-controller.js @@ -22,14 +22,18 @@ import {generateId, isObject} from '../../core/utilities.js';  import {OptionsUtil} from '../../data/options-util.js';  import {getAllPermissions} from '../../data/permissions-util.js';  import {HtmlTemplateCollection} from '../../dom/html-template-collection.js'; -import {yomitan} from '../../yomitan.js';  /**   * @augments EventDispatcher<import('settings-controller').Events>   */  export class SettingsController extends EventDispatcher { -    constructor() { +    /** +     * @param {import('../../application.js').Application} application +     */ +    constructor(application) {          super(); +        /** @type {import('../../application.js').Application} */ +        this._application = application;          /** @type {number} */          this._profileIndex = 0;          /** @type {string} */ @@ -43,6 +47,11 @@ export class SettingsController extends EventDispatcher {          this._templates.load(document);      } +    /** @type {import('../../application.js').Application} */ +    get application() { +        return this._application; +    } +      /** @type {string} */      get source() {          return this._source; @@ -60,7 +69,7 @@ export class SettingsController extends EventDispatcher {      /** */      async prepare() { -        yomitan.on('optionsUpdated', this._onOptionsUpdated.bind(this)); +        this._application.on('optionsUpdated', this._onOptionsUpdated.bind(this));          if (this._canObservePermissionsChanges()) {              chrome.permissions.onAdded.addListener(this._onPermissionsChanged.bind(this));              chrome.permissions.onRemoved.addListener(this._onPermissionsChanged.bind(this)); @@ -82,14 +91,14 @@ export class SettingsController extends EventDispatcher {       */      async getOptions() {          const optionsContext = this.getOptionsContext(); -        return await yomitan.api.optionsGet(optionsContext); +        return await this._application.api.optionsGet(optionsContext);      }      /**       * @returns {Promise<import('settings').Options>}       */      async getOptionsFull() { -        return await yomitan.api.optionsGetFull(); +        return await this._application.api.optionsGetFull();      }      /** @@ -97,7 +106,7 @@ export class SettingsController extends EventDispatcher {       */      async setAllSettings(value) {          const profileIndex = value.profileCurrent; -        await yomitan.api.setAllSettings(value, this._source); +        await this._application.api.setAllSettings(value, this._source);          this._setProfileIndex(profileIndex, true);      } @@ -171,7 +180,7 @@ export class SettingsController extends EventDispatcher {       * @returns {Promise<import('dictionary-importer').Summary[]>}       */      async getDictionaryInfo() { -        return await yomitan.api.getDictionaryInfo(); +        return await this._application.api.getDictionaryInfo();      }      /** @@ -279,7 +288,7 @@ export class SettingsController extends EventDispatcher {              this._modifyOptionsScope(target2);              return target2;          }); -        return await yomitan.api.getSettings(targets2); +        return await this._application.api.getSettings(targets2);      }      /** @@ -294,7 +303,7 @@ export class SettingsController extends EventDispatcher {              this._modifyOptionsScope(target2);              return target2;          }); -        return await yomitan.api.modifySettings(targets2, this._source); +        return await this._application.api.modifySettings(targets2, this._source);      }      /** diff --git a/ext/js/pages/settings/settings-main.js b/ext/js/pages/settings/settings-main.js index 7e458043..c3391173 100644 --- a/ext/js/pages/settings/settings-main.js +++ b/ext/js/pages/settings/settings-main.js @@ -16,10 +16,10 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {Application} from '../../application.js';  import {log} from '../../core/logger.js';  import {DocumentFocusController} from '../../dom/document-focus-controller.js';  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  import {ExtensionContentController} from '../common/extension-content-controller.js';  import {AnkiController} from './anki-controller.js';  import {AnkiTemplatesController} from './anki-templates-controller.js'; @@ -78,7 +78,8 @@ async function main() {              document.documentElement.dataset.loadingStalled = 'true';          }, 1000); -        await yomitan.prepare(); +        const application = new Application(); +        await application.prepare();          if (prepareTimer !== null) {              clearTimeout(prepareTimer); @@ -91,10 +92,10 @@ async function main() {          const modalController = new ModalController();          modalController.prepare(); -        const settingsController = new SettingsController(); +        const settingsController = new SettingsController(application);          await settingsController.prepare(); -        const persistentStorageController = new PersistentStorageController(); +        const persistentStorageController = new PersistentStorageController(application);          persistentStorageController.prepare();          const storageController = new StorageController(persistentStorageController); @@ -154,10 +155,10 @@ async function main() {          const extensionKeyboardShortcutController = new ExtensionKeyboardShortcutController(settingsController);          extensionKeyboardShortcutController.prepare(); -        const popupWindowController = new PopupWindowController(); +        const popupWindowController = new PopupWindowController(application.api);          popupWindowController.prepare(); -        const mecabController = new MecabController(); +        const mecabController = new MecabController(application.api);          mecabController.prepare();          const collapsibleDictionaryController = new CollapsibleDictionaryController(settingsController); diff --git a/ext/js/pages/settings/sort-frequency-dictionary-controller.js b/ext/js/pages/settings/sort-frequency-dictionary-controller.js index f5b230f0..c8e9918b 100644 --- a/ext/js/pages/settings/sort-frequency-dictionary-controller.js +++ b/ext/js/pages/settings/sort-frequency-dictionary-controller.js @@ -17,7 +17,6 @@   */  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  export class SortFrequencyDictionaryController {      /** @@ -42,7 +41,7 @@ export class SortFrequencyDictionaryController {      async prepare() {          await this._onDatabaseUpdated(); -        yomitan.on('databaseUpdated', this._onDatabaseUpdated.bind(this)); +        this._settingsController.application.on('databaseUpdated', this._onDatabaseUpdated.bind(this));          this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));          this._sortFrequencyDictionarySelect.addEventListener('change', this._onSortFrequencyDictionarySelectChange.bind(this));          this._sortFrequencyDictionaryOrderSelect.addEventListener('change', this._onSortFrequencyDictionaryOrderSelectChange.bind(this)); @@ -157,7 +156,7 @@ export class SortFrequencyDictionaryController {          const lessCommonTerms = ['行なう', '論じる', '過す', '行方', '人口', '猫', '犬', '滝', '理', '暁'];          const terms = [...moreCommonTerms, ...lessCommonTerms]; -        const frequencies = await yomitan.api.getTermFrequencies( +        const frequencies = await this._settingsController.application.api.getTermFrequencies(              terms.map((term) => ({term, reading: null})),              [dictionary]          ); diff --git a/ext/js/pages/settings/storage-controller.js b/ext/js/pages/settings/storage-controller.js index 6be1fe24..9a3aa23a 100644 --- a/ext/js/pages/settings/storage-controller.js +++ b/ext/js/pages/settings/storage-controller.js @@ -17,7 +17,6 @@   */  import {querySelectorNotNull} from '../../dom/query-selector.js'; -import {yomitan} from '../../yomitan.js';  export class StorageController {      /** @@ -61,7 +60,7 @@ export class StorageController {          const storageRefreshButton = querySelectorNotNull(document, '#storage-refresh');          storageRefreshButton.addEventListener('click', this._onStorageRefreshButtonClick.bind(this), false); -        yomitan.on('storageChanged', this._onStorageChanged.bind(this)); +        this._persistentStorageController.application.on('storageChanged', this._onStorageChanged.bind(this));          this._updateStats();      } diff --git a/ext/js/pages/welcome-main.js b/ext/js/pages/welcome-main.js index 35472ec2..030d2826 100644 --- a/ext/js/pages/welcome-main.js +++ b/ext/js/pages/welcome-main.js @@ -16,10 +16,10 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {Application} from '../application.js';  import {log} from '../core/logger.js';  import {DocumentFocusController} from '../dom/document-focus-controller.js';  import {querySelectorNotNull} from '../dom/query-selector.js'; -import {yomitan} from '../yomitan.js';  import {ExtensionContentController} from './common/extension-content-controller.js';  import {DictionaryController} from './settings/dictionary-controller.js';  import {DictionaryImportController} from './settings/dictionary-import-controller.js'; @@ -31,10 +31,12 @@ import {SettingsController} from './settings/settings-controller.js';  import {SettingsDisplayController} from './settings/settings-display-controller.js';  import {StatusFooter} from './settings/status-footer.js'; -/** */ -async function setupEnvironmentInfo() { +/** + * @param {import('../comm/api.js').API} api + */ +async function setupEnvironmentInfo(api) {      const {manifest_version: manifestVersion} = chrome.runtime.getManifest(); -    const {browser, platform} = await yomitan.api.getEnvironmentInfo(); +    const {browser, platform} = await api.getEnvironmentInfo();      document.documentElement.dataset.browser = browser;      document.documentElement.dataset.os = platform.os;      document.documentElement.dataset.manifestVersion = `${manifestVersion}`; @@ -62,9 +64,10 @@ async function main() {          const statusFooter = new StatusFooter(statusFooterElement);          statusFooter.prepare(); -        await yomitan.prepare(); +        const application = new Application(); +        await application.prepare(); -        setupEnvironmentInfo(); +        setupEnvironmentInfo(application.api);          chrome.storage.session.get({'needsCustomTemplatesWarning': false}).then((result) => {              if (result.needsCustomTemplatesWarning) { @@ -78,7 +81,7 @@ async function main() {          const modalController = new ModalController();          modalController.prepare(); -        const settingsController = new SettingsController(); +        const settingsController = new SettingsController(application);          await settingsController.prepare();          const dictionaryController = new DictionaryController(settingsController, modalController, statusFooter); diff --git a/test/utilities/anki.js b/test/utilities/anki.js index 69f4ce8b..d0e095bd 100644 --- a/test/utilities/anki.js +++ b/test/utilities/anki.js @@ -144,7 +144,8 @@ export async function getTemplateRenderResults(dictionaryEntries, type, mode, te                  }                  break;          } -        const ankiNoteBuilder = new AnkiNoteBuilder(ankiTemplateRenderer.templateRenderer); +        const api = new MinimalApi(); +        const ankiNoteBuilder = new AnkiNoteBuilder(api, ankiTemplateRenderer.templateRenderer);          const context = {              url: 'url:',              sentence: { @@ -186,3 +187,19 @@ export async function getTemplateRenderResults(dictionaryEntries, type, mode, te      return results;  } + +class MinimalApi { +    /** +     * @type {import('anki-note-builder.js').MinimalApi['injectAnkiNoteMedia']} +     */ +    async injectAnkiNoteMedia() { +        throw new Error('Not supported'); +    } + +    /** +     * @type {import('anki-note-builder.js').MinimalApi['parseText']} +     */ +    async parseText() { +        throw new Error('Not supported'); +    } +} diff --git a/types/ext/anki-note-builder.d.ts b/types/ext/anki-note-builder.d.ts index 092978ed..8aec3342 100644 --- a/types/ext/anki-note-builder.d.ts +++ b/types/ext/anki-note-builder.d.ts @@ -23,6 +23,7 @@ import type * as Dictionary from './dictionary';  import type * as Extension from './extension';  import type * as Settings from './settings';  import type * as TemplateRenderer from './template-renderer'; +import type * as Api from './api';  export type CreateNoteDetails = {      dictionaryEntry: Dictionary.DictionaryEntry; @@ -118,3 +119,22 @@ export type BatchedRequestData = {      reject: (reason?: unknown) => void;      marker: string;  }; + +export type MinimalApi = { +    injectAnkiNoteMedia( +        timestamp: Api.ApiParam<'injectAnkiNoteMedia', 'timestamp'>, +        definitionDetails: Api.ApiParam<'injectAnkiNoteMedia', 'definitionDetails'>, +        audioDetails: Api.ApiParam<'injectAnkiNoteMedia', 'audioDetails'>, +        screenshotDetails: Api.ApiParam<'injectAnkiNoteMedia', 'screenshotDetails'>, +        clipboardDetails: Api.ApiParam<'injectAnkiNoteMedia', 'clipboardDetails'>, +        dictionaryMediaDetails: Api.ApiParam<'injectAnkiNoteMedia', 'dictionaryMediaDetails'>, +    ): Promise<Api.ApiReturn<'injectAnkiNoteMedia'>>; + +    parseText( +        text: Api.ApiParam<'parseText', 'text'>, +        optionsContext: Api.ApiParam<'parseText', 'optionsContext'>, +        scanLength: Api.ApiParam<'parseText', 'scanLength'>, +        useInternalParser: Api.ApiParam<'parseText', 'useInternalParser'>, +        useMecabParser: Api.ApiParam<'parseText', 'useMecabParser'>, +    ): Promise<Api.ApiReturn<'parseText'>>; +}; diff --git a/types/ext/display.d.ts b/types/ext/display.d.ts index da24af75..351cf067 100644 --- a/types/ext/display.d.ts +++ b/types/ext/display.d.ts @@ -18,6 +18,7 @@  import type {DisplayContentManager} from '../../ext/js/display/display-content-manager';  import type {HotkeyHelpController} from '../../ext/js/input/hotkey-help-controller';  import type {TextSourceGenerator} from '../../ext/js/dom/text-source-generator'; +import type {API} from '../../ext/js/comm/api';  import type * as Dictionary from './dictionary';  import type * as Extension from './extension';  import type * as Settings from './settings'; @@ -127,6 +128,7 @@ export type SearchMode = null | 'popup' | 'action-popup';  export type GetSearchContextCallback = TextScannerTypes.GetSearchContextCallbackSync;  export type QueryParserConstructorDetails = { +    api: API;      getSearchContext: GetSearchContextCallback;      textSourceGenerator: TextSourceGenerator;  }; diff --git a/types/ext/frontend.d.ts b/types/ext/frontend.d.ts index 53a849a2..17f3d121 100644 --- a/types/ext/frontend.d.ts +++ b/types/ext/frontend.d.ts @@ -17,9 +17,12 @@  import type {PopupFactory} from '../../ext/js/app/popup-factory';  import type {HotkeyHandler} from '../../ext/js/input/hotkey-handler'; +import type {Application} from '../../ext/js/application';  /** Details about how to set up the instance. */  export type ConstructorDetails = { +    /** The main application instance. */ +    application: Application;      /** The type of page, one of 'web', 'popup', or 'search'. */      pageType: PageType;      /** A PopupFactory instance to use for generating popups. */ diff --git a/types/ext/popup.d.ts b/types/ext/popup.d.ts index 4246e24e..1ea25c15 100644 --- a/types/ext/popup.d.ts +++ b/types/ext/popup.d.ts @@ -19,6 +19,7 @@ import type {Popup} from '../../ext/js/app/popup';  import type {PopupProxy} from '../../ext/js/app/popup-proxy';  import type {PopupWindow} from '../../ext/js/app/popup-window';  import type {FrameOffsetForwarder} from '../../ext/js/comm/frame-offset-forwarder'; +import type {Application} from '../../ext/js/application';  import type * as DocumentUtil from './document-util';  import type * as Settings from './settings';  import type {EventNames, EventArgument as BaseEventArgument} from './core'; @@ -92,6 +93,8 @@ export type ValidSize = {  };  export type PopupConstructorDetails = { +    /** The main application instance. */ +    application: Application;      /** The ID of the popup. */      id: string;      /** The depth of the popup. */ @@ -103,6 +106,8 @@ export type PopupConstructorDetails = {  };  export type PopupWindowConstructorDetails = { +    /** The main application instance. */ +    application: Application;      /** The ID of the popup. */      id: string;      /** The depth of the popup. */ @@ -112,6 +117,8 @@ export type PopupWindowConstructorDetails = {  };  export type PopupProxyConstructorDetails = { +    /** The main application instance. */ +    application: Application;      /** The ID of the popup. */      id: string;      /** The depth of the popup. */ diff --git a/types/ext/text-scanner.d.ts b/types/ext/text-scanner.d.ts index 3e1cb6c2..4253d6cc 100644 --- a/types/ext/text-scanner.d.ts +++ b/types/ext/text-scanner.d.ts @@ -17,6 +17,7 @@  import type {TextScanner} from '../../ext/js/language/text-scanner';  import type {TextSourceGenerator} from '../../ext/js/dom/text-source-generator'; +import type {API} from '../../ext/js/comm/api';  import type * as Dictionary from './dictionary';  import type * as Display from './display';  import type * as Input from './input'; @@ -138,6 +139,7 @@ export type GetSearchContextCallbackSync = () => SearchContext;  export type GetSearchContextCallbackAsync = () => Promise<SearchContext>;  export type ConstructorDetails = { +    api: API;      node: HTMLElement | Window;      getSearchContext: GetSearchContextCallback;      ignoreElements?: (() => Element[]) | null; |