Commit d841e4d0 authored by lijunsong's avatar lijunsong Committed by Commit Bot

add unit test for username field search in FormDataParser

Test HTMLDetectorCache_SkipSomePredictions was deleted during resolving Bug
949519. This CL ports the test to the current FormDataParser.

Bug: 1090694
Change-Id: Ia48a825099577536e407ef1f324899b0276fce91
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2299179Reviewed-by: default avatarMaxim Kolosovskiy  <kolos@chromium.org>
Commit-Queue: Maxim Kolosovskiy  <kolos@chromium.org>
Cr-Commit-Position: refs/heads/master@{#795025}
parent 2e21a15f
...@@ -45,16 +45,6 @@ constexpr char kAutocompleteNewPassword[] = "new-password"; ...@@ -45,16 +45,6 @@ constexpr char kAutocompleteNewPassword[] = "new-password";
constexpr char kAutocompleteCreditCardPrefix[] = "cc-"; constexpr char kAutocompleteCreditCardPrefix[] = "cc-";
constexpr char kAutocompleteOneTimePassword[] = "one-time-code"; constexpr char kAutocompleteOneTimePassword[] = "one-time-code";
// The susbset of autocomplete flags related to passwords.
enum class AutocompleteFlag {
kNone,
kUsername,
kCurrentPassword,
kNewPassword,
// Represents the whole family of cc-* flags + OTP flag.
kNonPassword
};
// The autocomplete attribute has one of the following structures: // The autocomplete attribute has one of the following structures:
// [section-*] [shipping|billing] [type_hint] field_type // [section-*] [shipping|billing] [type_hint] field_type
// on | off | false // on | off | false
...@@ -89,42 +79,6 @@ AutocompleteFlag ExtractAutocompleteFlag(const std::string& attribute) { ...@@ -89,42 +79,6 @@ AutocompleteFlag ExtractAutocompleteFlag(const std::string& attribute) {
return AutocompleteFlag::kNone; return AutocompleteFlag::kNone;
} }
// How likely is user interaction for a given field?
// Note: higher numeric values should match higher likeliness to allow using the
// standard operator< for comparison of likeliness.
enum class Interactability {
// When the field is invisible.
kUnlikely = 0,
// When the field is visible/focusable.
kPossible = 1,
// When the user actually typed into the field before.
kCertain = 2,
};
// A wrapper around FormFieldData, carrying some additional data used during
// parsing.
struct ProcessedField {
// This points to the wrapped FormFieldData.
const FormFieldData* field;
// The flag derived from field->autocomplete_attribute.
AutocompleteFlag autocomplete_flag = AutocompleteFlag::kNone;
// True if field->form_control_type == "password".
bool is_password = false;
// True if field is predicted to be a password.
bool is_predicted_as_password = false;
// True if the server predicts that this field is not a password field.
bool server_hints_not_password = false;
// True if the server predicts that this field is not a username field.
bool server_hints_not_username = false;
Interactability interactability = Interactability::kUnlikely;
};
// Returns true if the |str| contains words related to CVC fields. // Returns true if the |str| contains words related to CVC fields.
bool StringMatchesCVC(const base::string16& str) { bool StringMatchesCVC(const base::string16& str) {
static const base::NoDestructor<base::string16> kCardCvcReCached( static const base::NoDestructor<base::string16> kCardCvcReCached(
...@@ -909,27 +863,6 @@ std::vector<ProcessedField> ProcessFields( ...@@ -909,27 +863,6 @@ std::vector<ProcessedField> ProcessFields(
return result; return result;
} }
// Find the first element in |username_predictions| (i.e. the most reliable
// prediction) that occurs in |processed_fields| and has interactability level
// at least |username_max|.
const FormFieldData* FindUsernameInPredictions(
const std::vector<autofill::FieldRendererId>& username_predictions,
const std::vector<ProcessedField>& processed_fields,
Interactability username_max) {
for (autofill::FieldRendererId predicted_id : username_predictions) {
auto iter = std::find_if(
processed_fields.begin(), processed_fields.end(),
[predicted_id, username_max](const ProcessedField& processed_field) {
return processed_field.field->unique_renderer_id == predicted_id &&
MatchesInteractability(processed_field, username_max);
});
if (iter != processed_fields.end()) {
return iter->field;
}
}
return nullptr;
}
// Return true if |significant_fields| has an username field and // Return true if |significant_fields| has an username field and
// |form_predictions| has |may_use_prefilled_placeholder| == true for the // |form_predictions| has |may_use_prefilled_placeholder| == true for the
// username field. // username field.
...@@ -1103,4 +1036,22 @@ std::string GetSignonRealm(const GURL& url) { ...@@ -1103,4 +1036,22 @@ std::string GetSignonRealm(const GURL& url) {
return url.ReplaceComponents(rep).spec(); return url.ReplaceComponents(rep).spec();
} }
const FormFieldData* FindUsernameInPredictions(
const std::vector<autofill::FieldRendererId>& username_predictions,
const std::vector<ProcessedField>& processed_fields,
Interactability username_max) {
for (autofill::FieldRendererId predicted_id : username_predictions) {
auto iter = std::find_if(
processed_fields.begin(), processed_fields.end(),
[predicted_id, username_max](const ProcessedField& processed_field) {
return processed_field.field->unique_renderer_id == predicted_id &&
MatchesInteractability(processed_field, username_max);
});
if (iter != processed_fields.end()) {
return iter->field;
}
}
return nullptr;
}
} // namespace password_manager } // namespace password_manager
...@@ -21,6 +21,52 @@ struct PasswordForm; ...@@ -21,6 +21,52 @@ struct PasswordForm;
namespace password_manager { namespace password_manager {
// The susbset of autocomplete flags related to passwords.
enum class AutocompleteFlag {
kNone,
kUsername,
kCurrentPassword,
kNewPassword,
// Represents the whole family of cc-* flags + OTP flag.
kNonPassword
};
// How likely is user interaction for a given field?
// Note: higher numeric values should match higher likeliness to allow using the
// standard operator< for comparison of likeliness.
enum class Interactability {
// When the field is invisible.
kUnlikely = 0,
// When the field is visible/focusable.
kPossible = 1,
// When the user actually typed into the field before.
kCertain = 2,
};
// A wrapper around FormFieldData, carrying some additional data used during
// parsing.
struct ProcessedField {
// This points to the wrapped FormFieldData.
const autofill::FormFieldData* field;
// The flag derived from field->autocomplete_attribute.
AutocompleteFlag autocomplete_flag = AutocompleteFlag::kNone;
// True if field->form_control_type == "password".
bool is_password = false;
// True if field is predicted to be a password.
bool is_predicted_as_password = false;
// True if the server predicts that this field is not a password field.
bool server_hints_not_password = false;
// True if the server predicts that this field is not a username field.
bool server_hints_not_username = false;
Interactability interactability = Interactability::kUnlikely;
};
// This class takes care of parsing FormData into PasswordForm and managing // This class takes care of parsing FormData into PasswordForm and managing
// related metadata. // related metadata.
class FormDataParser { class FormDataParser {
...@@ -104,6 +150,14 @@ class FormDataParser { ...@@ -104,6 +150,14 @@ class FormDataParser {
// origin |url|. // origin |url|.
std::string GetSignonRealm(const GURL& url); std::string GetSignonRealm(const GURL& url);
// Find the first element in |username_predictions| (i.e. the most reliable
// prediction) that occurs in |processed_fields| and has interactability level
// at least |username_max|.
const autofill::FormFieldData* FindUsernameInPredictions(
const std::vector<autofill::FieldRendererId>& username_predictions,
const std::vector<ProcessedField>& processed_fields,
Interactability username_max);
} // namespace password_manager } // namespace password_manager
#endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_FORM_PARSING_IOS_FORM_PARSER_H_ #endif // COMPONENTS_PASSWORD_MANAGER_CORE_BROWSER_FORM_PARSING_IOS_FORM_PARSER_H_
...@@ -2522,6 +2522,53 @@ TEST(FormParserTest, InvalidURL) { ...@@ -2522,6 +2522,53 @@ TEST(FormParserTest, InvalidURL) {
EXPECT_FALSE(parser.Parse(form_data, FormDataParser::Mode::kSaving)); EXPECT_FALSE(parser.Parse(form_data, FormDataParser::Mode::kSaving));
} }
TEST(FormParserTest, FindUsernameInPredictions_SkipPrediction) {
// Searching username field should skip prediction that is less
// likely to be user interaction. For example, if a field has no
// user input while others have, the field cannot be an username
// field.
// Create a form containing username, email, id, password, submit.
const FormParsingTestCase form_desc = {
.fields = {
{.name = "username", .form_control_type = "text"},
{.name = "email", .form_control_type = "text"},
{.name = "id", .form_control_type = "text"},
{.name = "password", .form_control_type = "password"},
{.name = "submit", .form_control_type = "submit"},
}};
FormPredictions no_predictions;
ParseResultIds dummy;
const FormData form_data =
GetFormDataAndExpectation(form_desc, &no_predictions, &dummy, &dummy);
// Add all form fields in ProcessedField. A user typed only into
// "id" and "password" fields. So, the prediction for "email" field
// should be ignored despite it is more reliable than prediction for
// "id" field.
std::vector<ProcessedField> processed_fields;
for (const auto& form_field_data : form_data.fields)
processed_fields.push_back(ProcessedField{.field = &form_field_data});
processed_fields[2].interactability = Interactability::kCertain; // id
processed_fields[3].interactability = Interactability::kCertain; // password
// Add predictions for "email" and "id" fields. The "email" is in
// front of "id", indicating "email" is more reliable.
const std::vector<autofill::FieldRendererId> predictions = {
form_data.fields[1].unique_renderer_id, // email
form_data.fields[2].unique_renderer_id, // id
};
// Now search the username field. The username field is supposed to
// be "id", not "email".
const autofill::FormFieldData* field_data = FindUsernameInPredictions(
predictions, processed_fields, Interactability::kCertain);
ASSERT_TRUE(field_data);
EXPECT_EQ(base::UTF8ToUTF16("id"), field_data->name);
}
} // namespace } // namespace
} // namespace password_manager } // namespace password_manager
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment