Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions test/integration/accessibility_spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -498,4 +498,50 @@ describe("accessibility", () => {
);
});
});

describe("Text elements must be aria-hidden when there's MathML and annotations", () => {
let pages;

beforeEach(async () => {
pages = await loadAndWait("bug2009627.pdf", ".textLayer");
});

afterEach(async () => {
await closePages(pages);
});

it("must check that the text in text layer is aria-hidden", async () => {
await Promise.all(
pages.map(async ([browserName, page]) => {
const isSanitizerSupported = await page.evaluate(() => {
try {
// eslint-disable-next-line no-undef
return typeof Sanitizer !== "undefined";
} catch {
return false;
}
});
const ariaHidden = await page.evaluate(() =>
Array.from(
document.querySelectorAll(".structTree :has(> math)")
).map(el =>
document
.getElementById(el.getAttribute("aria-owns"))
.getAttribute("aria-hidden")
)
);
if (isSanitizerSupported) {
expect(ariaHidden)
.withContext(`In ${browserName}`)
.toEqual(["true", "true", "true"]);
} else {
// eslint-disable-next-line no-console
console.log(
`Pending in Chrome: Sanitizer API (in ${browserName}) is not supported`
);
}
})
);
});
});
});
1 change: 1 addition & 0 deletions test/pdfs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -867,3 +867,4 @@
!bitmap-trailing-7fff-stripped.pdf
!bitmap.pdf
!bomb_giant.pdf
!bug2009627.pdf
Binary file added test/pdfs/bug2009627.pdf
Binary file not shown.
2 changes: 1 addition & 1 deletion web/pdf_page_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,7 @@ class PDFPageView extends BasePDFPageView {
const treeDom = await this.structTreeLayer?.render();
if (treeDom) {
this.l10n.pause();
this.structTreeLayer?.addElementsToTextLayer();
this.structTreeLayer?.updateTextLayer();
if (this.canvas && treeDom.parentNode !== this.canvas) {
// Pause translation when inserting the structTree in the DOM.
this.canvas.append(treeDom);
Expand Down
73 changes: 50 additions & 23 deletions web/struct_tree_layer_builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,10 @@ class StructTreeLayerBuilder {

#elementsToAddToTextLayer = null;

#elementsToHideInTextLayer = null;

#elementsToStealFromTextLayer = null;

/**
* @param {StructTreeLayerBuilderOptions} options
*/
Expand Down Expand Up @@ -304,15 +308,49 @@ class StructTreeLayerBuilder {
return true;
}

addElementsToTextLayer() {
if (!this.#elementsToAddToTextLayer) {
return;
updateTextLayer() {
if (this.#elementsToAddToTextLayer) {
for (const [id, img] of this.#elementsToAddToTextLayer) {
document.getElementById(id)?.append(img);
}
this.#elementsToAddToTextLayer.clear();
this.#elementsToAddToTextLayer = null;
}
for (const [id, img] of this.#elementsToAddToTextLayer) {
document.getElementById(id)?.append(img);
if (this.#elementsToHideInTextLayer) {
for (const id of this.#elementsToHideInTextLayer) {
const elem = document.getElementById(id);
if (elem) {
elem.ariaHidden = true;
}
}
this.#elementsToHideInTextLayer.length = 0;
this.#elementsToHideInTextLayer = null;
}
if (this.#elementsToStealFromTextLayer) {
for (
let i = 0, ii = this.#elementsToStealFromTextLayer.length;
i < ii;
i += 2
) {
const element = this.#elementsToStealFromTextLayer[i];
const ids = this.#elementsToStealFromTextLayer[i + 1];
let textContent = "";
for (const id of ids) {
const elem = document.getElementById(id);
if (elem) {
textContent += elem.textContent.trim() || "";
// Aria-hide the element in order to avoid duplicate reading of the
// math content by screen readers.
elem.ariaHidden = "true";
}
}
if (textContent) {
element.textContent = textContent;
}
}
this.#elementsToStealFromTextLayer.length = 0;
this.#elementsToStealFromTextLayer = null;
}
this.#elementsToAddToTextLayer.clear();
this.#elementsToAddToTextLayer = null;
}

#walk(node) {
Expand All @@ -325,21 +363,13 @@ class StructTreeLayerBuilder {
const { role } = node;
if (MathMLElements.has(role)) {
element = document.createElementNS(MathMLNamespace, role);
let text = "";
const ids = [];
(this.#elementsToStealFromTextLayer ||= []).push(element, ids);
for (const { type, id } of node.children || []) {
if (type !== "content" || !id) {
continue;
if (type === "content" && id) {
ids.push(id);
}
const elem = document.getElementById(id);
if (!elem) {
continue;
}
text += elem.textContent.trim() || "";
// Aria-hide the element in order to avoid duplicate reading of the
// math content by screen readers.
elem.ariaHidden = "true";
}
element.textContent = text;
} else {
element = document.createElement("span");
}
Expand All @@ -365,10 +395,7 @@ class StructTreeLayerBuilder {
if (!id) {
continue;
}
const elem = document.getElementById(id);
if (elem) {
elem.ariaHidden = true;
}
(this.#elementsToHideInTextLayer ||= []).push(id);
}
// For now, we don't want to keep the alt text if there's valid
// MathML (see https://github.com/w3c/mathml-aam/issues/37).
Expand Down
Loading