forked from firka/flutter
Revert "Revert "[web] Don't overwrite editing state with semantic updates (#38271)" (#38562)" (flutter/engine#38854)
This reverts commit d412d64cef.
This commit is contained in:
@@ -364,11 +364,7 @@ class TextField extends RoleManager {
|
|||||||
// element, so that both the framework and the browser agree on what's
|
// element, so that both the framework and the browser agree on what's
|
||||||
// currently focused.
|
// currently focused.
|
||||||
bool needsDomFocusRequest = false;
|
bool needsDomFocusRequest = false;
|
||||||
final EditingState editingState = EditingState(
|
|
||||||
text: semanticsObject.value,
|
|
||||||
baseOffset: semanticsObject.textSelectionBase,
|
|
||||||
extentOffset: semanticsObject.textSelectionExtent,
|
|
||||||
);
|
|
||||||
if (semanticsObject.hasFocus) {
|
if (semanticsObject.hasFocus) {
|
||||||
if (!_hasFocused) {
|
if (!_hasFocused) {
|
||||||
_hasFocused = true;
|
_hasFocused = true;
|
||||||
@@ -378,14 +374,9 @@ class TextField extends RoleManager {
|
|||||||
if (domDocument.activeElement != editableElement) {
|
if (domDocument.activeElement != editableElement) {
|
||||||
needsDomFocusRequest = true;
|
needsDomFocusRequest = true;
|
||||||
}
|
}
|
||||||
// Focused elements should have full text editing state applied.
|
|
||||||
SemanticsTextEditingStrategy.instance.setEditingState(editingState);
|
|
||||||
} else if (_hasFocused) {
|
} else if (_hasFocused) {
|
||||||
SemanticsTextEditingStrategy.instance.deactivate(this);
|
SemanticsTextEditingStrategy.instance.deactivate(this);
|
||||||
|
|
||||||
// Only apply text, because this node is not focused.
|
|
||||||
editingState.applyTextToDomElement(editableElement);
|
|
||||||
|
|
||||||
if (_hasFocused && domDocument.activeElement == editableElement) {
|
if (_hasFocused && domDocument.activeElement == editableElement) {
|
||||||
// Unlike `editableElement.focus()` we don't need to schedule `blur`
|
// Unlike `editableElement.focus()` we don't need to schedule `blur`
|
||||||
// post-update because `document.activeElement` implies that the
|
// post-update because `document.activeElement` implies that the
|
||||||
|
|||||||
@@ -4,6 +4,8 @@
|
|||||||
|
|
||||||
@TestOn('chrome || safari || firefox')
|
@TestOn('chrome || safari || firefox')
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
import 'package:test/bootstrap/browser.dart';
|
import 'package:test/bootstrap/browser.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
@@ -48,6 +50,11 @@ void testMain() {
|
|||||||
testTextEditing.configuration = singlelineConfig;
|
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 {
|
test('renders a text field', () async {
|
||||||
semantics()
|
semantics()
|
||||||
..debugOverrideTimestampFunction(() => _testTime)
|
..debugOverrideTimestampFunction(() => _testTime)
|
||||||
@@ -127,7 +134,7 @@ void testMain() {
|
|||||||
// TODO(yjbanov): https://github.com/flutter/flutter/issues/50754
|
// TODO(yjbanov): https://github.com/flutter/flutter/issues/50754
|
||||||
skip: browserEngine != BrowserEngine.blink);
|
skip: browserEngine != BrowserEngine.blink);
|
||||||
|
|
||||||
test('Syncs editing state from framework', () async {
|
test('Syncs semantic state from framework', () async {
|
||||||
semantics()
|
semantics()
|
||||||
..debugOverrideTimestampFunction(() => _testTime)
|
..debugOverrideTimestampFunction(() => _testTime)
|
||||||
..semanticsEnabled = true;
|
..semanticsEnabled = true;
|
||||||
@@ -159,7 +166,6 @@ void testMain() {
|
|||||||
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement);
|
||||||
expect(appHostNode.activeElement, strategy.domElement);
|
expect(appHostNode.activeElement, strategy.domElement);
|
||||||
expect(textField.editableElement, strategy.domElement);
|
expect(textField.editableElement, strategy.domElement);
|
||||||
expect((textField.editableElement as dynamic).value, 'hello');
|
|
||||||
expect(textField.editableElement.getAttribute('aria-label'), 'greeting');
|
expect(textField.editableElement.getAttribute('aria-label'), 'greeting');
|
||||||
expect(textField.editableElement.style.width, '10px');
|
expect(textField.editableElement.style.width, '10px');
|
||||||
expect(textField.editableElement.style.height, '15px');
|
expect(textField.editableElement.style.height, '15px');
|
||||||
@@ -174,7 +180,6 @@ void testMain() {
|
|||||||
expect(domDocument.activeElement, domDocument.body);
|
expect(domDocument.activeElement, domDocument.body);
|
||||||
expect(appHostNode.activeElement, null);
|
expect(appHostNode.activeElement, null);
|
||||||
expect(strategy.domElement, null);
|
expect(strategy.domElement, null);
|
||||||
expect((textField.editableElement as dynamic).value, 'bye');
|
|
||||||
expect(textField.editableElement.getAttribute('aria-label'), 'farewell');
|
expect(textField.editableElement.getAttribute('aria-label'), 'farewell');
|
||||||
expect(textField.editableElement.style.width, '12px');
|
expect(textField.editableElement.style.width, '12px');
|
||||||
expect(textField.editableElement.style.height, '17px');
|
expect(textField.editableElement.style.height, '17px');
|
||||||
@@ -188,6 +193,92 @@ void testMain() {
|
|||||||
expect(actionCount, 0);
|
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 {
|
test('Gives up focus after DOM blur', () async {
|
||||||
semantics()
|
semantics()
|
||||||
..debugOverrideTimestampFunction(() => _testTime)
|
..debugOverrideTimestampFunction(() => _testTime)
|
||||||
@@ -446,6 +537,8 @@ SemanticsObject createTextFieldSemantics({
|
|||||||
bool isFocused = false,
|
bool isFocused = false,
|
||||||
bool isMultiline = false,
|
bool isMultiline = false,
|
||||||
ui.Rect rect = const ui.Rect.fromLTRB(0, 0, 100, 50),
|
ui.Rect rect = const ui.Rect.fromLTRB(0, 0, 100, 50),
|
||||||
|
int textSelectionBase = 0,
|
||||||
|
int textSelectionExtent = 0,
|
||||||
}) {
|
}) {
|
||||||
final SemanticsTester tester = SemanticsTester(semantics());
|
final SemanticsTester tester = SemanticsTester(semantics());
|
||||||
tester.updateNode(
|
tester.updateNode(
|
||||||
@@ -458,6 +551,8 @@ SemanticsObject createTextFieldSemantics({
|
|||||||
hasTap: true,
|
hasTap: true,
|
||||||
rect: rect,
|
rect: rect,
|
||||||
textDirection: ui.TextDirection.ltr,
|
textDirection: ui.TextDirection.ltr,
|
||||||
|
textSelectionBase: textSelectionBase,
|
||||||
|
textSelectionExtent: textSelectionExtent
|
||||||
);
|
);
|
||||||
tester.apply();
|
tester.apply();
|
||||||
return tester.getSemanticsObject(0);
|
return tester.getSemanticsObject(0);
|
||||||
|
|||||||
Reference in New Issue
Block a user