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

Commit ad1343c

Browse files
authored
[web] switch from .didGain/LoseAccessibilityFocus to .focus (#53360)
This is a repeat of #53134, which was merged prematurely. > [!WARNING] > Only land this after: > * flutter/flutter#149840 lands in the framework. > * You have personally manually tested the change together with the latest framework on all browsers. ## Original PR description Stop using `SemanticsAction.didGain/LoseAccessibilityFocus` on the web, start using `SemanticsAction.focus`. This is because on the web, a11y focus is not observable, only input focus is. Sending `SemanticsAction.focus` will guarantee that the framework move focus to the respective widget. There currently is no "unfocus" signal, because it seems to be already covered: either another widget gains focus, or an HTML DOM element outside the Flutter view does, both of which have their respective signals already. More details in the discussion in the issue flutter/flutter#83809. Fixes flutter/flutter#83809 Fixes flutter/flutter#148285 Fixes flutter/flutter#143337
1 parent d6ee3ab commit ad1343c

File tree

9 files changed

+257
-898
lines changed

9 files changed

+257
-898
lines changed

lib/ui/semantics.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ class SemanticsAction {
220220
/// must immediately become editable, opening a virtual keyboard, if needed.
221221
/// Buttons must respond to tap/click events from the keyboard.
222222
///
223+
/// Widget reaction to this action must be idempotent. It is possible to
224+
/// receive this action more than once, or when the widget is already focused.
225+
///
223226
/// Focus behavior is specific to the platform and to the assistive technology
224227
/// used. Typically on desktop operating systems, such as Windows, macOS, and
225228
/// Linux, moving accessibility focus will also move the input focus. On

lib/web_ui/lib/src/engine/dom.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2748,6 +2748,30 @@ DomCompositionEvent createDomCompositionEvent(String type,
27482748
}
27492749
}
27502750

2751+
/// This is a pseudo-type for DOM elements that have the boolean `disabled`
2752+
/// property.
2753+
///
2754+
/// This type cannot be part of the actual type hierarchy because each DOM type
2755+
/// defines its `disabled` property ad hoc, without inheriting it from a common
2756+
/// type, e.g. [DomHTMLInputElement] and [DomHTMLTextAreaElement].
2757+
///
2758+
/// To use, simply cast any element known to have the `disabled` property to
2759+
/// this type using `as DomElementWithDisabledProperty`, then read and write
2760+
/// this property as normal.
2761+
@JS()
2762+
@staticInterop
2763+
class DomElementWithDisabledProperty extends DomHTMLElement {}
2764+
2765+
extension DomElementWithDisabledPropertyExtension on DomElementWithDisabledProperty {
2766+
@JS('disabled')
2767+
external JSBoolean? get _disabled;
2768+
bool? get disabled => _disabled?.toDart;
2769+
2770+
@JS('disabled')
2771+
external set _disabled(JSBoolean? value);
2772+
set disabled(bool? value) => _disabled = value?.toJS;
2773+
}
2774+
27512775
@JS()
27522776
@staticInterop
27532777
class DomHTMLInputElement extends DomHTMLElement {}

lib/web_ui/lib/src/engine/semantics/focusable.dart

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,6 @@ typedef _FocusTarget = ({
8181

8282
/// The listener for the "focus" DOM event.
8383
DomEventListener domFocusListener,
84-
85-
/// The listener for the "blur" DOM event.
86-
DomEventListener domBlurListener,
8784
});
8885

8986
/// Implements accessibility focus management for arbitrary elements.
@@ -135,7 +132,6 @@ class AccessibilityFocusManager {
135132
semanticsNodeId: semanticsNodeId,
136133
element: previousTarget.element,
137134
domFocusListener: previousTarget.domFocusListener,
138-
domBlurListener: previousTarget.domBlurListener,
139135
);
140136
return;
141137
}
@@ -148,14 +144,12 @@ class AccessibilityFocusManager {
148144
final _FocusTarget newTarget = (
149145
semanticsNodeId: semanticsNodeId,
150146
element: element,
151-
domFocusListener: createDomEventListener((_) => _setFocusFromDom(true)),
152-
domBlurListener: createDomEventListener((_) => _setFocusFromDom(false)),
147+
domFocusListener: createDomEventListener((_) => _didReceiveDomFocus()),
153148
);
154149
_target = newTarget;
155150

156151
element.tabIndex = 0;
157152
element.addEventListener('focus', newTarget.domFocusListener);
158-
element.addEventListener('blur', newTarget.domBlurListener);
159153
}
160154

161155
/// Stops managing the focus of the current element, if any.
@@ -170,10 +164,9 @@ class AccessibilityFocusManager {
170164
}
171165

172166
target.element.removeEventListener('focus', target.domFocusListener);
173-
target.element.removeEventListener('blur', target.domBlurListener);
174167
}
175168

176-
void _setFocusFromDom(bool acquireFocus) {
169+
void _didReceiveDomFocus() {
177170
final _FocusTarget? target = _target;
178171

179172
if (target == null) {
@@ -184,9 +177,7 @@ class AccessibilityFocusManager {
184177

185178
EnginePlatformDispatcher.instance.invokeOnSemanticsAction(
186179
target.semanticsNodeId,
187-
acquireFocus
188-
? ui.SemanticsAction.didGainAccessibilityFocus
189-
: ui.SemanticsAction.didLoseAccessibilityFocus,
180+
ui.SemanticsAction.focus,
190181
null,
191182
);
192183
}
@@ -229,7 +220,7 @@ class AccessibilityFocusManager {
229220
// a dialog, and nothing else in the dialog is focused. The Flutter
230221
// framework expects that the screen reader will focus on the first (in
231222
// traversal order) focusable element inside the dialog and send a
232-
// didGainAccessibilityFocus action. Screen readers on the web do not do
223+
// SemanticsAction.focus action. Screen readers on the web do not do
233224
// that, and so the web engine has to implement this behavior directly. So
234225
// the dialog will look for a focusable element and request focus on it,
235226
// but now there may be a race between this method unsetting the focus and

lib/web_ui/lib/src/engine/semantics/semantics.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2218,8 +2218,6 @@ class EngineSemantics {
22182218
'mousemove',
22192219
'mouseleave',
22202220
'mouseup',
2221-
'keyup',
2222-
'keydown',
22232221
];
22242222

22252223
if (pointerEventTypes.contains(event.type)) {

0 commit comments

Comments
 (0)