From 5932badce44a20deb43e78443d0bd4f33910c2ac Mon Sep 17 00:00:00 2001 From: htoor3 <110993981+htoor3@users.noreply.github.com> Date: Tue, 17 Jan 2023 11:32:07 -0600 Subject: [PATCH] Revert "Revert "[web] Don't overwrite editing state with semantic updates (#38271)" (#38562)" (#38854) This reverts commit 5713a216076f91e4f6891d1c1a75a2947f2026ce. --- .../lib/src/engine/semantics/text_field.dart | 11 +- .../engine/semantics/text_field_test.dart | 101 +++++++++++++++++- 2 files changed, 99 insertions(+), 13 deletions(-) diff --git a/lib/web_ui/lib/src/engine/semantics/text_field.dart b/lib/web_ui/lib/src/engine/semantics/text_field.dart index 3e36ffb0ad0bd..289248ea0c50f 100644 --- a/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -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 diff --git a/lib/web_ui/test/engine/semantics/text_field_test.dart b/lib/web_ui/test/engine/semantics/text_field_test.dart index 91129dc3648b1..fb87725ea3630 100644 --- a/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -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', { + '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);