Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 1c4e42e

Browse files
[TextInput] enroll in autofill by default (#28333)
1 parent cc6074f commit 1c4e42e

File tree

7 files changed

+484
-84
lines changed

7 files changed

+484
-84
lines changed

lib/web_ui/lib/src/engine/text_editing/text_editing.dart

Lines changed: 78 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,16 @@ void _hideAutofillElements(html.HtmlElement domElement,
129129

130130
/// Form that contains all the fields in the same AutofillGroup.
131131
///
132-
/// These values are to be used when autofill is enabled and there is a group of
133-
/// text fields with more than one text field.
132+
/// An [EngineAutofillForm] will only be constructed when autofill is enabled
133+
/// (the default) on the current input field. See the [fromFrameworkMessage]
134+
/// static method.
134135
class EngineAutofillForm {
135-
EngineAutofillForm(
136-
{required this.formElement,
137-
this.elements,
138-
this.items,
139-
this.formIdentifier = ''});
136+
EngineAutofillForm({
137+
required this.formElement,
138+
this.elements,
139+
this.items,
140+
this.formIdentifier = '',
141+
});
140142

141143
final html.FormElement formElement;
142144

@@ -153,12 +155,23 @@ class EngineAutofillForm {
153155
/// See [formsOnTheDom].
154156
final String formIdentifier;
155157

158+
/// Creates an [EngineAutofillFrom] from the JSON representation of a Flutter
159+
/// framework `TextInputConfiguration` object.
160+
///
161+
/// The `focusedElementAutofill` argument corresponds to the "autofill" field
162+
/// in a `TextInputConfiguration`. Not having this field indicates autofill
163+
/// is explicitly disabled on the text field by the developer.
164+
///
165+
/// The `fields` argument corresponds to the "fields" field in a
166+
/// `TextInputConfiguration`.
167+
///
168+
/// Returns null if autofill is disabled for the input field.
156169
static EngineAutofillForm? fromFrameworkMessage(
157170
Map<String, dynamic>? focusedElementAutofill,
158171
List<dynamic>? fields,
159172
) {
160-
// Autofill value can be null if focused text element does not have an
161-
// autofill hint set.
173+
// Autofill value will be null if the developer explicitly disables it on
174+
// the input field.
162175
if (focusedElementAutofill == null) {
163176
return null;
164177
}
@@ -287,7 +300,7 @@ class EngineAutofillForm {
287300
element.onInput.listen((html.Event e) {
288301
if (items![key] == null) {
289302
throw StateError(
290-
'Autofill would not work withuot Autofill value set');
303+
'AutofillInfo must have a valid uniqueIdentifier.');
291304
} else {
292305
final AutofillInfo autofillInfo = items![key]!;
293306
handleChange(element, autofillInfo);
@@ -330,11 +343,13 @@ class EngineAutofillForm {
330343
/// These values are to be used when a text field have autofill enabled.
331344
@visibleForTesting
332345
class AutofillInfo {
333-
AutofillInfo(
334-
{required this.editingState,
335-
required this.uniqueIdentifier,
336-
required this.hint,
337-
required this.textCapitalization});
346+
AutofillInfo({
347+
required this.editingState,
348+
required this.uniqueIdentifier,
349+
required this.autofillHint,
350+
required this.textCapitalization,
351+
this.placeholder,
352+
});
338353

339354
/// The current text and selection state of a text field.
340355
final EditingState editingState;
@@ -359,47 +374,67 @@ class AutofillInfo {
359374
/// other the focused field, we need to use this information.
360375
final TextCapitalizationConfig textCapitalization;
361376

362-
/// Attribute used for autofill.
377+
/// The type of information expected in the field, specified by the developer.
363378
///
364379
/// Used as a guidance to the browser as to the type of information expected
365380
/// in the field.
366381
/// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete
367-
final String hint;
382+
final String? autofillHint;
383+
384+
/// The optional hint text placed on the view that typically suggests what
385+
/// sort of input the field accepts, for example "enter your password here".
386+
///
387+
/// If the developer does not specify any [autofillHints], the [placeholder]
388+
/// can be a useful indication to the platform autofill service as to what
389+
/// information is expected in this field.
390+
final String? placeholder;
368391

369392
factory AutofillInfo.fromFrameworkMessage(Map<String, dynamic> autofill,
370393
{TextCapitalizationConfig textCapitalization =
371394
const TextCapitalizationConfig.defaultCapitalization()}) {
372395
assert(autofill != null); // ignore: unnecessary_null_comparison
373396
final String uniqueIdentifier = autofill.readString('uniqueIdentifier');
374-
final List<dynamic> hintsList = autofill.readList('hints');
397+
final List<dynamic>? hintsList = autofill.tryList('hints');
398+
final String? firstHint = (hintsList == null || hintsList.isEmpty) ? null : hintsList.first as String;
375399
final EditingState editingState =
376400
EditingState.fromFrameworkMessage(autofill.readJson('editingValue'));
377401
return AutofillInfo(
378402
uniqueIdentifier: uniqueIdentifier,
379-
hint: BrowserAutofillHints.instance.flutterToEngine(hintsList[0] as String),
403+
autofillHint: (firstHint != null) ? BrowserAutofillHints.instance.flutterToEngine(firstHint) : null,
380404
editingState: editingState,
405+
placeholder: autofill.tryString('hintText'),
381406
textCapitalization: textCapitalization,
382407
);
383408
}
384409

385410
void applyToDomElement(html.HtmlElement domElement,
386411
{bool focusedElement = false}) {
387-
domElement.id = hint;
412+
final String? autofillHint = this.autofillHint;
413+
final String? placeholder = this.placeholder;
388414
if (domElement is html.InputElement) {
389415
final html.InputElement element = domElement;
390-
element.name = hint;
391-
element.id = hint;
392-
element.autocomplete = hint;
393-
if (hint.contains('password')) {
394-
element.type = 'password';
395-
} else {
396-
element.type = 'text';
416+
if (placeholder != null) {
417+
element.placeholder = placeholder;
397418
}
419+
if (autofillHint != null) {
420+
element.name = autofillHint;
421+
element.id = autofillHint;
422+
if (autofillHint.contains('password')) {
423+
element.type = 'password';
424+
} else {
425+
element.type = 'text';
426+
}
427+
}
428+
element.autocomplete = autofillHint ?? 'on';
398429
} else if (domElement is html.TextAreaElement) {
399-
final html.TextAreaElement element = domElement;
400-
element.name = hint;
401-
element.id = hint;
402-
element.setAttribute('autocomplete', hint);
430+
if (placeholder != null) {
431+
domElement.placeholder = placeholder;
432+
}
433+
if (autofillHint != null) {
434+
domElement.name = autofillHint;
435+
domElement.id = autofillHint;
436+
}
437+
domElement.setAttribute('autocomplete', autofillHint ?? 'on');
403438
}
404439
}
405440
}
@@ -691,15 +726,13 @@ class GloballyPositionedTextEditingStrategy extends DefaultTextEditingStrategy {
691726

692727
@override
693728
void placeElement() {
729+
geometry?.applyToDomElement(activeDomElement);
694730
if (hasAutofillGroup) {
695-
geometry?.applyToDomElement(focusedFormElement!);
696731
placeForm();
697732
// Set the last editing state if it exists, this is critical for a
698733
// users ongoing work to continue uninterrupted when there is an update to
699734
// the transform.
700-
if (lastEditingState != null) {
701-
lastEditingState!.applyToDomElement(domElement);
702-
}
735+
lastEditingState?.applyToDomElement(domElement);
703736
// On Chrome, when a form is focused, it opens an autofill menu
704737
// immediately.
705738
// Flutter framework sends `setEditableSizeAndTransform` for informing
@@ -712,8 +745,6 @@ class GloballyPositionedTextEditingStrategy extends DefaultTextEditingStrategy {
712745
// Refocus on the elements after applying the geometry.
713746
focusedFormElement!.focus();
714747
activeDomElement.focus();
715-
} else {
716-
geometry?.applyToDomElement(activeDomElement);
717748
}
718749
}
719750
}
@@ -762,9 +793,7 @@ class SafariDesktopTextEditingStrategy extends DefaultTextEditingStrategy {
762793
// the transform.
763794
// If domElement is not focused cursor location will not be correct.
764795
activeDomElement.focus();
765-
if (lastEditingState != null) {
766-
lastEditingState!.applyToDomElement(activeDomElement);
767-
}
796+
lastEditingState?.applyToDomElement(activeDomElement);
768797
}
769798
}
770799

@@ -888,7 +917,12 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy {
888917
activeDomElement.setAttribute('inputmode', 'none');
889918
}
890919

891-
config.autofill?.applyToDomElement(activeDomElement, focusedElement: true);
920+
final AutofillInfo? autofill = config.autofill;
921+
if (autofill != null) {
922+
autofill.applyToDomElement(activeDomElement, focusedElement: true);
923+
} else {
924+
activeDomElement.setAttribute('autocomplete', 'off');
925+
}
892926

893927
final String autocorrectValue = config.autocorrect ? 'on' : 'off';
894928
activeDomElement.setAttribute('autocorrect', autocorrectValue);
@@ -1366,9 +1400,7 @@ class FirefoxTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
13661400
// Set the last editing state if it exists, this is critical for a
13671401
// users ongoing work to continue uninterrupted when there is an update to
13681402
// the transform.
1369-
if (lastEditingState != null) {
1370-
lastEditingState!.applyToDomElement(activeDomElement);
1371-
}
1403+
lastEditingState?.applyToDomElement(activeDomElement);
13721404
}
13731405
}
13741406

@@ -1753,11 +1785,9 @@ class HybridTextEditing {
17531785
///
17541786
/// The constructor also decides which text editing strategy to use depending
17551787
/// on the operating system and browser engine.
1756-
HybridTextEditing() {
1757-
channel = TextEditingChannel(this);
1758-
}
1788+
HybridTextEditing();
17591789

1760-
late TextEditingChannel channel;
1790+
late final TextEditingChannel channel = TextEditingChannel(this);
17611791

17621792
/// A CSS class name used to identify all elements used for text editing.
17631793
@visibleForTesting

0 commit comments

Comments
 (0)