mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Web - Fix selection jump on Chrome for Android (flutter/engine#41202)
## Description
This PR fixes cursor jump on Chrome for Android when the user taps in a multiline `TextField`.
Using the following code sample:
<details><summary>Code sample</summary>
```dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Text Field Focus',
home: MyCustomForm(),
);
}
}
// Define a custom Form widget.
class MyCustomForm extends StatelessWidget {
const MyCustomForm({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Text Field Focus'),
),
backgroundColor: Colors.amber,
body: Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
decoration: const InputDecoration(
fillColor: Colors.white,
filled: true
),
autofocus: true,
maxLines: 3,
controller: TextEditingController(text: '1\n2\n3\n4\n'),
),
),// This trailing comma makes auto-formatting nicer for build methods.
);
}
}
```
</details>
On a mobile browser, once the page is loaded, tap after the number 3:
- Before this PR: the TextField content is automaticaly scrolled and the selection is set after number 1.
https://user-images.githubusercontent.com/840911/232051413-b913f890-6cb1-4c60-92d0-7a3bf74cc688.mov
## Implementation
A multiline `TextField` relies on an HTML `<textarea>` elements. When a tap occurs the selection should be updated from Flutter not by the HTML element itself.
This PR prevents mouse events on Chrome for Android. Those events conflicts with Flutter selection changes.
Previously, mouse events were only prevented on desktop but they are also emitted on mobile, see https://bugs.chromium.org/p/chromium/issues/detail?id=119216#c11.
## Related Issue
Related to https://github.com/flutter/flutter/issues/124483 (partial fix because the issue is also reproducible on iOS/Safari).
## Tests
Adds 1 test.
This commit is contained in:
parent
13acb7d416
commit
d56b8bee57
@ -1415,9 +1415,12 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements
|
||||
|
||||
/// Prevent default behavior for mouse down, up and move.
|
||||
///
|
||||
/// When normal mouse events are not prevented, in desktop browsers, mouse
|
||||
/// selection conflicts with selection sent from the framework, which creates
|
||||
/// When normal mouse events are not prevented, mouse selection
|
||||
/// conflicts with selection sent from the framework, which creates
|
||||
/// flickering during selection by mouse.
|
||||
///
|
||||
/// On mobile browsers, mouse events are sent after a touch event,
|
||||
/// see: https://bugs.chromium.org/p/chromium/issues/detail?id=119216#c11.
|
||||
void preventDefaultForMouseEvents() {
|
||||
subscriptions.add(
|
||||
DomSubscription(activeDomElement, 'mousedown', (_) {
|
||||
@ -1704,6 +1707,8 @@ class AndroidTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
|
||||
owner.sendTextConnectionClosedToFrameworkIfAny();
|
||||
}
|
||||
}));
|
||||
|
||||
preventDefaultForMouseEvents();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -1896,6 +1896,60 @@ Future<void> testMain() async {
|
||||
hideKeyboard();
|
||||
});
|
||||
|
||||
test('prevent mouse events on Android', () {
|
||||
// Regression test for https://github.com/flutter/flutter/issues/124483.
|
||||
debugOperatingSystemOverride = OperatingSystem.android;
|
||||
debugBrowserEngineOverride = BrowserEngine.blink;
|
||||
|
||||
/// During initialization [HybridTextEditing] will pick the correct
|
||||
/// text editing strategy for [OperatingSystem.android].
|
||||
textEditing = HybridTextEditing();
|
||||
|
||||
final MethodCall setClient = MethodCall(
|
||||
'TextInput.setClient',
|
||||
<dynamic>[123, flutterMultilineConfig],
|
||||
);
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setClient));
|
||||
|
||||
// Editing shouldn't have started yet.
|
||||
expect(defaultTextEditingRoot.ownerDocument?.activeElement, domDocument.body);
|
||||
|
||||
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 List<double> transform = Matrix4.translationValues(10.0, 20.0, 30.0).storage.toList();
|
||||
final MethodCall setSizeAndTransform = configureSetSizeAndTransformMethodCall(150, 50, transform);
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setSizeAndTransform));
|
||||
|
||||
final DomHTMLTextAreaElement textarea = textEditing!.strategy.domElement! as DomHTMLTextAreaElement;
|
||||
checkTextAreaEditingState(textarea, '', 0, 0);
|
||||
|
||||
// Can set editing state and preserve new lines.
|
||||
const MethodCall setEditingState = MethodCall(
|
||||
'TextInput.setEditingState',
|
||||
<String, dynamic>{
|
||||
'text': '1\n2\n3\n4\n',
|
||||
'selectionBase': 8,
|
||||
'selectionExtent': 8,
|
||||
'composingBase': null,
|
||||
'composingExtent': null,
|
||||
},
|
||||
);
|
||||
sendFrameworkMessage(codec.encodeMethodCall(setEditingState));
|
||||
checkTextAreaEditingState(textarea, '1\n2\n3\n4\n', 8, 8);
|
||||
|
||||
// 'mousedown' event should be prevented.
|
||||
final DomEvent event = createDomEvent('Event', 'mousedown');
|
||||
textarea.dispatchEvent(event);
|
||||
expect(event.defaultPrevented, isTrue);
|
||||
|
||||
hideKeyboard();
|
||||
});
|
||||
|
||||
test('sets correct input type in iOS', () {
|
||||
// Test on ios-safari only.
|
||||
if (browserEngine == BrowserEngine.webkit &&
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user