Skip to content

Commit ebec9e4

Browse files
authored
Revert "[web] switch from .didGain/LoseAccessibilityFocus to .focus (… (flutter#53342)
flutter#53134)" This reverts commit a22270b. Reverting because the engine PR landed prematurely. It needs to wait for a framework change, otherwise, things will break.
1 parent 40cbeed commit ebec9e4

6 files changed

Lines changed: 66 additions & 105 deletions

File tree

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

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2737,30 +2737,6 @@ DomCompositionEvent createDomCompositionEvent(String type,
27372737
}
27382738
}
27392739

2740-
/// This is a pseudo-type for DOM elements that have the boolean `disabled`
2741-
/// property.
2742-
///
2743-
/// This type cannot be part of the actual type hierarchy because each DOM type
2744-
/// defines its `disabled` property ad hoc, without inheriting it from a common
2745-
/// type, e.g. [DomHTMLInputElement] and [DomHTMLTextAreaElement].
2746-
///
2747-
/// To use, simply cast any element known to have the `disabled` property to
2748-
/// this type using `as DomElementWithDisabledProperty`, then read and write
2749-
/// this property as normal.
2750-
@JS()
2751-
@staticInterop
2752-
class DomElementWithDisabledProperty extends DomHTMLElement {}
2753-
2754-
extension DomElementWithDisabledPropertyExtension on DomElementWithDisabledProperty {
2755-
@JS('disabled')
2756-
external JSBoolean? get _disabled;
2757-
bool? get disabled => _disabled?.toDart;
2758-
2759-
@JS('disabled')
2760-
external set _disabled(JSBoolean? value);
2761-
set disabled(bool? value) => _disabled = value?.toJS;
2762-
}
2763-
27642740
@JS()
27652741
@staticInterop
27662742
class DomHTMLInputElement extends DomHTMLElement {}

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

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ 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,
8487
});
8588

8689
/// Implements accessibility focus management for arbitrary elements.
@@ -132,6 +135,7 @@ class AccessibilityFocusManager {
132135
semanticsNodeId: semanticsNodeId,
133136
element: previousTarget.element,
134137
domFocusListener: previousTarget.domFocusListener,
138+
domBlurListener: previousTarget.domBlurListener,
135139
);
136140
return;
137141
}
@@ -144,12 +148,14 @@ class AccessibilityFocusManager {
144148
final _FocusTarget newTarget = (
145149
semanticsNodeId: semanticsNodeId,
146150
element: element,
147-
domFocusListener: createDomEventListener((_) => _didReceiveDomFocus()),
151+
domFocusListener: createDomEventListener((_) => _setFocusFromDom(true)),
152+
domBlurListener: createDomEventListener((_) => _setFocusFromDom(false)),
148153
);
149154
_target = newTarget;
150155

151156
element.tabIndex = 0;
152157
element.addEventListener('focus', newTarget.domFocusListener);
158+
element.addEventListener('blur', newTarget.domBlurListener);
153159
}
154160

155161
/// Stops managing the focus of the current element, if any.
@@ -164,9 +170,10 @@ class AccessibilityFocusManager {
164170
}
165171

166172
target.element.removeEventListener('focus', target.domFocusListener);
173+
target.element.removeEventListener('blur', target.domBlurListener);
167174
}
168175

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

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

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

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

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,6 @@ class TextField extends PrimaryRoleManager {
257257
editableElement = semanticsObject.hasFlag(ui.SemanticsFlag.isMultiline)
258258
? createDomHTMLTextAreaElement()
259259
: createDomHTMLInputElement();
260-
_updateEnabledState();
261260

262261
// On iOS, even though the semantic text field is transparent, the cursor
263262
// and text highlighting are still visible. The cursor and text selection
@@ -311,7 +310,16 @@ class TextField extends PrimaryRoleManager {
311310
}
312311

313312
EnginePlatformDispatcher.instance.invokeOnSemanticsAction(
314-
semanticsObject.id, ui.SemanticsAction.focus, null);
313+
semanticsObject.id, ui.SemanticsAction.didGainAccessibilityFocus, null);
314+
}));
315+
activeEditableElement.addEventListener('blur',
316+
createDomEventListener((DomEvent event) {
317+
if (EngineSemantics.instance.gestureMode != GestureMode.browserGestures) {
318+
return;
319+
}
320+
321+
EnginePlatformDispatcher.instance.invokeOnSemanticsAction(
322+
semanticsObject.id, ui.SemanticsAction.didLoseAccessibilityFocus, null);
315323
}));
316324
}
317325

