From bb19cf9b64c246068a1b8107ce3294a7cc0960d9 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Fri, 23 Feb 2024 14:55:54 +0100 Subject: [PATCH] [Editor] In caret browsing mode, get the caret position in the text layer (bug 1881692) The function caretPositionFromPoint return the position within the last visible element and sometimes there are some elements on top of the ones in the text layer. So the idea is to hide the visible elements which aren't in the text layer in order to get the right caret position. --- test/integration/highlight_editor_spec.mjs | 63 ++++++++++++++++++++++ web/app.js | 6 +++ web/caret_browsing.js | 28 ++++++++-- 3 files changed, 92 insertions(+), 5 deletions(-) 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) {