[Editor] Fix the role of the different editors in order to make them interactive elements (bug 1953290)

Having some interactive elements forces the screen readers to switch to form mode
and consequently they delegate the keyboard stuff to the browser.
This patch sets an aria label on each editor in order to have a better description than just
'application'.
This commit is contained in:
Calixte Denizet 2025-03-12 20:37:02 +01:00
parent bec6287b0a
commit 7fce3eac93
9 changed files with 41 additions and 35 deletions

View File

@ -324,6 +324,19 @@ pdfjs-editor-signature-button =
.title = Add signature .title = Add signature
pdfjs-editor-signature-button-label = Add signature pdfjs-editor-signature-button-label = Add signature
## Default editor aria labels
# “Highlight” is a noun, the string is used on the editor for highlights.
pdfjs-editor-highlight-editor =
.aria-label = Highlight editor
# “Drawing” is a noun, the string is used on the editor for drawings.
pdfjs-editor-ink-editor =
.aria-label = Drawing editor
pdfjs-editor-signature-editor =
.aria-label = Signature editor
pdfjs-editor-stamp-editor =
.aria-label = Image editor
## Remove button for the various kind of editor. ## Remove button for the various kind of editor.
pdfjs-editor-remove-ink-button = pdfjs-editor-remove-ink-button =
@ -360,10 +373,6 @@ pdfjs-editor-signature-add-signature-button-label = Add new signature
pdfjs-free-text2 = pdfjs-free-text2 =
.aria-label = Text Editor .aria-label = Text Editor
.default-content = Start typing… .default-content = Start typing…
pdfjs-ink =
.aria-label = Draw Editor
pdfjs-ink-canvas =
.aria-label = User-created image
## Alt-text dialog ## Alt-text dialog

View File

@ -330,7 +330,7 @@ class AltText {
button.append(tooltip); button.append(tooltip);
} }
const element = this.#editor.getImageForAltText(); const element = this.#editor.getElementForAltText();
element?.setAttribute("aria-describedby", tooltip.id); element?.setAttribute("aria-describedby", tooltip.id);
} }
} }

View File

@ -1142,13 +1142,17 @@ class AnnotationEditor {
* @returns {HTMLDivElement | null} * @returns {HTMLDivElement | null}
*/ */
render() { render() {
this.div = document.createElement("div"); const div = (this.div = document.createElement("div"));
this.div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360); div.setAttribute("data-editor-rotation", (360 - this.rotation) % 360);
this.div.className = this.name; div.className = this.name;
this.div.setAttribute("id", this.id); div.setAttribute("id", this.id);
this.div.tabIndex = this.#disabled ? -1 : 0; div.tabIndex = this.#disabled ? -1 : 0;
div.setAttribute("role", "application");
if (this.defaultL10nId) {
div.setAttribute("data-l10n-id", this.defaultL10nId);
}
if (!this._isVisible) { if (!this._isVisible) {
this.div.classList.add("hidden"); div.classList.add("hidden");
} }
this.setInForeground(); this.setInForeground();
@ -1156,23 +1160,22 @@ class AnnotationEditor {
const [parentWidth, parentHeight] = this.parentDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
if (this.parentRotation % 180 !== 0) { if (this.parentRotation % 180 !== 0) {
this.div.style.maxWidth = `${((100 * parentHeight) / parentWidth).toFixed( div.style.maxWidth = `${((100 * parentHeight) / parentWidth).toFixed(
2
)}%`;
div.style.maxHeight = `${((100 * parentWidth) / parentHeight).toFixed(
2 2
)}%`; )}%`;
this.div.style.maxHeight = `${(
(100 * parentWidth) /
parentHeight
).toFixed(2)}%`;
} }
const [tx, ty] = this.getInitialTranslation(); const [tx, ty] = this.getInitialTranslation();
this.translate(tx, ty); this.translate(tx, ty);
bindEvents(this, this.div, ["pointerdown"]); bindEvents(this, div, ["keydown", "pointerdown"]);
if (this.isResizable && this._uiManager._supportsPinchToZoom) { if (this.isResizable && this._uiManager._supportsPinchToZoom) {
this.#touchManager ||= new TouchManager({ this.#touchManager ||= new TouchManager({
container: this.div, container: div,
isPinchingDisabled: () => !this.isSelected, isPinchingDisabled: () => !this.isSelected,
onPinchStart: this.#touchPinchStartCallback.bind(this), onPinchStart: this.#touchPinchStartCallback.bind(this),
onPinching: this.#touchPinchCallback.bind(this), onPinching: this.#touchPinchCallback.bind(this),
@ -1183,7 +1186,7 @@ class AnnotationEditor {
this._uiManager._editorUndoBar?.hide(); this._uiManager._editorUndoBar?.hide();
return this.div; return div;
} }
#touchPinchStartCallback() { #touchPinchStartCallback() {
@ -1692,7 +1695,6 @@ class AnnotationEditor {
if (this.isResizable) { if (this.isResizable) {
this.#createResizers(); this.#createResizers();
this.#resizersDiv.classList.remove("hidden"); this.#resizersDiv.classList.remove("hidden");
bindEvents(this, this.div, ["keydown"]);
} }
} }
@ -1892,8 +1894,8 @@ class AnnotationEditor {
/** /**
* @returns {HTMLElement | null} the element requiring an alt text. * @returns {HTMLElement | null} the element requiring an alt text.
*/ */
getImageForAltText() { getElementForAltText() {
return null; return this.div;
} }
/** /**

View File

@ -111,6 +111,7 @@ class HighlightEditor extends AnnotationEditor {
this.#methodOfCreation = params.methodOfCreation || ""; this.#methodOfCreation = params.methodOfCreation || "";
this.#text = params.text || ""; this.#text = params.text || "";
this._isDraggable = false; this._isDraggable = false;
this.defaultL10nId = "pdfjs-editor-highlight-editor";
if (params.highlightId > -1) { if (params.highlightId > -1) {
this.#isFreeHighlight = true; this.#isFreeHighlight = true;

View File

@ -68,6 +68,7 @@ class InkEditor extends DrawingEditor {
constructor(params) { constructor(params) {
super({ ...params, name: "inkEditor" }); super({ ...params, name: "inkEditor" });
this._willKeepAspectRatio = true; this._willKeepAspectRatio = true;
this.defaultL10nId = "pdfjs-editor-ink-editor";
} }
/** @inheritdoc */ /** @inheritdoc */

View File

@ -79,6 +79,7 @@ class SignatureEditor extends DrawingEditor {
this._willKeepAspectRatio = true; this._willKeepAspectRatio = true;
this.#signatureData = params.signatureData || null; this.#signatureData = params.signatureData || null;
this.#description = null; this.#description = null;
this.defaultL10nId = "pdfjs-editor-signature-editor";
} }
/** @inheritdoc */ /** @inheritdoc */
@ -156,7 +157,6 @@ class SignatureEditor extends DrawingEditor {
} }
super.render(); super.render();
this.div.setAttribute("role", "figure");
if (this._drawId === null) { if (this._drawId === null) {
if (this.#signatureData) { if (this.#signatureData) {
@ -259,7 +259,7 @@ class SignatureEditor extends DrawingEditor {
const { outline } = (this.#signatureData = data); const { outline } = (this.#signatureData = data);
this.#isExtracted = outline instanceof ContourDrawOutline; this.#isExtracted = outline instanceof ContourDrawOutline;
this.#description = description; this.#description = description;
this.div.setAttribute("aria-label", description); this.div.setAttribute("aria-description", description);
let drawingOptions; let drawingOptions;
if (this.#isExtracted) { if (this.#isExtracted) {
drawingOptions = SignatureEditor.getDefaultDrawingOptions(); drawingOptions = SignatureEditor.getDefaultDrawingOptions();

View File

@ -56,6 +56,7 @@ class StampEditor extends AnnotationEditor {
super({ ...params, name: "stampEditor" }); super({ ...params, name: "stampEditor" });
this.#bitmapUrl = params.bitmapUrl; this.#bitmapUrl = params.bitmapUrl;
this.#bitmapFile = params.bitmapFile; this.#bitmapFile = params.bitmapFile;
this.defaultL10nId = "pdfjs-editor-stamp-editor";
} }
/** @inheritdoc */ /** @inheritdoc */
@ -353,7 +354,6 @@ class StampEditor extends AnnotationEditor {
super.render(); super.render();
this.div.hidden = true; this.div.hidden = true;
this.div.setAttribute("role", "figure");
this.addAltTextButton(); this.addAltTextButton();
@ -474,7 +474,7 @@ class StampEditor extends AnnotationEditor {
action: "inserted_image", action: "inserted_image",
}); });
if (this.#bitmapFileName) { if (this.#bitmapFileName) {
canvas.setAttribute("aria-label", this.#bitmapFileName); this.div.setAttribute("aria-description", this.#bitmapFileName);
} }
} }
@ -686,11 +686,6 @@ class StampEditor extends AnnotationEditor {
); );
} }
/** @inheritdoc */
getImageForAltText() {
return this.#canvas;
}
#serializeBitmap(toUrl) { #serializeBitmap(toUrl) {
if (toUrl) { if (toUrl) {
if (this.#isSvg) { if (this.#isSvg) {

View File

@ -184,7 +184,7 @@ describe("Signature Editor", () => {
// Check the aria label. // Check the aria label.
await page.waitForSelector( await page.waitForSelector(
`${editorSelector}[aria-label="Hello World"]` `${editorSelector}[aria-description="Hello World"]`
); );
// Edit the description. // Edit the description.

View File

@ -361,9 +361,7 @@ describe("Stamp Editor", () => {
await page.click(saveButtonSelector); await page.click(saveButtonSelector);
// Check that the canvas has an aria-describedby attribute. // Check that the canvas has an aria-describedby attribute.
await page.waitForSelector( await page.waitForSelector(`${editorSelector}[aria-describedby]`);
`${editorSelector} canvas[aria-describedby]`
);
// Wait for the alt-text button to have the correct icon. // Wait for the alt-text button to have the correct icon.
await page.waitForSelector(`${buttonSelector}.done`); await page.waitForSelector(`${buttonSelector}.done`);