From ea43bb43fba60cfc69024df91d98d319a787d038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Tue, 30 Jun 2026 11:43:17 +0200 Subject: [PATCH] Avoid text selection workaround in modern Chromium Chromium 148+ improved their selection behavior when it comes to absolutely positioned elements, thus making text selectino in PDF.js much better. Unfortunately this does not only mean that the workaround we currently have for Chromium is unnecessary, but it actually become harmful. It conflicts with Chromium's new behavior, making text selection *worse* on mobile. As the change has been released in Chrome only a month ago, this patch keeps the workaround for older Chromium versions. There is no easy way to feture-detect is, so unfortunately we need to do user agent version detection. --- test/integration/text_layer_spec.mjs | 4 +-- web/text_layer_builder.js | 44 +++++++++++++++++++--------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/test/integration/text_layer_spec.mjs b/test/integration/text_layer_spec.mjs index 1bb8099fe..063c1969e 100644 --- a/test/integration/text_layer_spec.mjs +++ b/test/integration/text_layer_spec.mjs @@ -757,7 +757,7 @@ describe("Text layer", () => { .withContext(`In ${browserName}`) .toHaveRoughlySelected( "rs as the railway projects under\n" + - "development enter the construction phase (estimated at" + "development enter the construction phase (estimated a" ); }) ); @@ -801,7 +801,7 @@ describe("Text layer", () => { .withContext(`In ${browserName}`) .toHaveRoughlySelected( "quarters as the railway projects under\n" + - "development enter the construction phase (estimated at around" + "development enter the construction phase (estimated at" ); }) ); diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js index 62c8e5fd3..0275e961b 100644 --- a/web/text_layer_builder.js +++ b/web/text_layer_builder.js @@ -264,7 +264,7 @@ class TextLayerBuilder { if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { // eslint-disable-next-line no-var - var isFirefox, prevRange; + var isFirefoxOrModernChromium, prevRange; } document.addEventListener( @@ -304,22 +304,38 @@ class TextLayerBuilder { if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { return; } - if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) { - isFirefox ??= - getComputedStyle( - this.#textLayers.values().next().value - ).getPropertyValue("-moz-user-select") === "none"; + if (isFirefoxOrModernChromium === undefined) { + if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("CHROME")) { + isFirefoxOrModernChromium = + getComputedStyle( + this.#textLayers.values().next().value + ).getPropertyValue("-moz-user-select") === "none"; + } + if ( + (typeof PDFJSDev !== "undefined" && PDFJSDev.test("CHROME")) || + !isFirefoxOrModernChromium + ) { + // navigator.userAgentData is only available in secure contexts + const chromiumVersion = navigator.userAgentData + ? navigator.userAgentData.brands.find( + ({ brand }) => brand === "Chromium" + )?.version + : /\bChrome\/(\d+)\b/.exec(navigator.userAgent)?.[1]; - if (isFirefox) { - return; + isFirefoxOrModernChromium = + !!chromiumVersion && parseInt(chromiumVersion, 10) >= 148; } } - // In non-Firefox browsers, when hovering over an empty space (thus, - // on .endOfContent), the selection will expand to cover all the - // text between the current selection and .endOfContent. By moving - // .endOfContent to right after (or before, depending on which side - // of the selection the user is moving), we limit the selection jump - // to at most cover the enteirety of the where the selection + if (isFirefoxOrModernChromium) { + return; + } + + // In browsers other than Firefox or Chromium 148+, when hovering over + // an empty space (thus, on .endOfContent), the selection will expand to + // cover all the text between the current selection and .endOfContent. + // By moving .endOfContent to right after (or before, depending on which + // side of the selection the user is moving), we limit the selection + // jump to at most cover the entirety of the where the selection // is being modified. const range = selection.getRangeAt(0); const modifyStart =