diff options
Diffstat (limited to 'chromium/components/autofill/core/browser/form_structure.cc')
-rw-r--r-- | chromium/components/autofill/core/browser/form_structure.cc | 324 |
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. |