mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[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:
parent
dcee06a479
commit
4a88a2f248
@ -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
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user