Skip to content

[web] Keep the keyboard up during an iOS caret drag - #190014

Merged
auto-submit[bot] merged 13 commits into
flutter:masterfrom
flutter-zl:issue_189744_fix
Aug 10, 2026
Merged

auto-submit[bot] merged 13 commits into
flutter:masterfrom
flutter-zl:issue_189744_fix

Conversation

@flutter-zl

@flutter-zl flutter-zl commented Jul 24, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #189744

Problem
On iOS 27, long-press-dragging the selection caret in a Web TextField dismisses the keyboard mid-gesture. WebKit transiently blurs the hidden input with a null relatedTarget while the document keeps focus, then refocuses it a frame later,and the engine reacted to that blink on two listeners that each tore the input down. A plain input ignores the same blur, so the cause is Flutter's reaction, not a WebKit limitation.

Fix
On iOS both listeners now defer their teardown by 100ms and cancel it if the input refocuses, mirroring the existing #155265 deferred close. Done and tap-away never refocus so they still close, and the deferral is narrowed to the exact drag signature so every other focus transition is unaffected.

Demo
Before: https://flutter-demo-52-before.web.app (keyboard dismisses mid caret drag)
After: https://flutter-demo-52-after.web.app (keyboard stays up)

Repro on iOS 27 Safari: tap the field to raise the keyboard, then long-press and drag the selection caret. Before dismisses; after stays up. Done and tap-away still dismiss.

@flutter-dashboard flutter-dashboard Bot added the CICD Run CI/CD label Jul 24, 2026
@github-actions github-actions Bot added a: text input Entering text in a text field or keyboard related problems engine flutter/engine related. See also e: labels. platform-web Web applications specifically f: focus Focus traversal, gaining or losing focus team-web Owned by Web platform team labels Jul 24, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces deferred handling for focusout events and text connection closures on iOS Safari to prevent transient blurs (such as during caret or selection dragging) from prematurely dismissing the keyboard. This is achieved by implementing timers in ViewFocusBinding and DefaultTextEditingStrategy that delay the blur reporting, allowing an immediate refocus to cancel the operation. Corresponding unit tests have been added to verify these changes. There are no review comments, so I have no feedback to provide.

@Renzo-Olivares Renzo-Olivares left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few small comments. I think the overall approach looks good.

/// iOS caret-drag deferral tests do not depend on the headless browser
/// reporting the test document as focused. Mirrors
/// `DefaultTextEditingStrategy.debugDocumentHasFocusOverride`.
bool? debugViewFocusDocumentHasFocusOverride;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think we should add a @visibleForTesting if this is only meant to be used in tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that we do use it in the focus out listener.

I think similar to _documentVisibilityState below, we should have a seperate private variable:

/// Overrides the result of [domDocument.visibilityState] for testing.
@visibleForTesting
String? debugDocumentVisibilityStateOverride;
bool get _documentHasFocus => debugDocumentHasFocusOverride ?? domDocument.hasFocus();
String get _documentVisibilityState =>
debugDocumentVisibilityStateOverride ?? domDocument.visibilityState;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a _documentHasFocus getter mirroring DefaultTextEditingStrategy._documentHasFocus, and the focusout listener reads that instead of naming the override directly.

