diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 9ec66fb1..01f5c13d 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -17,17 +17,16 @@ */ -class Frontend { +class Frontend extends TextScanner { constructor(popup, ignoreNodes) { - this.popup = popup; - this.textScanner = new TextScanner( + super( window, ignoreNodes, - [this.popup.container], + [popup.container], [(x, y) => this.popup.containsPoint(x, y)] ); - this.textScanner.subscribe('textSearch', ({textSource, cause}) => this.searchSource(textSource, cause)); - this.textScanner.subscribe('searchClear', () => this.searchClear(true)); + + this.popup = popup; this.options = null; this.optionsContext = { @@ -35,9 +34,6 @@ class Frontend { url: popup.url }; - this.enabled = false; - this.eventListeners = []; - this.isPreparedPromiseResolve = null; this.isPreparedPromise = new Promise((resolve) => { this.isPreparedPromiseResolve = resolve; }); @@ -70,7 +66,7 @@ class Frontend { } async onResize() { - const textSource = this.textScanner.getCurrentTextSource(); + const textSource = this.textSourceCurrent; if (textSource !== null && await this.popup.isVisibleAsync()) { this.lastShowPromise = this.popup.showContent( textSource.getRect(), @@ -97,51 +93,21 @@ class Frontend { } } - onError(error) { - logError(error, false); - } - - setEnabled(enabled) { - this.textScanner.setEnabled(enabled); - if (enabled) { - if (!this.enabled) { - this.hookEvents(); - this.enabled = true; - } - } else { - if (this.enabled) { - this.clearEventListeners(); - this.enabled = false; - } - this.searchClear(false); - } - } - - hookEvents() { - this.addEventListener(window, 'message', this.onWindowMessage.bind(this)); - this.addEventListener(window, 'resize', this.onResize.bind(this)); - } - - addEventListener(node, type, listener, options) { - node.addEventListener(type, listener, options); - this.eventListeners.push([node, type, listener, options]); - } - - clearEventListeners() { - for (const [node, type, listener, options] of this.eventListeners) { - node.removeEventListener(type, listener, options); - } - this.eventListeners = []; + getMouseEventListeners() { + return [ + ...super.getMouseEventListeners(), + [window, 'message', this.onWindowMessage.bind(this)], + [window, 'resize', this.onResize.bind(this)] + ]; } async updateOptions() { this.options = await apiOptionsGet(this.getOptionsContext()); - this.textScanner.setOptions(this.options); await this.popup.setOptions(this.options); this.setEnabled(this.options.general.enable); } - async searchSource(textSource, cause) { + async onSearchSource(textSource, cause) { let results = null; try { @@ -169,7 +135,7 @@ class Frontend { } } finally { if (results === null && this.options.scanning.autoHideResults) { - this.searchClear(true); + this.onSearchClear(true); } } @@ -188,7 +154,7 @@ class Frontend { } async findTerms(textSource) { - this.textScanner.setTextSourceScanLength(textSource, this.options.scanning.length); + this.setTextSourceScanLength(textSource, this.options.scanning.length); const searchText = textSource.text(); if (searchText.length === 0) { return null; } @@ -202,7 +168,7 @@ class Frontend { } async findKanji(textSource) { - this.textScanner.setTextSourceScanLength(textSource, 1); + this.setTextSourceScanLength(textSource, 1); const searchText = textSource.text(); if (searchText.length === 0) { return null; } @@ -213,10 +179,10 @@ class Frontend { return {definitions, type: 'kanji'}; } - searchClear(changeFocus) { + onSearchClear(changeFocus) { this.popup.hide(changeFocus); this.popup.clearAutoPlayTimer(); - this.textScanner.searchClear(); + super.onSearchClear(changeFocus); } getOptionsContext() { @@ -227,7 +193,7 @@ class Frontend { Frontend.windowMessageHandlers = { popupClose: (self) => { - self.searchClear(true); + self.onSearchClear(true); }, selectionCopy: () => { diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index 0adcc0bd..ac5d68d1 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -31,10 +31,6 @@ class TextScanner { this.enabled = false; this.eventListeners = []; - this.subscribers = { - searchClear: [], - textSearch: [] - }; this.primaryTouchIdentifier = null; this.preventNextContextMenu = false; @@ -90,7 +86,7 @@ class TextScanner { if (DOM.isMouseButtonDown(e, 'primary')) { this.scanTimerClear(); - this.onSearchClear(); + this.onSearchClear(true); } } @@ -196,41 +192,14 @@ class TextScanner { e.preventDefault(); // Disable scroll } - async onSearchClear() { - this.searchClear(); - await this.publish('searchClear', {}); - } - - async onTextSearch(textSource, cause) { - this.pendingLookup = true; - const results = await this.publish('textSearch', {textSource, cause}); - if (results.some((r) => r)) { - this.textSourceCurrent = textSource; - if (this.options.scanning.selectText) { - textSource.select(); - } - } - this.pendingLookup = false; + async onSearchSource(_textSource, _cause) { + throw new Error('Override me'); } onError(error) { logError(error, false); } - subscribe(eventName, subscriber) { - if (this.subscribers[eventName].includes(subscriber)) { return; } - this.subscribers[eventName].push(subscriber); - } - - async publish(eventName, data) { - const results = []; - for (const subscriber of this.subscribers[eventName]) { - const result = await subscriber(data); - results.push(result); - } - return results; - } - async scanTimerWait() { const delay = this.options.scanning.delay; const promise = promiseTimeout(delay, true); @@ -262,35 +231,50 @@ class TextScanner { this.clearEventListeners(); this.enabled = false; } - this.onSearchClear(); + this.onSearchClear(false); } } hookEvents() { - this.addEventListener('mousedown', this.onMouseDown.bind(this)); - this.addEventListener('mousemove', this.onMouseMove.bind(this)); - this.addEventListener('mouseover', this.onMouseOver.bind(this)); - this.addEventListener('mouseout', this.onMouseOut.bind(this)); - + let eventListeners = this.getMouseEventListeners(); if (this.options.scanning.touchInputEnabled) { - this.addEventListener('click', this.onClick.bind(this)); - this.addEventListener('auxclick', this.onAuxClick.bind(this)); - this.addEventListener('touchstart', this.onTouchStart.bind(this)); - this.addEventListener('touchend', this.onTouchEnd.bind(this)); - this.addEventListener('touchcancel', this.onTouchCancel.bind(this)); - this.addEventListener('touchmove', this.onTouchMove.bind(this), {passive: false}); - this.addEventListener('contextmenu', this.onContextMenu.bind(this)); + eventListeners = eventListeners.concat(this.getTouchEventListeners()); + } + + for (const [node, type, listener, options] of eventListeners) { + this.addEventListener(node, type, listener, options); } } - addEventListener(type, listener, options) { - this.node.addEventListener(type, listener, options); - this.eventListeners.push([type, listener, options]); + getMouseEventListeners() { + return [ + [this.node, 'mousedown', this.onMouseDown.bind(this)], + [this.node, 'mousemove', this.onMouseMove.bind(this)], + [this.node, 'mouseover', this.onMouseOver.bind(this)], + [this.node, 'mouseout', this.onMouseOut.bind(this)] + ]; + } + + getTouchEventListeners() { + return [ + [this.node, 'click', this.onClick.bind(this)], + [this.node, 'auxclick', this.onAuxClick.bind(this)], + [this.node, 'touchstart', this.onTouchStart.bind(this)], + [this.node, 'touchend', this.onTouchEnd.bind(this)], + [this.node, 'touchcancel', this.onTouchCancel.bind(this)], + [this.node, 'touchmove', this.onTouchMove.bind(this), {passive: false}], + [this.node, 'contextmenu', this.onContextMenu.bind(this)] + ]; + } + + addEventListener(node, type, listener, options) { + node.addEventListener(type, listener, options); + this.eventListeners.push([node, type, listener, options]); } clearEventListeners() { - for (const [type, listener, options] of this.eventListeners) { - this.node.removeEventListener(type, listener, options); + for (const [node, type, listener, options] of this.eventListeners) { + node.removeEventListener(type, listener, options); } this.eventListeners = []; } @@ -319,7 +303,15 @@ class TextScanner { } try { - await this.onTextSearch(textSource, cause); + this.pendingLookup = true; + const result = await this.onSearchSource(textSource, cause); + if (result !== null) { + this.textSourceCurrent = textSource; + if (this.options.scanning.selectText) { + textSource.select(); + } + } + this.pendingLookup = false; } finally { if (textSource !== null) { textSource.cleanup(); @@ -347,7 +339,7 @@ class TextScanner { } } - searchClear() { + onSearchClear(_) { if (this.textSourceCurrent !== null) { if (this.options.scanning.selectText) { this.textSourceCurrent.deselect();