From 5ef7c3048421658055b063027548a9847b521c6e Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Mon, 9 Oct 2023 11:41:04 +0200 Subject: [PATCH] Don't steal the copy event (bug 1857823) There were no specific reasons to stop the propagation of the event and it can be useful for some third parties to catch the event to let them do their own stuff. --- test/integration/copy_paste_spec.js | 113 ++++++++++++++++++++++++++++ web/text_layer_builder.js | 31 +++++--- 2 files changed, 133 insertions(+), 11 deletions(-) diff --git a/test/integration/copy_paste_spec.js b/test/integration/copy_paste_spec.js index 721cfe585100e..f4c65abc6d94a 100644 --- a/test/integration/copy_paste_spec.js +++ b/test/integration/copy_paste_spec.js @@ -145,6 +145,7 @@ describe("Copy and paste", () => { ); }); }); + describe("Copy/paste and ligatures", () => { let pages; @@ -197,4 +198,116 @@ describe("Copy and paste", () => { ); }); }); + + describe("Copy/paste and ligatures (but without selecting all)", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait( + "copy_paste_ligatures.pdf", + "#hiddenCopyElement", + 100, + async page => { + await page.waitForFunction( + () => !!window.PDFViewerApplication.eventBus + ); + await waitForTextLayer(page); + } + ); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check that the ligatures have been removed when the text has been copied", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.evaluate(async () => { + const promise = new Promise(resolve => { + document.addEventListener("selectionchange", resolve, { + once: true, + }); + }); + const range = document.createRange(); + range.selectNodeContents(document.querySelector(".textLayer")); + const selection = window.getSelection(); + selection.addRange(range); + await promise; + }); + const promise = page.evaluate( + () => + new Promise(resolve => { + document.addEventListener( + "copy", + e => resolve(e.clipboardData.getData("text/plain")), + { + once: true, + } + ); + }) + ); + await page.keyboard.down("Control"); + await page.keyboard.press("c"); + await page.keyboard.up("Control"); + const text = await promise; + + if (browserName === "chrome") { + // In Firefox the copy event hasn't the correct value. + expect(text) + .withContext(`In ${browserName}`) + .toEqual("abcdeffffiflffifflſtstghijklmno"); + } + }) + ); + }); + }); + + describe("Copy event", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait( + "tracemonkey.pdf", + ".textLayer .endOfContent", + 100, + async page => { + await page.waitForFunction( + () => !!window.PDFViewerApplication.eventBus + ); + await waitForTextLayer(page); + } + ); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must check that the copy event is not stolen (bug 1857823)", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.evaluate(async () => { + const promise = new Promise(resolve => { + document.addEventListener("selectionchange", resolve, { + once: true, + }); + }); + const range = document.createRange(); + const span = document.querySelector(".textLayer span"); + range.selectNode(span); + const selection = window.getSelection(); + selection.addRange(range); + await promise; + }); + + const promise = waitForEvent(page, "copy", 300000); + await page.keyboard.down("Control"); + await page.keyboard.press("c"); + await page.keyboard.up("Control"); + await promise; + }) + ); + }); + }); }); diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js index e9db36e949bf1..b8a38e45abd34 100644 --- a/web/text_layer_builder.js +++ b/web/text_layer_builder.js @@ -218,17 +218,26 @@ class TextLayerBuilder { end.classList.remove("active"); }); - div.addEventListener("copy", event => { - if (!this.#enablePermissions) { - const selection = document.getSelection(); - event.clipboardData.setData( - "text/plain", - removeNullCharacters(normalizeUnicode(selection.toString())) - ); - } - event.preventDefault(); - event.stopPropagation(); - }); + div.addEventListener( + "copy", + event => { + if (!this.#enablePermissions) { + const selection = document.getSelection(); + event.clipboardData.setData( + "text/plain", + removeNullCharacters(normalizeUnicode(selection.toString())) + ); + } else { + event.stopPropagation(); + } + // It's required after the clipboard data have been changed else the + // clipboard will contain the selection! + event.preventDefault(); + }, + // Capture the event in order to be sure that the copy event has the + // correct value. + { capture: true } + ); } }