[web] reland fix text selection offset in multi-line fields (#166714)

Fixes https://github.com/flutter/flutter/issues/162698

This reland contains the original changes from
https://github.com/flutter/flutter/pull/166565, plus Safari-specific
test fixes.
This commit is contained in:
Yegor 2025-04-07 15:37:08 -07:00 committed by GitHub
parent d9c9955504
commit 0a29c6d444
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 73 additions and 5 deletions

View File

@ -543,6 +543,7 @@ extension type DomCSSStyleDeclaration._(JSObject _) implements JSObject {
set textAlign(String value) => setProperty('text-align', value);
set font(String value) => setProperty('font', value);
set cursor(String value) => setProperty('cursor', value);
set scrollbarWidth(String value) => setProperty('scrollbar-width', value);
String get width => getPropertyValue('width');
String get height => getPropertyValue('height');
String get position => getPropertyValue('position');
@ -604,6 +605,7 @@ extension type DomCSSStyleDeclaration._(JSObject _) implements JSObject {
String get textAlign => getPropertyValue('text-align');
String get font => getPropertyValue('font');
String get cursor => getPropertyValue('cursor');
String get scrollbarWidth => getPropertyValue('scrollbar-width');
external String getPropertyValue(String property);

View File

@ -6,6 +6,7 @@ import 'package:ui/ui.dart' as ui;
import '../dom.dart';
import '../platform_dispatcher.dart';
import '../text_editing/input_type.dart';
import '../text_editing/text_editing.dart';
import 'semantics.dart';
@ -229,7 +230,7 @@ class SemanticTextField extends SemanticRole {
}
DomHTMLTextAreaElement _createMultiLineField() {
final textArea = createDomHTMLTextAreaElement();
final textArea = createMultilineTextArea();
if (semanticsObject.hasFlag(ui.SemanticsFlag.isObscured)) {
// -webkit-text-security is not standard, but it's the best we can do.

View File

@ -120,7 +120,7 @@ class MultilineNoTextInputType extends MultilineInputType {
String? get inputmodeAttribute => 'none';
@override
DomHTMLElement createDomElement() => createDomHTMLTextAreaElement();
DomHTMLElement createDomElement() => createMultilineTextArea();
}
/// Single-line text input type.
@ -184,5 +184,12 @@ class MultilineInputType extends EngineInputType {
String? get inputmodeAttribute => null;
@override
DomHTMLElement createDomElement() => createDomHTMLTextAreaElement();
DomHTMLElement createDomElement() => createMultilineTextArea();
}
DomHTMLTextAreaElement createMultilineTextArea() {
final element = createDomHTMLTextAreaElement();
// Scrollbar width affects text layout. This zeroes out the scrollbar width.
element.style.scrollbarWidth = 'none';
return element;
}

View File

@ -64,7 +64,6 @@ void _setStaticStyleAttributes(DomHTMLElement domElement) {
// For more details, see: https://developer.mozilla.org/en-US/docs/Web/CSS/forced-color-adjust
..setProperty('forced-color-adjust', 'none')
..whiteSpace = 'pre-wrap'
..alignContent = 'center'
..position = 'absolute'
..top = '0'
..left = '0'
@ -108,7 +107,6 @@ void _styleAutofillElements(
final DomCSSStyleDeclaration elementStyle = domElement.style;
elementStyle
..whiteSpace = 'pre-wrap'
..alignContent = 'center'
..padding = '0'
..opacity = '1'
..color = 'transparent'

View File

@ -840,6 +840,56 @@ Future<void> testMain() async {
expect(spy.messages, isEmpty);
});
test('Does not align content in autofill group elements', () {
final setClient = MethodCall('TextInput.setClient', <dynamic>[
123,
createFlutterConfig('text'),
]);
sendFrameworkMessage(codec.encodeMethodCall(setClient));
const setEditingState = MethodCall('TextInput.setEditingState', <String, dynamic>{
'text': 'abcd',
'selectionBase': 2,
'selectionExtent': 3,
});
sendFrameworkMessage(codec.encodeMethodCall(setEditingState));
const 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));
// Form elements
{
final formElement = textEditing!.configuration!.autofillGroup!.formElement;
expect(formElement.style.alignContent, isEmpty);
// Should contain one <input type="text"> and one <input type="submit">
expect(formElement.children, hasLength(2));
final inputElement = formElement.children.first;
expect(inputElement.style.alignContent, isEmpty);
final submitElement = formElement.children.last;
expect(submitElement.style.alignContent, isEmpty);
}
// Active element
{
final DomHTMLElement activeElement = textEditing!.strategy.activeDomElement;
expect(activeElement.style.alignContent, isEmpty);
}
});
test('focus and connection with blur', () async {
// In all the desktop browsers we are keeping the connection
// open, keep the text editing element focused if it receives a blur
@ -3585,6 +3635,16 @@ Future<void> testMain() async {
// though it supports forced-colors. Safari doesn't support forced-colors
// so this isn't a problem there.
}, skip: isFirefox || isSafari);
test('Multi-line text area scrollbars are zero-width', () {
final allowedScrollbarWidthValues = <String>[
'none',
// Safari introduced scrollbarWidth support in 18.2. Older Safari versions
// return empty string instead of 'none'.
if (isSafari) '',
];
expect(allowedScrollbarWidthValues, contains(createMultilineTextArea().style.scrollbarWidth));
});
});
}