From ae3074895681bdade5b4712ab3b41175687aec64 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 6 Jun 2026 14:22:45 +0200 Subject: [PATCH 1/2] Add basic integration-tests for the `PDFCursorTools` functionality --- test/integration/caret_browsing_spec.mjs | 15 +-- test/integration/cursor_tools_spec.mjs | 134 +++++++++++++++++++++++ test/integration/jasmine-boot.js | 1 + test/integration/test_utils.mjs | 10 ++ 4 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 test/integration/cursor_tools_spec.mjs diff --git a/test/integration/caret_browsing_spec.mjs b/test/integration/caret_browsing_spec.mjs index 33fb59774..559ceac6a 100644 --- a/test/integration/caret_browsing_spec.mjs +++ b/test/integration/caret_browsing_spec.mjs @@ -13,15 +13,12 @@ * limitations under the License. */ -import { closePages, getRect, loadAndWait } from "./test_utils.mjs"; - -const waitForSelectionChange = (page, selection) => - page.waitForFunction( - // We need to replace EOL on Windows to make the test pass. - sel => document.getSelection().toString().replaceAll("\r\n", "\n") === sel, - {}, - selection - ); +import { + closePages, + getRect, + loadAndWait, + waitForSelectionChange, +} from "./test_utils.mjs"; describe("Caret browsing", () => { describe("Selection", () => { diff --git a/test/integration/cursor_tools_spec.mjs b/test/integration/cursor_tools_spec.mjs new file mode 100644 index 000000000..d7f453252 --- /dev/null +++ b/test/integration/cursor_tools_spec.mjs @@ -0,0 +1,134 @@ +/* Copyright 2026 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + closePages, + getRect, + loadAndWait, + waitForSelectionChange, +} from "./test_utils.mjs"; + +async function enableSelectTool(page) { + await page.click("#secondaryToolbarToggleButton"); + await page.waitForSelector("#secondaryToolbar", { hidden: false }); + + await page.click("#cursorSelectTool"); + await page.waitForFunction( + "window.PDFViewerApplication.pdfCursorTools.activeTool === 0" + ); +} + +async function enableHandTool(page) { + await page.click("#secondaryToolbarToggleButton"); + await page.waitForSelector("#secondaryToolbar", { hidden: false }); + + await page.click("#cursorHandTool"); + await page.waitForFunction( + "window.PDFViewerApplication.pdfCursorTools.activeTool === 1" + ); +} + +describe("Cursor tools", () => { + describe("Text selection", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "tracemonkey.pdf", + ".textLayer .endOfContent", + 100 + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("check that text selection works", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await enableSelectTool(page); + + const spanRect = await getRect( + page, + `.page[data-page-number="1"] > .textLayer > span` + ); + const x = spanRect.x + 1, + y = spanRect.y + spanRect.height / 2; + + await page.mouse.click(x, y, { count: 3 }); + await waitForSelectionChange( + page, + "Trace-based Just-in-Time Type Specialization for Dynamic" + ); + + // Remove the selection. + await page.mouse.click(x, y); + await waitForSelectionChange(page, ""); + }) + ); + }); + }); + + describe("Hand tool", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "tracemonkey.pdf", + ".textLayer .endOfContent", + 100 + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("check that hand tool scrolling works", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await enableHandTool(page); + + const pageHeight = await page.evaluate( + () => + document.querySelector(`.page[data-page-number="1"]`).offsetHeight + ); + const steps = 10, + delta = Math.floor(pageHeight / steps); + + const spanRect = await getRect( + page, + `.page[data-page-number="1"] > .textLayer > span` + ); + const x = spanRect.x + 1, + y = spanRect.y + spanRect.height / 2; + + for (let i = 0; i < steps; i++) { + await page.mouse.move(x, y); + await page.mouse.down(); + await page.mouse.move(x, y - delta); + await page.mouse.up(); + } + // Ensure that the second page is visible. + await page.waitForFunction("window.PDFViewerApplication.page === 2"); + + // Finally, disable the hand tool. + await enableSelectTool(page); + }) + ); + }); + }); +}); diff --git a/test/integration/jasmine-boot.js b/test/integration/jasmine-boot.js index f61cab215..9a11871da 100644 --- a/test/integration/jasmine-boot.js +++ b/test/integration/jasmine-boot.js @@ -33,6 +33,7 @@ async function runTests(results) { "caret_browsing_spec.mjs", "comment_spec.mjs", "copy_paste_spec.mjs", + "cursor_tools_spec.mjs", "document_properties_spec.mjs", "find_spec.mjs", "freetext_editor_spec.mjs", diff --git a/test/integration/test_utils.mjs b/test/integration/test_utils.mjs index 2ef42d68e..67796e420 100644 --- a/test/integration/test_utils.mjs +++ b/test/integration/test_utils.mjs @@ -1108,6 +1108,15 @@ async function waitForBrowserTrip(page) { await awaitPromise(handle); } +function waitForSelectionChange(page, selection) { + return page.waitForFunction( + // We need to replace EOL on Windows to make the test pass. + sel => document.getSelection().toString().replaceAll("\r\n", "\n") === sel, + {}, + selection + ); +} + // Unicode bidi isolation characters, Fluent adds these markers to the text. const FSI = "\u2068"; const PDI = "\u2069"; @@ -1190,6 +1199,7 @@ export { waitForPointerUp, waitForSandboxTrip, waitForSelectedEditor, + waitForSelectionChange, waitForSerialized, waitForStorageEntries, waitForTextToBe, From 0b3b101dbc4e360863b4f208a19e3ed65f9496dc Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Sat, 6 Jun 2026 17:26:51 +0200 Subject: [PATCH 2/2] Remove the unused `GrabToPan.prototype.toggle` method Given that the cursor tools are managed via the `PDFCursorTools` class, of which the `GrabToPan` instance is essentially a (semi) private implementation detail, the `GrabToPan.prototype.toggle` method is completely unused and can thus be removed. --- web/grab_to_pan.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/web/grab_to_pan.js b/web/grab_to_pan.js index 05316a024..9835d8540 100644 --- a/web/grab_to_pan.js +++ b/web/grab_to_pan.js @@ -73,14 +73,6 @@ class GrabToPan { } } - toggle() { - if (this.#activateAC) { - this.deactivate(); - } else { - this.activate(); - } - } - /** * Whether to not pan if the target element is clicked. * Override this method to change the default behaviour.