Commit 7ab26af9 authored by Demetrios Papadopoulos's avatar Demetrios Papadopoulos Committed by Commit Bot

WebUI: Use role="alert" from within cr-input for better a11y.

The IronA11Announcer approach does not work when a modal dialog is
showing. A large number of cr-input instances reside within modal
dialogs, and errors do not get announced to screen reader users.

By using role="alert" the error gets announced, even when the
cr-input resides in a modal dialog.

Moreover, VoiceOver seems to not announce the same error
text, even if the cr-input transitions from invalid->valid->invalid.
A workaround has been employed to always removing role="alert" when
the error is fixed and re-adding it when the error re-occurs which
forces VoiceOver to always announce it.

Bug: 1130778
Change-Id: I646e25814f4719fb1a39d206e0c970a8bb75aae9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2429943
Commit-Queue: dpapad <dpapad@chromium.org>
Auto-Submit: dpapad <dpapad@chromium.org>
Reviewed-by: default avatarJohn Lee <johntlee@chromium.org>
Cr-Commit-Position: refs/heads/master@{#811352}
parent 34f49d5a
...@@ -240,7 +240,8 @@ suite('cr-input', function() { ...@@ -240,7 +240,8 @@ suite('cr-input', function() {
const originalLabelColor = getComputedStyle(label).color; const originalLabelColor = getComputedStyle(label).color;
const originalLineColor = getComputedStyle(underline).borderBottomColor; const originalLineColor = getComputedStyle(underline).borderBottomColor;
assertEquals(crInput.errorMessage, errorLabel.textContent); assertEquals('', errorLabel.textContent);
assertFalse(errorLabel.hasAttribute('role'));
assertEquals('0', getComputedStyle(underline).opacity); assertEquals('0', getComputedStyle(underline).opacity);
assertEquals(0, underline.offsetWidth); assertEquals(0, underline.offsetWidth);
assertEquals('hidden', getComputedStyle(errorLabel).visibility); assertEquals('hidden', getComputedStyle(errorLabel).visibility);
...@@ -251,6 +252,8 @@ suite('cr-input', function() { ...@@ -251,6 +252,8 @@ suite('cr-input', function() {
crInput.invalid = true; crInput.invalid = true;
Polymer.dom.flush(); Polymer.dom.flush();
assertTrue(crInput.hasAttribute('invalid')); assertTrue(crInput.hasAttribute('invalid'));
assertEquals('alert', errorLabel.getAttribute('role'));
assertEquals(crInput.errorMessage, errorLabel.textContent);
assertEquals('visible', getComputedStyle(errorLabel).visibility); assertEquals('visible', getComputedStyle(errorLabel).visibility);
assertTrue(originalLabelColor !== getComputedStyle(label).color); assertTrue(originalLabelColor !== getComputedStyle(label).color);
assertTrue( assertTrue(
...@@ -392,21 +395,4 @@ suite('cr-input', function() { ...@@ -392,21 +395,4 @@ suite('cr-input', function() {
assertTrue(test_util.isChildVisible(crInput, '#suffix', true)); assertTrue(test_util.isChildVisible(crInput, '#suffix', true));
assertTrue(test_util.isChildVisible(crInput, '#inline-suffix', true)); assertTrue(test_util.isChildVisible(crInput, '#inline-suffix', true));
}); });
test('announce error message when invalid', async () => {
const errorMessagesAnnounced = [];
const handler = e => {
errorMessagesAnnounced.push(e.detail.text);
};
crInput.addEventListener('iron-announce', handler);
crInput.invalid = false;
crInput.errorMessage = '1';
crInput.errorMessage = '2';
crInput.invalid = true;
crInput.errorMessage = '3';
crInput.invalid = false;
crInput.errorMessage = '4';
assertDeepEquals(['2', '3'], errorMessagesAnnounced);
crInput.removeEventListener('iron-announce', handler);
});
}); });
...@@ -10,10 +10,7 @@ js_type_check("closure_compile") { ...@@ -10,10 +10,7 @@ js_type_check("closure_compile") {
} }
js_library("cr_input") { js_library("cr_input") {
deps = [ deps = [ "//ui/webui/resources/js:assert" ]
"//third_party/polymer/v1_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer-extracted",
"//ui/webui/resources/js:assert",
]
} }
group("polymer3_elements") { group("polymer3_elements") {
......
<link rel="import" href="../../html/polymer.html"> <link rel="import" href="../../html/polymer.html">
<link rel="import" href="../../html/assert.html"> <link rel="import" href="../../html/assert.html">
<link rel="import" href="chrome://resources/polymer/v1_0/iron-a11y-announcer/iron-a11y-announcer.html">
<link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html"> <link rel="import" href="chrome://resources/polymer/v1_0/paper-styles/color.html">
<link rel="import" href="../hidden_style_css.html"> <link rel="import" href="../hidden_style_css.html">
<link rel="import" href="../shared_style_css.html"> <link rel="import" href="../shared_style_css.html">
...@@ -145,13 +144,7 @@ ...@@ -145,13 +144,7 @@
</div> </div>
<slot name="suffix"></slot> <slot name="suffix"></slot>
</div> </div>
<!-- <div id="error">[[displayErrorMessage_]]</div>
Note: role="alert" is not working as expected because screen readers
expect live regions to be in the DOM at time of document loading.
cr-input will automatically fire iron-announce for the error message.
Also aria-hidden="true" to avoid noise for screen reader users.
-->
<div id="error" aria-hidden="true">[[errorMessage]]</div>
</template> </template>
<script src="cr_input.js"></script> <script src="cr_input.js"></script>
</dom-module> </dom-module>
...@@ -78,6 +78,12 @@ Polymer({ ...@@ -78,6 +78,12 @@ Polymer({
observer: 'onInvalidOrErrorMessageChanged_', observer: 'onInvalidOrErrorMessageChanged_',
}, },
/** @private */
displayErrorMessage_: {
type: String,
value: '',
},
/** /**
* This is strictly used internally for styling, do not attempt to use * This is strictly used internally for styling, do not attempt to use
* this to set focus. * this to set focus.
...@@ -230,14 +236,21 @@ Polymer({ ...@@ -230,14 +236,21 @@ Polymer({
} }
}, },
/** /** @private */
* Uses IronA11yAnnouncer to notify screen readers that an error is set.
* @private
*/
onInvalidOrErrorMessageChanged_() { onInvalidOrErrorMessageChanged_() {
Polymer.IronA11yAnnouncer.requestAvailability(); this.displayErrorMessage_ = this.invalid ? this.errorMessage : '';
// On VoiceOver role="alert" is not consistently announced when its content
// changes. Adding and removing the |role| attribute every time there
// is an error, triggers VoiceOver to consistently announce.
const ERROR_ID = 'error';
const errorElement = this.$$(`#${ERROR_ID}`);
if (this.invalid) { if (this.invalid) {
this.fire('iron-announce', {text: this.errorMessage}); errorElement.setAttribute('role', 'alert');
this.inputElement.setAttribute('aria-errormessage', ERROR_ID);
} else {
errorElement.removeAttribute('role');
this.inputElement.removeAttribute('aria-errormessage');
} }
}, },
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment