From b4476d9739056c5f98d9dd74582298b5c7e645f2 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Sat, 10 Jan 2026 15:57:18 +0100 Subject: [PATCH 1/2] fix(display): allow text selection under markup annotations (issue #18190) Add pointer-events: none to markup annotation containers to prevent blocking text selection. Add integration test. --- test/integration/highlight_selection_spec.mjs | 58 +++++++++++++++++++ web/annotation_layer_builder.css | 34 +++++++---- 2 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 test/integration/highlight_selection_spec.mjs diff --git a/test/integration/highlight_selection_spec.mjs b/test/integration/highlight_selection_spec.mjs new file mode 100644 index 0000000000000..a32ad45d06d87 --- /dev/null +++ b/test/integration/highlight_selection_spec.mjs @@ -0,0 +1,58 @@ +/* Copyright 2024 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, loadAndWait } from "./test_utils.mjs"; + +describe("Highlight Selection", () => { + let pages; + + beforeAll(async () => { + pages = await loadAndWait("issue18190.pdf", { + annotationMode: 1, // ENABLE + }); + }); + + afterAll(async () => { + await closePages(pages); + }); + + it("must allow text selection under highlight annotation", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + // Wait for annotations/text layer + await page.waitForSelector(".annotationLayer"); + await page.waitForSelector(".textLayer"); + + // Coordinates of the highlighted text (approximate from PDF structure) + // We will simulate a drag to select text. + // Assuming the highlighted text is on the standard text layer. + // We verify pointer-events via evaluation if selection is flaky without + // exact coords. + + // Check computed style. + const highlightStyle = await page.evaluate(() => { + const highlight = document.querySelector(".highlightAnnotation"); + if (!highlight) { + return null; + } + return window.getComputedStyle(highlight).pointerEvents; + }); + + // The critical check: pointer-events must be 'none' + expect(highlightStyle).withContext(`In ${browserName}`).toBe("none"); + }) + ); + }); +}); diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index c1c2a8d54ce72..7d3d87b6f05ce 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -44,7 +44,7 @@ backdrop-filter: var(--hcm-highlight-filter); } - & > a:hover { + &>a:hover { opacity: 0 !important; background: none !important; box-shadow: none; @@ -82,14 +82,17 @@ &[data-main-rotation="90"] .norotate { transform: rotate(270deg) translateX(-100%); } + &[data-main-rotation="180"] .norotate { transform: rotate(180deg) translate(-100%, -100%); } + &[data-main-rotation="270"] .norotate { transform: rotate(90deg) translateY(-100%); } &.disabled { + section, .popup { pointer-events: none; @@ -138,11 +141,11 @@ } } - .textLayer.selecting ~ & section { + .textLayer.selecting~& section { pointer-events: none; } - :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton) > a { + :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton)>a { position: absolute; font-size: 1em; top: 0; @@ -151,8 +154,7 @@ height: 100%; } - :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder) - > a:hover { + :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder)>a:hover { opacity: 0.2; background-color: rgb(255 255 0); } @@ -174,6 +176,14 @@ left: 0; } + .highlightAnnotation, + .underlineAnnotation, + .squigglyAnnotation, + .strikeoutAnnotation { + cursor: pointer; + pointer-events: none; + } + .textWidgetAnnotation :is(input, textarea), .choiceWidgetAnnotation select, .buttonWidgetAnnotation:is(.checkBox, .radioButton) input { @@ -218,6 +228,7 @@ .buttonWidgetAnnotation:is(.checkBox, .radioButton) input:hover { border: 2px solid var(--input-hover-border-color); } + .textWidgetAnnotation :is(input, textarea):hover, .choiceWidgetAnnotation select:hover, .buttonWidgetAnnotation.checkBox input:hover { @@ -317,8 +328,7 @@ .popup { background-color: rgb(255 255 153); color: black; - box-shadow: 0 calc(2px * var(--total-scale-factor)) - calc(5px * var(--total-scale-factor)) rgb(136 136 136); + box-shadow: 0 calc(2px * var(--total-scale-factor)) calc(5px * var(--total-scale-factor)) rgb(136 136 136); border-radius: calc(2px * var(--total-scale-factor)); outline: 1.5px solid rgb(255 255 74); padding: calc(6px * var(--total-scale-factor)); @@ -338,16 +348,16 @@ font-size: calc(9px * var(--total-scale-factor)); } - .popup > .header { + .popup>.header { display: inline-block; } - .popup > .header > .title { + .popup>.header>.title { display: inline; font-weight: bold; } - .popup > .header .popupDate { + .popup>.header .popupDate { display: inline-block; margin-left: calc(5px * var(--total-scale-factor)); width: fit-content; @@ -359,7 +369,7 @@ padding-top: calc(2px * var(--total-scale-factor)); } - .richText > * { + .richText>* { white-space: pre-wrap; font-size: calc(9px * var(--total-scale-factor)); } @@ -404,4 +414,4 @@ left: 0; z-index: -1; } -} +} \ No newline at end of file From a3f12872e4071e12d20dbdc687e7add2611f20b7 Mon Sep 17 00:00:00 2001 From: Alessio Attilio Date: Sat, 10 Jan 2026 16:18:13 +0100 Subject: [PATCH 2/2] style: fix lint issues in annotation_layer_builder.css --- web/annotation_layer_builder.css | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/web/annotation_layer_builder.css b/web/annotation_layer_builder.css index 7d3d87b6f05ce..fb850174d7d3e 100644 --- a/web/annotation_layer_builder.css +++ b/web/annotation_layer_builder.css @@ -44,7 +44,7 @@ backdrop-filter: var(--hcm-highlight-filter); } - &>a:hover { + & > a:hover { opacity: 0 !important; background: none !important; box-shadow: none; @@ -92,7 +92,6 @@ } &.disabled { - section, .popup { pointer-events: none; @@ -141,11 +140,11 @@ } } - .textLayer.selecting~& section { + .textLayer.selecting ~ & section { pointer-events: none; } - :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton)>a { + :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton) > a { position: absolute; font-size: 1em; top: 0; @@ -154,7 +153,8 @@ height: 100%; } - :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder)>a:hover { + :is(.linkAnnotation, .buttonWidgetAnnotation.pushButton):not(.hasBorder) + > a:hover { opacity: 0.2; background-color: rgb(255 255 0); } @@ -328,7 +328,8 @@ .popup { background-color: rgb(255 255 153); color: black; - box-shadow: 0 calc(2px * var(--total-scale-factor)) calc(5px * var(--total-scale-factor)) rgb(136 136 136); + box-shadow: 0 calc(2px * var(--total-scale-factor)) + calc(5px * var(--total-scale-factor)) rgb(136 136 136); border-radius: calc(2px * var(--total-scale-factor)); outline: 1.5px solid rgb(255 255 74); padding: calc(6px * var(--total-scale-factor)); @@ -348,16 +349,16 @@ font-size: calc(9px * var(--total-scale-factor)); } - .popup>.header { + .popup > .header { display: inline-block; } - .popup>.header>.title { + .popup > .header > .title { display: inline; font-weight: bold; } - .popup>.header .popupDate { + .popup > .header .popupDate { display: inline-block; margin-left: calc(5px * var(--total-scale-factor)); width: fit-content; @@ -369,7 +370,7 @@ padding-top: calc(2px * var(--total-scale-factor)); } - .richText>* { + .richText > * { white-space: pre-wrap; font-size: calc(9px * var(--total-scale-factor)); } @@ -414,4 +415,4 @@ left: 0; z-index: -1; } -} \ No newline at end of file +}