@@ -129,14 +129,16 @@ void _hideAutofillElements(html.HtmlElement domElement,
129
129
130
130
/// Form that contains all the fields in the same AutofillGroup.
131
131
///
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.
134
135
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
+ });
140
142
141
143
final html.FormElement formElement;
142
144
@@ -153,12 +155,23 @@ class EngineAutofillForm {
153
155
/// See [formsOnTheDom] .
154
156
final String formIdentifier;
155
157
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.
156
169
static EngineAutofillForm ? fromFrameworkMessage (
157
170
Map <String , dynamic >? focusedElementAutofill,
158
171
List <dynamic >? fields,
159
172
) {
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 .
162
175
if (focusedElementAutofill == null ) {
163
176
return null ;
164
177
}
@@ -287,7 +300,7 @@ class EngineAutofillForm {
287
300
element.onInput.listen ((html.Event e) {
288
301
if (items! [key] == null ) {
289
302
throw StateError (
290
- 'Autofill would not work withuot Autofill value set ' );
303
+ 'AutofillInfo must have a valid uniqueIdentifier. ' );
291
304
} else {
292
305
final AutofillInfo autofillInfo = items! [key]! ;
293
306
handleChange (element, autofillInfo);
@@ -330,11 +343,13 @@ class EngineAutofillForm {
330
343
/// These values are to be used when a text field have autofill enabled.
331
344
@visibleForTesting
332
345
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
+ });
338
353
339
354
/// The current text and selection state of a text field.
340
355
final EditingState editingState;
@@ -359,47 +374,67 @@ class AutofillInfo {
359
374
/// other the focused field, we need to use this information.
360
375
final TextCapitalizationConfig textCapitalization;
361
376
362
- /// Attribute used for autofill .
377
+ /// The type of information expected in the field, specified by the developer .
363
378
///
364
379
/// Used as a guidance to the browser as to the type of information expected
365
380
/// in the field.
366
381
/// 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;
368
391
369
392
factory AutofillInfo .fromFrameworkMessage (Map <String , dynamic > autofill,
370
393
{TextCapitalizationConfig textCapitalization =
371
394
const TextCapitalizationConfig .defaultCapitalization ()}) {
372
395
assert (autofill != null ); // ignore: unnecessary_null_comparison
373
396
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 ;
375
399
final EditingState editingState =
376
400
EditingState .fromFrameworkMessage (autofill.readJson ('editingValue' ));
377
401
return AutofillInfo (
378
402
uniqueIdentifier: uniqueIdentifier,
379
- hint : BrowserAutofillHints .instance.flutterToEngine (hintsList[ 0 ] as String ) ,
403
+ autofillHint : (firstHint != null ) ? BrowserAutofillHints .instance.flutterToEngine (firstHint) : null ,
380
404
editingState: editingState,
405
+ placeholder: autofill.tryString ('hintText' ),
381
406
textCapitalization: textCapitalization,
382
407
);
383
408
}
384
409
385
410
void applyToDomElement (html.HtmlElement domElement,
386
411
{bool focusedElement = false }) {
387
- domElement.id = hint;
412
+ final String ? autofillHint = this .autofillHint;
413
+ final String ? placeholder = this .placeholder;
388
414
if (domElement is html.InputElement ) {
389
415
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;
397
418
}
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' ;
398
429
} 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' );
403
438
}
404
439
}
405
440
}
@@ -691,15 +726,13 @@ class GloballyPositionedTextEditingStrategy extends DefaultTextEditingStrategy {
691
726
692
727
@override
693
728
void placeElement () {
729
+ geometry? .applyToDomElement (activeDomElement);
694
730
if (hasAutofillGroup) {
695
- geometry? .applyToDomElement (focusedFormElement! );
696
731
placeForm ();
697
732
// Set the last editing state if it exists, this is critical for a
698
733
// users ongoing work to continue uninterrupted when there is an update to
699
734
// the transform.
700
- if (lastEditingState != null ) {
701
- lastEditingState! .applyToDomElement (domElement);
702
- }
735
+ lastEditingState? .applyToDomElement (domElement);
703
736
// On Chrome, when a form is focused, it opens an autofill menu
704
737
// immediately.
705
738
// Flutter framework sends `setEditableSizeAndTransform` for informing
@@ -712,8 +745,6 @@ class GloballyPositionedTextEditingStrategy extends DefaultTextEditingStrategy {
712
745
// Refocus on the elements after applying the geometry.
713
746
focusedFormElement! .focus ();
714
747
activeDomElement.focus ();
715
- } else {
716
- geometry? .applyToDomElement (activeDomElement);
717
748
}
718
749
}
719
750
}
@@ -762,9 +793,7 @@ class SafariDesktopTextEditingStrategy extends DefaultTextEditingStrategy {
762
793
// the transform.
763
794
// If domElement is not focused cursor location will not be correct.
764
795
activeDomElement.focus ();
765
- if (lastEditingState != null ) {
766
- lastEditingState! .applyToDomElement (activeDomElement);
767
- }
796
+ lastEditingState? .applyToDomElement (activeDomElement);
768
797
}
769
798
}
770
799
@@ -888,7 +917,12 @@ abstract class DefaultTextEditingStrategy implements TextEditingStrategy {
888
917
activeDomElement.setAttribute ('inputmode' , 'none' );
889
918
}
890
919
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
+ }
892
926
893
927
final String autocorrectValue = config.autocorrect ? 'on' : 'off' ;
894
928
activeDomElement.setAttribute ('autocorrect' , autocorrectValue);
@@ -1366,9 +1400,7 @@ class FirefoxTextEditingStrategy extends GloballyPositionedTextEditingStrategy {
1366
1400
// Set the last editing state if it exists, this is critical for a
1367
1401
// users ongoing work to continue uninterrupted when there is an update to
1368
1402
// the transform.
1369
- if (lastEditingState != null ) {
1370
- lastEditingState! .applyToDomElement (activeDomElement);
1371
- }
1403
+ lastEditingState? .applyToDomElement (activeDomElement);
1372
1404
}
1373
1405
}
1374
1406
@@ -1753,11 +1785,9 @@ class HybridTextEditing {
1753
1785
///
1754
1786
/// The constructor also decides which text editing strategy to use depending
1755
1787
/// on the operating system and browser engine.
1756
- HybridTextEditing () {
1757
- channel = TextEditingChannel (this );
1758
- }
1788
+ HybridTextEditing ();
1759
1789
1760
- late TextEditingChannel channel;
1790
+ late final TextEditingChannel channel = TextEditingChannel ( this ) ;
1761
1791
1762
1792
/// A CSS class name used to identify all elements used for text editing.
1763
1793
@visibleForTesting
0 commit comments