Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

[web] move AccessibilityAnnouncements into SemanticsOwner #52138

Merged
merged 2 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions lib/web_ui/lib/src/engine/platform_dispatcher.dart
Original file line number Diff line number Diff line change
Expand Up @@ -688,9 +688,10 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
case 'flutter/accessibility':
// In widget tests we want to bypass processing of platform messages.
const StandardMessageCodec codec = StandardMessageCodec();
// TODO(yjbanov): Dispatch the announcement to the correct view?
// https://github.com/flutter/flutter/issues/137445
implicitView?.accessibilityAnnouncements.handleMessage(codec, data);
final EngineSemantics semantics = EngineSemantics.instance;
if (semantics.semanticsEnabled) {
semantics.accessibilityAnnouncements.handleMessage(codec, data);
}
replyToPlatformMessage(callback, codec.encodeMessage(true));
return;

Expand Down
3 changes: 1 addition & 2 deletions lib/web_ui/lib/src/engine/semantics/live_region.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import 'package:meta/meta.dart';

import '../platform_dispatcher.dart';
import 'accessibility.dart';
import 'label_and_value.dart';
import 'semantics.dart';
Expand Down Expand Up @@ -32,7 +31,7 @@ class LiveRegion extends RoleManager {

AccessibilityAnnouncements get _accessibilityAnnouncements =>
_accessibilityAnnouncementsOverride ??
EnginePlatformDispatcher.instance.implicitView!.accessibilityAnnouncements;
EngineSemantics.instance.accessibilityAnnouncements;

@override
void update() {
Expand Down
14 changes: 14 additions & 0 deletions lib/web_ui/lib/src/engine/semantics/semantics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import '../platform_dispatcher.dart';
import '../util.dart';
import '../vector_math.dart';
import '../window.dart';
import 'accessibility.dart';
import 'checkable.dart';
import 'dialog.dart';
import 'focusable.dart';
Expand Down Expand Up @@ -1938,6 +1939,19 @@ class EngineSemantics {

static EngineSemantics? _instance;

/// The tag name for the accessibility announcements host.
static const String announcementsHostTagName = 'flt-announcement-host';

/// Implements verbal accessibility announcements.
final AccessibilityAnnouncements accessibilityAnnouncements =
AccessibilityAnnouncements(hostElement: _initializeAccessibilityAnnouncementHost());

static DomElement _initializeAccessibilityAnnouncementHost() {
final DomElement host = createDomElement(announcementsHostTagName);
domDocument.body!.append(host);
return host;
}

/// Disables semantics and uninitializes the singleton [instance].
///
/// Instances of [EngineSemanticsOwner] are no longer valid after calling this
Expand Down
12 changes: 0 additions & 12 deletions lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import 'style_manager.dart';
/// | | | |
/// | | | +- <flt-scene>
/// | | |
/// | | +- [announcementsHost] <flt-announcement-host>
/// | | |
/// | | +- <style>
/// | |
/// | +- ...platform views
Expand All @@ -50,7 +48,6 @@ class DomManager {
final DomElement sceneHost = domDocument.createElement(DomManager.sceneHostTagName);
final DomElement textEditingHost = domDocument.createElement(DomManager.textEditingHostTagName);
final DomElement semanticsHost = domDocument.createElement(DomManager.semanticsHostTagName);
final DomElement announcementsHost = createDomElement(DomManager.announcementsHostTagName);

// Root element children.
rootElement.appendChild(platformViewsHost);
Expand All @@ -71,7 +68,6 @@ class DomManager {
// Rendering host (shadow root) children.

renderingHost.append(sceneHost);
renderingHost.append(announcementsHost);

// Styling.

Expand Down Expand Up @@ -106,7 +102,6 @@ class DomManager {
sceneHost: sceneHost,
textEditingHost: textEditingHost,
semanticsHost: semanticsHost,
announcementsHost: announcementsHost,
);
}

Expand All @@ -117,7 +112,6 @@ class DomManager {
required this.sceneHost,
required this.textEditingHost,
required this.semanticsHost,
required this.announcementsHost,
});

/// The tag name for the Flutter View root element.
Expand All @@ -135,9 +129,6 @@ class DomManager {
/// The tag name for the semantics host.
static const String semanticsHostTagName = 'flt-semantics-host';

/// The tag name for the accessibility announcements host.
static const String announcementsHostTagName = 'flt-announcement-host';

/// The root DOM element for the entire Flutter View.
///
/// This is where input events are captured, such as pointer events.
Expand Down Expand Up @@ -168,9 +159,6 @@ class DomManager {
/// around the UI.
final DomElement semanticsHost;

/// This is where accessibility announcements are inserted.
final DomElement announcementsHost;

DomElement? _lastSceneElement;

/// Inserts the [sceneElement] into the DOM and removes the existing scene (if
Expand Down
5 changes: 0 additions & 5 deletions lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,11 +135,6 @@ base class EngineFlutterView implements ui.FlutterView {
semantics.updateSemantics(update);
}

// TODO(yjbanov): How should this look like for multi-view?
// https://github.com/flutter/flutter/issues/137445
late final AccessibilityAnnouncements accessibilityAnnouncements =
AccessibilityAnnouncements(hostElement: dom.announcementsHost);

late final GlobalHtmlAttributes _globalHtmlAttributes = GlobalHtmlAttributes(
rootElement: dom.rootElement,
hostElement: embeddingStrategy.hostElement,
Expand Down
24 changes: 24 additions & 0 deletions lib/web_ui/test/engine/semantics/semantics_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,30 @@ void _testEngineSemanticsOwner() {
expect(semantics().mode, AccessibilityMode.unknown);
});

// Expecting the following DOM structure by default:
//
// <body>
// <flt-announcement-host>
// <flt-announcement-polite></flt-announcement-polite>
// <flt-announcement-assertive></flt-announcement-assertive>
// </flt-announcement-host>
// </body>
test('places accessibility announcements in the <body> tag', () {
final AccessibilityAnnouncements accessibilityAnnouncements = semantics().accessibilityAnnouncements;
final DomElement politeElement = accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite);
final DomElement assertiveElement = accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive);
final DomElement announcementHost = politeElement.parent!;

// Polite and assertive elements share the same host.
expect(
assertiveElement.parent,
announcementHost,
);

// The host is a direct child of <body>
expect(announcementHost.parent, domDocument.body);
});

test('accessibilityFeatures copyWith function works', () {
const EngineAccessibilityFeatures original = EngineAccessibilityFeatures(0);
EngineAccessibilityFeatures copy =
Expand Down
6 changes: 2 additions & 4 deletions lib/web_ui/test/engine/view_embedder/dom_manager_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ void doTests() {
expect(domManager.platformViewsHost.tagName, equalsIgnoringCase(DomManager.glassPaneTagName));
expect(domManager.textEditingHost.tagName, equalsIgnoringCase(DomManager.textEditingHostTagName));
expect(domManager.semanticsHost.tagName, equalsIgnoringCase(DomManager.semanticsHostTagName));
expect(domManager.announcementsHost.tagName, equalsIgnoringCase(DomManager.announcementsHostTagName));

// Check parent-child relationships.

Expand All @@ -39,10 +38,9 @@ void doTests() {
expect(rootChildren[3].tagName, equalsIgnoringCase('style'));

final List<DomElement> shadowChildren = domManager.renderingHost.childNodes.cast<DomElement>().toList();
expect(shadowChildren.length, 3);
expect(shadowChildren.length, 2);
expect(shadowChildren[0], domManager.sceneHost);
expect(shadowChildren[1], domManager.announcementsHost);
expect(shadowChildren[2].tagName, equalsIgnoringCase('style'));
expect(shadowChildren[1].tagName, equalsIgnoringCase('style'));
});

test('hide placeholder text for textfield', () {
Expand Down