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

[TextInput] enroll in autofill by default #28333

Merged

Conversation

LongCatIsLooong
Copy link
Contributor

@LongCatIsLooong LongCatIsLooong commented Aug 26, 2021

Related: flutter/flutter#85554
Framework PR: https://github.com/flutter/flutter/pull/86312/files

The framework will be setting the autofill field to null to indicate that autofill is explicitly disabled on the text field (flutter/flutter#86312).
This PR allows the platform to make autofill attempts using heuristics when the framework sends a TextInputConfiguration that has 0 autofill hints.

@yjbanov I'll try to wrap each input field in a Form (and potentially make the autofill implementation a mixin) in a separate PR.

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide and the C++, Objective-C, Java style guides.
  • I listed at least one issue that this PR fixes in the description above.
  • I added new tests to check the change I am making or feature I am adding, or Hixie said the PR is test-exempt. See testing the engine for instructions on
    writing and running engine tests.
  • I updated/added relevant documentation (doc comments with ///).
  • I signed the CLA.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard bot added platform-android platform-ios platform-web Code specifically for the web engine labels Aug 26, 2021
@google-cla google-cla bot added the cla: yes label Aug 26, 2021
@LongCatIsLooong LongCatIsLooong force-pushed the autofill-enabled-by-default branch 3 times, most recently from 7f47689 to a39c769 Compare August 27, 2021 06:41
@LongCatIsLooong LongCatIsLooong force-pushed the autofill-enabled-by-default branch from a39c769 to 1d23024 Compare August 27, 2021 07:02
@LongCatIsLooong LongCatIsLooong force-pushed the autofill-enabled-by-default branch from 1153f5f to 4b8e30d Compare August 29, 2021 04:51
@LongCatIsLooong LongCatIsLooong force-pushed the autofill-enabled-by-default branch from dc94599 to 6b37ca5 Compare August 30, 2021 20:07
@LongCatIsLooong LongCatIsLooong marked this pull request as ready for review August 30, 2021 20:58
@@ -462,6 +479,11 @@ void testMain() {
const MethodCall show = MethodCall('TextInput.show');
sendFrameworkMessage(codec.encodeMethodCall(show));

final MethodCall setSizeAndTransform =
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "setSizeAndTransform" message has to be there since on some platforms (e.g. Desktop Safari) we don't put the input element on DOM until we get its correct dimensions from the framework.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put this in a comment in the code so people later can easily see why setSizeAndTransform is necessary.

@LongCatIsLooong LongCatIsLooong force-pushed the autofill-enabled-by-default branch from 6b37ca5 to 87e1927 Compare August 31, 2021 18:12
Copy link
Contributor

@mdebbar mdebbar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only reviewed the web part and it looks good overall with a couple comments.

@@ -462,6 +479,11 @@ void testMain() {
const MethodCall show = MethodCall('TextInput.show');
sendFrameworkMessage(codec.encodeMethodCall(show));

final MethodCall setSizeAndTransform =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please put this in a comment in the code so people later can easily see why setSizeAndTransform is necessary.

Comment on lines 416 to 437
element.name = autofillHint;
if (placeholder != null) {
element.placeholder = placeholder;
}
element.autocomplete = autofillHint ?? 'on';
if (autofillHint == null) {
return;
}
element.id = autofillHint;
if (autofillHint.contains('password')) {
element.type = 'password';
} else {
element.type = 'text';
}
} else if (domElement is html.TextAreaElement) {
final html.TextAreaElement element = domElement;
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');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason these two are different? The implementation for input and textarea should remain mostly identical.

@LongCatIsLooong LongCatIsLooong force-pushed the autofill-enabled-by-default branch from 87e1927 to ea2b425 Compare September 1, 2021 21:41
@LongCatIsLooong LongCatIsLooong force-pushed the autofill-enabled-by-default branch from ea2b425 to 040581d Compare September 1, 2021 21:43
Copy link
Contributor

@justinmc justinmc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

/// The `fields` argument corresponds to the "fields" field in a
/// `TextInputConfiguration`.
///
/// Returns null if the input field is not within an autofill group.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the added docs here. We need an analyzer rule to require this like in the framework.

} else if (domElement is html.TextAreaElement) {
final html.TextAreaElement element = domElement;
Copy link
Contributor

Choose a reason for hiding this comment

The 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.


late TextEditingChannel channel;
late final TextEditingChannel channel = TextEditingChannel(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this still need late?

I feel like I'm wrong and I got tripped up by this recently...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think late is still required because the right-hand-side expression refers to this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. This needs lazy initialization because this has to be initialized first, to be passed to the TextEditingChannel constructor, otherwise we'll have a circular dependency.

@@ -462,6 +479,15 @@ void testMain() {
const MethodCall show = MethodCall('TextInput.show');
sendFrameworkMessage(codec.encodeMethodCall(show));

// The "setSizeAndTransform" message has to be here before we call
// checkInputEditingState, since on some platforms (e.g. Desktop Safari)
// we don't put the input element on DOM until we get its correct
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: "on DOM" => "into the DOM"

If that's correct and I'm not misunderstanding, I think it's more explicit this way.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same for the duplicate comments below.

Copy link
Contributor

@yjbanov yjbanov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM if LGT @mdebbar

@@ -129,8 +129,9 @@ 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.
/// These values are to be used when autofill is enabled (the default) on the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "These values" refer to? Is there a better way to phrase it?

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

/// Creates an [EngineAutofillFrom] from the JSON representation of a flutter
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/flutter/Flutter/

@@ -287,7 +299,7 @@ class EngineAutofillForm {
element.onInput.listen((html.Event e) {
if (items![key] == null) {
throw StateError(
'Autofill would not work withuot Autofill value set');
'Autofill would not work without Autofill value set');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this an error that could happen due to a framework-side bug? If so, can the error message be more specific? For example, would it help if it mentioned the key that was missing and why it was expected?

required this.textCapitalization});
required this.autofillHint,
required this.textCapitalization,
this.placeholder});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: code style could be improved here, e.g.:

  AutofillInfo({
    required this.editingState,
    required this.uniqueIdentifier,
    required this.hint,
    required this.textCapitalization});
    required this.autofillHint,
    required this.textCapitalization,
    this.placeholder,
  });

@@ -364,42 +377,64 @@ class AutofillInfo {
/// 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;
Copy link
Contributor

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.


late TextEditingChannel channel;
late final TextEditingChannel channel = TextEditingChannel(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this still need to be late?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think late is still required because the right-hand-side expression refers to this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

element.type = 'text';
}
}
element.autocomplete = autofillHint ?? 'on';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we want to enable autocomplete when autofill hint is null?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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), EditableTextState will still generate an autofill JSON object for the text field when it's focused, and the autofill service can try to determine the type of the text field (or whether the field should be autofilled at all) using heuristics.
if the developer explicitly sets EditableText.autofillHints to null, then autofill will be disabled.

final String? autofillHint = this.autofillHint;
final String? placeholder = this.placeholder;
if (autofillHint != null) {
domElement.id = autofillHint;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are setting the id again below. Is this still necessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@LongCatIsLooong LongCatIsLooong force-pushed the autofill-enabled-by-default branch from f85e4d3 to a2c62b4 Compare September 4, 2021 01:12
@LongCatIsLooong LongCatIsLooong added the waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land. label Sep 4, 2021
@fluttergithubbot fluttergithubbot merged commit 1c4e42e into flutter:master Sep 4, 2021
@LongCatIsLooong LongCatIsLooong deleted the autofill-enabled-by-default branch September 4, 2021 03:27
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Sep 4, 2021
fluttergithubbot pushed a commit to flutter/flutter that referenced this pull request Sep 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
cla: yes platform-android platform-ios platform-web Code specifically for the web engine waiting for tree to go green This PR is approved and tested, but waiting for the tree to be green to land.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants