fix: Reject stale dictionary lookups

The dictionary used a lookup only when the selected words still belonged
to the visible page. If the selection or sentence disappeared while
the page answered, the reader reported the result as unavailable
instead of guessing its language.
This commit is contained in:
Chmouel Boudjnah 2026-10-13 20:32:25 +02:00
parent cc219fb8e2
commit 5bf98ec353
3 changed files with 11 additions and 3 deletions

View file

@ -340,10 +340,10 @@ internal object PageSwaps {
selectionIntersectsAppliedSwap(url, s) { selectionIntersectsAppliedSwap(url, s) {
if (!here(url)) return null; if (!here(url)) return null;
const selection = window.getSelection(); const selection = window.getSelection();
if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return false; if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return null;
let at = null; let at = null;
try { at = locate(s.selector, s.before, s.text); } catch (e) {} try { at = locate(s.selector, s.before, s.text); } catch (e) {}
if (!at) return false; if (!at) return null;
const bounds = new Map(); const bounds = new Map();
for (let i = at.start; i <= at.end; i++) { for (let i = at.start; i <= at.end; i++) {
const node = at.nodes[i]; const node = at.nodes[i];

View file

@ -69,6 +69,8 @@ internal object TranslatedPages {
/** The selected words as the translated page currently shows them. */ /** The selected words as the translated page currently shows them. */
@OptIn(ExperimentalReadiumApi::class) @OptIn(ExperimentalReadiumApi::class)
suspend fun selectedText(nav: EpubNavigatorFragment): String? { suspend fun selectedText(nav: EpubNavigatorFragment): String? {
val web = visibleWebView(nav.publicationView) ?: return null
val url = web.url ?: return null
val result = try { val result = try {
withTimeoutOrNull(ANSWER_MS) { withTimeoutOrNull(ANSWER_MS) {
nav.evaluateJavascript(SELECTION_TEXT_SCRIPT) nav.evaluateJavascript(SELECTION_TEXT_SCRIPT)
@ -78,7 +80,7 @@ internal object TranslatedPages {
} catch (_: Exception) { } catch (_: Exception) {
null null
} ?: return null } ?: return null
return decodeSelectionText(result) return decodeSelectionText(result)?.takeIf { stillShows(nav, web, url) }
} }
@OptIn(ExperimentalReadiumApi::class) @OptIn(ExperimentalReadiumApi::class)

View file

@ -78,6 +78,12 @@ class PageSwapsTest {
assertTrue(js.contains("edits.get(node)")) assertTrue(js.contains("edits.get(node)"))
assertTrue(js.contains("range.compareBoundaryPoints(Range.END_TO_START, changed)")) assertTrue(js.contains("range.compareBoundaryPoints(Range.END_TO_START, changed)"))
assertTrue(js.contains("range.compareBoundaryPoints(Range.START_TO_END, changed)")) assertTrue(js.contains("range.compareBoundaryPoints(Range.START_TO_END, changed)"))
val body = js.substringAfter("selectionIntersectsAppliedSwap(url, s) {")
.substringBefore("markedAt(url, s, fx, fy) {")
assertTrue(
body.contains("if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return null;"),
)
assertTrue(body.contains("if (!at) return null;"))
} }
@Test @Test