blob: 7ddf8e5d15a938c1ac714fa03dc61edc210d9a54 [file]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/autofill/content/browser/integrators/actor/autofill_annotations_provider_impl.h"
#include "base/containers/map_util.h"
#include "base/functional/function_ref.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/data_manager/personal_data_manager.h"
#include "components/autofill/core/browser/form_types.h"
#include "components/autofill/core/browser/foundations/autofill_client.h"
#include "components/autofill/core/browser/foundations/autofill_manager.h"
#include "components/optimization_guide/content/browser/page_content_proto_util.h"
#include "components/optimization_guide/proto/features/common_quality_data.pb.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
namespace optimization_guide {
using autofill::AutofillClient;
using autofill::AutofillField;
using autofill::AutofillManager;
using autofill::ContentAutofillDriver;
using autofill::DenseSet;
using autofill::FieldGlobalId;
using autofill::FieldRendererId;
using autofill::FieldType;
using autofill::FieldTypeGroup;
using autofill::FieldTypeSet;
using autofill::FormGlobalId;
using autofill::FormStructure;
using autofill::FormType;
using autofill::LocalFrameToken;
using autofill::PersonalDataManager;
using autofill::ValuablesDataManager;
namespace {
// Key for `base::SupportsUserData` aspects of `SectionMapping`.
const void* const kSectionMappingKey = &kSectionMappingKey;
std::string FormGlobalIdToString(FormGlobalId form_id) {
return base::StrCat({form_id.frame_token.ToString(), "_",
base::NumberToString(form_id.renderer_id.value())});
}
class SectionMapping : public base::SupportsUserData::Data {
public:
SectionMapping();
SectionMapping(const SectionMapping&) = delete;
SectionMapping& operator=(const SectionMapping&) = delete;
~SectionMapping() override;
static SectionMapping* GetInstance(
ConvertAIPageContentToProtoSession& session);
uint32_t GetOrCreateSectionIdentifier(const FormGlobalId& form_id,
const std::string& section);
private:
base::flat_map<std::string, uint32_t> autofill_section_numbers_;
};
SectionMapping::SectionMapping() = default;
SectionMapping::~SectionMapping() = default;
// static
SectionMapping* SectionMapping::GetInstance(
ConvertAIPageContentToProtoSession& session) {
SectionMapping* mapping =
static_cast<SectionMapping*>(session.GetUserData(kSectionMappingKey));
if (!mapping) {
auto new_mapping = std::make_unique<SectionMapping>();
mapping = new_mapping.get();
session.SetUserData(kSectionMappingKey, std::move(new_mapping));
}
return mapping;
}
uint32_t SectionMapping::GetOrCreateSectionIdentifier(
const FormGlobalId& form_id,
const std::string& section) {
// Because different forms can have the same section titles, we use
// (form_id, section) as the key for `section_numbers`.
const std::string section_id = FormGlobalIdToString(form_id) + section;
// Find the current section or create a new one.
auto iter = autofill_section_numbers_.find(section_id);
if (iter == autofill_section_numbers_.end()) {
iter = autofill_section_numbers_
.emplace(section_id, autofill_section_numbers_.size())
.first;
}
return iter->second;
}
const FormStructure* GetAutofillForm(
content::RenderFrameHost& render_frame_host,
const FieldGlobalId& field_global_id) {
content::WebContents& web_contents =
*content::WebContents::FromRenderFrameHost(&render_frame_host);
// Use the `ContentAutofillDriver` of the main frame because forms are
// flattened and propagated into the primary main frame `AutofillManager`.
ContentAutofillDriver* autofill_driver =
ContentAutofillDriver::GetForRenderFrameHost(
web_contents.GetPrimaryMainFrame());
if (!autofill_driver) {
return nullptr;
}
AutofillManager& autofill_manager = autofill_driver->GetAutofillManager();
return autofill_manager.FindCachedFormById(field_global_id);
}
// Returns the redaction reason for a given Autofill `field_type`. Note that
// some field types return 'no redaction needed' from this method only because a
// decision has not yet been made on them.
AutofillFieldRedactionReason GetRedactionReason(FieldType field_type) {
switch (field_type) {
// We should not redact cases where we have not identified the field type.
case autofill::NO_SERVER_DATA:
case autofill::UNKNOWN_TYPE:
return AutofillFieldRedactionReason::kNoRedactionNeeded;
// Names are not redacted.
case autofill::NAME_FIRST:
case autofill::NAME_MIDDLE:
case autofill::NAME_LAST:
case autofill::NAME_MIDDLE_INITIAL:
case autofill::NAME_FULL:
case autofill::NAME_SUFFIX:
case autofill::NAME_LAST_FIRST:
case autofill::NAME_LAST_CONJUNCTION:
case autofill::NAME_LAST_SECOND:
case autofill::NAME_HONORIFIC_PREFIX:
case autofill::ALTERNATIVE_FULL_NAME:
case autofill::ALTERNATIVE_GIVEN_NAME:
case autofill::ALTERNATIVE_FAMILY_NAME:
return AutofillFieldRedactionReason::kNoRedactionNeeded;
// Email address is not redacted.
case autofill::EMAIL_ADDRESS:
return AutofillFieldRedactionReason::kNoRedactionNeeded;
// Cardholder name is not redacted.
case autofill::CREDIT_CARD_NAME_FULL:
case autofill::CREDIT_CARD_NAME_FIRST:
case autofill::CREDIT_CARD_NAME_LAST:
return AutofillFieldRedactionReason::kNoRedactionNeeded;
// Other credit card data is redacted.
case autofill::CREDIT_CARD_NUMBER:
case autofill::CREDIT_CARD_EXP_MONTH:
case autofill::CREDIT_CARD_EXP_2_DIGIT_YEAR:
case autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR:
case autofill::CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR:
case autofill::CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR:
case autofill::CREDIT_CARD_TYPE:
case autofill::CREDIT_CARD_VERIFICATION_CODE:
case autofill::CREDIT_CARD_STANDALONE_VERIFICATION_CODE:
return AutofillFieldRedactionReason::kShouldRedactForPayments;
// Password fields have already been redacted on the renderer side.
case autofill::PASSWORD:
case autofill::ACCOUNT_CREATION_PASSWORD:
case autofill::NOT_ACCOUNT_CREATION_PASSWORD:
case autofill::USERNAME:
case autofill::USERNAME_AND_EMAIL_ADDRESS:
case autofill::NEW_PASSWORD:
case autofill::PROBABLY_NEW_PASSWORD:
case autofill::NOT_NEW_PASSWORD:
case autofill::CONFIRMATION_PASSWORD:
case autofill::SINGLE_USERNAME:
case autofill::SINGLE_USERNAME_FORGOT_PASSWORD:
case autofill::SINGLE_USERNAME_WITH_INTERMEDIATE_VALUES:
return AutofillFieldRedactionReason::kNoRedactionNeeded;
// The following fields have not yet been decided upon. By default they are
// considered non-sensitive to avoid over-redacting. Any item here may
// change from `AutofillFieldRedactionReason::kNoRedactionNeeded` to `true`
// in the future.
case autofill::ADDRESS_HOME_LINE1:
case autofill::ADDRESS_HOME_LINE2:
case autofill::ADDRESS_HOME_APT_NUM:
case autofill::ADDRESS_HOME_CITY:
case autofill::ADDRESS_HOME_STATE:
case autofill::ADDRESS_HOME_ZIP:
case autofill::ADDRESS_HOME_COUNTRY:
case autofill::ADDRESS_HOME_STREET_ADDRESS:
case autofill::ADDRESS_HOME_SORTING_CODE:
case autofill::ADDRESS_HOME_DEPENDENT_LOCALITY:
case autofill::ADDRESS_HOME_LINE3:
case autofill::ADDRESS_HOME_STREET_NAME:
case autofill::ADDRESS_HOME_HOUSE_NUMBER:
case autofill::ADDRESS_HOME_SUBPREMISE:
case autofill::ADDRESS_HOME_OTHER_SUBUNIT:
case autofill::ADDRESS_HOME_ADDRESS:
case autofill::ADDRESS_HOME_ADDRESS_WITH_NAME:
case autofill::ADDRESS_HOME_FLOOR:
case autofill::ADDRESS_HOME_OVERFLOW:
case autofill::ADDRESS_HOME_LANDMARK:
case autofill::ADDRESS_HOME_OVERFLOW_AND_LANDMARK:
case autofill::ADDRESS_HOME_ADMIN_LEVEL2:
case autofill::ADDRESS_HOME_STREET_LOCATION:
case autofill::ADDRESS_HOME_BETWEEN_STREETS:
case autofill::ADDRESS_HOME_BETWEEN_STREETS_OR_LANDMARK:
case autofill::ADDRESS_HOME_STREET_LOCATION_AND_LOCALITY:
case autofill::ADDRESS_HOME_STREET_LOCATION_AND_LANDMARK:
case autofill::ADDRESS_HOME_DEPENDENT_LOCALITY_AND_LANDMARK:
case autofill::ADDRESS_HOME_BETWEEN_STREETS_1:
case autofill::ADDRESS_HOME_BETWEEN_STREETS_2:
case autofill::ADDRESS_HOME_HOUSE_NUMBER_AND_APT:
case autofill::ADDRESS_HOME_APT:
case autofill::ADDRESS_HOME_APT_TYPE:
case autofill::ADDRESS_HOME_ZIP_AND_CITY:
case autofill::ADDRESS_HOME_ZIP_PREFIX:
case autofill::ADDRESS_HOME_ZIP_SUFFIX:
case autofill::PHONE_HOME_NUMBER:
case autofill::PHONE_HOME_CITY_CODE:
case autofill::PHONE_HOME_COUNTRY_CODE:
case autofill::PHONE_HOME_CITY_AND_NUMBER:
case autofill::PHONE_HOME_WHOLE_NUMBER:
case autofill::PHONE_HOME_EXTENSION:
case autofill::PHONE_HOME_CITY_CODE_WITH_TRUNK_PREFIX:
case autofill::PHONE_HOME_CITY_AND_NUMBER_WITHOUT_TRUNK_PREFIX:
case autofill::PHONE_HOME_NUMBER_PREFIX:
case autofill::PHONE_HOME_NUMBER_SUFFIX:
case autofill::COMPANY_NAME:
case autofill::MERCHANT_EMAIL_SIGNUP:
case autofill::MERCHANT_PROMO_CODE:
case autofill::AMBIGUOUS_TYPE:
case autofill::SEARCH_TERM:
case autofill::PRICE:
case autofill::NOT_PASSWORD:
case autofill::NOT_USERNAME:
case autofill::IBAN_VALUE:
case autofill::NUMERIC_QUANTITY:
case autofill::DELIVERY_INSTRUCTIONS:
case autofill::LOYALTY_MEMBERSHIP_ID:
case autofill::PASSPORT_NUMBER:
case autofill::PASSPORT_ISSUING_COUNTRY:
case autofill::PASSPORT_EXPIRATION_DATE:
case autofill::PASSPORT_ISSUE_DATE:
case autofill::LOYALTY_MEMBERSHIP_PROGRAM:
case autofill::LOYALTY_MEMBERSHIP_PROVIDER:
case autofill::VEHICLE_LICENSE_PLATE:
case autofill::VEHICLE_VIN:
case autofill::VEHICLE_MAKE:
case autofill::VEHICLE_MODEL:
case autofill::DRIVERS_LICENSE_REGION:
case autofill::DRIVERS_LICENSE_NUMBER:
case autofill::DRIVERS_LICENSE_EXPIRATION_DATE:
case autofill::DRIVERS_LICENSE_ISSUE_DATE:
case autofill::VEHICLE_YEAR:
case autofill::VEHICLE_PLATE_STATE:
case autofill::EMAIL_OR_LOYALTY_MEMBERSHIP_ID:
case autofill::NATIONAL_ID_CARD_NUMBER:
case autofill::NATIONAL_ID_CARD_EXPIRATION_DATE:
case autofill::NATIONAL_ID_CARD_ISSUE_DATE:
case autofill::NATIONAL_ID_CARD_ISSUING_COUNTRY:
case autofill::KNOWN_TRAVELER_NUMBER:
case autofill::KNOWN_TRAVELER_NUMBER_EXPIRATION_DATE:
case autofill::REDRESS_NUMBER:
case autofill::FLIGHT_RESERVATION_FLIGHT_NUMBER:
case autofill::FLIGHT_RESERVATION_CONFIRMATION_CODE:
case autofill::FLIGHT_RESERVATION_TICKET_NUMBER:
case autofill::FLIGHT_RESERVATION_DEPARTURE_DATE:
case autofill::ORDER_ID:
case autofill::ORDER_DATE:
case autofill::ORDER_MERCHANT_NAME:
case autofill::SHIPMENT_TRACKING_NUMBER:
return AutofillFieldRedactionReason::kNoRedactionNeeded;
// OTPs are sensitive short-lived secrets and should be redacted.
case autofill::ONE_TIME_CODE:
return AutofillFieldRedactionReason::kShouldRedactForOtp;
// These cases are not produced by field classification, but have to be
// handled so that the switch is complete.
case autofill::EMPTY_TYPE:
case autofill::MAX_VALID_FIELD_TYPE:
return AutofillFieldRedactionReason::kNoRedactionNeeded;
}
}
// Returns the AutofillFieldRedactionReason for a set of field types.
// Payment wins over OTP when both feature gates are enabled for the same field
// so mixed predictions resolve deterministically.
AutofillFieldRedactionReason GetRedactionReason(
const FieldTypeSet& field_types) {
bool should_redact_for_payments = false;
bool should_redact_for_otp = false;
for (const FieldType field_type : field_types) {
switch (GetRedactionReason(field_type)) {
case AutofillFieldRedactionReason::kNoRedactionNeeded:
break;
case AutofillFieldRedactionReason::kShouldRedactForPayments:
should_redact_for_payments = true;
break;
case AutofillFieldRedactionReason::kShouldRedactForOtp:
should_redact_for_otp = true;
break;
}
}
if (should_redact_for_payments &&
IsAutofillRedactionReasonEnabled(
AutofillFieldRedactionReason::kShouldRedactForPayments)) {
return AutofillFieldRedactionReason::kShouldRedactForPayments;
}
if (should_redact_for_otp &&
IsAutofillRedactionReasonEnabled(
AutofillFieldRedactionReason::kShouldRedactForOtp)) {
return AutofillFieldRedactionReason::kShouldRedactForOtp;
}
return AutofillFieldRedactionReason::kNoRedactionNeeded;
}
std::string CoarseFieldTypeToString(proto::CoarseAutofillFieldType type) {
switch (type) {
case proto::COARSE_AUTOFILL_FIELD_TYPE_UNSUPPORTED:
return "COARSE_AUTOFILL_FIELD_TYPE_UNSUPPORTED";
case proto::COARSE_AUTOFILL_FIELD_TYPE_ADDRESS:
return "COARSE_AUTOFILL_FIELD_TYPE_ADDRESS";
case proto::COARSE_AUTOFILL_FIELD_TYPE_CREDIT_CARD:
return "COARSE_AUTOFILL_FIELD_TYPE_CREDIT_CARD";
case proto::COARSE_AUTOFILL_FIELD_TYPE_OTP:
return "COARSE_AUTOFILL_FIELD_TYPE_OTP";
default:
// Covers future extensions to the proto - if you see this logged please
// add a case above!
return "Unknown CoarseAutofillFieldType value";
}
}
} // namespace
AutofillAnnotationsProviderImpl::~AutofillAnnotationsProviderImpl() = default;
std::optional<AutofillFieldMetadata>
AutofillAnnotationsProviderImpl::GetAutofillFieldData(
content::RenderFrameHost& render_frame_host,
int32_t dom_node_id,
ConvertAIPageContentToProtoSession& session) {
// Determine `AutofillField` from Autofill.
FieldGlobalId field_global_id = {
LocalFrameToken(render_frame_host.GetFrameToken().value()),
FieldRendererId(dom_node_id)};
const FormStructure* form =
GetAutofillForm(render_frame_host, field_global_id);
if (!form) {
// This vlog is very spammy, as we are called for every form control that
// APC encounters.
VLOG(3) << "GetAutofillFieldData - No form found for DOM node "
<< dom_node_id << " in render frame host for "
<< render_frame_host.GetLastCommittedOrigin();
return std::nullopt;
}
const AutofillField* field = form->GetFieldById(field_global_id);
if (!field) {
// This vlog is very spammy, as we are called for every form control that
// APC encounters.
VLOG(3) << "GetAutofillFieldData - No field found for DOM node "
<< dom_node_id << " in render frame host for "
<< render_frame_host.GetLastCommittedOrigin();
return std::nullopt;
}
AutofillFieldMetadata metadata;
metadata.section_id =
SectionMapping::GetInstance(session)->GetOrCreateSectionIdentifier(
form->global_id(), field->section().ToString());
const DenseSet<FormType>& form_types = field->Type().GetFormTypes();
const FieldTypeSet& field_types = field->Type().GetTypes();
metadata.coarse_field_type = [&] {
if (form_types.contains(FormType::kAddressForm)) {
return proto::COARSE_AUTOFILL_FIELD_TYPE_ADDRESS;
} else if (form_types.contains(FormType::kCreditCardForm) ||
form_types.contains(FormType::kStandaloneCvcForm)) {
return proto::COARSE_AUTOFILL_FIELD_TYPE_CREDIT_CARD;
} else if (form_types.contains(FormType::kOneTimePasswordForm)) {
return proto::COARSE_AUTOFILL_FIELD_TYPE_OTP;
}
return proto::COARSE_AUTOFILL_FIELD_TYPE_UNSUPPORTED;
}();
metadata.redaction_reason = GetRedactionReason(field_types);
VLOG(2) << "GetAutofillFieldData - DOM node " << dom_node_id << " ("
<< field->name() << ") in " << field->origin()
<< " has form_types: " << form_types
<< ", field_types: " << field_types << ", coarse_field_type: "
<< CoarseFieldTypeToString(metadata.coarse_field_type)
<< ", redaction_reason: " << metadata.redaction_reason;
return metadata;
}
AutofillAvailability AutofillAnnotationsProviderImpl::GetAutofillAvailability(
content::RenderFrameHost& render_frame_host) {
content::WebContents& web_contents =
*content::WebContents::FromRenderFrameHost(&render_frame_host);
ContentAutofillDriver* autofill_driver =
ContentAutofillDriver::GetForRenderFrameHost(
web_contents.GetPrimaryMainFrame());
if (!autofill_driver) {
VLOG(2) << "GetAutofillAvailability - no autofill driver for "
<< render_frame_host.GetLastCommittedOrigin();
return {};
}
AutofillClient& client = autofill_driver->GetAutofillClient();
if (!client.HasPersonalDataManager()) {
VLOG(2) << "GetAutofillAvailability - no personal data manager for "
<< render_frame_host.GetLastCommittedOrigin();
return {};
}
const PersonalDataManager& pdm = client.GetPersonalDataManager();
const bool address_autofill_enabled = client.IsAutofillProfileEnabled();
const bool has_address_profiles =
!pdm.address_data_manager().GetProfiles().empty();
const bool payment_autofill_enabled =
client.GetPaymentsAutofillClient()->IsAutofillPaymentMethodsEnabled();
const bool has_credit_cards =
!pdm.payments_data_manager().GetCreditCards().empty();
VLOG(2) << "GetAutofillAvailability - url: "
<< render_frame_host.GetLastCommittedOrigin()
<< ", address_autofill_enabled: " << address_autofill_enabled
<< ", has_address_profiles: " << has_address_profiles
<< ", payment_autofill_enabled: " << payment_autofill_enabled
<< ", has_credit_cards: " << has_credit_cards;
return AutofillAvailability{
.has_fillable_address = address_autofill_enabled && has_address_profiles,
.has_fillable_credit_card = payment_autofill_enabled && has_credit_cards,
};
}
} // namespace optimization_guide