Merge pull request #20055 from calixteman/bug1975264

[Editor] When an annotation is added, announce it if the user is using a screen reader (bug 1975264)
This commit is contained in:
calixteman 2025-07-03 17:23:56 +02:00 committed by GitHub
commit ad31385792
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 86 additions and 20 deletions

View File

@ -533,6 +533,14 @@ pdfjs-editor-alt-text-settings-show-dialog-button-label = Show alt text editor r
pdfjs-editor-alt-text-settings-show-dialog-description = Helps you make sure all your images have alt text.
pdfjs-editor-alt-text-settings-close-button = Close
## Accessibility labels (announced by screen readers) for objects added to the editor.
pdfjs-editor-highlight-added-alert = Highlight added
pdfjs-editor-freetext-added-alert = Text added
pdfjs-editor-ink-added-alert = Drawing added
pdfjs-editor-stamp-added-alert = Image added
pdfjs-editor-signature-added-alert = Signature added
## "Annotations removed" bar
pdfjs-editor-undo-bar-message-highlight = Highlight removed

View File

@ -106,6 +106,9 @@ class DrawingEditor extends AnnotationEditor {
#createDrawOutlines({ drawOutlines, drawId, drawingOptions }) {
this.#drawOutlines = drawOutlines;
this._drawingOptions ||= drawingOptions;
if (!this.annotationElementId) {
this._uiManager.a11yAlert(`pdfjs-editor-${this.editorType}-added-alert`);
}
if (drawId >= 0) {
this._drawId = drawId;

View File

@ -180,6 +180,7 @@ class AnnotationEditor {
this._willKeepAspectRatio = false;
this._initialOptions.isCentered = parameters.isCentered;
this._structTreeParentId = null;
this.annotationElementId = parameters.annotationElementId || null;
const {
rotation,
@ -427,6 +428,9 @@ class AnnotationEditor {
* Commit the data contained in this editor.
*/
commit() {
if (!this.isInEditMode()) {
return;
}
this.addToAnnotationStorage();
}
@ -1641,6 +1645,7 @@ class AnnotationEditor {
parent,
id: parent.getNextId(),
uiManager,
annotationElementId: data.annotationElementId,
});
editor.rotation = data.rotation;
editor.#accessibilityData = data.accessibilityData;

View File

@ -131,6 +131,9 @@ class FreeTextEditor extends AnnotationEditor {
FreeTextEditor._defaultColor ||
AnnotationEditor._defaultLineColor;
this.#fontSize = params.fontSize || FreeTextEditor._defaultFontSize;
if (!this.annotationElementId) {
this._uiManager.a11yAlert("pdfjs-editor-freetext-added-alert");
}
}
/** @inheritdoc */
@ -782,6 +785,7 @@ class FreeTextEditor extends AnnotationEditor {
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
annotationElementId: id,
id,
deleted: false,
popupRef,
@ -791,7 +795,6 @@ class FreeTextEditor extends AnnotationEditor {
editor.#fontSize = data.fontSize;
editor.#color = Util.makeHexColor(...data.color);
editor.#content = FreeTextEditor.#deserializeContent(data.value);
editor.annotationElementId = data.id || null;
editor._initialData = initialData;
return editor;

View File

@ -126,6 +126,10 @@ class HighlightEditor extends AnnotationEditor {
this.#addToDrawLayer();
this.rotate(this.rotation);
}
if (!this.annotationElementId) {
this._uiManager.a11yAlert("pdfjs-editor-highlight-added-alert");
}
}
/** @inheritdoc */
@ -876,6 +880,7 @@ class HighlightEditor extends AnnotationEditor {
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
annotationElementId: id,
id,
deleted: false,
popupRef,
@ -904,6 +909,7 @@ class HighlightEditor extends AnnotationEditor {
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
annotationElementId: id,
id,
deleted: false,
popupRef,
@ -918,7 +924,6 @@ class HighlightEditor extends AnnotationEditor {
if (inkLists) {
editor.#thickness = data.thickness;
}
editor.annotationElementId = data.id || null;
editor._initialData = initialData;
const [pageWidth, pageHeight] = editor.pageDimensions;

View File

@ -164,6 +164,7 @@ class InkEditor extends DrawingEditor {
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
annotationElementId: id,
id,
deleted: false,
popupRef,
@ -171,7 +172,6 @@ class InkEditor extends DrawingEditor {
}
const editor = await super.deserialize(data, parent, uiManager);
editor.annotationElementId = data.id || null;
editor._initialData = initialData;
return editor;

View File

@ -484,6 +484,9 @@ class StampEditor extends AnnotationEditor {
if (this.#bitmapFileName) {
this.div.setAttribute("aria-description", this.#bitmapFileName);
}
if (!this.annotationElementId) {
this._uiManager.a11yAlert("pdfjs-editor-stamp-added-alert");
}
}
copyCanvas(maxDataDimension, maxPreviewDimension, createImageData = false) {
@ -781,6 +784,7 @@ class StampEditor extends AnnotationEditor {
pageIndex: pageNumber - 1,
rect: rect.slice(0),
rotation,
annotationElementId: id,
id,
deleted: false,
accessibilityData: {
@ -812,7 +816,6 @@ class StampEditor extends AnnotationEditor {
editor.width = (rect[2] - rect[0]) / parentWidth;
editor.height = (rect[3] - rect[1]) / parentHeight;
editor.annotationElementId = data.id || null;
if (accessibilityData) {
editor.altTextData = accessibilityData;
}

View File

@ -676,6 +676,8 @@ class AnnotationEditorUIManager {
#viewer = null;
#viewerAlert = null;
#updateModeCapability = null;
static TRANSLATE_SMALL = 1; // page units.
@ -818,6 +820,7 @@ class AnnotationEditorUIManager {
constructor(
container,
viewer,
viewerAlert,
altTextManager,
signatureManager,
eventBus,
@ -834,6 +837,7 @@ class AnnotationEditorUIManager {
const signal = (this._signal = this.#abortController.signal);
this.#container = container;
this.#viewer = viewer;
this.#viewerAlert = viewerAlert;
this.#altTextManager = altTextManager;
this.#signatureManager = signatureManager;
this._eventBus = eventBus;
@ -1175,6 +1179,19 @@ class AnnotationEditorUIManager {
}
}
a11yAlert(messageId, args = null) {
const viewerAlert = this.#viewerAlert;
if (!viewerAlert) {
return;
}
viewerAlert.setAttribute("data-l10n-id", messageId);
if (args) {
viewerAlert.setAttribute("data-l10n-args", JSON.stringify(args));
} else {
viewerAlert.removeAttribute("data-l10n-args");
}
}
#selectionChange() {
const selection = document.getSelection();
if (!selection || selection.isCollapsed) {

View File

@ -110,6 +110,9 @@ describe("FreeText Editor", () => {
await waitForSelectedEditor(page, editorSelector);
await waitForStorageEntries(page, 1);
const alert = await page.$eval("#viewer-alert", el => el.textContent);
expect(alert).toEqual("Text added");
let content = await page.$eval(editorSelector, el =>
el.innerText.trimEnd()
);

View File

@ -78,6 +78,9 @@ describe("Highlight Editor", () => {
await page.waitForSelector(`${getEditorSelector(0)}`);
const alert = await page.$eval("#viewer-alert", el => el.textContent);
expect(alert).toEqual("Highlight added");
const oneToOne = Array.from(new Array(13).keys(), n => n + 2).concat(
Array.from(new Array(13).keys(), n => 13 - n)
);

View File

@ -84,6 +84,9 @@ describe("Ink Editor", () => {
await commit(page);
}
const alert = await page.$eval("#viewer-alert", el => el.textContent);
expect(alert).toEqual("Drawing added");
await clearAll(page);
await kbUndo(page);

View File

@ -181,6 +181,9 @@ describe("Signature Editor", () => {
{ visible: true }
);
const alert = await page.$eval("#viewer-alert", el => el.textContent);
expect(alert).toEqual("Signature added");
// Check the tooltip.
await page.waitForSelector(
`.altText.editDescription[title="Hello World"]`

View File

@ -125,6 +125,9 @@ describe("Stamp Editor", () => {
const editorSelector = getEditorSelector(0);
await waitForImage(page, editorSelector);
const alert = await page.$eval("#viewer-alert", el => el.textContent);
expect(alert).toEqual("Image added");
const { width } = await getEditorDimensions(page, editorSelector);
// The image is bigger than the page, so it has been scaled down to

View File

@ -49,22 +49,6 @@
--new-alt-text-warning-image: url(images/altText_warning.svg);
}
/* The following class is used to hide an element but keep it available to
* for screen readers. */
.visuallyHidden {
position: absolute;
top: 0;
left: 0;
border: 0;
margin: 0;
padding: 0;
width: 0;
height: 0;
overflow: hidden;
white-space: nowrap;
font-size: 0;
}
.textLayer {
&.highlighting {
cursor: var(--editorFreeHighlight-editing-cursor);

View File

@ -492,6 +492,7 @@ const PDFViewerApplication = {
const pdfViewer = (this.pdfViewer = new PDFViewer({
container,
viewer,
viewerAlert: appConfig.viewerAlert,
eventBus,
renderingQueue,
linkService,

View File

@ -278,6 +278,8 @@ class PDFViewer {
#textLayerMode = TextLayerMode.ENABLE;
#viewerAlert = null;
/**
* @param {PDFViewerOptions} options
*/
@ -291,6 +293,7 @@ class PDFViewer {
}
this.container = options.container;
this.viewer = options.viewer || options.container.firstElementChild;
this.#viewerAlert = options.viewerAlert || null;
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
if (this.container?.tagName !== "DIV" || this.viewer?.tagName !== "DIV") {
@ -927,6 +930,7 @@ class PDFViewer {
this.#annotationEditorUIManager = new AnnotationEditorUIManager(
this.container,
viewer,
this.#viewerAlert,
this.#altTextManager,
this.#signatureManager,
eventBus,

View File

@ -229,6 +229,22 @@ body {
}
}
/* The following class is used to hide an element but keep it available to
* for screen readers. */
.visuallyHidden {
position: absolute;
top: 0;
left: 0;
border: 0;
margin: 0;
padding: 0;
width: 0;
height: 0;
overflow: hidden;
white-space: nowrap;
font-size: 0;
}
.hidden,
[hidden] {
display: none !important;

View File

@ -98,6 +98,7 @@ See https://github.com/adobe-type-tools/cmap-resources
</head>
<body tabindex="0">
<span id="viewer-alert" class="visuallyHidden" role="alert"></span>
<div id="outerContainer">
<div id="sidebarContainer">

View File

@ -33,6 +33,7 @@ function getViewerConfiguration() {
principalContainer: document.getElementById("mainContainer"),
mainContainer: document.getElementById("viewerContainer"),
viewerContainer: document.getElementById("viewer"),
viewerAlert: document.getElementById("viewer-alert"),
toolbar: {
container: document.getElementById("toolbarContainer"),
numPages: document.getElementById("numPages"),