From bdfce08b64d57b5b6dd7998d3b15a1eae2d1ce99 Mon Sep 17 00:00:00 2001 From: Yegor Date: Wed, 10 May 2023 15:40:20 -0700 Subject: [PATCH] [web] generalize focusability in semantics (flutter/engine#41831) Introduce 2 new classes for a11y focus management: * `AccessibilityFocusManager`: a generic class that attaches "focus" and "blur" event handlers, and forwards the events to the framework as `SemanticsAction.didGainAccessibilityFocus` and `SemanticsAction.didLoseAccessibilityFocus` respectively. Provides the `changeFocus` method for the framework to move a11y focus to the target element. * `Focusable`: a role manager that provides generic focus management functionality to `SemanticsObject`s that don't need anything special. Rewrites focus management using the above two classes as follows: * All focusable nodes except text fields and incrementables get the `Focusable` role (all custom focus stuff in `Tappable` was removed and delegated to `Focusable`). * `Incrementable` uses a custom `` internally and so it cannot use the `Focusable` role. Instead, it uses `AccessibilityFocusManager` to manage the focus on the `` element. Behavioral changes: * Fixes https://github.com/flutter/flutter/issues/118737, but more generally fixes all nodes that use the `isFocusable` and `hasFocus` bits. * `Tappable` only partially implemented focusability (e.g. it didn't generate the respective `SemanticsAction` events). Now by delegating to `Focusable`, it will inherit all the functionality. * `Incrementable` and `Checkable` (checkboxes, radios, switches) get focus management features for the first time. * Elements that are not inherently focusable (text, images) can now be focused if semantics requires them to be. * `TextField` is left alone for now as focus and on-screen keyboard interact with each other in non-obvious ways. --- .../ci/licenses_golden/licenses_flutter | 2 + .../flutter/lib/web_ui/lib/src/engine.dart | 1 + .../lib/web_ui/lib/src/engine/semantics.dart | 1 + .../lib/src/engine/semantics/focusable.dart | 193 ++++++++++++ .../src/engine/semantics/incrementable.dart | 9 +- .../lib/src/engine/semantics/semantics.dart | 29 +- .../lib/src/engine/semantics/tappable.dart | 11 - .../test/engine/semantics/semantics_test.dart | 285 +++++++++++++++++- .../engine/semantics/text_field_test.dart | 1 - 9 files changed, 515 insertions(+), 17 deletions(-) create mode 100644 engine/src/flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart diff --git a/engine/src/flutter/ci/licenses_golden/licenses_flutter b/engine/src/flutter/ci/licenses_golden/licenses_flutter index 4a12e0362a..0fe35d134f 100644 --- a/engine/src/flutter/ci/licenses_golden/licenses_flutter +++ b/engine/src/flutter/ci/licenses_golden/licenses_flutter @@ -1977,6 +1977,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart + ../../../flu ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/dialog.dart + ../../../flutter/LICENSE +ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/image.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/incrementable.dart + ../../../flutter/LICENSE ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/semantics/label_and_value.dart + ../../../flutter/LICENSE @@ -4592,6 +4593,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/accessibility.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/checkable.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/dialog.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/image.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/incrementable.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/semantics/label_and_value.dart diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine.dart b/engine/src/flutter/lib/web_ui/lib/src/engine.dart index c523692e7b..1a71a5e615 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine.dart @@ -134,6 +134,7 @@ export 'engine/safe_browser_api.dart'; export 'engine/semantics/accessibility.dart'; export 'engine/semantics/checkable.dart'; export 'engine/semantics/dialog.dart'; +export 'engine/semantics/focusable.dart'; export 'engine/semantics/image.dart'; export 'engine/semantics/incrementable.dart'; export 'engine/semantics/label_and_value.dart'; diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics.dart index 224fd988cd..201478e912 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics.dart @@ -4,6 +4,7 @@ export 'semantics/accessibility.dart'; export 'semantics/checkable.dart'; +export 'semantics/focusable.dart'; export 'semantics/image.dart'; export 'semantics/incrementable.dart'; export 'semantics/label_and_value.dart'; diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart new file mode 100644 index 0000000000..2f1e871280 --- /dev/null +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/focusable.dart @@ -0,0 +1,193 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:ui/ui.dart' as ui; + +import '../dom.dart'; +import '../platform_dispatcher.dart'; +import '../util.dart'; +import 'semantics.dart'; + +/// Supplies generic accessibility focus features to semantics nodes that have +/// [ui.SemanticsFlag.isFocusable] set. +/// +/// Assumes that the element being focused on is [SemanticsObject.element]. Role +/// managers with special needs can implement custom focus management and +/// exclude this role manager. +/// +/// `"tab-index=0"` is used because `` is not intrinsically +/// focusable. Examples of intrinsically focusable elements include: +/// +/// *