diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 43a4830f..2adbde36 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -20,7 +20,14 @@ class Frontend { constructor(popup, ignoreNodes) { this.popup = popup; - this.textScanner = new TextScanner(window, ignoreNodes, this.popup, this.searchSource.bind(this)); + this.textScanner = new TextScanner( + window, + ignoreNodes, + [this.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.options = null; this.optionsContext = { @@ -138,7 +145,6 @@ class Frontend { let results = null; try { - this.textScanner.pendingLookup = true; if (textSource !== null) { results = ( await this.findTerms(textSource) || @@ -165,8 +171,6 @@ class Frontend { if (results === null && this.options.scanning.autoHideResults) { this.searchClear(true); } - - this.textScanner.pendingLookup = false; } return results; @@ -182,7 +186,6 @@ class Frontend { {definitions, context: {sentence, url, focus, disableHistory: true}} ); - this.textScanner.setCurrentTextSource(textSource); if (this.options.scanning.selectText) { textSource.select(); } diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index cf6e5397..fc57d6c3 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -18,19 +18,23 @@ class TextScanner { - constructor(node, ignoreNodes, popup, onTextSearch) { + constructor(node, ignoreNodes, ignoreElements, ignorePoints) { this.node = node; this.ignoreNodes = (Array.isArray(ignoreNodes) && ignoreNodes.length > 0 ? ignoreNodes.join(',') : null); - this.popup = popup; - this.onTextSearch = onTextSearch; + this.ignoreElements = ignoreElements; + this.ignorePoints = ignorePoints; - this.popupTimerPromise = null; + this.scanTimerPromise = null; this.textSourceCurrent = null; this.pendingLookup = false; this.options = null; this.enabled = false; this.eventListeners = []; + this.subscribers = { + searchClear: [], + textSearch: [] + }; this.primaryTouchIdentifier = null; this.preventNextContextMenu = false; @@ -40,13 +44,13 @@ class TextScanner { } onMouseOver(e) { - if (this.popup && e.target === this.popup.container) { - this.popupTimerClear(); + if (this.ignoreElements.includes(e.target)) { + this.scanTimerClear(); } } onMouseMove(e) { - this.popupTimerClear(); + this.scanTimerClear(); if (this.pendingLookup || DOM.isMouseButtonDown(e, 'primary')) { return; @@ -63,7 +67,7 @@ class TextScanner { const search = async () => { if (scanningModifier === 'none') { - if (!await this.popupTimerWait()) { + if (!await this.scanTimerWait()) { // Aborted return; } @@ -84,14 +88,14 @@ class TextScanner { return false; } - if (DOM.isMouseButtonPressed(e, 'primary')) { - this.popupTimerClear(); - this.searchClear(); + if (DOM.isMouseButtonDown(e, 'primary')) { + this.scanTimerClear(); + this.onSearchClear(); } } onMouseOut() { - this.popupTimerClear(); + this.scanTimerClear(); } onClick(e) { @@ -192,23 +196,55 @@ class TextScanner { e.preventDefault(); // Disable scroll } - async popupTimerWait() { + 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; + } + this.pendingLookup = false; + } + + 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); - this.popupTimerPromise = promise; + this.scanTimerPromise = promise; try { return await promise; } finally { - if (this.popupTimerPromise === promise) { - this.popupTimerPromise = null; + if (this.scanTimerPromise === promise) { + this.scanTimerPromise = null; } } } - popupTimerClear() { - if (this.popupTimerPromise !== null) { - this.popupTimerPromise.resolve(false); - this.popupTimerPromise = null; + scanTimerClear() { + if (this.scanTimerPromise !== null) { + this.scanTimerPromise.resolve(false); + this.scanTimerPromise = null; } } @@ -223,7 +259,7 @@ class TextScanner { this.clearEventListeners(); this.enabled = false; } - this.searchClear(); + this.onSearchClear(); } } @@ -262,12 +298,18 @@ class TextScanner { async searchAt(x, y, cause) { try { - this.popupTimerClear(); + this.scanTimerClear(); - if (this.pendingLookup || (this.popup && await this.popup.containsPoint(x, y))) { + if (this.pendingLookup) { return; } + for (const ignorePointFn of this.ignorePoints) { + if (await ignorePointFn(x, y)) { + return; + } + } + const textSource = docRangeFromPoint(x, y, this.options); if (this.textSourceCurrent !== null && this.textSourceCurrent.equals(textSource)) { return;