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

fix: TapTextFieldTapRegion not working on ios safari/chrome/webview #53249

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions lib/web_ui/lib/src/engine/dom.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3080,10 +3080,11 @@ extension DomScreenOrientationExtension on DomScreenOrientation {
// remove the listener.
class DomSubscription {
DomSubscription(
this.target, String typeString, DartDomEventListener dartListener)
this.target, String typeString, DartDomEventListener dartListener,
{bool userCapture = false})
: type = typeString.toJS,
listener = createDomEventListener(dartListener) {
target._addEventListener1(type, listener);
target.addEventListener(typeString, listener, userCapture);
}

final JSString type;
Expand Down
20 changes: 15 additions & 5 deletions lib/web_ui/lib/src/engine/text_editing/text_editing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1687,6 +1687,11 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy {

// Record start time of blur subscription.
final Stopwatch blurWatch = Stopwatch()..start();
final Stopwatch touchWatch = Stopwatch()..start();

subscriptions.add(DomSubscription(domDocument, 'touchstart', userCapture: true, (_) {
touchWatch.reset();
}));

// On iOS, blur is trigerred in the following cases:
//
Expand All @@ -1695,10 +1700,14 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
// so we close the input connection with the framework.
// 2. The user taps on another focusable element. In this case, we refocus
// the input field and wait for the framework to manage the focus change.
// 3. The virtual keyboard is closed by tapping "done". We can't detect this
// programmatically, so we end up refocusing the input field. This is
// okay because the virtual keyboard will hide, and as soon as the user
// taps the text field again, the virtual keyboard will come up.
// 3. The virtual keyboard is closed by tapping "done". We detect this by
// whether or not the window has touched just before the blur event. If
// the window has been touched just now, we know that the user has not
// tapped the "done" button on the virtual keyboard. In this case, we
// refocus the input field and wait for the framework to manage the focus
// change. If the window has not been touched, we know that the user has
// tapped the "done" button, so we close the input connection with the
// framework.
// 4. Safari sometimes sends a blur event immediately after activating the
// input field. In this case, we want to keep the focus on the input field.
// In order to detect this, we measure how much time has passed since the
Expand All @@ -1707,7 +1716,8 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
subscriptions.add(DomSubscription(activeDomElement, 'blur',
(_) {
final bool isFastCallback = blurWatch.elapsed < _blurFastCallbackInterval;
if (windowHasFocus && isFastCallback) {
final bool isTouchWindowJustNow = touchWatch.elapsed < _blurFastCallbackInterval;
if (windowHasFocus && (isFastCallback || isTouchWindowJustNow)) {
activeDomElement.focus();
} else {
owner.sendTextConnectionClosedToFrameworkIfAny();
Expand Down
9 changes: 9 additions & 0 deletions lib/web_ui/test/engine/text_editing_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,15 @@ Future<void> testMain() async {
textEditing!.strategy.domElement, 'abcd', 2, 3);
expect(textEditing!.isEditing, isTrue);

// touchstart event is dispatched to the DOM element.
domDocument.dispatchEvent(createDomEvent('Event', 'touchstart'));
// No connection close message sent.
expect(spy.messages, hasLength(0));
await Future<void>.delayed(Duration.zero);
// DOM element still keeps the focus.
expect(defaultTextEditingRoot.ownerDocument?.activeElement,
textEditing!.strategy.domElement);

// Delay for not to be a fast callback with blur.
await Future<void>.delayed(const Duration(milliseconds: 200));
// DOM element is blurred.
Expand Down