[web] Refactor a11y announcements out of FlutterViewEmbedder (flutter/engine#47487)
- Remove a11y announcements from `FlutterViewEmbedder`. - Simplify a11y announcements tests so they don't need `FlutterViewEmbedder` nor `DomManager`. - Left a few a11y-multi-view TODOs (cc @yjbanov). Part of https://github.com/flutter/flutter/issues/134443
This commit is contained in:
@@ -133,8 +133,8 @@ class FlutterViewEmbedder {
|
||||
DomElement get textEditingHostNodeDEPRECATED => _textEditingHostNode;
|
||||
late DomElement _textEditingHostNode;
|
||||
|
||||
AccessibilityAnnouncements get accessibilityAnnouncements => _accessibilityAnnouncements;
|
||||
late AccessibilityAnnouncements _accessibilityAnnouncements;
|
||||
DomElement get announcementsHostDEPRECATED => _announcementsHost;
|
||||
late DomElement _announcementsHost;
|
||||
|
||||
static const String defaultFontStyle = 'normal';
|
||||
static const String defaultFontWeight = 'normal';
|
||||
@@ -214,12 +214,11 @@ class FlutterViewEmbedder {
|
||||
.instance.semanticsHelper
|
||||
.prepareAccessibilityPlaceholder();
|
||||
|
||||
final DomElement announcementsElement = createDomElement('flt-announcement-host');
|
||||
_accessibilityAnnouncements = AccessibilityAnnouncements(hostElement: announcementsElement);
|
||||
_announcementsHost = createDomElement('flt-announcement-host');
|
||||
|
||||
shadowRoot.append(accessibilityPlaceholder);
|
||||
shadowRoot.append(_sceneHostElement);
|
||||
shadowRoot.append(announcementsElement);
|
||||
shadowRoot.append(_announcementsHost);
|
||||
|
||||
// The semantic host goes last because hit-test order-wise it must be
|
||||
// first. If semantics goes under the scene host, platform views will
|
||||
@@ -248,11 +247,6 @@ class FlutterViewEmbedder {
|
||||
window.onResize.listen(_metricsDidChange);
|
||||
}
|
||||
|
||||
/// For tests only.
|
||||
void debugOverrideAccessibilityAnnouncements(AccessibilityAnnouncements override) {
|
||||
_accessibilityAnnouncements = override;
|
||||
}
|
||||
|
||||
/// The framework specifies semantics in physical pixels, but CSS uses
|
||||
/// logical pixels. To compensate, an inverse scale is injected at the root
|
||||
/// level.
|
||||
|
||||
@@ -601,7 +601,9 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
case 'flutter/accessibility':
|
||||
// In widget tests we want to bypass processing of platform messages.
|
||||
const StandardMessageCodec codec = StandardMessageCodec();
|
||||
flutterViewEmbedder.accessibilityAnnouncements.handleMessage(codec, data);
|
||||
// TODO(yjbanov): Dispatch the announcement to the correct view?
|
||||
// https://github.com/flutter/flutter/issues/137445
|
||||
implicitView!.accessibilityAnnouncements.handleMessage(codec, data);
|
||||
replyToPlatformMessage(callback, codec.encodeMessage(true));
|
||||
return;
|
||||
|
||||
|
||||
@@ -2,14 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import '../embedder.dart' show flutterViewEmbedder;
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import '../platform_dispatcher.dart';
|
||||
import 'accessibility.dart';
|
||||
import 'label_and_value.dart';
|
||||
import 'semantics.dart';
|
||||
|
||||
/// Manages semantics configurations that represent live regions.
|
||||
///
|
||||
/// Assistive technologies treat "aria-live" attribute differently. To keep
|
||||
/// the behavior consistent, [accessibilityAnnouncements.announce] is used.
|
||||
/// the behavior consistent, [AccessibilityAnnouncements.announce] is used.
|
||||
///
|
||||
/// When there is an update to [LiveRegion], assistive technologies read the
|
||||
/// label of the element. See [LabelAndValue]. If there is no label provided
|
||||
@@ -20,6 +23,17 @@ class LiveRegion extends RoleManager {
|
||||
|
||||
String? _lastAnnouncement;
|
||||
|
||||
static AccessibilityAnnouncements? _accessibilityAnnouncementsOverride;
|
||||
|
||||
@visibleForTesting
|
||||
static void debugOverrideAccessibilityAnnouncements(AccessibilityAnnouncements? value) {
|
||||
_accessibilityAnnouncementsOverride = value;
|
||||
}
|
||||
|
||||
AccessibilityAnnouncements get _accessibilityAnnouncements =>
|
||||
_accessibilityAnnouncementsOverride ??
|
||||
EnginePlatformDispatcher.instance.implicitView!.accessibilityAnnouncements;
|
||||
|
||||
@override
|
||||
void update() {
|
||||
if (!semanticsObject.isLiveRegion) {
|
||||
@@ -30,8 +44,9 @@ class LiveRegion extends RoleManager {
|
||||
if (_lastAnnouncement != semanticsObject.label) {
|
||||
_lastAnnouncement = semanticsObject.label;
|
||||
if (semanticsObject.hasLabel) {
|
||||
flutterViewEmbedder.accessibilityAnnouncements.announce(
|
||||
_lastAnnouncement! , Assertiveness.polite
|
||||
_accessibilityAnnouncements.announce(
|
||||
_lastAnnouncement!,
|
||||
Assertiveness.polite,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ import '../embedder.dart';
|
||||
/// | | | |
|
||||
/// | | | +- <flt-scene>
|
||||
/// | | |
|
||||
/// | | +- <flt-announcement-host>
|
||||
/// | | +- [announcementsHost] <flt-announcement-host>
|
||||
/// | |
|
||||
/// | +- ...platform views
|
||||
/// |
|
||||
@@ -63,4 +63,7 @@ class DomManager {
|
||||
/// Otherwise, the phone will disable focusing by touch, only by tabbing
|
||||
/// around the UI.
|
||||
DomElement get semanticsHost => _embedder.semanticsHostElementDEPRECATED;
|
||||
|
||||
/// This is where accessibility announcements are inserted.
|
||||
DomElement get announcementsHost => _embedder.announcementsHostDEPRECATED;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import 'mouse/cursor.dart';
|
||||
import 'navigation/history.dart';
|
||||
import 'platform_dispatcher.dart';
|
||||
import 'platform_views/message_handler.dart';
|
||||
import 'semantics/accessibility.dart';
|
||||
import 'services.dart';
|
||||
import 'util.dart';
|
||||
import 'view_embedder/dom_manager.dart';
|
||||
@@ -63,6 +64,11 @@ base class EngineFlutterView implements ui.FlutterView {
|
||||
@override
|
||||
void updateSemantics(ui.SemanticsUpdate update) => platformDispatcher.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 MouseCursor mouseCursor = MouseCursor(dom.rootElement);
|
||||
|
||||
late final ContextMenu contextMenu = ContextMenu(dom.rootElement);
|
||||
|
||||
@@ -7,11 +7,7 @@ import 'dart:typed_data';
|
||||
|
||||
import 'package:test/bootstrap/browser.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:ui/src/engine/dom.dart';
|
||||
import 'package:ui/src/engine/embedder.dart';
|
||||
import 'package:ui/src/engine/semantics.dart';
|
||||
import 'package:ui/src/engine/services.dart';
|
||||
import 'package:ui/src/engine/view_embedder/dom_manager.dart';
|
||||
import 'package:ui/src/engine.dart';
|
||||
|
||||
const StandardMessageCodec codec = StandardMessageCodec();
|
||||
|
||||
@@ -20,27 +16,24 @@ void main() {
|
||||
}
|
||||
|
||||
void testMain() {
|
||||
late DomManager domManager;
|
||||
late AccessibilityAnnouncements accessibilityAnnouncements;
|
||||
|
||||
setUp(() {
|
||||
final FlutterViewEmbedder embedder = FlutterViewEmbedder();
|
||||
domManager = DomManager.fromFlutterViewEmbedderDEPRECATED(embedder);
|
||||
accessibilityAnnouncements = embedder.accessibilityAnnouncements;
|
||||
final DomElement announcementsHost = createDomElement('flt-announcement-host');
|
||||
accessibilityAnnouncements = AccessibilityAnnouncements(hostElement: announcementsHost);
|
||||
setLiveMessageDurationForTest(const Duration(milliseconds: 10));
|
||||
expect(
|
||||
domManager.renderingHost.querySelector('flt-announcement-polite'),
|
||||
announcementsHost.querySelector('flt-announcement-polite'),
|
||||
accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.polite),
|
||||
);
|
||||
expect(
|
||||
domManager.renderingHost.querySelector('flt-announcement-assertive'),
|
||||
announcementsHost.querySelector('flt-announcement-assertive'),
|
||||
accessibilityAnnouncements.ariaLiveElementFor(Assertiveness.assertive),
|
||||
);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await Future<void>.delayed(liveMessageDuration * 2);
|
||||
domManager.rootElement.remove();
|
||||
});
|
||||
|
||||
group('$AccessibilityAnnouncements', () {
|
||||
|
||||
@@ -2331,6 +2331,10 @@ class MockAccessibilityAnnouncements implements AccessibilityAnnouncements {
|
||||
}
|
||||
|
||||
void _testLiveRegion() {
|
||||
tearDown(() {
|
||||
LiveRegion.debugOverrideAccessibilityAnnouncements(null);
|
||||
});
|
||||
|
||||
test('announces the label after an update', () async {
|
||||
semantics()
|
||||
..debugOverrideTimestampFunction(() => _testTime)
|
||||
@@ -2338,7 +2342,7 @@ void _testLiveRegion() {
|
||||
|
||||
final MockAccessibilityAnnouncements mockAccessibilityAnnouncements =
|
||||
MockAccessibilityAnnouncements();
|
||||
flutterViewEmbedder.debugOverrideAccessibilityAnnouncements(mockAccessibilityAnnouncements);
|
||||
LiveRegion.debugOverrideAccessibilityAnnouncements(mockAccessibilityAnnouncements);
|
||||
|
||||
final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
|
||||
updateNode(
|
||||
@@ -2361,7 +2365,7 @@ void _testLiveRegion() {
|
||||
|
||||
final MockAccessibilityAnnouncements mockAccessibilityAnnouncements =
|
||||
MockAccessibilityAnnouncements();
|
||||
flutterViewEmbedder.debugOverrideAccessibilityAnnouncements(mockAccessibilityAnnouncements);
|
||||
LiveRegion.debugOverrideAccessibilityAnnouncements(mockAccessibilityAnnouncements);
|
||||
|
||||
final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
|
||||
updateNode(
|
||||
@@ -2383,7 +2387,7 @@ void _testLiveRegion() {
|
||||
|
||||
final MockAccessibilityAnnouncements mockAccessibilityAnnouncements =
|
||||
MockAccessibilityAnnouncements();
|
||||
flutterViewEmbedder.debugOverrideAccessibilityAnnouncements(mockAccessibilityAnnouncements);
|
||||
LiveRegion.debugOverrideAccessibilityAnnouncements(mockAccessibilityAnnouncements);
|
||||
|
||||
ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
|
||||
updateNode(
|
||||
|
||||
@@ -22,6 +22,7 @@ void doTests() {
|
||||
expect(domManager.platformViewsHost, embedder.glassPaneElementDEPRECATED);
|
||||
expect(domManager.textEditingHost, embedder.textEditingHostNodeDEPRECATED);
|
||||
expect(domManager.semanticsHost, embedder.semanticsHostElementDEPRECATED);
|
||||
expect(domManager.announcementsHost, embedder.announcementsHostDEPRECATED);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user