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.
This commit is contained in:
Nicolò Ribaudo 2026-06-30 11:43:17 +02:00
parent 04eeeec4a4
commit ea43bb43fb
No known key found for this signature in database
GPG Key ID: AAFDA9101C58F338
2 changed files with 32 additions and 16 deletions

View File

@ -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"
);
})
);

View File

@ -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 <span> 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 <span> where the selection
// is being modified.
const range = selection.getRangeAt(0);
const modifyStart =