[web] Don't overwrite editing state with semantic updates (flutter/engine#38271)

* Remove editing state overwrite on semantic updates

* Add tests

* Fix test name

* Assert that framework messages will update semantic editing state

* Change test name

* Whitespace
This commit is contained in:
htoor3 2022-12-19 12:54:24 -06:00 committed by GitHub
parent dcee06a479
commit 4a88a2f248
2 changed files with 99 additions and 13 deletions

View File

@ -364,11 +364,7 @@ class TextField extends RoleManager {
// element, so that both the framework and the browser agree on what's
// currently focused.
bool needsDomFocusRequest = false;
final EditingState editingState = EditingState(
text: semanticsObject.value,
baseOffset: semanticsObject.textSelectionBase,
extentOffset: semanticsObject.textSelectionExtent,
);
if (semanticsObject.hasFocus) {
if (!_hasFocused) {
_hasFocused = true;
@ -378,14 +374,9 @@ class TextField extends RoleManager {
if (domDocument.activeElement != editableElement) {
needsDomFocusRequest = true;
}
// Focused elements should have full text editing state applied.
SemanticsTextEditingStrategy.instance.setEditingState(editingState);
} else if (_hasFocused) {
SemanticsTextEditingStrategy.instance.deactivate(this);
// Only apply text, because this node is not focused.
editingState.applyTextToDomElement(editableElement);
if (_hasFocused && domDocument.activeElement == editableElement) {
// Unlike `editableElement.focus()` we don't need to schedule `blur`
// post-update because `document.activeElement` implies that the

View File

@ -4,6 +4,8 @@
@TestOn('chrome || safari || firefox')
import 'dart:typed_data';
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
@ -48,6 +50,11 @@ void testMain() {
testTextEditing.configuration = singlelineConfig;
});
/// Emulates sending of a message by the framework to the engine.
void sendFrameworkMessage(ByteData? message) {
testTextEditing.channel.handleTextInput(message, (ByteData? data) {});
}
test('renders a text field', () async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
@ -127,7 +134,7 @@ void testMain() {
// TODO(yjbanov): https://github.com/flutter/flutter/issues/50754
skip: browserEngine != BrowserEngine.blink);
test('Syncs editing state from framework', () async {
test('Syncs semantic state from framework', () async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
..semanticsEnabled = true;
@ -159,7 +166,6 @@ void testMain() {
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
expect(appHostNode.activeElement, strategy.domElement);
expect(textField.editableElement, strategy.domElement);
expect((textField.editableElement as dynamic).value, 'hello');
expect(textField.editableElement.getAttribute('aria-label'), 'greeting');
expect(textField.editableElement.style.width, '10px');
expect(textField.editableElement.style.height, '15px');
@ -174,7 +180,6 @@ void testMain() {
expect(domDocument.activeElement, domDocument.body);
expect(appHostNode.activeElement, null);
expect(strategy.domElement, null);
expect((textField.editableElement as dynamic).value, 'bye');
expect(textField.editableElement.getAttribute('aria-label'), 'farewell');
expect(textField.editableElement.style.width, '12px');
expect(textField.editableElement.style.height, '17px');
@ -188,6 +193,92 @@ void testMain() {
expect(actionCount, 0);
});
test(
'Does not overwrite text value and selection editing state on semantic updates',
() async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
..semanticsEnabled = true;
strategy.enable(
singlelineConfig,
onChange: (_, __) {},
onAction: (_) {},
);
final SemanticsObject textFieldSemantics = createTextFieldSemantics(
value: 'hello',
textSelectionBase: 1,
textSelectionExtent: 3,
isFocused: true,
rect: const ui.Rect.fromLTWH(0, 0, 10, 15));
final TextField textField =
textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField;
final DomHTMLInputElement editableElement =
textField.editableElement as DomHTMLInputElement;
expect(editableElement, strategy.domElement);
expect(editableElement.value, '');
expect(editableElement.selectionStart, 0);
expect(editableElement.selectionEnd, 0);
strategy.disable();
semantics().semanticsEnabled = false;
});
test(
'Updates editing state when receiving framework messages from the text input channel',
() async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
..semanticsEnabled = true;
expect(domDocument.activeElement, domDocument.body);
expect(appHostNode.activeElement, null);
strategy.enable(
singlelineConfig,
onChange: (_, __) {},
onAction: (_) {},
);
final SemanticsObject textFieldSemantics = createTextFieldSemantics(
value: 'hello',
textSelectionBase: 1,
textSelectionExtent: 3,
isFocused: true,
rect: const ui.Rect.fromLTWH(0, 0, 10, 15));
final TextField textField =
textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField;
final DomHTMLInputElement editableElement =
textField.editableElement as DomHTMLInputElement;
// No updates expected on semantic updates
expect(editableElement, strategy.domElement);
expect(editableElement.value, '');
expect(editableElement.selectionStart, 0);
expect(editableElement.selectionEnd, 0);
// Update from framework
const MethodCall setEditingState =
MethodCall('TextInput.setEditingState', <String, dynamic>{
'text': 'updated',
'selectionBase': 2,
'selectionExtent': 3,
});
sendFrameworkMessage(codec.encodeMethodCall(setEditingState));
// Editing state should now be updated
expect(editableElement.value, 'updated');
expect(editableElement.selectionStart, 2);
expect(editableElement.selectionEnd, 3);
strategy.disable();
semantics().semanticsEnabled = false;
});
test('Gives up focus after DOM blur', () async {
semantics()
..debugOverrideTimestampFunction(() => _testTime)
@ -446,6 +537,8 @@ SemanticsObject createTextFieldSemantics({
bool isFocused = false,
bool isMultiline = false,
ui.Rect rect = const ui.Rect.fromLTRB(0, 0, 100, 50),
int textSelectionBase = 0,
int textSelectionExtent = 0,
}) {
final SemanticsTester tester = SemanticsTester(semantics());
tester.updateNode(
@ -458,6 +551,8 @@ SemanticsObject createTextFieldSemantics({
hasTap: true,
rect: rect,
textDirection: ui.TextDirection.ltr,
textSelectionBase: textSelectionBase,
textSelectionExtent: textSelectionExtent
);
tester.apply();
return tester.getSemanticsObject(0);