summaryrefslogtreecommitdiff
path: root/chromium/components/autofill/core/browser/form_structure.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/components/autofill/core/browser/form_structure.cc')
-rw-r--r--chromium/components/autofill/core/browser/form_structure.cc324
1 files changed, 302 insertions, 22 deletions
diff --git a/chromium/components/autofill/core/browser/form_structure.cc b/chromium/components/autofill/core/browser/form_structure.cc
index 66c5757c8f8..f712a2d4983 100644
--- a/chromium/components/autofill/core/browser/form_structure.cc
+++ b/chromium/components/autofill/core/browser/form_structure.cc
@@ -43,6 +43,7 @@
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/form_field_data_predictions.h"
#include "components/autofill/core/common/signatures_util.h"
+#include "components/security_state/core/security_state.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
#include "url/origin.h"
@@ -307,26 +308,30 @@ bool AllTypesCaptured(const FormStructure& form,
return true;
}
-// Encode password attributes |vote| into |upload|.
+// Encode password attributes and length into |upload|.
void EncodePasswordAttributesVote(
- const std::pair<PasswordAttribute, bool>& vote,
+ const std::pair<PasswordAttribute, bool>& password_attributes_vote,
+ const size_t password_length_vote,
AutofillUploadContents* upload) {
- switch (vote.first) {
+ switch (password_attributes_vote.first) {
case PasswordAttribute::kHasLowercaseLetter:
- upload->set_password_has_lowercase_letter(vote.second);
+ upload->set_password_has_lowercase_letter(
+ password_attributes_vote.second);
break;
case PasswordAttribute::kHasUppercaseLetter:
- upload->set_password_has_uppercase_letter(vote.second);
+ upload->set_password_has_uppercase_letter(
+ password_attributes_vote.second);
break;
case PasswordAttribute::kHasNumeric:
- upload->set_password_has_numeric(vote.second);
+ upload->set_password_has_numeric(password_attributes_vote.second);
break;
case PasswordAttribute::kHasSpecialSymbol:
- upload->set_password_has_special_symbol(vote.second);
+ upload->set_password_has_special_symbol(password_attributes_vote.second);
break;
case PasswordAttribute::kPasswordAttributesCount:
NOTREACHED();
}
+ upload->set_password_length(password_length_vote);
}
} // namespace
@@ -376,7 +381,8 @@ FormStructure::FormStructure(const FormData& form)
FormStructure::~FormStructure() {}
-void FormStructure::DetermineHeuristicTypes(ukm::UkmRecorder* ukm_recorder) {
+void FormStructure::DetermineHeuristicTypes(ukm::UkmRecorder* ukm_recorder,
+ ukm::SourceId source_id) {
const auto determine_heuristic_types_start_time = base::TimeTicks::Now();
// First, try to detect field types based on each field's |autocomplete|
@@ -420,8 +426,9 @@ void FormStructure::DetermineHeuristicTypes(ukm::UkmRecorder* ukm_recorder) {
if (developer_engagement_metrics) {
AutofillMetrics::LogDeveloperEngagementUkm(
- ukm_recorder, main_frame_origin().GetURL(), IsCompleteCreditCardForm(),
- GetFormTypes(), developer_engagement_metrics, form_signature());
+ ukm_recorder, source_id, main_frame_origin().GetURL(),
+ IsCompleteCreditCardForm(), GetFormTypes(),
+ developer_engagement_metrics, form_signature());
}
if (base::FeatureList::IsEnabled(kAutofillRationalizeFieldTypePredictions))
@@ -446,8 +453,10 @@ bool FormStructure::EncodeUploadRequest(
upload->set_autofill_used(form_was_autofilled);
upload->set_data_present(EncodeFieldTypes(available_field_types));
upload->set_passwords_revealed(passwords_were_revealed_);
- if (password_attributes_vote_)
- EncodePasswordAttributesVote(*password_attributes_vote_, upload);
+ if (password_attributes_vote_) {
+ EncodePasswordAttributesVote(*password_attributes_vote_,
+ password_length_vote_, upload);
+ }
if (IsAutofillFieldMetadataEnabled()) {
upload->set_action_signature(StrToHash64Bit(target_url_.host()));
@@ -502,7 +511,8 @@ bool FormStructure::EncodeQueryRequest(
// static
void FormStructure::ParseQueryResponse(
std::string payload,
- const std::vector<FormStructure*>& forms) {
+ const std::vector<FormStructure*>& forms,
+ AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
AutofillMetrics::LogServerQueryMetric(
AutofillMetrics::QUERY_RESPONSE_RECEIVED);
@@ -558,6 +568,9 @@ void FormStructure::ParseQueryResponse(
if (heuristic_type != field->Type().GetStorableType())
query_response_overrode_heuristics = true;
+ if (current_field->has_password_requirements())
+ field->SetPasswordRequirements(current_field->password_requirements());
+
++current_field;
}
@@ -565,6 +578,10 @@ void FormStructure::ParseQueryResponse(
!query_response_has_no_server_data);
form->UpdateAutofillCount();
+ if (base::FeatureList::IsEnabled(
+ features::kAutofillRationalizeRepeatedServerPredictions))
+ form->RationalizeRepeatedFields(form_interactions_ukm_logger);
+
if (base::FeatureList::IsEnabled(kAutofillRationalizeFieldTypePredictions))
form->RationalizeFieldTypePredictions();
@@ -788,7 +805,8 @@ void FormStructure::LogQualityMetrics(
auto* const field = this->field(i);
if (IsUPIVirtualPaymentAddress(field->value)) {
AutofillMetrics::LogUserHappinessMetric(
- AutofillMetrics::USER_DID_ENTER_UPI_VPA, field->Type().group());
+ AutofillMetrics::USER_DID_ENTER_UPI_VPA, field->Type().group(),
+ security_state::SecurityLevel::SECURITY_LEVEL_COUNT);
}
form_interactions_ukm_logger->LogFieldFillStatus(*this, *field,
@@ -868,9 +886,7 @@ void FormStructure::LogQualityMetrics(
GetFormTypes(), did_autofill_some_possible_fields, elapsed);
}
}
- if (form_interactions_ukm_logger->url() != main_frame_origin().GetURL())
- form_interactions_ukm_logger->UpdateSourceURL(
- main_frame_origin().GetURL());
+
AutofillMetrics::LogAutofillFormSubmittedState(
state, is_for_credit_card, GetFormTypes(), form_parsed_timestamp_,
form_signature(), form_interactions_ukm_logger);
@@ -1103,6 +1119,10 @@ bool FormStructure::operator!=(const FormData& form) const {
return !operator==(form);
}
+FormStructure::SectionedFieldsIndexes::SectionedFieldsIndexes() {}
+
+FormStructure::SectionedFieldsIndexes::~SectionedFieldsIndexes() {}
+
void FormStructure::RationalizeCreditCardFieldPredictions() {
bool cc_first_name_found = false;
bool cc_last_name_found = false;
@@ -1259,6 +1279,270 @@ void FormStructure::RationalizePhoneNumbersInSection(std::string section) {
phone_rationalized_[section] = true;
}
+void FormStructure::ApplyRationalizationsToFieldAndLog(
+ size_t field_index,
+ ServerFieldType new_type,
+ AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
+ if (field_index >= fields_.size())
+ return;
+ auto old_type = fields_[field_index]->Type().GetStorableType();
+ fields_[field_index]->SetTypeTo(AutofillType(new_type));
+ if (form_interactions_ukm_logger) {
+ form_interactions_ukm_logger->LogRepeatedServerTypePredictionRationalized(
+ form_signature_, *fields_[field_index], old_type);
+ }
+}
+
+void FormStructure::RationalizeAddressLineFields(
+ SectionedFieldsIndexes& sections_of_address_indexes,
+ AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
+ // The rationalization happens within sections.
+ for (sections_of_address_indexes.Reset();
+ !sections_of_address_indexes.IsFinished();
+ sections_of_address_indexes.WalkForwardToTheNextSection()) {
+ auto current_section = sections_of_address_indexes.CurrentSection();
+
+ // The rationalization only applies to sections that have 2 or 3 visible
+ // street address predictions.
+ if (current_section.size() != 2 && current_section.size() != 3) {
+ continue;
+ }
+
+ int nb_address_rationalized = 0;
+ for (auto field_index : current_section) {
+ switch (nb_address_rationalized) {
+ case 0:
+ ApplyRationalizationsToFieldAndLog(field_index, ADDRESS_HOME_LINE1,
+ form_interactions_ukm_logger);
+ break;
+ case 1:
+ ApplyRationalizationsToFieldAndLog(field_index, ADDRESS_HOME_LINE2,
+ form_interactions_ukm_logger);
+ break;
+ case 2:
+ ApplyRationalizationsToFieldAndLog(field_index, ADDRESS_HOME_LINE3,
+ form_interactions_ukm_logger);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ ++nb_address_rationalized;
+ }
+ }
+}
+
+void FormStructure::ApplyRationalizationsToHiddenSelects(
+ size_t field_index,
+ ServerFieldType new_type,
+ AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
+ ServerFieldType old_type = fields_[field_index]->Type().GetStorableType();
+
+ // Walk on the hidden select fields right after the field_index which share
+ // the same type with the field_index, and apply the rationalization to them
+ // as well. These fields, if any, function as one field with the field_index.
+ for (auto current_index = field_index + 1; current_index < fields_.size();
+ current_index++) {
+ if (fields_[current_index]->IsVisible() ||
+ fields_[current_index]->form_control_type != "select-one" ||
+ fields_[current_index]->Type().GetStorableType() != old_type)
+ break;
+ ApplyRationalizationsToFieldAndLog(current_index, new_type,
+ form_interactions_ukm_logger);
+ }
+
+ // Same for the fields coming right before the field_index. (No need to check
+ // for the fields appearing before the first field!)
+ if (field_index == 0)
+ return;
+ for (auto current_index = field_index - 1;; current_index--) {
+ if (fields_[current_index]->IsVisible() ||
+ fields_[current_index]->form_control_type != "select-one" ||
+ fields_[current_index]->Type().GetStorableType() != old_type)
+ break;
+ ApplyRationalizationsToFieldAndLog(current_index, new_type,
+ form_interactions_ukm_logger);
+ if (current_index == 0)
+ break;
+ }
+}
+
+bool FormStructure::HeuristicsPredictionsAreApplicable(
+ size_t upper_index,
+ size_t lower_index,
+ ServerFieldType first_type,
+ ServerFieldType second_type) {
+ // The predictions are applicable if one field has one of the two types, and
+ // the other has the other type.
+ if (fields_[upper_index]->heuristic_type() ==
+ fields_[lower_index]->heuristic_type())
+ return false;
+ if ((fields_[upper_index]->heuristic_type() == first_type ||
+ fields_[upper_index]->heuristic_type() == second_type) &&
+ (fields_[lower_index]->heuristic_type() == first_type ||
+ fields_[lower_index]->heuristic_type() == second_type))
+ return true;
+ return false;
+}
+
+void FormStructure::ApplyRationalizationsToFields(
+ size_t upper_index,
+ size_t lower_index,
+ ServerFieldType upper_type,
+ ServerFieldType lower_type,
+ AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
+ // Hidden fields are ignored during the rationalization, but 'select' hidden
+ // fields also get autofilled to support their corresponding visible
+ // 'synthetic fields'. So, if a field's type is rationalized, we should make
+ // sure that the rationalization is also applied to its corresponding hidden
+ // fields, if any.
+ ApplyRationalizationsToHiddenSelects(upper_index, upper_type,
+ form_interactions_ukm_logger);
+ ApplyRationalizationsToFieldAndLog(upper_index, upper_type,
+ form_interactions_ukm_logger);
+
+ ApplyRationalizationsToHiddenSelects(lower_index, lower_type,
+ form_interactions_ukm_logger);
+ ApplyRationalizationsToFieldAndLog(lower_index, lower_type,
+ form_interactions_ukm_logger);
+}
+
+bool FormStructure::FieldShouldBeRationalizedToCountry(size_t upper_index) {
+ // Upper field is country if and only if it's the first visible address field
+ // in its section. Otherwise, the upper field is a state, and the lower one
+ // is a country.
+ for (int field_index = upper_index - 1; field_index >= 0; --field_index) {
+ if (fields_[field_index]->IsVisible() &&
+ AutofillType(fields_[field_index]->Type().GetStorableType()).group() ==
+ ADDRESS_HOME &&
+ fields_[field_index]->section == fields_[upper_index]->section) {
+ return false;
+ }
+ }
+ return true;
+}
+
+void FormStructure::RationalizeAddressStateCountry(
+ SectionedFieldsIndexes& sections_of_state_indexes,
+ SectionedFieldsIndexes& sections_of_country_indexes,
+ AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
+ // Walk on the sections of state and country indexes simultaneously. If they
+ // both point to the same section, it means that that section includes both
+ // the country and the state type. This means that no that rationalization is
+ // needed. So, walk both pointers forward. Otherwise, look at the section that
+ // appears earlier on the form. That section doesn't have any field of the
+ // other type. Rationalize the fields on the earlier section if needed. Walk
+ // the pointer that points to the earlier section forward. Stop when both
+ // sections of indexes are processed. (This resembles the merge in the merge
+ // sort.)
+ sections_of_state_indexes.Reset();
+ sections_of_country_indexes.Reset();
+
+ while (!sections_of_state_indexes.IsFinished() ||
+ !sections_of_country_indexes.IsFinished()) {
+ auto current_section_of_state_indexes =
+ sections_of_state_indexes.CurrentSection();
+ auto current_section_of_country_indexes =
+ sections_of_country_indexes.CurrentSection();
+ // If there are still sections left with both country and state type, and
+ // state and country current sections are equal, then that section has both
+ // state and country. No rationalization needed.
+ if (!sections_of_state_indexes.IsFinished() &&
+ !sections_of_country_indexes.IsFinished() &&
+ fields_[sections_of_state_indexes.CurrentIndex()]->section ==
+ fields_[sections_of_country_indexes.CurrentIndex()]->section) {
+ sections_of_state_indexes.WalkForwardToTheNextSection();
+ sections_of_country_indexes.WalkForwardToTheNextSection();
+ continue;
+ }
+
+ size_t upper_index = 0, lower_index = 0;
+
+ // If country section is before the state ones, it means that that section
+ // misses states, and the other way around.
+ if (current_section_of_state_indexes < current_section_of_country_indexes) {
+ // We only rationalize when we have exactly two visible fields of a kind.
+ if (current_section_of_state_indexes.size() == 2) {
+ upper_index = current_section_of_state_indexes[0];
+ lower_index = current_section_of_state_indexes[1];
+ }
+ sections_of_state_indexes.WalkForwardToTheNextSection();
+ } else {
+ // We only rationalize when we have exactly two visible fields of a kind.
+ if (current_section_of_country_indexes.size() == 2) {
+ upper_index = current_section_of_country_indexes[0];
+ lower_index = current_section_of_country_indexes[1];
+ }
+ sections_of_country_indexes.WalkForwardToTheNextSection();
+ }
+
+ // This is when upper and lower indexes are not changed, meaning that there
+ // is no need for rationalization.
+ if (upper_index == lower_index) {
+ continue;
+ }
+
+ if (HeuristicsPredictionsAreApplicable(upper_index, lower_index,
+ ADDRESS_HOME_STATE,
+ ADDRESS_HOME_COUNTRY)) {
+ ApplyRationalizationsToFields(
+ upper_index, lower_index, fields_[upper_index]->heuristic_type(),
+ fields_[lower_index]->heuristic_type(), form_interactions_ukm_logger);
+ continue;
+ }
+
+ if (FieldShouldBeRationalizedToCountry(upper_index)) {
+ ApplyRationalizationsToFields(upper_index, lower_index,
+ ADDRESS_HOME_COUNTRY, ADDRESS_HOME_STATE,
+ form_interactions_ukm_logger);
+ } else {
+ ApplyRationalizationsToFields(upper_index, lower_index,
+ ADDRESS_HOME_STATE, ADDRESS_HOME_COUNTRY,
+ form_interactions_ukm_logger);
+ }
+ }
+}
+
+void FormStructure::RationalizeRepeatedFields(
+ AutofillMetrics::FormInteractionsUkmLogger* form_interactions_ukm_logger) {
+ // The type of every field whose index is in
+ // sectioned_field_indexes_by_type[|type|] is predicted by server as |type|.
+ // Example: sectioned_field_indexes_by_type[FULL_NAME] is a sectioned fields
+ // indexes of fields whose types are predicted as FULL_NAME by the server.
+ SectionedFieldsIndexes sectioned_field_indexes_by_type[MAX_VALID_FIELD_TYPE];
+
+ for (const auto& field : fields_) {
+ // The hidden fields are not considered when rationalizing.
+ if (!field->IsVisible())
+ continue;
+ // The billing and non-billing types are aggregated.
+ auto current_type = field->Type().GetStorableType();
+
+ if (current_type != UNKNOWN_TYPE && current_type < MAX_VALID_FIELD_TYPE) {
+ // Look at the sectioned field indexes for the current type, if the
+ // current field belongs to that section, then the field index should be
+ // added to that same section, otherwise, start a new section.
+ sectioned_field_indexes_by_type[current_type].AddFieldIndex(
+ &field - &fields_[0],
+ /*is_new_section*/ sectioned_field_indexes_by_type[current_type]
+ .Empty() ||
+ fields_[sectioned_field_indexes_by_type[current_type]
+ .LastFieldIndex()]
+ ->section != field->section);
+ }
+ }
+
+ RationalizeAddressLineFields(
+ sectioned_field_indexes_by_type[ADDRESS_HOME_STREET_ADDRESS],
+ form_interactions_ukm_logger);
+ // Since the billing types are mapped to the non-billing ones, no need to
+ // take care of ADDRESS_BILLING_STATE and .. .
+ RationalizeAddressStateCountry(
+ sectioned_field_indexes_by_type[ADDRESS_HOME_STATE],
+ sectioned_field_indexes_by_type[ADDRESS_HOME_COUNTRY],
+ form_interactions_ukm_logger);
+}
+
void FormStructure::RationalizeFieldTypePredictions() {
RationalizeCreditCardFieldPredictions();
for (const auto& field : fields_) {
@@ -1379,18 +1663,14 @@ void FormStructure::IdentifySections(bool has_author_specified_sections) {
field->section = "credit-card";
continue;
}
-
bool already_saw_current_type = seen_types.count(current_type) > 0;
-
// Forms often ask for multiple phone numbers -- e.g. both a daytime and
// evening phone number. Our phone number detection is also generally a
// little off. Hence, ignore this field type as a signal here.
if (AutofillType(current_type).group() == PHONE_HOME)
already_saw_current_type = false;
- bool ignored_field =
- !field->is_focusable ||
- field->role == FormFieldData::ROLE_ATTRIBUTE_PRESENTATION;
+ bool ignored_field = !field->IsVisible();
// This is the first visible field after a hidden section. Consider it as
// the continuation of the last visible section.