From 95f576ca9aa0395345b744617e254d88d2d4eb58 Mon Sep 17 00:00:00 2001 From: crisbeto Date: Wed, 20 Feb 2019 22:47:53 +0100 Subject: [PATCH] fix(a11y): don't set aria description if it's the same as the node's aria-label As per a recent discussion, reworks the `AriaDescriber` not to set the `aria-describedby`, if the message will be exactly the same as an existing `aria-label` on the element. This avoids the same text being read out twice. Fixes #15048. --- .../a11y/aria-describer/aria-describer.spec.ts | 7 +++++++ src/cdk/a11y/aria-describer/aria-describer.ts | 18 +++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/cdk/a11y/aria-describer/aria-describer.spec.ts b/src/cdk/a11y/aria-describer/aria-describer.spec.ts index aa3b932552d0..22e7dbea12c9 100644 --- a/src/cdk/a11y/aria-describer/aria-describer.spec.ts +++ b/src/cdk/a11y/aria-describer/aria-describer.spec.ts @@ -132,6 +132,13 @@ describe('AriaDescriber', () => { // Use `querySelectorAll` with an attribute since `getElementById` will stop at the first match. expect(document.querySelectorAll(`[id='${MESSAGES_CONTAINER_ID}']`).length).toBe(1); }); + + it('should not describe messages that match up with the aria-label of the element', () => { + component.element1.setAttribute('aria-label', 'Hello'); + ariaDescriber.describe(component.element1, 'Hello'); + ariaDescriber.describe(component.element1, 'Hi'); + expectMessages(['Hi']); + }); }); function getMessagesContainer() { diff --git a/src/cdk/a11y/aria-describer/aria-describer.ts b/src/cdk/a11y/aria-describer/aria-describer.ts index ae866c1e074d..10a6bbecc9fb 100644 --- a/src/cdk/a11y/aria-describer/aria-describer.ts +++ b/src/cdk/a11y/aria-describer/aria-describer.ts @@ -83,7 +83,7 @@ export class AriaDescriber implements OnDestroy { /** Removes the host element's aria-describedby reference to the message element. */ removeDescription(hostElement: Element, message: string) { - if (!this._canBeDescribed(hostElement, message)) { + if (!this._isElementNode(hostElement)) { return; } @@ -218,10 +218,22 @@ export class AriaDescriber implements OnDestroy { /** Determines whether a message can be described on a particular element. */ private _canBeDescribed(element: Element, message: string): boolean { - return element.nodeType === this._document.ELEMENT_NODE && message != null && - !!`${message}`.trim(); + if (!this._isElementNode(element)) { + return false; + } + + const trimmedMessage = message == null ? '' : `${message}`.trim(); + const ariaLabel = element.getAttribute('aria-label'); + + // We shouldn't set descriptions if they're exactly the same as the `aria-label` of the element, + // because screen readers will end up reading out the same text twice in a row. + return trimmedMessage ? (!ariaLabel || ariaLabel.trim() !== trimmedMessage) : false; } + /** Checks whether a node is an Element node. */ + private _isElementNode(element: Node): element is Element { + return element.nodeType === this._document.ELEMENT_NODE; + } }