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() {
const originalLabelColor = getComputedStyle(label).color;
const originalLineColor = getComputedStyle(underline).borderBottomColor;
assertEquals(crInput.errorMessage, errorLabel.textContent);
assertEquals('', errorLabel.textContent);
assertFalse(errorLabel.hasAttribute('role'));
assertEquals('0', getComputedStyle(underline).opacity);
assertEquals(0, underline.offsetWidth);
assertEquals('hidden', getComputedStyle(errorLabel).visibility);
......@@ -251,6 +252,8 @@ suite('cr-input', function() {
crInput.invalid = true;
Polymer.dom.flush();
assertTrue(crInput.hasAttribute('invalid'));
assertEquals('alert', errorLabel.getAttribute('role'));
assertEquals(crInput.errorMessage, errorLabel.textContent);
assertEquals('visible', getComputedStyle(errorLabel).visibility);
assertTrue(originalLabelColor !== getComputedStyle(label).color);
assertTrue(
......@@ -392,21 +395,4 @@ suite('cr-input', function() {
assertTrue(test_util.isChildVisible(crInput, '#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") {
}
js_library("cr_input") {
deps = [
"//third_party/polymer/v1_0/components-chromium/iron-a11y-announcer:iron-a11y-announcer-extracted",
"//ui/webui/resources/js:assert",
]
deps = [ "//ui/webui/resources/js:assert" ]
}
group("polymer3_elements") {
......
<link rel="import" href="../../html/polymer.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="../hidden_style_css.html">
<link rel="import" href="../shared_style_css.html">
......@@ -145,13 +144,7 @@
</div>
<slot name="suffix"></slot>
</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>
<div id="error">[[displayErrorMessage_]]</div>
</template>
<script src="cr_input.js"></script>
</dom-module>
......@@ -78,6 +78,12 @@ Polymer({
observer: 'onInvalidOrErrorMessageChanged_',
},
/** @private */
displayErrorMessage_: {
type: String,
value: '',
},
/**
* This is strictly used internally for styling, do not attempt to use
* this to set focus.
......@@ -230,14 +236,21 @@ Polymer({
}
},
/**
* Uses IronA11yAnnouncer to notify screen readers that an error is set.
* @private
*/
/** @private */
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) {
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