From 717fcc05fb356d3052f1a904c6818421f5af6ae2 Mon Sep 17 00:00:00 2001 From: Hassan Toor Date: Mon, 12 Dec 2022 15:05:42 -0600 Subject: [PATCH 1/6] Remove editing state overwrite on semantic updates --- lib/web_ui/lib/src/engine/semantics/text_field.dart | 11 +---------- 1 file changed, 1 insertion(+), 10 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 From 19d72e6e2d3f494c3010db3a6a865a592ca9c558 Mon Sep 17 00:00:00 2001 From: Hassan Toor Date: Tue, 13 Dec 2022 17:34:49 -0600 Subject: [PATCH 2/6] Add tests --- .../engine/semantics/text_field_test.dart | 41 +++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) 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..359293ada940e 100644 --- a/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -127,7 +127,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 +159,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 +173,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 +186,39 @@ void testMain() { expect(actionCount, 0); }); + test('Does not overwrite editing state like text value and selection', + () 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('Gives up focus after DOM blur', () async { semantics() ..debugOverrideTimestampFunction(() => _testTime) @@ -446,6 +477,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 +491,8 @@ SemanticsObject createTextFieldSemantics({ hasTap: true, rect: rect, textDirection: ui.TextDirection.ltr, + textSelectionBase: textSelectionBase, + textSelectionExtent: textSelectionExtent ); tester.apply(); return tester.getSemanticsObject(0); From acabdc00e5de9d60a9996b5f15510b951636a513 Mon Sep 17 00:00:00 2001 From: Hassan Toor Date: Tue, 13 Dec 2022 17:43:03 -0600 Subject: [PATCH 3/6] Fix test name --- lib/web_ui/test/engine/semantics/text_field_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 359293ada940e..905409eeb17e9 100644 --- a/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -186,7 +186,7 @@ void testMain() { expect(actionCount, 0); }); - test('Does not overwrite editing state like text value and selection', + test('Does not overwrite text value and selection editing state', () async { semantics() ..debugOverrideTimestampFunction(() => _testTime) From 85bb97e3fce60ca778bea41526486a49d632bfed Mon Sep 17 00:00:00 2001 From: Hassan Toor Date: Wed, 14 Dec 2022 16:08:33 -0600 Subject: [PATCH 4/6] Assert that framework messages will update semantic editing state --- .../engine/semantics/text_field_test.dart | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) 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 905409eeb17e9..8a634cf088aac 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) @@ -219,6 +226,58 @@ void testMain() { 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) From 2e790713627bd7ff72fab5b61308a1f4484c363f Mon Sep 17 00:00:00 2001 From: Hassan Toor Date: Wed, 14 Dec 2022 16:12:17 -0600 Subject: [PATCH 5/6] Change test name --- lib/web_ui/test/engine/semantics/text_field_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 8a634cf088aac..7b3373a606582 100644 --- a/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -193,7 +193,8 @@ void testMain() { expect(actionCount, 0); }); - test('Does not overwrite text value and selection editing state', + test( + 'Does not overwrite text value and selection editing state on semantic updates', () async { semantics() ..debugOverrideTimestampFunction(() => _testTime) From 0040d9834fb059f66a2a8fe25447d74dd5308ec4 Mon Sep 17 00:00:00 2001 From: Hassan Toor Date: Thu, 15 Dec 2022 14:02:17 -0600 Subject: [PATCH 6/6] Whitespace --- lib/web_ui/test/engine/semantics/text_field_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 7b3373a606582..fb87725ea3630 100644 --- a/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -551,8 +551,8 @@ SemanticsObject createTextFieldSemantics({ hasTap: true, rect: rect, textDirection: ui.TextDirection.ltr, - textSelectionBase: textSelectionBase, - textSelectionExtent: textSelectionExtent + textSelectionBase: textSelectionBase, + textSelectionExtent: textSelectionExtent ); tester.apply(); return tester.getSemanticsObject(0);