Skip to content

Commit 0a29c6d

Browse files
authored
[web] reland fix text selection offset in multi-line fields (flutter#166714)
Fixes flutter#162698 This reland contains the original changes from flutter#166565, plus Safari-specific test fixes.
1 parent d9c9955 commit 0a29c6d

File tree

5 files changed

+73
-5
lines changed

5 files changed

+73
-5
lines changed

engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -543,6 +543,7 @@ extension type DomCSSStyleDeclaration._(JSObject _) implements JSObject {
543543
set textAlign(String value) => setProperty('text-align', value);
544544
set font(String value) => setProperty('font', value);
545545
set cursor(String value) => setProperty('cursor', value);
546+
set scrollbarWidth(String value) => setProperty('scrollbar-width', value);
546547
String get width => getPropertyValue('width');
547548
String get height => getPropertyValue('height');
548549
String get position => getPropertyValue('position');
@@ -604,6 +605,7 @@ extension type DomCSSStyleDeclaration._(JSObject _) implements JSObject {
604605
String get textAlign => getPropertyValue('text-align');
605606
String get font => getPropertyValue('font');
606607
String get cursor => getPropertyValue('cursor');
608+
String get scrollbarWidth => getPropertyValue('scrollbar-width');
607609

608610
external String getPropertyValue(String property);
609611

engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'package:ui/ui.dart' as ui;
66

77
import '../dom.dart';
88
import '../platform_dispatcher.dart';
9+
import '../text_editing/input_type.dart';
910
import '../text_editing/text_editing.dart';
1011
import 'semantics.dart';
1112

@@ -229,7 +230,7 @@ class SemanticTextField extends SemanticRole {
229230
}
230231

231232
DomHTMLTextAreaElement _createMultiLineField() {
232-
final textArea = createDomHTMLTextAreaElement();
233+
final textArea = createMultilineTextArea();
233234

234235
if (semanticsObject.hasFlag(ui.SemanticsFlag.isObscured)) {
235236
// -webkit-text-security is not standard, but it's the best we can do.

engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/input_type.dart

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ class MultilineNoTextInputType extends MultilineInputType {
120120
String? get inputmodeAttribute => 'none';
121121

122122
@override
123-
DomHTMLElement createDomElement() => createDomHTMLTextAreaElement();
123+
DomHTMLElement createDomElement() => createMultilineTextArea();
124124
}
125125

126126
/// Single-line text input type.
@@ -184,5 +184,12 @@ class MultilineInputType extends EngineInputType {
184184
String? get inputmodeAttribute => null;
185185

186186
@override
187-
DomHTMLElement createDomElement() => createDomHTMLTextAreaElement();
187+
DomHTMLElement createDomElement() => createMultilineTextArea();
188+
}
189+
190+
DomHTMLTextAreaElement createMultilineTextArea() {
191+
final element = createDomHTMLTextAreaElement();
192+
// Scrollbar width affects text layout. This zeroes out the scrollbar width.
193+
element.style.scrollbarWidth = 'none';
194+
return element;
188195
}

engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ void _setStaticStyleAttributes(DomHTMLElement domElement) {
6464
// For more details, see: https://developer.mozilla.org/en-US/docs/Web/CSS/forced-color-adjust
6565
..setProperty('forced-color-adjust', 'none')
6666
..whiteSpace = 'pre-wrap'
67-
..alignContent = 'center'
6867
..position = 'absolute'
6968
..top = '0'
7069
..left = '0'
@@ -108,7 +107,6 @@ void _styleAutofillElements(
108107
final DomCSSStyleDeclaration elementStyle = domElement.style;
109108
elementStyle
110109
..whiteSpace = 'pre-wrap'
111-
..alignContent = 'center'
112110
..padding = '0'
113111
..opacity = '1'
114112
..color = 'transparent'

engine/src/flutter/lib/web_ui/test/engine/text_editing_test.dart

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,56 @@ Future<void> testMain() async {
840840
expect(spy.messages, isEmpty);
841841
});
842842

843+
test('Does not align content in autofill group elements', () {
844+
final setClient = MethodCall('TextInput.setClient', <dynamic>[
845+
123,
846+
createFlutterConfig('text'),
847+
]);
848+
sendFrameworkMessage(codec.encodeMethodCall(setClient));
849+
850+
const setEditingState = MethodCall('TextInput.setEditingState', <String, dynamic>{
851+
'text': 'abcd',
852+
'selectionBase': 2,
853+
'selectionExtent': 3,
854+
});
855+
sendFrameworkMessage(codec.encodeMethodCall(setEditingState));
856+
857+
const show = MethodCall('TextInput.show');
858+
sendFrameworkMessage(codec.encodeMethodCall(show));
859+
860+
// The "setSizeAndTransform" message has to be here before we call
861+
// checkInputEditingState, since on some platforms (e.g. Desktop Safari)
862+
// we don't put the input element into the DOM until we get its correct
863+
// dimensions from the framework.
864+
final MethodCall setSizeAndTransform = configureSetSizeAndTransformMethodCall(
865+
150,
866+
50,
867+
Matrix4.translationValues(10.0, 20.0, 30.0).storage.toList(),
868+
);
869+
sendFrameworkMessage(codec.encodeMethodCall(setSizeAndTransform));
870+
871+
// Form elements
872+
{
873+
final formElement = textEditing!.configuration!.autofillGroup!.formElement;
874+
expect(formElement.style.alignContent, isEmpty);
875+
876+
// Should contain one <input type="text"> and one <input type="submit">
877+
expect(formElement.children, hasLength(2));
878+
879+
final inputElement = formElement.children.first;
880+
expect(inputElement.style.alignContent, isEmpty);
881+
882+
final submitElement = formElement.children.last;
883+
expect(submitElement.style.alignContent, isEmpty);
884+
}
885+
886+
// Active element
887+
{
888+
final DomHTMLElement activeElement = textEditing!.strategy.activeDomElement;
889+
expect(activeElement.style.alignContent, isEmpty);
890+
}
891+
});
892+
843893
test('focus and connection with blur', () async {
844894
// In all the desktop browsers we are keeping the connection
845895
// open, keep the text editing element focused if it receives a blur
@@ -3585,6 +3635,16 @@ Future<void> testMain() async {
35853635
// though it supports forced-colors. Safari doesn't support forced-colors
35863636
// so this isn't a problem there.
35873637
}, skip: isFirefox || isSafari);
3638+
3639+
test('Multi-line text area scrollbars are zero-width', () {
3640+
final allowedScrollbarWidthValues = <String>[
3641+
'none',
3642+
// Safari introduced scrollbarWidth support in 18.2. Older Safari versions
3643+
// return empty string instead of 'none'.
3644+
if (isSafari) '',
3645+
];
3646+
expect(allowedScrollbarWidthValues, contains(createMultilineTextArea().style.scrollbarWidth));
3647+
});
35883648
});
35893649
}
35903650

0 commit comments

Comments
 (0)