-
Notifications
You must be signed in to change notification settings - Fork 6k
[TextInput] enroll in autofill by default #28333
Changes from all commits
846462d
151023a
1d23024
1153f5f
4b8e30d
cc3d028
318e941
d76e1b9
6c6aa9e
040581d
a2c62b4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -129,14 +129,16 @@ void _hideAutofillElements(html.HtmlElement domElement, | |
|
||
/// Form that contains all the fields in the same AutofillGroup. | ||
/// | ||
/// These values are to be used when autofill is enabled and there is a group of | ||
/// text fields with more than one text field. | ||
/// An [EngineAutofillForm] will only be constructed when autofill is enabled | ||
/// (the default) on the current input field. See the [fromFrameworkMessage] | ||
/// static method. | ||
class EngineAutofillForm { | ||
EngineAutofillForm( | ||
{required this.formElement, | ||
this.elements, | ||
this.items, | ||
this.formIdentifier = ''}); | ||
EngineAutofillForm({ | ||
required this.formElement, | ||
this.elements, | ||
this.items, | ||
this.formIdentifier = '', | ||
}); | ||
|
||
final html.FormElement formElement; | ||
|
||
|
@@ -153,12 +155,23 @@ class EngineAutofillForm { | |
/// See [formsOnTheDom]. | ||
final String formIdentifier; | ||
|
||
/// Creates an [EngineAutofillFrom] from the JSON representation of a Flutter | ||
/// framework `TextInputConfiguration` object. | ||
/// | ||
/// The `focusedElementAutofill` argument corresponds to the "autofill" field | ||
/// in a `TextInputConfiguration`. Not having this field indicates autofill | ||
/// is explicitly disabled on the text field by the developer. | ||
/// | ||
/// The `fields` argument corresponds to the "fields" field in a | ||
/// `TextInputConfiguration`. | ||
/// | ||
/// Returns null if autofill is disabled for the input field. | ||
static EngineAutofillForm? fromFrameworkMessage( | ||
Map<String, dynamic>? focusedElementAutofill, | ||
List<dynamic>? fields, | ||
) { | ||
// Autofill value can be null if focused text element does not have an | ||
// autofill hint set. | ||
// Autofill value will be null if the developer explicitly disables it on | ||
// the input field. | ||
if (focusedElementAutofill == null) { | ||
return null; | ||
} | ||
|
@@ -287,7 +300,7 @@ class EngineAutofillForm { | |
element.onInput.listen((html.Event e) { | ||
if (items![key] == null) { | ||
throw StateError( | ||
'Autofill would not work withuot Autofill value set'); | ||
'AutofillInfo must have a valid uniqueIdentifier.'); | ||
} else { | ||
final AutofillInfo autofillInfo = items![key]!; | ||
handleChange(element, autofillInfo); | ||
|
@@ -330,11 +343,13 @@ class EngineAutofillForm { | |
/// These values are to be used when a text field have autofill enabled. | ||
@visibleForTesting | ||
class AutofillInfo { | ||
AutofillInfo( | ||
{required this.editingState, | ||
required this.uniqueIdentifier, | ||
required this.hint, | ||
required this.textCapitalization}); | ||
AutofillInfo({ | ||
required this.editingState, | ||
required this.uniqueIdentifier, | ||
required this.autofillHint, | ||
required this.textCapitalization, | ||
this.placeholder, | ||
}); | ||
|
||
/// The current text and selection state of a text field. | ||
final EditingState editingState; | ||
|
@@ -359,47 +374,67 @@ class AutofillInfo { | |
/// other the focused field, we need to use this information. | ||
final TextCapitalizationConfig textCapitalization; | ||
|
||
/// Attribute used for autofill. | ||
/// The type of information expected in the field, specified by the developer. | ||
/// | ||
/// Used as a guidance to the browser as to the type of information expected | ||
/// in the field. | ||
/// See: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete | ||
final String hint; | ||
final String? autofillHint; | ||
|
||
/// The optional hint text placed on the view that typically suggests what | ||
/// sort of input the field accepts, for example "enter your password here". | ||
/// | ||
/// If the developer does not specify any [autofillHints], the [placeholder] | ||
/// can be a useful indication to the platform autofill service as to what | ||
/// information is expected in this field. | ||
final String? placeholder; | ||
|
||
factory AutofillInfo.fromFrameworkMessage(Map<String, dynamic> autofill, | ||
{TextCapitalizationConfig textCapitalization = | ||
const TextCapitalizationConfig.defaultCapitalization()}) { | ||
assert(autofill != null); // ignore: unnecessary_null_comparison | ||
final String uniqueIdentifier = autofill.readString('uniqueIdentifier'); | ||
final List<dynamic> hintsList = autofill.readList('hints'); | ||
final List<dynamic>? hintsList = autofill.tryList('hints'); | ||
final String? firstHint = (hintsList == null || hintsList.isEmpty) ? null : hintsList.first as String; | ||
final EditingState editingState = | ||
EditingState.fromFrameworkMessage(autofill.readJson('editingValue')); | ||
return AutofillInfo( | ||
uniqueIdentifier: uniqueIdentifier, | ||
hint: BrowserAutofillHints.instance.flutterToEngine(hintsList[0] as String), | ||
autofillHint: (firstHint != null) ? BrowserAutofillHints.instance.flutterToEngine(firstHint) : null, | ||
editingState: editingState, | ||
placeholder: autofill.tryString('hintText'), | ||
textCapitalization: textCapitalization, | ||
); | ||
} | ||
|
||
void applyToDomElement(html.HtmlElement domElement, | ||
{bool focusedElement = false}) { | ||
domElement.id = hint; | ||
final String? autofillHint = this.autofillHint; | ||
final String? placeholder = this.placeholder; | ||
if (domElement is html.InputElement) { | ||
final html.InputElement element = domElement; | ||
element.name = hint; | ||
element.id = hint; | ||
element.autocomplete = hint; | ||
if (hint.contains('password')) { | ||
element.type = 'password'; | ||
} else { | ||
element.type = 'text'; | ||
if (placeholder != null) { | ||
element.placeholder = placeholder; | ||
} | ||
if (autofillHint != null) { | ||
element.name = autofillHint; | ||
element.id = autofillHint; | ||
if (autofillHint.contains('password')) { | ||
element.type = 'password'; | ||
} else { | ||
element.type = 'text'; | ||
} | ||
} | ||
element.autocomplete = autofillHint ?? 'on'; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to enable autocomplete when autofill hint is null? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah that's what the pull request trying to do: flutter/flutter#85554. If we want to do it on Android then the default behavior should probably be consistent across platforms (or does it make sense on the web?). https://github.com/flutter/flutter/pull/86312/files tries to make so that even if the developer leaves the hint list empty (the default), |
||
} else if (domElement is html.TextAreaElement) { | ||
final html.TextAreaElement element = domElement; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure why this variable was created in the first place, good idea to remove it. |
||
element.name = hint; | ||
element.id = hint; | ||
element.setAttribute('autocomplete', hint); | ||
if (placeholder != null) { | ||
domElement.placeholder = placeholder; | ||
} | ||
if (autofillHint != null) { | ||
domElement.name = autofillHint; | ||
domElement.id = autofillHint; | ||
} | ||
domElement.setAttribute('autocomplete', autofillHint ?? 'on'); | ||
} | ||
} | ||
} | ||
|
@@ -691,15 +726,13 @@ class GloballyPositionedTextEditingStrategy extends DefaultTextEditingStrategy { | |
|
||
@override | ||
void placeElement() { | ||
geometry?.applyToDomElement(activeDomElement); | ||
if (hasAutofillGroup) { | ||
geometry?.applyToDomElement(focusedFormElement!); | ||
placeForm(); | ||
// Set the last editing state if it exists, this is critical for a | ||
// users ongoing work to continue uninterrupted when there is an update to | ||
// the transform. | ||
if (lastEditingState != null) { | ||
lastEditingState!.applyToDomElement(domElement); | ||
} | ||
lastEditingState?.applyToDomElement(domElement); | ||
// On Chrome, when a form is focused, it opens an autofill menu | ||
// immediately. | ||
// Flutter framework sends `setEditableSizeAndTransform` for informing | ||
|
@@ -712,8 +745,6 @@ class GloballyPositionedTextEditingStrategy extends DefaultTextEditingStrategy { | |
// Refocus on the elements after applying the geometry. | ||
focusedFormElement!.focus(); | ||
activeDomElement.focus(); | ||
} else { | ||
geometry?.applyToDomElement(activeDomElement); | ||
} | ||
} | ||
} | ||
|
@@ -762,9 +793,7 @@ class SafariDesktopTextEditingStrategy extends DefaultTextEditingStrategy { | |
// the transform. | ||
// If domElement is not focused cursor location will not be correct. | ||
activeDomElement.focus(); | ||
if (lastEditingState != null) { | ||
lastEditingState!.applyToDomElement(activeDomElement); | ||
} | ||
lastEditingState?.applyToDomElement(activeDomElement); | ||
} | ||
} | ||
|
||
|
@@ -888,7 +917,12 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy { | |
activeDomElement.setAttribute('inputmode', 'none'); | ||
} | ||
|
||
config.autofill?.applyToDomElement(activeDomElement, focusedElement: true); | ||
final AutofillInfo? autofill = config.autofill; | ||
if (autofill != null) { | ||
autofill.applyToDomElement(activeDomElement, focusedElement: true); | ||
} else { | ||
activeDomElement.setAttribute('autocomplete', 'off'); | ||
} | ||
|
||
final String autocorrectValue = config.autocorrect ? 'on' : 'off'; | ||
activeDomElement.setAttribute('autocorrect', autocorrectValue); | ||
|
@@ -1366,9 +1400,7 @@ class FirefoxTextEditingStrategy extends GloballyPositionedTextEditingStrategy { | |
// Set the last editing state if it exists, this is critical for a | ||
// users ongoing work to continue uninterrupted when there is an update to | ||
// the transform. | ||
if (lastEditingState != null) { | ||
lastEditingState!.applyToDomElement(activeDomElement); | ||
} | ||
lastEditingState?.applyToDomElement(activeDomElement); | ||
} | ||
} | ||
|
||
|
@@ -1753,11 +1785,9 @@ class HybridTextEditing { | |
/// | ||
/// The constructor also decides which text editing strategy to use depending | ||
/// on the operating system and browser engine. | ||
HybridTextEditing() { | ||
channel = TextEditingChannel(this); | ||
} | ||
HybridTextEditing(); | ||
|
||
late TextEditingChannel channel; | ||
late final TextEditingChannel channel = TextEditingChannel(this); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this still need I feel like I'm wrong and I got tripped up by this recently... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. This needs lazy initialization because There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this still need to be There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes: #28333 (comment) |
||
|
||
/// A CSS class name used to identify all elements used for text editing. | ||
@visibleForTesting | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: is the first sentence in the dartdoc still useful? Seems obvious now that this is used for autofill.