diff --git a/test/integration/highlight_editor_spec.mjs b/test/integration/highlight_editor_spec.mjs index 984d54b0c6f55..188c65cc7eca7 100644 --- a/test/integration/highlight_editor_spec.mjs +++ b/test/integration/highlight_editor_spec.mjs @@ -1030,4 +1030,67 @@ describe("Highlight Editor", () => { ); }); }); + + describe("Highlight and caret browsing", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait( + "tracemonkey.pdf", + ".annotationEditorLayer", + null, + null, + { + highlightEditorColors: "red=#AB0000", + supportsCaretBrowsingMode: true, + } + ); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check that the caret can move a highlighted text", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.click("#editorHighlight"); + await page.waitForSelector(".annotationEditorLayer.highlightEditing"); + + const rect = await getSpanRectFromText(page, 1, "Abstract"); + const x = rect.x + rect.width / 2; + const y = rect.y + rect.height / 2; + await page.mouse.click(x, y, { count: 2 }); + + await page.waitForSelector(`${getEditorSelector(0)}`); + await page.keyboard.press("Escape"); + await page.waitForSelector( + `${getEditorSelector(0)}:not(.selectedEditor)` + ); + + await page.evaluate(() => { + const text = + "Dynamic languages such as JavaScript are more difficult to com-"; + for (const el of document.querySelectorAll( + `.page[data-page-number="${1}"] > .textLayer > span` + )) { + if (el.textContent === text) { + window.getSelection().setPosition(el.firstChild, 1); + break; + } + } + }); + + await page.keyboard.press("ArrowUp"); + const [text, offset] = await page.evaluate(() => { + const selection = window.getSelection(); + return [selection.anchorNode.textContent, selection.anchorOffset]; + }); + + expect(text).withContext(`In ${browserName}`).toEqual("Abstract"); + expect(offset).withContext(`In ${browserName}`).toEqual(1); + }) + ); + }); + }); }); diff --git a/web/app.js b/web/app.js index 49cf9a9f53faf..e9d40b0e24914 100644 --- a/web/app.js +++ b/web/app.js @@ -329,6 +329,12 @@ const PDFViewerApplication = { params.get("highlighteditorcolors") ); } + if (params.has("supportscaretbrowsingmode")) { + AppOptions.set( + "supportsCaretBrowsingMode", + params.get("supportscaretbrowsingmode") === "true" + ); + } } }, diff --git a/web/caret_browsing.js b/web/caret_browsing.js index 7a86206a740a6..9fc3275bec6dd 100644 --- a/web/caret_browsing.js +++ b/web/caret_browsing.js @@ -130,11 +130,29 @@ class CaretBrowsingMode { } const midY = rect.y + rect.height / 2; - const caretPosition = CaretBrowsingMode.#caretPositionFromPoint( - caretX, - midY - ); - if (caretPosition.offsetNode?.parentElement !== element) { + let caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY); + let parentElement = caretPosition.offsetNode?.parentElement; + if (parentElement && parentElement !== element) { + // There is an element on top of the one in the text layer, so we + // need to hide all the elements (except the one in the text layer) + // at this position in order to get the correct caret position. + const elementsAtPoint = document.elementsFromPoint(caretX, midY); + const savedVisibilities = []; + for (const el of elementsAtPoint) { + if (el === element) { + break; + } + const { style } = el; + savedVisibilities.push([el, style.visibility]); + style.visibility = "hidden"; + } + caretPosition = CaretBrowsingMode.#caretPositionFromPoint(caretX, midY); + parentElement = caretPosition.offsetNode?.parentElement; + for (const [el, visibility] of savedVisibilities) { + el.style.visibility = visibility; + } + } + if (parentElement !== element) { // The element targeted by caretPositionFromPoint isn't in the text // layer. if (select) {