// https://github.com/flutter/flutter/issues/189744
if (isIosSafari) {
_pendingBlurConnectionCloseTimer?.cancel();
_pendingBlurConnectionCloseTimer = Timer(const Duration(milliseconds: 100), () {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we use the same Duration(milliseconds: 100) 2 times in this file and once in view_focus_binding.dart for the same focus related reasons. Consider extracting a shared const member that can be used across both files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Done.

view.dom.rootElement.append(other);
other.focusWithoutScroll();
dispatchedViewFocusEvents.clear();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we be setting debugViewFocusDocumentHasFocusOverride in this test as well?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

/// [ViewFocusBinding] also matches on this class to detect blurs originating
/// from the text-editing element, so it is a production contract, not just a
/// testing hook.
static const String textEditingClass = 'flt-text-editing';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of exposing this, have we considered instead adding a public member that signals this is an active text editing client to HybridTextEditing so that we don't expose this and rely on the name of a CSS class. And so the solution also works for a11y. As is it does not because I don't think flt-text-editing is added by SemanticsTextEditingStrategy only DefaultTextEditingStrategy.initializeTextEditing which is overriden by SemanticsTextEditingStrategy.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced with a member on HybridTextEditing:

bool isActiveTextEditingElement(DomElement? element) =>
    isEditing && element != null && element == strategy.domElement;

Renzo-Olivares
Renzo-Olivares previously approved these changes Aug 6, 2026

@Renzo-Olivares Renzo-Olivares left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, w/ some small docs comments.

/// [ViewFocusBinding] uses this to recognize a `focusout` that originated
/// from the active text-editing element.
///
/// Prefer this over matching on [textEditingClass]. That class is applied by

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider shrinking this paragraph to the below in case [SemanticsTextEditingStrategy] ever changes so this documentation doesn't get stale.

/// Prefer this over matching on [textEditingClass]. That class is
/// not guaranteed to be applied by all strategies.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion. Done.

///
/// Sets the real singleton state rather than applying
/// [HybridTextEditing.textEditingClass], so these tests exercise the same signal
/// production code reads. The class is only applied by

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider softening this last sentence to:

The class is not guaranteed to be applied by all text editing strategies, so keying tests off it would not reflect the prodution code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion. Done.

// semantics enabled the live editing element never carries it. Keying off
// the class left the fix dead under VoiceOver.
// `flt-text-editing` class, which is not guaranteed to be applied by all
// text editing strategies. An earlier revision matched on the class, which

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would remove this last sentence it's helpful for me as a reviewer but not to contributors in the future.

or maybe change to:

Matching on the class would leave the deferral dead for strategies that do not apply it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion. Done.

@Renzo-Olivares Renzo-Olivares left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thank you for the fix!

@flutter-zl flutter-zl added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 6, 2026
@auto-submit

auto-submit Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/190014, because - The status or check suite Dashboard Checks has failed. Please fix the issues identified (or deflake) before re-applying this label.

@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 6, 2026
@flutter-zl flutter-zl added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 6, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 6, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 7, 2026
@flutter-zl flutter-zl added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 7, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 7, 2026
@auto-submit

auto-submit Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/190014, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

@flutter-zl flutter-zl added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 7, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 7, 2026
@auto-submit

auto-submit Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/190014, because - The status or check suite Google testing has failed. Please fix the issues identified (or deflake) before re-applying this label.

@flutter-zl flutter-zl added the autosubmit Merge PR when tree becomes green via auto submit App label Aug 10, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Aug 10, 2026
Merged via the queue into flutter:master with commit 347dff3 Aug 10, 2026
21 of 22 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Aug 10, 2026
@jtmcdole

Copy link
Copy Markdown
Member

Reason for revert: This is failing in post submit on a suppressed test. Since this is a hard failure, we should revert:

00:10 +400 ~30 -1: text_editing_test.dart: GloballyPositionedTextEditingStrategy closes the text connection on iOS when the input is not refocused after a null-relatedTarget blur [E]                 
  Expected: an object with length of <1>
    Actual: []
     Which: has length of <0>

https://logs.chromium.org/logs/flutter/buildbucket/cr-buildbucket/8673753854704709425/+/u/test:_run_suite_safari-dart2js-canvaskit-engine/stdout

image

@jtmcdole jtmcdole added the revert Autorevert PR (with "Reason for revert:" comment) label Aug 11, 2026
@flutteractionsbot

Copy link
Copy Markdown
Contributor

Successfully created revert PR: #190926

@flutteractionsbot flutteractionsbot removed the revert Autorevert PR (with "Reason for revert:" comment) label Aug 11, 2026
zijiehe-google-com pushed a commit to zijiehe-google-com/flutter that referenced this pull request Aug 11, 2026
…190926)

Reverts: [[web] Keep the keyboard up during an iOS caret
drag](flutter#190014)

Initiated by: @jtmcdole

Reason for reverting: 

Original PR Author: @flutter-zl

Reviewed By: @Renzo-Olivares

The original PR description is provided below:

Fixes flutter#189744

**Problem**
On iOS 27, long-press-dragging the selection caret in a Web TextField
dismisses the keyboard mid-gesture. WebKit transiently blurs the hidden
input with a null relatedTarget while the document keeps focus, then
refocuses it a frame later,and the engine reacted to that blink on two
listeners that each tore the input down. A plain input ignores the same
blur, so the cause is Flutter's reaction, not a WebKit limitation.

**Fix**
On iOS both listeners now defer their teardown by 100ms and cancel it if
the input refocuses, mirroring the existing flutter#155265 deferred close. Done
and tap-away never refocus so they still close, and the deferral is
narrowed to the exact drag signature so every other focus transition is
unaffected.

**Demo**
Before: https://flutter-demo-52-before.web.app (keyboard dismisses mid
caret drag)
After:  https://flutter-demo-52-after.web.app   (keyboard stays up)

Repro on iOS 27 Safari: tap the field to raise the keyboard, then
long-press and drag the selection caret. Before dismisses; after stays
up. Done and tap-away still dismiss.
pull Bot pushed a commit to edisplay/flutter that referenced this pull request Sep 18, 2026
…#190942)

Fixes flutter#189744

**Reland of flutter#190014, reverted in flutter#190926.**

Cause: three of the new tests dispatch a real input.blur(), but desktop
Safari uses SafariDesktopTextEditingStrategy, which does not subscribe
to blur, so handleBlur never ran. Real iOS does subscribe, so the fix
itself was unaffected.

Delta: one test-only commit adding skip: isSafari to those three tests.
No production change.

**Problem**
On iOS 27, long-press-dragging the selection caret in a Web TextField
dismisses the keyboard mid-gesture. WebKit transiently blurs the hidden
input with a null relatedTarget while the document keeps focus, then
refocuses it a frame later,and the engine reacted to that blink on two
listeners that each tore the input down. A plain input ignores the same
blur, so the cause is Flutter's reaction, not a WebKit limitation.

**Fix**
On iOS both listeners now defer their teardown by 100ms and cancel it if
the input refocuses, mirroring the existing
flutter#155265 deferred close. Done
and tap-away never refocus so they still close, and the deferral is
narrowed to the exact drag signature so every other focus transition is
unaffected.

**Demo**
Before: https://flutter-demo-52-before.web.app/ (keyboard dismisses mid
caret drag)
After: https://flutter-demo-52-after.web.app/ (keyboard stays up)

Repro on iOS 27 Safari: tap the field to raise the keyboard, then
long-press and drag the selection caret. Before dismisses; after stays
up. Done and tap-away still dismiss.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: text input Entering text in a text field or keyboard related problems CICD Run CI/CD engine flutter/engine related. See also e: labels. f: focus Focus traversal, gaining or losing focus platform-web Web applications specifically team-web Owned by Web platform team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[web][iOS 27] Keyboard dismisses when long-press-dragging the text selection caret

4 participants