mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
[web] - Fix autofill group input ordering (flutter/engine#42268)
Ordering of input elements inside of the DOM tree for autofill groups does not reflect the order of the form rendered on screen. This is causing some issues with password managers and autofill, specifically Bitwarden. We are currently always appending the currently focused input element to the end of the form. This leads to a tree that appears out of order: <img width="354" alt="Screenshot 2023-05-23 at 2 57 37 PM" src="https://github.com/flutter/engine/assets/110993981/7e90a93f-5522-4482-8fb6-a1607b403d10"> This fix is tracking the position of where the focused input node should be inserted and inserting it there, rather than always at the end of the form. Once the tree is ordered correctly, Bitwarden's autofill logic works in Flutter forms. Tree order after fix: <img width="502" alt="Screenshot 2023-05-23 at 6 01 05 PM" src="https://github.com/flutter/engine/assets/110993981/bd15a8a1-71f4-4f28-a86e-1903953bf030"> Fixes https://github.com/flutter/flutter/issues/61301
This commit is contained in:
parent
b6e8bd6234
commit
f0e2596c5c
@ -145,6 +145,7 @@ class EngineAutofillForm {
|
||||
this.elements,
|
||||
this.items,
|
||||
this.formIdentifier = '',
|
||||
this.insertionReferenceNode,
|
||||
});
|
||||
|
||||
final DomHTMLFormElement formElement;
|
||||
@ -153,6 +154,7 @@ class EngineAutofillForm {
|
||||
|
||||
final Map<String, AutofillInfo>? items;
|
||||
|
||||
final DomHTMLElement? insertionReferenceNode;
|
||||
/// Identifier for the form.
|
||||
///
|
||||
/// It is constructed by concatenating unique ids of input elements on the
|
||||
@ -189,6 +191,7 @@ class EngineAutofillForm {
|
||||
final Map<String, DomHTMLElement> elements = <String, DomHTMLElement>{};
|
||||
final Map<String, AutofillInfo> items = <String, AutofillInfo>{};
|
||||
final DomHTMLFormElement formElement = createDomHTMLFormElement();
|
||||
DomHTMLElement? insertionReferenceNode;
|
||||
|
||||
// Validation is in the framework side.
|
||||
formElement.noValidate = true;
|
||||
@ -209,6 +212,7 @@ class EngineAutofillForm {
|
||||
AutofillInfo.fromFrameworkMessage(focusedElementAutofill);
|
||||
|
||||
if (fields != null) {
|
||||
bool fieldIsFocusedElement = false;
|
||||
for (final Map<String, dynamic> field in
|
||||
fields.cast<Map<String, dynamic>>()) {
|
||||
final Map<String, dynamic> autofillInfo = field.readJson('autofill');
|
||||
@ -234,6 +238,17 @@ class EngineAutofillForm {
|
||||
items[autofill.uniqueIdentifier] = autofill;
|
||||
elements[autofill.uniqueIdentifier] = htmlElement;
|
||||
formElement.append(htmlElement);
|
||||
|
||||
// We want to track the node in the position directly after our focused
|
||||
// element, so we can later insert that element in the correct position
|
||||
// right before this node.
|
||||
if(fieldIsFocusedElement){
|
||||
insertionReferenceNode = htmlElement;
|
||||
fieldIsFocusedElement = false;
|
||||
}
|
||||
} else {
|
||||
// current field is the focused element that we create elsewhere
|
||||
fieldIsFocusedElement = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -268,16 +283,21 @@ class EngineAutofillForm {
|
||||
|
||||
formElement.append(submitButton);
|
||||
|
||||
// If the focused node is at the end of the form, we'll default to inserting
|
||||
// it before the submit field.
|
||||
insertionReferenceNode ??= submitButton;
|
||||
|
||||
return EngineAutofillForm(
|
||||
formElement: formElement,
|
||||
elements: elements,
|
||||
items: items,
|
||||
formIdentifier: formIdentifier,
|
||||
insertionReferenceNode: insertionReferenceNode
|
||||
);
|
||||
}
|
||||
|
||||
void placeForm(DomHTMLElement mainTextEditingElement) {
|
||||
formElement.append(mainTextEditingElement);
|
||||
formElement.insertBefore(mainTextEditingElement, insertionReferenceNode);
|
||||
defaultTextEditingRoot.append(formElement);
|
||||
}
|
||||
|
||||
|
||||
@ -2176,6 +2176,47 @@ Future<void> testMain() async {
|
||||
expect(autofillForm, isNull);
|
||||
});
|
||||
|
||||
test('placeForm() should place element in correct position', () {
|
||||
final List<dynamic> fields = createFieldValues(<String>[
|
||||
'email',
|
||||
'username',
|
||||
'password',
|
||||
], <String>[
|
||||
'field1',
|
||||
'field2',
|
||||
'field3'
|
||||
]);
|
||||
final EngineAutofillForm autofillForm =
|
||||
EngineAutofillForm.fromFrameworkMessage(
|
||||
createAutofillInfo('email', 'field1'), fields)!;
|
||||
|
||||
expect(autofillForm.elements, hasLength(2));
|
||||
|
||||
List<DomHTMLInputElement> formChildNodes =
|
||||
autofillForm.formElement.childNodes.toList() as List<DomHTMLInputElement>;
|
||||
|
||||
// Only username, password, submit nodes are created
|
||||
expect(formChildNodes, hasLength(3));
|
||||
expect(formChildNodes[0].name, 'username');
|
||||
expect(formChildNodes[1].name, 'current-password');
|
||||
expect(formChildNodes[2].type, 'submit');
|
||||
// insertion point for email should be before username
|
||||
expect(autofillForm.insertionReferenceNode, formChildNodes[0]);
|
||||
|
||||
final DomHTMLInputElement testInputElement = createDomHTMLInputElement();
|
||||
testInputElement.name = 'email';
|
||||
autofillForm.placeForm(testInputElement);
|
||||
|
||||
formChildNodes = autofillForm.formElement.childNodes.toList()
|
||||
as List<DomHTMLInputElement>;
|
||||
// email node should be placed before username
|
||||
expect(formChildNodes, hasLength(4));
|
||||
expect(formChildNodes[0].name, 'email');
|
||||
expect(formChildNodes[1].name, 'username');
|
||||
expect(formChildNodes[2].name, 'current-password');
|
||||
expect(formChildNodes[3].type, 'submit');
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
clearForms();
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user