Skip to content

Commit cffd54e

Browse files
committed
Hide the text in the text layer associated with MathML elements (bug 2009627)
The bug was supposed to be fixed by #20471 but here there are some annotations in the pdf. When those annotations are added to the DOM, the struct tree has to be rendered but without the text layer (because of asynchronicity). So this patch is making sure that the modifications in the text layer are done once the layer is rendered.
1 parent f40ab1a commit cffd54e

5 files changed

Lines changed: 98 additions & 24 deletions

File tree

test/integration/accessibility_spec.mjs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,4 +498,50 @@ describe("accessibility", () => {
498498
);
499499
});
500500
});
501+
502+
describe("Text elements must be aria-hidden when there's MathML and annotations", () => {
503+
let pages;
504+
505+
beforeEach(async () => {
506+
pages = await loadAndWait("bug2009627.pdf", ".textLayer");
507+
});
508+
509+
afterEach(async () => {
510+
await closePages(pages);
511+
});
512+
513+
it("must check that the text in text layer is aria-hidden", async () => {
514+
await Promise.all(
515+
pages.map(async ([browserName, page]) => {
516+
const isSanitizerSupported = await page.evaluate(() => {
517+
try {
518+
// eslint-disable-next-line no-undef
519+
return typeof Sanitizer !== "undefined";
520+
} catch {
521+
return false;
522+
}
523+
});
524+
const ariaHidden = await page.evaluate(() =>
525+
Array.from(
526+
document.querySelectorAll(".structTree :has(> math)")
527+
).map(el =>
528+
document
529+
.getElementById(el.getAttribute("aria-owns"))
530+
.getAttribute("aria-hidden")
531+
)
532+
);
533+
if (isSanitizerSupported) {
534+
expect(ariaHidden)
535+
.withContext(`In ${browserName}`)
536+
.toEqual(["true", "true", "true"]);
537+
} else {
538+
// eslint-disable-next-line no-console
539+
console.log(
540+
`Pending in Chrome: Sanitizer API (in ${browserName}) is not supported`
541+
);
542+
}
543+
})
544+
);
545+
});
546+
});
501547
});

test/pdfs/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,3 +867,4 @@
867867
!bitmap-trailing-7fff-stripped.pdf
868868
!bitmap.pdf
869869
!bomb_giant.pdf
870+
!bug2009627.pdf

test/pdfs/bug2009627.pdf

39.8 KB
Binary file not shown.

web/pdf_page_view.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -493,7 +493,7 @@ class PDFPageView extends BasePDFPageView {
493493
const treeDom = await this.structTreeLayer?.render();
494494
if (treeDom) {
495495
this.l10n.pause();
496-
this.structTreeLayer?.addElementsToTextLayer();
496+
this.structTreeLayer?.updateTextLayer();
497497
if (this.canvas && treeDom.parentNode !== this.canvas) {
498498
// Pause translation when inserting the structTree in the DOM.
499499
this.canvas.append(treeDom);

web/struct_tree_layer_builder.js

Lines changed: 50 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,10 @@ class StructTreeLayerBuilder {
184184

185185
#elementsToAddToTextLayer = null;
186186

187+
#elementsToHideInTextLayer = null;
188+
189+
#elementsToStealFromTextLayer = null;
190+
187191
/**
188192
* @param {StructTreeLayerBuilderOptions} options
189193
*/
@@ -304,15 +308,49 @@ class StructTreeLayerBuilder {
304308
return true;
305309
}
306310

307-
addElementsToTextLayer() {
308-
if (!this.#elementsToAddToTextLayer) {
309-
return;
311+
updateTextLayer() {
312+
if (this.#elementsToAddToTextLayer) {
313+
for (const [id, img] of this.#elementsToAddToTextLayer) {
314+
document.getElementById(id)?.append(img);
315+
}
316+
this.#elementsToAddToTextLayer.clear();
317+
this.#elementsToAddToTextLayer = null;
310318
}
311-
for (const [id, img] of this.#elementsToAddToTextLayer) {
312-
document.getElementById(id)?.append(img);
319+
if (this.#elementsToHideInTextLayer) {
320+
for (const id of this.#elementsToHideInTextLayer) {
321+
const elem = document.getElementById(id);
322+
if (elem) {
323+
elem.ariaHidden = true;
324+
}
325+
}
326+
this.#elementsToHideInTextLayer.length = 0;
327+
this.#elementsToHideInTextLayer = null;
328+
}
329+
if (this.#elementsToStealFromTextLayer) {
330+
for (
331+
let i = 0, ii = this.#elementsToStealFromTextLayer.length;
332+
i < ii;
333+
i += 2
334+
) {
335+
const element = this.#elementsToStealFromTextLayer[i];
336+
const ids = this.#elementsToStealFromTextLayer[i + 1];
337+
let textContent = "";
338+
for (const id of ids) {
339+
const elem = document.getElementById(id);
340+
if (elem) {
341+
textContent += elem.textContent.trim() || "";
342+
// Aria-hide the element in order to avoid duplicate reading of the
343+
// math content by screen readers.
344+
elem.ariaHidden = "true";
345+
}
346+
}
347+
if (textContent) {
348+
element.textContent = textContent;
349+
}
350+
}
351+
this.#elementsToStealFromTextLayer.length = 0;
352+
this.#elementsToStealFromTextLayer = null;
313353
}
314-
this.#elementsToAddToTextLayer.clear();
315-
this.#elementsToAddToTextLayer = null;
316354
}
317355

318356
#walk(node) {
@@ -325,21 +363,13 @@ class StructTreeLayerBuilder {
325363
const { role } = node;
326364
if (MathMLElements.has(role)) {
327365
element = document.createElementNS(MathMLNamespace, role);
328-
let text = "";
366+
const ids = [];
367+
(this.#elementsToStealFromTextLayer ||= []).push(element, ids);
329368
for (const { type, id } of node.children || []) {
330-
if (type !== "content" || !id) {
331-
continue;
369+
if (type === "content" && id) {
370+
ids.push(id);
332371
}
333-
const elem = document.getElementById(id);
334-
if (!elem) {
335-
continue;
336-
}
337-
text += elem.textContent.trim() || "";
338-
// Aria-hide the element in order to avoid duplicate reading of the
339-
// math content by screen readers.
340-
elem.ariaHidden = "true";
341372
}
342-
element.textContent = text;
343373
} else {
344374
element = document.createElement("span");
345375
}
@@ -365,10 +395,7 @@ class StructTreeLayerBuilder {
365395
if (!id) {
366396
continue;
367397
}
368-
const elem = document.getElementById(id);
369-
if (elem) {
370-
elem.ariaHidden = true;
371-
}
398+
(this.#elementsToHideInTextLayer ||= []).push(id);
372399
}
373400
// For now, we don't want to keep the alt text if there's valid
374401
// MathML (see https://github.com/w3c/mathml-aam/issues/37).

0 commit comments

Comments
 (0)