Skip to content

Commit

Permalink
Revert "Revert "[web] Don't overwrite editing state with semantic upd…
Browse files Browse the repository at this point in the history
…ates (flutter#38271)" (flutter#38562)" (flutter#38854)

This reverts commit 5713a21.
  • Loading branch information
htoor3 authored Jan 17, 2023
1 parent e655580 commit 5932bad
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 13 deletions.
11 changes: 1 addition & 10 deletions lib/web_ui/lib/src/engine/semantics/text_field.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand Down
101 changes: 98 additions & 3 deletions lib/web_ui/test/engine/semantics/text_field_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

@TestOn('chrome || safari || firefox')

import 'dart:typed_data';

import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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');
Expand All @@ -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');
Expand All @@ -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)
Expand Down Expand Up @@ -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(
Expand All @@ -458,6 +551,8 @@ SemanticsObject createTextFieldSemantics({
hasTap: true,
rect: rect,
textDirection: ui.TextDirection.ltr,
textSelectionBase: textSelectionBase,
textSelectionExtent: textSelectionExtent
);
tester.apply();
return tester.getSemanticsObject(0);
Expand Down

0 comments on commit 5932bad

Please sign in to comment.