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 2 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
15 changes: 14 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.
Comment thread
JunbinDeng marked this conversation as resolved.
Outdated
///
/// 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.
Comment thread
JunbinDeng marked this conversation as resolved.
Outdated
///
/// Lesser than the interval allows the virtual keyboard to keep showing up
Comment thread
JunbinDeng marked this conversation as resolved.
Outdated
/// 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();
Comment thread
JunbinDeng marked this conversation as resolved.
Outdated
Comment thread
JunbinDeng marked this conversation as resolved.
Outdated

// 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,7 @@ 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.
Comment thread
JunbinDeng marked this conversation as resolved.
subscriptions.add(activeDomElement.onBlur.listen((_) {
if (windowHasFocus) {
if (DateTime.now().difference(blurSubscriptionStart) < _blurFastCallbackInterval) {
Comment thread
JunbinDeng marked this conversation as resolved.
Outdated
activeDomElement.focus();
} else {
owner.sendTextConnectionClosedToFrameworkIfAny();
Expand Down
50 changes: 50 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,56 @@ 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 {
// Test on ios-safari only.
if (browserEngine == BrowserEngine.webkit &&
operatingSystem == OperatingSystem.iOs) {
Comment thread
mdebbar marked this conversation as resolved.
Outdated
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('finishAutofillContext closes connection no autofill element',
() async {
final MethodCall setClient = MethodCall(
Expand Down