@@ -425,19 +433,20 @@ class TextField extends PrimaryRoleManager {
425433
// and wait for a tap event before invoking the iOS workaround and creating
426434
// the editable element.
427435
if (editableElement != null) {
428-
_updateEnabledState();
429436
activeEditableElement.style
430437
..width = '${semanticsObject.rect!.width}px'
431438
..height = '${semanticsObject.rect!.height}px';
432439

433440
if (semanticsObject.hasFocus) {
434-
if (domDocument.activeElement != activeEditableElement && semanticsObject.isEnabled) {
441+
if (domDocument.activeElement !=
442+
activeEditableElement) {
435443
semanticsObject.owner.addOneTimePostUpdateCallback(() {
436444
activeEditableElement.focus();
437445
});
438446
}
439447
SemanticsTextEditingStrategy._instance?.activate(this);
440-
} else if (domDocument.activeElement == activeEditableElement) {
448+
} else if (domDocument.activeElement ==
449+
activeEditableElement) {
441450
if (!isIosSafari) {
442451
SemanticsTextEditingStrategy._instance?.deactivate(this);
443452
// Only apply text, because this node is not focused.
@@ -457,16 +466,6 @@ class TextField extends PrimaryRoleManager {
457466
}
458467
}
459468

460-
void _updateEnabledState() {
461-
final DomElement? element = editableElement;
462-
463-
if (element == null) {
464-
return;
465-
}
466-
467-
(element as DomElementWithDisabledProperty).disabled = !semanticsObject.isEnabled;
468-
}
469-
470469
@override
471470
void dispose() {
472471
super.dispose();

lib/web_ui/test/engine/semantics/semantics_test.dart

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1776,7 +1776,7 @@ void _testIncrementables() {
17761776

17771777
pumpSemantics(isFocused: true);
17781778
expect(capturedActions, <CapturedAction>[
1779-
(0, ui.SemanticsAction.focus, null),
1779+
(0, ui.SemanticsAction.didGainAccessibilityFocus, null),
17801780
]);
17811781
capturedActions.clear();
17821782

@@ -1787,12 +1787,10 @@ void _testIncrementables() {
17871787
isEmpty,
17881788
);
17891789

1790-
// The web doesn't send didLoseAccessibilityFocus as on the web,
1791-
// accessibility focus is not observable, only input focus is. As of this
1792-
// writing, there is no SemanticsAction.unfocus action, so the test simply
1793-
// asserts that no actions are being sent as a result of blur.
17941790
element.blur();
1795-
expect(capturedActions, isEmpty);
1791+
expect(capturedActions, <CapturedAction>[
1792+
(0, ui.SemanticsAction.didLoseAccessibilityFocus, null),
1793+
]);
17961794

17971795
semantics().semanticsEnabled = false;
17981796
});
@@ -1823,14 +1821,15 @@ void _testTextField() {
18231821

18241822

18251823
final SemanticsObject node = owner().debugSemanticsTree![0]!;
1826-
final TextField textFieldRole = node.primaryRole! as TextField;
1827-
final DomHTMLInputElement inputElement = textFieldRole.activeEditableElement as DomHTMLInputElement;
18281824

18291825
// TODO(yjbanov): this used to attempt to test that value="hello" but the
18301826
// test was a false positive. We should revise this test and
18311827
// make sure it tests the right things:
18321828
// https://github.com/flutter/flutter/issues/147200
1833-
expect(inputElement.value, '');
1829+
expect(
1830+
(node.element as DomHTMLInputElement).value,
1831+
isNull,
1832+
);
18341833

18351834
expect(node.primaryRole?.role, PrimaryRole.textField);
18361835
expect(
@@ -1853,8 +1852,8 @@ void _testTextField() {
18531852
final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder();
18541853
updateNode(
18551854
builder,
1856-
actions: 0 | ui.SemanticsAction.focus.index,
1857-
flags: 0 | ui.SemanticsFlag.isTextField.index | ui.SemanticsFlag.isEnabled.index,
1855+
actions: 0 | ui.SemanticsAction.didGainAccessibilityFocus.index,
1856+
flags: 0 | ui.SemanticsFlag.isTextField.index,
18581857
value: 'hello',
18591858
transform: Matrix4.identity().toFloat64(),
18601859
rect: const ui.Rect.fromLTRB(0, 0, 100, 50),
@@ -1871,7 +1870,7 @@ void _testTextField() {
18711870

18721871
expect(owner().semanticsHost.ownerDocument?.activeElement, textField);
18731872
expect(await logger.idLog.first, 0);
1874-
expect(await logger.actionLog.first, ui.SemanticsAction.focus);
1873+
expect(await logger.actionLog.first, ui.SemanticsAction.didGainAccessibilityFocus);
18751874

18761875
semantics().semanticsEnabled = false;
18771876
}, // TODO(yjbanov): https://github.com/flutter/flutter/issues/46638
@@ -2157,7 +2156,7 @@ void _testCheckables() {
21572156

21582157
pumpSemantics(isFocused: true);
21592158
expect(capturedActions, <CapturedAction>[
2160-
(0, ui.SemanticsAction.focus, null),
2159+
(0, ui.SemanticsAction.didGainAccessibilityFocus, null),
21612160
]);
21622161
capturedActions.clear();
21632162

@@ -2167,12 +2166,15 @@ void _testCheckables() {
21672166
pumpSemantics(isFocused: false);
21682167
expect(capturedActions, isEmpty);
21692168

2170-
// The web doesn't send didLoseAccessibilityFocus as on the web,
2171-
// accessibility focus is not observable, only input focus is. As of this
2172-
// writing, there is no SemanticsAction.unfocus action, so the test simply
2173-
// asserts that no actions are being sent as a result of blur.
2169+
// If the element is blurred by the browser, then we do want to notify the
2170+
// framework. This is because screen reader can be focused on something
2171+
// other than what the framework is focused on, and notifying the framework
2172+
// about the loss of focus on a node is information that the framework did
2173+
// not have before.
21742174
element.blur();
2175-
expect(capturedActions, isEmpty);
2175+
expect(capturedActions, <CapturedAction>[
2176+
(0, ui.SemanticsAction.didLoseAccessibilityFocus, null),
2177+
]);
21762178

21772179
semantics().semanticsEnabled = false;
21782180
});
@@ -2338,19 +2340,17 @@ void _testTappable() {
23382340

23392341
pumpSemantics(isFocused: true);
23402342
expect(capturedActions, <CapturedAction>[
2341-
(0, ui.SemanticsAction.focus, null),
2343+
(0, ui.SemanticsAction.didGainAccessibilityFocus, null),
23422344
]);
23432345
capturedActions.clear();
23442346

23452347
pumpSemantics(isFocused: false);
23462348
expect(capturedActions, isEmpty);
23472349

2348-
// The web doesn't send didLoseAccessibilityFocus as on the web,
2349-
// accessibility focus is not observable, only input focus is. As of this
2350-
// writing, there is no SemanticsAction.unfocus action, so the test simply
2351-
// asserts that no actions are being sent as a result of blur.
23522350
element.blur();
2353-
expect(capturedActions, isEmpty);
2351+
expect(capturedActions, <CapturedAction>[
2352+
(0, ui.SemanticsAction.didLoseAccessibilityFocus, null),
2353+
]);
23542354

23552355
semantics().semanticsEnabled = false;
23562356
});
@@ -3180,7 +3180,7 @@ void _testDialog() {
31803180
expect(
31813181
capturedActions,
31823182
<CapturedAction>[
3183-
(2, ui.SemanticsAction.focus, null),
3183+
(2, ui.SemanticsAction.didGainAccessibilityFocus, null),
31843184
],
31853185
);
31863186

@@ -3242,7 +3242,7 @@ void _testDialog() {
32423242
expect(
32433243
capturedActions,
32443244
<CapturedAction>[
3245-
(3, ui.SemanticsAction.focus, null),
3245+
(3, ui.SemanticsAction.didGainAccessibilityFocus, null),
32463246
],
32473247
);
32483248

@@ -3392,7 +3392,7 @@ void _testFocusable() {
33923392
pumpSemantics(); // triggers post-update callbacks
33933393
expect(domDocument.activeElement, element);
33943394
expect(capturedActions, <CapturedAction>[
3395-
(1, ui.SemanticsAction.focus, null),
3395+
(1, ui.SemanticsAction.didGainAccessibilityFocus, null),
33963396
]);
33973397
capturedActions.clear();
33983398

@@ -3405,19 +3405,17 @@ void _testFocusable() {
34053405
// Browser blurs the element
34063406
element.blur();
34073407
expect(domDocument.activeElement, isNot(element));
3408-
// The web doesn't send didLoseAccessibilityFocus as on the web,
3409-
// accessibility focus is not observable, only input focus is. As of this
3410-
// writing, there is no SemanticsAction.unfocus action, so the test simply
3411-
// asserts that no actions are being sent as a result of blur.
3412-
expect(capturedActions, isEmpty);
3408+
expect(capturedActions, <CapturedAction>[
3409+
(1, ui.SemanticsAction.didLoseAccessibilityFocus, null),
3410+
]);
34133411
capturedActions.clear();
34143412

34153413
// Request focus again
34163414
manager.changeFocus(true);
34173415
pumpSemantics(); // triggers post-update callbacks
34183416
expect(domDocument.activeElement, element);
34193417
expect(capturedActions, <CapturedAction>[
3420-
(1, ui.SemanticsAction.focus, null),
3418+
(1, ui.SemanticsAction.didGainAccessibilityFocus, null),
34213419
]);
34223420
capturedActions.clear();
34233421

lib/web_ui/test/engine/semantics/semantics_tester.dart

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,6 @@ class SemanticsTester {
7575
bool? hasPaste,
7676
bool? hasDidGainAccessibilityFocus,
7777
bool? hasDidLoseAccessibilityFocus,
78-
bool? hasFocus,
7978
bool? hasCustomAction,
8079
bool? hasDismiss,
8180
bool? hasMoveCursorForwardByWord,
@@ -243,9 +242,6 @@ class SemanticsTester {
243242
if (hasDidLoseAccessibilityFocus ?? false) {
244243
actions |= ui.SemanticsAction.didLoseAccessibilityFocus.index;
245244
}
246-
if (hasFocus ?? false) {
247-
actions |= ui.SemanticsAction.focus.index;
248-
}
249245
if (hasCustomAction ?? false) {
250246
actions |= ui.SemanticsAction.customAction.index;
251247
}

0 commit comments

Comments
 (0)