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

Commit 2f41581

Browse files
committed
Check if element.contains(activeElement) also.
Provide semantic method names instead of a bag of configuration: * safeBlur (asynchronously transfer focus back to rootElement) * safeRemove (asynchronously remove after transferring focus) * safeRemoveSync (synchronously remove after transferring focus)
1 parent a33158b commit 2f41581

File tree

5 files changed

+48
-32
lines changed

5 files changed

+48
-32
lines changed

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1988,11 +1988,7 @@ class SemanticsObject {
19881988
assert(!_isDisposed);
19891989
_isDisposed = true;
19901990

1991-
EnginePlatformDispatcher.instance.viewManager.safelyBlurElement(
1992-
element,
1993-
delayed: false,
1994-
removeElement: true,
1995-
);
1991+
EnginePlatformDispatcher.instance.viewManager.safeRemoveSync(element);
19961992

19971993
_parent = null;
19981994
semanticRole?.dispose();

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ class SemanticsTextEditingStrategy extends DefaultTextEditingStrategy {
114114
}
115115
subscriptions.clear();
116116
lastEditingState = null;
117-
EnginePlatformDispatcher.instance.viewManager.safelyBlurElement(activeDomElement);
117+
EnginePlatformDispatcher.instance.viewManager.safeBlur(activeDomElement);
118118
domElement = null;
119119
activeTextField = null;
120120
_queuedStyle = null;

lib/web_ui/lib/src/engine/text_editing/text_editing.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1411,9 +1411,9 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements
14111411
inputConfiguration.autofillGroup?.formElement != null) {
14121412
_styleAutofillElements(activeDomElement, isOffScreen: true);
14131413
inputConfiguration.autofillGroup?.storeForm();
1414-
EnginePlatformDispatcher.instance.viewManager.safelyBlurElement(activeDomElement);
1414+
EnginePlatformDispatcher.instance.viewManager.safeBlur(activeDomElement);
14151415
} else {
1416-
EnginePlatformDispatcher.instance.viewManager.safelyBlurElement(activeDomElement, removeElement: true);
1416+
EnginePlatformDispatcher.instance.viewManager.safeRemove(activeDomElement);
14171417
}
14181418
domElement = null;
14191419
}

lib/web_ui/lib/src/engine/view_embedder/flutter_view_manager.dart

Lines changed: 40 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,38 @@ class FlutterViewManager {
122122
return _viewData[viewId];
123123
}
124124

125+
/// Blurs [element] by transferring its focus to its [EngineFlutterView]
126+
/// `rootElement`.
127+
///
128+
/// This operation is asynchronous, but happens as soon as possible
129+
/// (see [Timer.run]).
130+
Future<void> safeBlur(DomElement element) {
131+
return Future<void>(() {
132+
_transferFocusToViewRoot(element);
133+
});
134+
}
135+
136+
/// Removes [element] after transferring its focus to its [EngineFlutterView]
137+
/// `rootElement`.
138+
///
139+
/// This operation is asynchronous, but happens as soon as possible
140+
/// (see [Timer.run]).
141+
///
142+
/// There's a synchronous version of this method: [safeRemoveSync].
143+
Future<void> safeRemove(DomElement element) {
144+
return Future<void>(() {
145+
_transferFocusToViewRoot(element, removeElement: true);
146+
});
147+
}
148+
149+
/// Synchronously removes [element] after transferring its focus to its
150+
/// [EngineFlutterView] `rootElement`.
151+
///
152+
/// This is the synchronous version of [safeRemove].
153+
void safeRemoveSync(DomElement element) {
154+
_transferFocusToViewRoot(element, removeElement: true);
155+
}
156+
125157
/// Attempts to transfer focus (blur) from [element] to its
126158
/// [EngineFlutterView] DOM's `rootElement`.
127159
///
@@ -143,33 +175,18 @@ class FlutterViewManager {
143175
///
144176
/// When [delayed] is true, the blur operation is executed asynchronously as
145177
/// soon as possible (see [Timer.run]). Else it runs immediately.
146-
void safelyBlurElement(
147-
DomElement element, {
148-
bool removeElement = false,
149-
bool delayed = true
150-
}) {
151-
if (delayed) {
152-
Timer.run(() {
153-
_transferFocusToViewRoot(element, removeElement: removeElement);
154-
});
155-
return;
156-
}
157-
_transferFocusToViewRoot(element, removeElement: removeElement);
158-
}
159-
160-
// The actual implementation of [safelyBlurElement].
161178
void _transferFocusToViewRoot(
162179
DomElement element, {
163-
required bool removeElement
180+
bool removeElement = false,
164181
}) {
165-
// If by the time this method is called the focused element is no longer
166-
// `element`, there's no need to move the focus.
167-
//
168-
// This can happen when another element grabs focus when this method runs
169-
// "delayed".
170-
if (element == domDocument.activeElement) {
171-
final EngineFlutterView? view = findViewForElement(element);
182+
final DomElement? activeElement = domDocument.activeElement;
183+
// If the element we're operating on is not active anymore (it can happen
184+
// when this method is called asynchronously),
185+
// OR...
186+
// If the element that we want to remove *contains* the `activeElement`...
187+
if (element == activeElement || removeElement && element.contains(activeElement)) {
172188
// Transfer the browser focus to the root element of `view`
189+
final EngineFlutterView? view = findViewForElement(element);
173190
view?.dom.rootElement.focusWithoutScroll();
174191
}
175192
if (removeElement) {

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ EngineSemanticsOwner owner() => EnginePlatformDispatcher.instance.implicitView!.
2525
DomElement get platformViewsHost =>
2626
EnginePlatformDispatcher.instance.implicitView!.dom.platformViewsHost;
2727

28+
DomElement get flutterViewRoot =>
29+
EnginePlatformDispatcher.instance.implicitView!.dom.rootElement;
30+
2831
void main() {
2932
internalBootstrapBrowserTest(() {
3033
return testMain;
@@ -3567,7 +3570,7 @@ void _testRoute() {
35673570
tester.apply();
35683571

35693572
expect(capturedActions, isEmpty);
3570-
expect(domDocument.activeElement, domDocument.body);
3573+
expect(domDocument.activeElement, flutterViewRoot);
35713574

35723575
semantics().semanticsEnabled = false;
35733576
});

0 commit comments

Comments
 (0)