Make screen reader announcement append a non-breaking space every other message. (flutter/engine#50151)

This fixes an issue when the same message was sent to the screen reader
again and was not subsequently announced by VoiceOver despite the prior
message having long been removed from the DOM.

Fixes https://github.com/flutter/flutter/issues/142250
This commit is contained in:
Ashish Myles 2024-01-31 17:30:15 -05:00 committed by GitHub
parent 2869030de1
commit 1ee98ba880
2 changed files with 32 additions and 4 deletions

View File

@ -51,6 +51,15 @@ class AccessibilityAnnouncements {
/// accouncements assertively.
final DomHTMLElement _assertiveElement;
/// Whether to append a non-breaking space to the end of the message
/// before outputting it.
///
/// It's used to work around a VoiceOver bug where announcing the same message
/// repeatedly results in subsequent messages not being announced despite the
/// fact that the previous announcement was already removed from the DOM a
/// long while back. See https://github.com/flutter/flutter/issues/142250.
bool _appendSpace = false;
/// Looks up the element used to announce messages of the given [assertiveness].
DomHTMLElement ariaLiveElementFor(Assertiveness assertiveness) {
switch (assertiveness) {
@ -84,7 +93,9 @@ class AccessibilityAnnouncements {
final DomHTMLElement ariaLiveElement = ariaLiveElementFor(assertiveness);
final DomHTMLDivElement messageElement = createDomHTMLDivElement();
messageElement.text = message;
// See the doc-comment for [_appendSpace] for the rationale.
messageElement.text = _appendSpace ? '$message\u00A0' : message;
_appendSpace = !_appendSpace;
ariaLiveElement.append(messageElement);
Timer(liveMessageDuration, () => messageElement.remove());
}

View File

@ -91,21 +91,38 @@ void testMain() {
expectNoMessages();
});
test('Rapid-fire messages are each announced.', () async {
test('Rapid-fire messages are each announced', () async {
sendAnnouncementMessage(message: 'Hello');
expectMessages(polite: 'Hello');
await Future<void>.delayed(liveMessageDuration * 0.5);
sendAnnouncementMessage(message: 'There');
expectMessages(polite: 'HelloThere');
expectMessages(polite: 'HelloThere\u00A0');
await Future<void>.delayed(liveMessageDuration * 0.6);
expectMessages(polite: 'There');
expectMessages(polite: 'There\u00A0');
await Future<void>.delayed(liveMessageDuration * 0.5);
expectNoMessages();
});
test('Repeated announcements are modified to ensure screen readers announce them', () async {
sendAnnouncementMessage(message: 'Hello');
expectMessages(polite: 'Hello');
await Future<void>.delayed(liveMessageDuration);
expectNoMessages();
sendAnnouncementMessage(message: 'Hello');
expectMessages(polite: 'Hello\u00A0');
await Future<void>.delayed(liveMessageDuration);
expectNoMessages();
sendAnnouncementMessage(message: 'Hello');
expectMessages(polite: 'Hello');
await Future<void>.delayed(liveMessageDuration);
expectNoMessages();
});
test('announce() polite', () async {
accessibilityAnnouncements.announce('polite message', Assertiveness.polite);
expectMessages(polite: 'polite message');