Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 6 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
17 changes: 16 additions & 1 deletion lib/web_ui/lib/src/engine/text_editing/text_editing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1389,6 +1389,16 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
Timer? _positionInputElementTimer;
static const Duration _delayBeforePlacement = Duration(milliseconds: 100);

/// The interval is considered as fast between blur subscription and callback.
///
/// This is only used for iOS. The blur callback may trigger as soon as
/// the creation of the subscription. In that case, the virtual keyboard will
/// show and hide again occasionally.
///
/// Lesser than the interval allows the virtual keyboard to keep showing up
/// instead of hiding rapidly.
static const Duration _blurFastCallbackInterval = Duration(milliseconds: 200);

/// Whether or not the input element can be positioned at this point in time.
///
/// This is currently only used in iOS. It's set to false before focusing the
Expand Down Expand Up @@ -1453,6 +1463,9 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy {

_addTapListener();

// Record start time of blur subscription.
final DateTime blurSubscriptionStart = DateTime.now();

// On iOS, blur is trigerred in the following cases:
//
// 1. The browser app is sent to the background (or the tab is changed). In
Expand All @@ -1465,7 +1478,9 @@ class IOSTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
// okay because the virtual keyboard will hide, and as soon as the user
// taps the text field again, the virtual keyboard will come up.
subscriptions.add(activeDomElement.onBlur.listen((_) {
if (windowHasFocus) {
if (windowHasFocus &&
DateTime.now().difference(blurSubscriptionStart) <
_blurFastCallbackInterval) {
activeDomElement.focus();
} else {
owner.sendTextConnectionClosedToFrameworkIfAny();
Expand Down
49 changes: 49 additions & 0 deletions lib/web_ui/test/text_editing_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,55 @@ void testMain() {
// TODO(mdebbar): https://github.com/flutter/flutter/issues/50769
skip: browserEngine == BrowserEngine.edge);

test('focus and disconnection with delaying blur in iOS', () async {
final MethodCall setClient = MethodCall(
'TextInput.setClient', <dynamic>[123, flutterSinglelineConfig]);
sendFrameworkMessage(codec.encodeMethodCall(setClient));

const MethodCall setEditingState =
MethodCall('TextInput.setEditingState', <String, dynamic>{
'text': 'abcd',
'selectionBase': 2,
'selectionExtent': 3,
});
sendFrameworkMessage(codec.encodeMethodCall(setEditingState));

// Editing shouldn't have started yet.
expect(defaultTextEditingRoot.activeElement, null);

const MethodCall show = MethodCall('TextInput.show');
sendFrameworkMessage(codec.encodeMethodCall(show));

// The "setSizeAndTransform" message has to be here before we call
// checkInputEditingState, since on some platforms (e.g. Desktop Safari)
// we don't put the input element into the DOM until we get its correct
// dimensions from the framework.
final MethodCall setSizeAndTransform =
configureSetSizeAndTransformMethodCall(150, 50,
Matrix4.translationValues(10.0, 20.0, 30.0).storage.toList());
sendFrameworkMessage(codec.encodeMethodCall(setSizeAndTransform));

checkInputEditingState(
textEditing!.strategy.domElement, 'abcd', 2, 3);
expect(textEditing!.isEditing, isTrue);

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

expect(spy.messages, hasLength(1));
expect(spy.messages[0].channel, 'flutter/textinput');
expect(
spy.messages[0].methodName, 'TextInputClient.onConnectionClosed');
await Future<void>.delayed(Duration.zero);
// DOM element loses the focus.
expect(defaultTextEditingRoot.activeElement, null);
},
// Test on ios-safari only.
skip: browserEngine != BrowserEngine.webkit ||
operatingSystem != OperatingSystem.iOs);

test('finishAutofillContext closes connection no autofill element',
() async {
final MethodCall setClient = MethodCall(
Expand Down