[go: nahoru, domu]

blob: 35c2c4e19815e9035d5d7711830cda6d4fe18d23 [file] [log] [blame]
// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stddef.h>
#include <memory>
#include <optional>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/base64.h"
#include "base/command_line.h"
#include "base/containers/span.h"
#include "base/feature_list.h"
#include "base/hash/hash.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/metrics_hashes.h"
#include "base/ranges/algorithm.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "components/autofill/core/browser/autofill_compose_delegate.h"
#include "components/autofill/core/browser/autofill_experiments.h"
#include "components/autofill/core/browser/autofill_form_test_utils.h"
#include "components/autofill/core/browser/autofill_plus_address_delegate.h"
#include "components/autofill/core/browser/autofill_suggestion_generator.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/browser_autofill_manager.h"
#include "components/autofill/core/browser/browser_autofill_manager_test_api.h"
#include "components/autofill/core/browser/crowdsourcing/mock_autofill_crowdsourcing_manager.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/credit_card.h"
#include "components/autofill/core/browser/filling_product.h"
#include "components/autofill/core/browser/form_data_importer_test_api.h"
#include "components/autofill/core/browser/form_structure_test_api.h"
#include "components/autofill/core/browser/geo/alternative_state_name_map_test_utils.h"
#include "components/autofill/core/browser/metrics/form_events/form_events.h"
#include "components/autofill/core/browser/metrics/log_event.h"
#include "components/autofill/core/browser/mock_autofill_compose_delegate.h"
#include "components/autofill/core/browser/mock_autofill_optimization_guide.h"
#include "components/autofill/core/browser/mock_autofill_plus_address_delegate.h"
#include "components/autofill/core/browser/mock_single_field_form_fill_router.h"
#include "components/autofill/core/browser/payments/test_credit_card_save_manager.h"
#include "components/autofill/core/browser/payments/test_payments_network_interface.h"
#include "components/autofill/core/browser/personal_data_manager.h"
#include "components/autofill/core/browser/profile_token_quality.h"
#include "components/autofill/core/browser/strike_databases/payments/test_credit_card_save_strike_database.h"
#include "components/autofill/core/browser/test_autofill_client.h"
#include "components/autofill/core/browser/test_autofill_clock.h"
#include "components/autofill/core/browser/test_autofill_driver.h"
#include "components/autofill/core/browser/test_autofill_external_delegate.h"
#include "components/autofill/core/browser/test_autofill_manager_waiter.h"
#include "components/autofill/core/browser/test_browser_autofill_manager.h"
#include "components/autofill/core/browser/test_form_data_importer.h"
#include "components/autofill/core/browser/test_personal_data_manager.h"
#include "components/autofill/core/browser/test_utils/vote_uploads_test_matchers.h"
#include "components/autofill/core/browser/ui/payments/bubble_show_options.h"
#include "components/autofill/core/browser/ui/popup_item_ids.h"
#include "components/autofill/core/browser/ui/suggestion.h"
#include "components/autofill/core/browser/ui/suggestion_test_helpers.h"
#include "components/autofill/core/browser/validation.h"
#include "components/autofill/core/common/autocomplete_parsing_util.h"
#include "components/autofill/core/common/autofill_clock.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/autofill/core/common/autofill_switches.h"
#include "components/autofill/core/common/autofill_test_utils.h"
#include "components/autofill/core/common/autofill_util.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_field_data.h"
#include "components/autofill/core/common/mojom/autofill_types.mojom-shared.h"
#include "components/autofill/core/common/signatures.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/prefs/pref_service.h"
#include "components/security_interstitials/core/pref_names.h"
#include "components/security_state/core/security_state.h"
#include "components/strings/grit/components_strings.h"
#include "components/sync/test/test_sync_service.h"
#include "components/translate/core/common/language_detection_details.h"
#include "components/variations/variations_associated_data.h"
#include "components/version_info/channel.h"
#include "net/base/url_util.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gfx/geometry/rect.h"
#include "url/gurl.h"
#include "url/url_canon.h"
namespace autofill {
namespace {
using ::base::UTF8ToUTF16;
using mojom::SubmissionIndicatorEvent;
using mojom::SubmissionSource;
using test::CreateTestAddressFormData;
using test::CreateTestFormField;
using test::CreateTestIbanFormData;
using test::CreateTestPersonalInformationFormData;
using test::CreateTestSelectField;
using test::CreateTestSelectOrSelectListField;
using ::testing::_;
using ::testing::AllOf;
using ::testing::AnyNumber;
using ::testing::AnyOf;
using ::testing::AtLeast;
using ::testing::Contains;
using ::testing::DoAll;
using ::testing::Each;
using ::testing::ElementsAre;
using ::testing::Eq;
using ::testing::Field;
using ::testing::HasSubstr;
using ::testing::InSequence;
using ::testing::Matcher;
using ::testing::MockFunction;
using ::testing::NiceMock;
using ::testing::Not;
using ::testing::Property;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::UnorderedElementsAre;
using ::testing::VariantWith;
using upload_contents_matchers::FieldAutofillTypeIs;
using upload_contents_matchers::FieldsAre;
using upload_contents_matchers::FormSignatureIs;
using upload_contents_matchers::ObservedSubmissionIs;
const std::string kArbitraryNickname = "Grocery Card";
const std::u16string kArbitraryNickname16 = u"Grocery Card";
Suggestion::Icon kAddressEntryIcon = Suggestion::Icon::kAccount;
bool ShouldSplitCardNameAndLastFourDigitsForMetadata() {
// Splitting card name and last four logic does not apply to iOS because the
// AutofillSuggestionGenerator on iOS doesn't currently support it.
#if BUILDFLAG(IS_IOS)
return false;
#else
return base::FeatureList::IsEnabled(
features::kAutofillEnableVirtualCardMetadata) &&
base::FeatureList::IsEnabled(
features::kAutofillEnableCardProductName) &&
base::FeatureList::IsEnabled(features::kAutofillEnableCardArtImage);
#endif
}
// The number of obfuscation dots we use as a prefix when showing a credit
// card's last four.
int ObfuscationLengthForCreditCardLastFourDigits() {
#if BUILDFLAG(IS_ANDROID)
return 2;
#elif BUILDFLAG(IS_IOS)
return base::FeatureList::IsEnabled(
features::kAutofillUseTwoDotsForLastFourDigits)
? 2
: 4;
#else
return 4;
#endif
}
// Generates credit card suggestion labels. If metadata is enabled, we produce
// shortened labels regardless of whether there is card metadata or not.
std::vector<std::vector<Suggestion::Text>> GenerateLabelsFromCreditCard(
CreditCard& card) {
std::vector<std::vector<Suggestion::Text>> suggestion_labels;
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
// Android and iOS do not adhere to card label splitting.
suggestion_labels = {
{Suggestion::Text(card.ObfuscatedNumberWithVisibleLastFourDigits(
ObfuscationLengthForCreditCardLastFourDigits()))}};
#else
if (ShouldSplitCardNameAndLastFourDigitsForMetadata()) {
// First label contains card name details and second label contains
// obfuscated last four.
suggestion_labels = {
{Suggestion::Text(card.CardNameForAutofillDisplay(),
Suggestion::Text::IsPrimary(false),
Suggestion::Text::ShouldTruncate(true)),
Suggestion::Text(card.ObfuscatedNumberWithVisibleLastFourDigits(
ObfuscationLengthForCreditCardLastFourDigits()))}};
} else {
// Desktop uses the descriptive label.
suggestion_labels = {
{Suggestion::Text(card.CardIdentifierStringAndDescriptiveExpiration(
/*app_locale=*/"en-US"))}};
}
#endif
return suggestion_labels;
}
Suggestion GenerateSuggestionFromCardDetails(
const std::string& network,
const Suggestion::Icon icon,
const std::string& last_four,
std::string expiration_date_label,
const std::string& nickname = std::string()) {
const std::string& network_or_nickname =
nickname.empty()
? base::UTF16ToUTF8(CreditCard::NetworkForDisplay(network))
: nickname;
if (ShouldSplitCardNameAndLastFourDigitsForMetadata()) {
return Suggestion(
network_or_nickname,
test::ObfuscatedCardDigitsAsUTF8(
last_four, ObfuscationLengthForCreditCardLastFourDigits()),
expiration_date_label, icon, PopupItemId::kCreditCardEntry);
} else {
#if !BUILDFLAG(IS_ANDROID) && !BUILDFLAG(IS_IOS)
// We use a longer label on desktop platforms.
expiration_date_label = std::string("Expires on ") + expiration_date_label;
#endif
return Suggestion(
base::StrCat(
{network_or_nickname, std::string(" "),
test::ObfuscatedCardDigitsAsUTF8(
last_four, ObfuscationLengthForCreditCardLastFourDigits())}),
expiration_date_label, icon, PopupItemId::kCreditCardEntry);
}
}
Suggestion GetCardSuggestion(const std::string& network,
const std::string& nickname = std::string()) {
Suggestion::Icon icon = Suggestion::Icon::kCardGeneric;
std::string last_four;
std::string expiration_date;
if (network == kVisaCard) {
icon = Suggestion::Icon::kCardVisa;
last_four = "3456";
expiration_date = "04/99";
} else if (network == kMasterCard) {
icon = Suggestion::Icon::kCardMasterCard;
last_four = "8765";
expiration_date = "10/98";
} else if (network == kAmericanExpressCard) {
icon = Suggestion::Icon::kCardAmericanExpress;
last_four = "0005";
expiration_date = "04/10";
} else {
NOTREACHED_NORETURN();
}
return GenerateSuggestionFromCardDetails(network, icon, last_four,
expiration_date, nickname);
}
struct TestAddressFillData {
TestAddressFillData(const char* first,
const char* middle,
const char* last,
const char* address1,
const char* address2,
const char* city,
const char* state,
const char* postal_code,
const char* country,
const char* country_short,
const char* phone,
const char* email,
const char* company)
: first(first),
middle(middle),
last(last),
address1(address1),
address2(address2),
city(city),
state(state),
postal_code(postal_code),
country(country),
country_short(country_short),
phone(phone),
email(email),
company(company) {}
const char* first;
const char* middle;
const char* last;
const char* address1;
const char* address2;
const char* city;
const char* state;
const char* postal_code;
const char* country;
const char* country_short;
const char* phone;
const char* email;
const char* company;
};
struct TestCardFillData {
TestCardFillData(const char* name_on_card,
const char* card_number,
const char* expiration_month,
const char* expiration_year,
bool use_month_type)
: name_on_card(name_on_card),
card_number(card_number),
expiration_month(expiration_month),
expiration_year(expiration_year),
use_month_type(use_month_type) {}
const char* name_on_card;
const char* card_number;
const char* expiration_month;
const char* expiration_year;
bool use_month_type;
};
const TestAddressFillData
kEmptyAddressFillData("", "", "", "", "", "", "", "", "", "", "", "", "");
const TestCardFillData kEmptyCardFillData("",
"",
"",
"",
/*use_month_type=*/false);
const TestCardFillData kElvisCardFillData("Elvis Presley",
"4234567890123456",
"04",
"2999",
/*use_month_type*/ false);
TestAddressFillData GetElvisAddressFillData() {
return {
"Elvis",
"Aaron",
"Presley",
"3734 Elvis Presley Blvd.",
"Apt. 10",
"Memphis",
"Tennessee",
"38116",
"United States",
"US",
base::FeatureList::IsEnabled(features::kAutofillDefaultToCityAndNumber)
? "2345678901"
: "12345678901",
"theking@gmail.com",
"RCA"};
}
// Matches a FillFieldLogEvent by equality of fields. Use FillEventId(-1) if
// you want to ignore the fill_event_id.
auto EqualsFillFieldLogEvent(const FillFieldLogEvent& expected) {
return AllOf(
testing::Conditional(
expected.fill_event_id == FillEventId(-1), _,
Field("fill_event_id", &FillFieldLogEvent::fill_event_id,
expected.fill_event_id)),
Field("had_value_before_filling",
&FillFieldLogEvent::had_value_before_filling,
expected.had_value_before_filling),
Field("autofill_skipped_status",
&FillFieldLogEvent::autofill_skipped_status,
expected.autofill_skipped_status),
Field("was_autofilled_before_security_policy",
&FillFieldLogEvent::was_autofilled_before_security_policy,
expected.was_autofilled_before_security_policy),
Field("had_value_after_filling",
&FillFieldLogEvent::had_value_after_filling,
expected.had_value_after_filling),
Field("filling_method", &FillFieldLogEvent::filling_method,
expected.filling_method),
Field("filling_prevented_by_iframe_security_policy",
&FillFieldLogEvent::filling_prevented_by_iframe_security_policy,
expected.filling_prevented_by_iframe_security_policy));
}
// Creates a GUID for testing. For example,
// MakeGuid(123) = "00000000-0000-0000-0000-000000000123";
std::string MakeGuid(size_t last_digit) {
return base::StringPrintf("00000000-0000-0000-0000-%012zu", last_digit);
}
std::string kElvisProfileGuid = MakeGuid(1);
class MockCreditCardAccessManager : public CreditCardAccessManager {
public:
MockCreditCardAccessManager(AutofillDriver* driver,
AutofillClient* client,
PersonalDataManager* personal_data_manager,
autofill_metrics::CreditCardFormEventLogger*
credit_card_form_event_logger)
: CreditCardAccessManager(driver,
client,
personal_data_manager,
credit_card_form_event_logger) {}
MOCK_METHOD(void, PrepareToFetchCreditCard, (), (override));
};
class MockAutofillClient : public TestAutofillClient {
public:
MockAutofillClient() {
ON_CALL(*this, GetChannel())
.WillByDefault(Return(version_info::Channel::UNKNOWN));
ON_CALL(*this, IsPasswordManagerEnabled()).WillByDefault(Return(true));
}
MockAutofillClient(const MockAutofillClient&) = delete;
MockAutofillClient& operator=(const MockAutofillClient&) = delete;
~MockAutofillClient() override = default;
MOCK_METHOD(version_info::Channel, GetChannel, (), (const override));
MOCK_METHOD(MockAutofillOptimizationGuide*,
GetAutofillOptimizationGuide,
(),
(const override));
MOCK_METHOD(profile_metrics::BrowserProfileType,
GetProfileType,
(),
(const override));
MOCK_METHOD(void, HideAutofillPopup, (PopupHidingReason reason), (override));
MOCK_METHOD(bool, IsPasswordManagerEnabled, (), (override));
MOCK_METHOD(void,
DidFillOrPreviewForm,
(mojom::ActionPersistence action_persistence,
AutofillTriggerSource trigger_source,
bool is_refill),
(override));
MOCK_METHOD(bool, HasCreditCardScanFeature, (), (const override));
MOCK_METHOD(void,
TriggerUserPerceptionOfAutofillSurvey,
((const std::map<std::string, std::string>&)),
(override));
MOCK_METHOD(AutofillComposeDelegate*, GetComposeDelegate, (), (override));
MOCK_METHOD(void,
OnVirtualCardDataAvailable,
(const VirtualCardManualFallbackBubbleOptions&),
(override));
};
class MockTouchToFillDelegate : public TouchToFillDelegate {
public:
MockTouchToFillDelegate() = default;
MockTouchToFillDelegate(const MockTouchToFillDelegate&) = delete;
MockTouchToFillDelegate& operator=(const MockTouchToFillDelegate&) = delete;
~MockTouchToFillDelegate() override = default;
MOCK_METHOD(BrowserAutofillManager*, GetManager, (), (override));
MOCK_METHOD(bool,
IntendsToShowTouchToFill,
(FormGlobalId, FieldGlobalId, const FormData&),
(override));
MOCK_METHOD(bool,
TryToShowTouchToFill,
(const FormData&, const FormFieldData&),
(override));
MOCK_METHOD(bool, IsShowingTouchToFill, (), (override));
MOCK_METHOD(void, HideTouchToFill, (), (override));
MOCK_METHOD(void, Reset, (), (override));
MOCK_METHOD(bool, ShouldShowScanCreditCard, (), (override));
MOCK_METHOD(void, ScanCreditCard, (), (override));
MOCK_METHOD(void, OnCreditCardScanned, (const CreditCard& card), (override));
MOCK_METHOD(void, ShowCreditCardSettings, (), (override));
MOCK_METHOD(void,
SuggestionSelected,
(std::string unique_id, bool is_virtual),
(override));
MOCK_METHOD(void, OnDismissed, (bool dismissed_by_user), (override));
MOCK_METHOD(void,
LogMetricsAfterSubmission,
(const FormStructure&),
(override));
};
class MockFastCheckoutDelegate : public FastCheckoutDelegate {
public:
MockFastCheckoutDelegate() = default;
MockFastCheckoutDelegate(const MockFastCheckoutDelegate&) = delete;
MockFastCheckoutDelegate& operator=(const MockFastCheckoutDelegate&) = delete;
~MockFastCheckoutDelegate() override = default;
MOCK_METHOD(bool,
TryToShowFastCheckout,
(const FormData&,
const FormFieldData&,
base::WeakPtr<AutofillManager>),
(override));
MOCK_METHOD(bool,
IntendsToShowFastCheckout,
(AutofillManager&, FormGlobalId, FieldGlobalId, const FormData&),
(const, override));
MOCK_METHOD(bool, IsShowingFastCheckoutUI, (), (const, override));
MOCK_METHOD(void, HideFastCheckout, (bool), (override));
};
AutofillProfile FillDataToAutofillProfile(const TestAddressFillData& data) {
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile, data.first, data.middle, data.last, data.email,
data.company, data.address1, data.address2, data.city,
data.state, data.postal_code, data.country_short,
data.phone);
return profile;
}
void ExpectFilledField(const char* expected_label,
const char* expected_name,
const char* expected_value,
FormControlType expected_form_control_type,
const FormFieldData& field) {
SCOPED_TRACE(expected_label);
EXPECT_EQ(UTF8ToUTF16(expected_label), field.label);
EXPECT_EQ(UTF8ToUTF16(expected_name), field.name);
EXPECT_EQ(UTF8ToUTF16(expected_value), field.value);
EXPECT_EQ(expected_form_control_type, field.form_control_type);
}
// Verifies that the |filled_form| has been filled with the given data.
// Verifies address fields if |has_address_fields| is true, and verifies
// credit card fields if |has_credit_card_fields| is true. Verifies both if both
// are true. |use_month_type| is used for credit card input month type.
void ExpectFilledForm(
const FormData& filled_form,
const std::optional<TestAddressFillData>& address_fill_data,
const std::optional<TestCardFillData>& card_fill_data) {
// The number of fields in the address and credit card forms created above.
const size_t kAddressFormSize = 11;
const size_t kCreditCardFormSizeMonthType = 4;
const size_t kCreditCardFormSizeNotMonthType = 5;
EXPECT_EQ(u"MyForm", filled_form.name);
EXPECT_EQ(GURL("https://myform.com/form.html"), filled_form.url);
EXPECT_EQ(GURL("https://myform.com/submit.html"), filled_form.action);
size_t form_size = 0;
if (address_fill_data) {
form_size += kAddressFormSize;
}
if (card_fill_data) {
form_size += card_fill_data->use_month_type
? kCreditCardFormSizeMonthType
: kCreditCardFormSizeNotMonthType;
}
ASSERT_EQ(form_size, filled_form.fields.size());
if (address_fill_data) {
ExpectFilledField("First Name", "firstname", address_fill_data->first,
FormControlType::kInputText, filled_form.fields[0]);
ExpectFilledField("Middle Name", "middlename", address_fill_data->middle,
FormControlType::kInputText, filled_form.fields[1]);
ExpectFilledField("Last Name", "lastname", address_fill_data->last,
FormControlType::kInputText, filled_form.fields[2]);
ExpectFilledField("Address Line 1", "addr1", address_fill_data->address1,
FormControlType::kInputText, filled_form.fields[3]);
ExpectFilledField("Address Line 2", "addr2", address_fill_data->address2,
FormControlType::kInputText, filled_form.fields[4]);
ExpectFilledField("City", "city", address_fill_data->city,
FormControlType::kInputText, filled_form.fields[5]);
ExpectFilledField("State", "state", address_fill_data->state,
FormControlType::kInputText, filled_form.fields[6]);
ExpectFilledField("Postal Code", "zipcode", address_fill_data->postal_code,
FormControlType::kInputText, filled_form.fields[7]);
ExpectFilledField("Country", "country", address_fill_data->country,
FormControlType::kInputText, filled_form.fields[8]);
ExpectFilledField("Phone Number", "phonenumber", address_fill_data->phone,
FormControlType::kInputTelephone, filled_form.fields[9]);
ExpectFilledField("Email", "email", address_fill_data->email,
FormControlType::kInputEmail, filled_form.fields[10]);
}
if (card_fill_data) {
size_t offset = address_fill_data ? kAddressFormSize : 0;
ExpectFilledField("Name on Card", "nameoncard",
card_fill_data->name_on_card, FormControlType::kInputText,
filled_form.fields[offset + 0]);
ExpectFilledField("Card Number", "cardnumber", card_fill_data->card_number,
FormControlType::kInputText,
filled_form.fields[offset + 1]);
if (card_fill_data->use_month_type) {
std::string exp_year = card_fill_data->expiration_year;
std::string exp_month = card_fill_data->expiration_month;
std::string date;
if (!exp_year.empty() && !exp_month.empty())
date = exp_year + "-" + exp_month;
ExpectFilledField("Expiration Date", "ccmonth", date.c_str(),
FormControlType::kInputMonth,
filled_form.fields[offset + 2]);
} else {
ExpectFilledField(
"Expiration Date", "ccmonth", card_fill_data->expiration_month,
FormControlType::kInputText, filled_form.fields[offset + 2]);
ExpectFilledField("", "ccyear", card_fill_data->expiration_year,
FormControlType::kInputText,
filled_form.fields[offset + 3]);
}
}
}
void ExpectFilledAddressFormElvis(const FormData& filled_form,
bool has_credit_card_fields) {
std::optional<TestCardFillData> expected_card_fill_data;
if (has_credit_card_fields) {
expected_card_fill_data = kEmptyCardFillData;
}
ExpectFilledForm(filled_form, GetElvisAddressFillData(),
expected_card_fill_data);
}
void ExpectFilledCreditCardFormElvis(const FormData& filled_form,
bool has_address_fields) {
std::optional<TestAddressFillData> expected_address_fill_data;
if (has_address_fields) {
expected_address_fill_data = kEmptyAddressFillData;
}
ExpectFilledForm(filled_form, expected_address_fill_data, kElvisCardFillData);
}
// Returns a matcher that checks a `FormStructure`'s renderer id.
auto FormStructureHasRendererId(FormRendererId form_renderer_id) {
return Pointee(Property(&FormStructure::global_id,
Field(&FormGlobalId::renderer_id, form_renderer_id)));
}
class MockAutofillDriver : public TestAutofillDriver {
public:
MockAutofillDriver() = default;
MockAutofillDriver(const MockAutofillDriver&) = delete;
MockAutofillDriver& operator=(const MockAutofillDriver&) = delete;
// Mock methods to enable testability.
MOCK_METHOD((base::flat_set<FieldGlobalId>),
ApplyFormAction,
(mojom::ActionType action_type,
mojom::ActionPersistence action_persistence,
const FormData& data,
const url::Origin& triggered_origin,
(const base::flat_map<FieldGlobalId, FieldType>&)),
(override));
MOCK_METHOD(void,
ApplyFieldAction,
(mojom::ActionPersistence action_persistence,
mojom::TextReplacement text_replacement,
const FieldGlobalId& field_id,
const std::u16string& value),
(override));
MOCK_METHOD(
void,
SendAutofillTypePredictionsToRenderer,
(const std::vector<vector_experimental_raw_ptr<FormStructure>>& forms),
(override));
};
} // namespace
class BrowserAutofillManagerTest : public testing::Test {
public:
void SetUp() override {
// Advance the mock clock to a fixed, arbitrary, somewhat recent date.
base::Time year2020;
ASSERT_TRUE(base::Time::FromString("01/01/20", &year2020));
task_environment_.FastForwardBy(year2020 - AutofillClock::Now());
autofill_client_.SetPrefs(test::PrefServiceForTesting());
personal_data().set_auto_accept_address_imports_for_testing(true);
personal_data().SetPrefService(autofill_client_.GetPrefs());
personal_data().SetSyncServiceForTest(&sync_service_);
autofill_driver_ = std::make_unique<NiceMock<MockAutofillDriver>>();
autofill_client_.set_test_payments_network_interface(
std::make_unique<payments::TestPaymentsNetworkInterface>(
autofill_client_.GetURLLoaderFactory(),
autofill_client_.GetIdentityManager(), &personal_data()));
auto credit_card_save_manager = std::make_unique<TestCreditCardSaveManager>(
autofill_driver_.get(), &autofill_client_,
&personal_data());
credit_card_save_manager->SetCreditCardUploadEnabled(true);
autofill_client_.set_test_form_data_importer(
std::make_unique<autofill::TestFormDataImporter>(
&autofill_client_, std::move(credit_card_save_manager),
/*iban_save_manager=*/nullptr, &personal_data(), "en-US"));
ResetBrowserAutofillManager();
// By default, if we offer single field form fill, suggestions should be
// returned because it is assumed |field.should_autocomplete| is set to
// true. This should be overridden in tests where
// |field.should_autocomplete| is set to false.
ON_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.WillByDefault(Return(true));
autofill_client_.set_crowdsourcing_manager(
std::make_unique<NiceMock<MockAutofillCrowdsourcingManager>>(
&autofill_client_));
browser_autofill_manager_->set_touch_to_fill_delegate(
std::make_unique<NiceMock<MockTouchToFillDelegate>>());
ON_CALL(touch_to_fill_delegate(), GetManager())
.WillByDefault(Return(browser_autofill_manager_.get()));
ON_CALL(touch_to_fill_delegate(), IsShowingTouchToFill())
.WillByDefault(Return(false));
browser_autofill_manager_->set_fast_checkout_delegate(
std::make_unique<NiceMock<MockFastCheckoutDelegate>>());
ON_CALL(fast_checkout_delegate(), IsShowingFastCheckoutUI())
.WillByDefault(Return(false));
autofill_client_.set_test_strike_database(
std::make_unique<TestStrikeDatabase>());
// Initialize the TestPersonalDataManager with some default data.
CreateTestAutofillProfiles();
CreateTestCreditCards();
// Mandatory re-auth is required for credit card autofill on automotive, so
// the authenticator response needs to be properly mocked.
#if BUILDFLAG(IS_ANDROID)
autofill_client_.SetUpDeviceBiometricAuthenticatorSuccessOnAutomotive();
#endif
}
void TearDown() override {
// Order of destruction is important as BrowserAutofillManager relies on
// PersonalDataManager to be around when it gets destroyed.
browser_autofill_manager_.reset();
personal_data().SetPrefService(nullptr);
personal_data().ClearCreditCards();
}
MockTouchToFillDelegate& touch_to_fill_delegate() {
return *static_cast<MockTouchToFillDelegate*>(
browser_autofill_manager_->touch_to_fill_delegate());
}
MockFastCheckoutDelegate& fast_checkout_delegate() {
return *static_cast<MockFastCheckoutDelegate*>(
browser_autofill_manager_->fast_checkout_delegate());
}
void GetAutofillSuggestions(
const FormData& form,
const FormFieldData& field,
AutofillSuggestionTriggerSource trigger_source =
AutofillSuggestionTriggerSource::kTextFieldDidChange) {
browser_autofill_manager_->OnAskForValuesToFill(form, field, gfx::RectF(),
trigger_source);
}
void DidShowAutofillSuggestions(const FormData& form,
size_t field_index = 0) {
browser_autofill_manager_->DidShowSuggestions(
std::vector<PopupItemId>({PopupItemId::kAddressEntry}), form,
form.fields[field_index]);
}
void TryToShowTouchToFill(const FormData& form,
const FormFieldData& field,
bool form_element_was_clicked) {
browser_autofill_manager_->OnAskForValuesToFill(
form, field, gfx::RectF(),
form_element_was_clicked
? AutofillSuggestionTriggerSource::kFormControlElementClicked
: AutofillSuggestionTriggerSource::kTextFieldDidChange);
}
void FormsSeen(const std::vector<FormData>& forms) {
browser_autofill_manager_->OnFormsSeen(/*updated_forms=*/forms,
/*removed_forms=*/{});
}
void FormSubmitted(const FormData& form) {
browser_autofill_manager_->OnFormSubmitted(
form, false, SubmissionSource::FORM_SUBMISSION);
}
// TODO(crbug.com/1330108): Have separate functions for profile and credit
// card filling.
void FillAutofillFormData(
const FormData& form,
const FormFieldData& field,
std::string guid,
AutofillTriggerDetails trigger_details = {
.trigger_source = AutofillTriggerSource::kPopup}) {
browser_autofill_manager_->OnAskForValuesToFill(
form, field, {},
AutofillSuggestionTriggerSource::kTextFieldDidReceiveKeyDown);
if (const AutofillProfile* profile =
personal_data().GetProfileByGUID(guid)) {
browser_autofill_manager_->FillOrPreviewProfileForm(
mojom::ActionPersistence::kFill, form, field, *profile,
trigger_details);
} else if (const CreditCard* card =
personal_data().GetCreditCardByGUID(guid)) {
browser_autofill_manager_->FillOrPreviewCreditCardForm(
mojom::ActionPersistence::kFill, form, field, *card, trigger_details);
}
}
// Calls |browser_autofill_manager_->OnFillAutofillFormData()| with the
// specified input parameters after setting up the expectation that the mock
// driver's |ApplyFormAction()| method will be called and saving the parameter
// of that call into the |response_data| output parameter.
FormData FillAutofillFormDataAndGetResults(
const FormData& input_form,
const FormFieldData& input_field,
std::string guid,
AutofillTriggerDetails trigger_details = {
.trigger_source = AutofillTriggerSource::kPopup}) {
FormData response_data;
std::vector<FieldGlobalId> global_ids;
for (const auto& field : input_form.fields) {
global_ids.push_back(field.global_id());
}
EXPECT_CALL(*autofill_driver_, ApplyFormAction)
.WillOnce(DoAll(SaveArg<2>(&response_data), Return(global_ids)));
FillAutofillFormData(input_form, input_field, guid, trigger_details);
return response_data;
}
FormData PreviewVirtualCardDataAndGetResults(
mojom::ActionPersistence action_persistence,
const FormData& input_form,
const FormFieldData& input_field,
const CreditCard& virtual_card) {
FormData response_data;
EXPECT_CALL(*autofill_driver_, ApplyFormAction)
.WillOnce((DoAll(SaveArg<2>(&response_data),
Return(std::vector<FieldGlobalId>{}))));
browser_autofill_manager_->FillOrPreviewCreditCardForm(
action_persistence, input_form, input_field, virtual_card,
{.trigger_source = AutofillTriggerSource::kPopup});
return response_data;
}
FormData CreateTestCreditCardFormData(bool is_https, bool use_month_type) {
FormData form;
CreateTestCreditCardFormData(&form, is_https, use_month_type);
return form;
}
// Populates |form| with data corresponding to a simple credit card form.
// Note that this actually appends fields to the form data, which can be
// useful for building up more complex test forms.
void CreateTestCreditCardFormData(FormData* form,
bool is_https,
bool use_month_type) {
form->name = u"MyForm";
if (is_https) {
GURL::Replacements replacements;
replacements.SetSchemeStr(url::kHttpsScheme);
autofill_client_.set_form_origin(
autofill_client_.form_origin().ReplaceComponents(replacements));
form->url = GURL("https://myform.com/form.html");
form->action = GURL("https://myform.com/submit.html");
} else {
// If we are testing a form that submits over HTTP, we also need to set
// the main frame to HTTP, otherwise mixed form warnings will trigger and
// autofill will be disabled.
GURL::Replacements replacements;
replacements.SetSchemeStr(url::kHttpScheme);
autofill_client_.set_form_origin(
autofill_client_.form_origin().ReplaceComponents(replacements));
form->url = GURL("http://myform.com/form.html");
form->action = GURL("http://myform.com/submit.html");
}
form->fields.push_back(CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText));
form->fields.push_back(CreateTestFormField("Card Number", "cardnumber", "",
FormControlType::kInputText));
if (use_month_type) {
form->fields.push_back(CreateTestFormField(
"Expiration Date", "ccmonth", "", FormControlType::kInputMonth));
} else {
form->fields.push_back(CreateTestFormField(
"Expiration Date", "ccmonth", "", FormControlType::kInputText));
form->fields.push_back(
CreateTestFormField("", "ccyear", "", FormControlType::kInputText));
}
form->fields.push_back(
CreateTestFormField("CVC", "cvc", "", FormControlType::kInputText));
}
void PrepareForRealPanResponse(FormData* form, CreditCard* card) {
// This line silences the warning from PaymentsNetworkInterface about
// matching sync and Payments server types.
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
"sync-url", "https://google.com");
CreateTestCreditCardFormData(form, true, false);
FormsSeen({*form});
*card = CreditCard(CreditCard::RecordType::kMaskedServerCard, "a123");
test::SetCreditCardInfo(card, "John Dillinger", "1881" /* Visa */, "01",
"2017", "1");
card->SetNetworkForMaskedCard(kVisaCard);
EXPECT_CALL(*autofill_driver_, ApplyFormAction).Times(AtLeast(1));
browser_autofill_manager_->FillOrPreviewCreditCardForm(
mojom::ActionPersistence::kFill, *form, form->fields[0], *card,
{.trigger_source = AutofillTriggerSource::kPopup});
}
void OnDidGetRealPan(AutofillClient::PaymentsRpcResult result,
const std::string& real_pan,
bool is_virtual_card = false) {
payments::FullCardRequest* full_card_request =
browser_autofill_manager_->client()
.GetCvcAuthenticator()
->full_card_request_.get();
DCHECK(full_card_request);
// Mock user response.
payments::FullCardRequest::UserProvidedUnmaskDetails details;
details.cvc = u"123";
full_card_request->OnUnmaskPromptAccepted(details);
// Mock payments response.
payments::PaymentsNetworkInterface::UnmaskResponseDetails response;
response.card_type = is_virtual_card
? AutofillClient::PaymentsRpcCardType::kVirtualCard
: AutofillClient::PaymentsRpcCardType::kServerCard;
full_card_request->OnDidGetRealPan(result,
response.with_real_pan(real_pan));
}
// Convenience method to cast the FullCardRequest into a CardUnmaskDelegate.
CardUnmaskDelegate* full_card_unmask_delegate() {
payments::FullCardRequest* full_card_request =
browser_autofill_manager_->client()
.GetCvcAuthenticator()
->full_card_request_.get();
DCHECK(full_card_request);
return static_cast<CardUnmaskDelegate*>(full_card_request);
}
void DisableAutofillViaAblation(
base::test::ScopedFeatureList& scoped_feature_list,
bool for_addresses,
bool for_credit_cards) {
base::FieldTrialParams feature_parameters{
{features::kAutofillAblationStudyEnabledForAddressesParam.name,
for_addresses ? "true" : "false"},
{features::kAutofillAblationStudyEnabledForPaymentsParam.name,
for_credit_cards ? "true" : "false"},
{features::kAutofillAblationStudyAblationWeightPerMilleParam.name,
"1000"},
};
scoped_feature_list.InitAndEnableFeatureWithParameters(
features::kAutofillEnableAblationStudy, feature_parameters);
}
void ResetBrowserAutofillManager() {
browser_autofill_manager_ = std::make_unique<TestBrowserAutofillManager>(
autofill_driver_.get(), &autofill_client_);
test_api(*browser_autofill_manager_)
.set_single_field_form_fill_router(
std::make_unique<NiceMock<MockSingleFieldFormFillRouter>>(
autofill_client_.GetMockAutocompleteHistoryManager(),
autofill_client_.GetMockIbanManager(),
autofill_client_.GetMockMerchantPromoCodeManager()));
test_api(*browser_autofill_manager_)
.SetExternalDelegate(std::make_unique<TestAutofillExternalDelegate>(
browser_autofill_manager_.get(),
/*call_parent_methods=*/true));
test_api(*browser_autofill_manager_)
.set_credit_card_access_manager(
std::make_unique<NiceMock<MockCreditCardAccessManager>>(
autofill_driver_.get(), &autofill_client_, &personal_data(),
test_api(*browser_autofill_manager_)
.credit_card_form_event_logger()));
}
// Matches a AskForValuesToFillFieldLogEvent by equality of fields.
auto Equal(const AskForValuesToFillFieldLogEvent& expected) {
return VariantWith<AskForValuesToFillFieldLogEvent>(
AllOf(Field("has_suggestion",
&AskForValuesToFillFieldLogEvent::has_suggestion,
expected.has_suggestion),
Field("suggestion_is_shown",
&AskForValuesToFillFieldLogEvent::suggestion_is_shown,
expected.suggestion_is_shown)));
}
// Matches a TriggerFillFieldLogEvent by equality of fields.
auto Equal(const TriggerFillFieldLogEvent& expected) {
return VariantWith<TriggerFillFieldLogEvent>(
AllOf(Field("fill_event_id", &TriggerFillFieldLogEvent::fill_event_id,
expected.fill_event_id),
Field("data_type", &TriggerFillFieldLogEvent::data_type,
expected.data_type),
Field("associated_country_code",
&TriggerFillFieldLogEvent::associated_country_code,
expected.associated_country_code),
Field("timestamp", &TriggerFillFieldLogEvent::timestamp,
expected.timestamp)));
}
// Matches a FillFieldLogEvent by equality of fields. Use FillEventId(-1) if
// you want to ignore the fill_event_id.
auto Equal(const FillFieldLogEvent& expected) {
return VariantWith<FillFieldLogEvent>(AllOf(
testing::Conditional(
expected.fill_event_id == FillEventId(-1), _,
Field("fill_event_id", &FillFieldLogEvent::fill_event_id,
expected.fill_event_id)),
Field("had_value_before_filling",
&FillFieldLogEvent::had_value_before_filling,
expected.had_value_before_filling),
Field("autofill_skipped_status",
&FillFieldLogEvent::autofill_skipped_status,
expected.autofill_skipped_status),
Field("was_autofilled_before_security_policy",
&FillFieldLogEvent::was_autofilled_before_security_policy,
expected.was_autofilled_before_security_policy),
Field("had_value_after_filling",
&FillFieldLogEvent::had_value_after_filling,
expected.had_value_after_filling),
Field("filling_method", &FillFieldLogEvent::filling_method,
expected.filling_method),
Field("filling_prevented_by_iframe_security_policy",
&FillFieldLogEvent::filling_prevented_by_iframe_security_policy,
expected.filling_prevented_by_iframe_security_policy)));
}
// Matches a TypingFieldLogEvent by equality of fields.
auto Equal(const TypingFieldLogEvent& expected) {
return VariantWith<TypingFieldLogEvent>(Field(
"has_value_after_typing", &TypingFieldLogEvent::has_value_after_typing,
expected.has_value_after_typing));
}
// Matches a HeuristicPredictionFieldLogEvent by equality of fields.
auto Equal(const HeuristicPredictionFieldLogEvent& expected) {
return VariantWith<HeuristicPredictionFieldLogEvent>(AllOf(
Field("field_type", &HeuristicPredictionFieldLogEvent::field_type,
expected.field_type),
Field("pattern_source",
&HeuristicPredictionFieldLogEvent::pattern_source,
expected.pattern_source),
Field("is_active_pattern_source",
&HeuristicPredictionFieldLogEvent::is_active_pattern_source,
expected.is_active_pattern_source),
Field("rank_in_field_signature_group",
&HeuristicPredictionFieldLogEvent::rank_in_field_signature_group,
expected.rank_in_field_signature_group)));
}
// Matches a AutocompleteAttributeFieldLogEvent by equality of fields.
auto Equal(const AutocompleteAttributeFieldLogEvent& expected) {
return VariantWith<AutocompleteAttributeFieldLogEvent>(AllOf(
Field("html_type", &AutocompleteAttributeFieldLogEvent::html_type,
expected.html_type),
Field("html_mode", &AutocompleteAttributeFieldLogEvent::html_mode,
expected.html_mode),
Field(
"rank_in_field_signature_group",
&AutocompleteAttributeFieldLogEvent::rank_in_field_signature_group,
expected.rank_in_field_signature_group)));
}
// Matches a ServerPredictionFieldLogEvent by equality of fields.
auto Equal(const ServerPredictionFieldLogEvent& expected) {
return VariantWith<ServerPredictionFieldLogEvent>(AllOf(
Field("server_type1", &ServerPredictionFieldLogEvent::server_type1,
expected.server_type1),
Field("prediction_source1",
&ServerPredictionFieldLogEvent::prediction_source1,
expected.prediction_source1),
Field("server_type2", &ServerPredictionFieldLogEvent::server_type2,
expected.server_type2),
Field("prediction_source2",
&ServerPredictionFieldLogEvent::prediction_source2,
expected.prediction_source2),
Field(
"server_type_prediction_is_override",
&ServerPredictionFieldLogEvent::server_type_prediction_is_override,
expected.server_type_prediction_is_override),
Field("rank_in_field_signature_group",
&ServerPredictionFieldLogEvent::rank_in_field_signature_group,
expected.rank_in_field_signature_group)));
}
// Matches a RationalizationFieldLogEvent by equality of fields.
auto Equal(const RationalizationFieldLogEvent& expected) {
return VariantWith<RationalizationFieldLogEvent>(
AllOf(Field("field_type", &RationalizationFieldLogEvent::field_type,
expected.field_type),
Field("section_id", &RationalizationFieldLogEvent::section_id,
expected.section_id),
Field("type_changed", &RationalizationFieldLogEvent::type_changed,
expected.type_changed)));
}
// Matches a vector of FieldLogEventType objects by equality of fields of each
// log event type.
auto ArrayEquals(
const std::vector<AutofillField::FieldLogEventType>& expected) {
static_assert(
absl::variant_size<AutofillField::FieldLogEventType>() == 9,
"If you add a new field event type, you need to update this function");
std::vector<Matcher<AutofillField::FieldLogEventType>> matchers;
for (const auto& event : expected) {
if (absl::holds_alternative<AskForValuesToFillFieldLogEvent>(event)) {
matchers.push_back(
Equal(absl::get<AskForValuesToFillFieldLogEvent>(event)));
} else if (absl::holds_alternative<TriggerFillFieldLogEvent>(event)) {
matchers.push_back(Equal(absl::get<TriggerFillFieldLogEvent>(event)));
} else if (absl::holds_alternative<FillFieldLogEvent>(event)) {
matchers.push_back(Equal(absl::get<FillFieldLogEvent>(event)));
} else if (absl::holds_alternative<TypingFieldLogEvent>(event)) {
matchers.push_back(Equal(absl::get<TypingFieldLogEvent>(event)));
} else if (absl::holds_alternative<HeuristicPredictionFieldLogEvent>(
event)) {
matchers.push_back(
Equal(absl::get<HeuristicPredictionFieldLogEvent>(event)));
} else if (absl::holds_alternative<AutocompleteAttributeFieldLogEvent>(
event)) {
matchers.push_back(
Equal(absl::get<AutocompleteAttributeFieldLogEvent>(event)));
} else if (absl::holds_alternative<ServerPredictionFieldLogEvent>(
event)) {
matchers.push_back(
Equal(absl::get<ServerPredictionFieldLogEvent>(event)));
} else if (absl::holds_alternative<RationalizationFieldLogEvent>(event)) {
matchers.push_back(
Equal(absl::get<RationalizationFieldLogEvent>(event)));
} else {
NOTREACHED();
}
}
return ElementsAreArray(matchers);
}
// Always show only the `last_four` digits.
std::string MakeCardLabel(const std::string& nickname,
const std::string& last_four) {
return nickname + " " +
test::ObfuscatedCardDigitsAsUTF8(
last_four, ObfuscationLengthForCreditCardLastFourDigits());
}
TestPersonalDataManager& personal_data() {
return *autofill_client_.GetPersonalDataManager();
}
MockCreditCardAccessManager& cc_access_manager() {
return static_cast<MockCreditCardAccessManager&>(
browser_autofill_manager_->GetCreditCardAccessManager());
}
protected:
MockAutofillCrowdsourcingManager* crowdsourcing_manager() {
return static_cast<MockAutofillCrowdsourcingManager*>(
autofill_client_.GetCrowdsourcingManager());
}
TestAutofillExternalDelegate* external_delegate() {
return static_cast<TestAutofillExternalDelegate*>(
test_api(*browser_autofill_manager_).external_delegate());
}
TestFormDataImporter& form_data_importer() {
return static_cast<TestFormDataImporter&>(
*autofill_client_.GetFormDataImporter());
}
MockSingleFieldFormFillRouter& single_field_form_fill_router() {
return static_cast<MockSingleFieldFormFillRouter&>(
test_api(*browser_autofill_manager_).single_field_form_fill_router());
}
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
test::AutofillUnitTestEnvironment autofill_test_environment_;
NiceMock<MockAutofillClient> autofill_client_;
std::unique_ptr<MockAutofillDriver> autofill_driver_;
syncer::TestSyncService sync_service_;
std::unique_ptr<TestBrowserAutofillManager> browser_autofill_manager_;
private:
int ToHistogramSample(autofill_metrics::CardUploadDecision metric) {
for (int sample = 0; sample < metric + 1; ++sample)
if (metric & (1 << sample))
return sample;
NOTREACHED();
return 0;
}
void CreateTestAutofillProfiles() {
AutofillProfile profile1 =
FillDataToAutofillProfile(GetElvisAddressFillData());
profile1.set_guid(kElvisProfileGuid);
profile1.set_use_date(AutofillClock::Now() - base::Days(2));
personal_data().AddProfile(profile1);
AutofillProfile profile2(
i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile2, "Charles", "Hardin", "Holley",
"buddy@gmail.com", "Decca", "123 Apple St.", "unit 6",
"Lubbock", "Texas", "79401", "US", "23456789012");
profile2.set_guid(MakeGuid(2));
profile2.set_use_date(AutofillClock::Now() - base::Days(1));
personal_data().AddProfile(profile2);
AutofillProfile profile3(
i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile3, "", "", "", "", "", "", "", "", "", "", "",
"");
profile3.set_guid(MakeGuid(3));
profile3.set_use_date(AutofillClock::Now());
personal_data().AddProfile(profile3);
}
void CreateTestCreditCards() {
CreditCard credit_card1;
test::SetCreditCardInfo(&credit_card1, "Elvis Presley",
"4234567890123456", // Visa
"04", "2999", "1");
credit_card1.set_guid(MakeGuid(4));
credit_card1.set_use_count(10);
credit_card1.set_use_date(AutofillClock::Now() - base::Days(5));
personal_data().AddCreditCard(credit_card1);
CreditCard credit_card2;
test::SetCreditCardInfo(&credit_card2, "Buddy Holly",
"5187654321098765", // Mastercard
"10", "2998", "1");
credit_card2.set_guid(MakeGuid(5));
credit_card2.set_use_count(5);
credit_card2.set_use_date(AutofillClock::Now() - base::Days(4));
personal_data().AddCreditCard(credit_card2);
CreditCard credit_card3;
test::SetCreditCardInfo(&credit_card3, "", "", "08", "2999", "");
credit_card3.set_guid(MakeGuid(6));
personal_data().AddCreditCard(credit_card3);
}
};
class SuggestionMatchingTest : public BrowserAutofillManagerTest,
public testing::WithParamInterface<bool> {
protected:
void SetUp() override {
BrowserAutofillManagerTest::SetUp();
InitializeFeatures();
}
bool IsMetadataEnabled() const { return GetParam(); }
void InitializeFeatures();
base::test::ScopedFeatureList features_;
};
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
void SuggestionMatchingTest::InitializeFeatures() {}
#else
void SuggestionMatchingTest::InitializeFeatures() {
features_.InitWithFeatureStates(
{{features::kAutofillEnableVirtualCardMetadata, IsMetadataEnabled()},
{features::kAutofillEnableCardProductName, IsMetadataEnabled()},
{features::kAutofillEnableCardArtImage, IsMetadataEnabled()}});
}
#endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
// Credit card suggestion tests related with keyboard accessory.
class CreditCardSuggestionTest : public BrowserAutofillManagerTest {
protected:
void SetUp() override {
BrowserAutofillManagerTest::SetUp();
feature_list_card_metadata_and_product_name_.InitWithFeatures(
/* enabled_features */ {},
/* disabled_features */ {features::kAutofillEnableVirtualCardMetadata,
features::kAutofillEnableCardProductName});
}
private:
base::test::ScopedFeatureList feature_list_card_metadata_and_product_name_;
};
// Test that calling OnFormsSeen with an empty set of forms (such as when
// reloading a page or when the renderer processes a set of forms but detects
// no changes) does not load the forms again.
TEST_F(BrowserAutofillManagerTest, OnFormsSeen_Empty) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
base::HistogramTester histogram_tester;
FormsSeen({form});
histogram_tester.ExpectUniqueSample("Autofill.UserHappiness",
0 /* FORMS_LOADED */, 1);
// No more forms, metric is not logged.
FormsSeen({});
histogram_tester.ExpectUniqueSample("Autofill.UserHappiness",
0 /* FORMS_LOADED */, 1);
}
// Test that calling OnFormsSeen consecutively with a different set of forms
// will query for each separately.
TEST_F(BrowserAutofillManagerTest, OnFormsSeen_DifferentFormStructures) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormData form2;
form2.host_frame = test::MakeLocalFrameToken();
form2.renderer_id = test::MakeFormRendererId();
form2.name = u"MyForm";
form2.url = GURL("https://myform.com/form.html");
form2.action = GURL("https://myform.com/submit.html");
form2.fields = {
CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText),
CreateTestFormField("Email", "email", "", FormControlType::kInputText)};
EXPECT_CALL(*crowdsourcing_manager(), StartQueryRequest).Times(AnyNumber());
EXPECT_CALL(
*crowdsourcing_manager(),
StartQueryRequest(
ElementsAre(FormStructureHasRendererId(form.renderer_id)), _, _));
EXPECT_CALL(
*crowdsourcing_manager(),
StartQueryRequest(
ElementsAre(FormStructureHasRendererId(form2.renderer_id)), _, _));
base::HistogramTester histogram_tester;
FormsSeen({form});
histogram_tester.ExpectUniqueSample("Autofill.UserHappiness",
0 /* FORMS_LOADED */, 1);
FormsSeen({form2});
histogram_tester.ExpectUniqueSample("Autofill.UserHappiness",
0 /* FORMS_LOADED */, 2);
}
// Test that when forms are seen, the renderer is updated with the predicted
// field types
TEST_F(BrowserAutofillManagerTest,
OnFormsSeen_SendAutofillTypePredictionsToRenderer) {
// Set up a queryable form.
FormData form1 = CreateTestAddressFormData();
// Set up a non-queryable form.
FormData form2;
form2.host_frame = test::MakeLocalFrameToken();
form2.renderer_id = test::MakeFormRendererId();
form2.name = u"NonQueryable";
form2.url = form1.url;
form2.action = GURL("https://myform.com/submit.html");
form2.fields = {
CreateTestFormField("Querty", "qwerty", "", FormControlType::kInputText)};
// Package the forms for observation.
// Setup expectations.
EXPECT_CALL(*autofill_driver_, SendAutofillTypePredictionsToRenderer(_))
.Times(2);
FormsSeen({form1, form2});
}
// Test that no autofill suggestions are returned for a field with an
// unrecognized autocomplete attribute on desktop.
// On mobile, the keyboard accessory is shown unconditionally.
TEST_F(BrowserAutofillManagerTest,
GetProfileSuggestions_UnrecognizedAttribute) {
// Set up our form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
// Set a valid autocomplete attribute for the first name.
CreateTestFormField("First name", "firstname", "",
FormControlType::kInputText, "given-name"),
// Set no autocomplete attribute for the middle name.
CreateTestFormField("Middle name", "middle", "",
FormControlType::kInputText, ""),
// Set an unrecognized autocomplete attribute for the last name.
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText, "unrecognized")};
FormsSeen({form});
// Ensure that the SingleFieldFormFillRouter is not called for
// suggestions either.
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.Times(0);
// Suggestions should be returned for the first two fields.
GetAutofillSuggestions(form, form.fields[0]);
external_delegate()->CheckSuggestionCount(form.fields[0].global_id(), 4);
GetAutofillSuggestions(form, form.fields[1]);
external_delegate()->CheckSuggestionCount(form.fields[1].global_id(), 4);
GetAutofillSuggestions(form, form.fields[2]);
// For the third field, suggestions should only be shown on mobile due to the
// unrecognized autocomplete attribute.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
external_delegate()->CheckSuggestionCount(form.fields[2].global_id(), 4);
#else
external_delegate()->CheckNoSuggestions(form.fields[2].global_id());
#endif
}
// Tests that ac=unrecognized fields only activate suggestions when triggered
// through manual fallbacks (even though the field has a type in both cases) on
// desktop.
// On mobile, suggestions are shown even for ac=unrecognized fields.
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
TEST_F(BrowserAutofillManagerTest,
GetProfileSuggestions_UnrecognizedAttribute_Predictions_Mobile) {
// Create a form where the first field has ac=unrecognized.
FormData form = CreateTestAddressFormData();
form.fields[0].parsed_autocomplete =
AutocompleteParsingResult{.field_type = HtmlFieldType::kUnrecognized};
FormsSeen({form});
// Expect that two suggestions + footer are returned for all fields,
// independently of the autocomplete attribute. Two, because the fixture
// created three profiles during set up, one of which is empty and cannot be
// suggested (see `CreateTestAutofillProfiles()`).
for (const FormFieldData& field : form.fields) {
GetAutofillSuggestions(form, field);
external_delegate()->CheckSuggestionCount(field.global_id(), 4);
}
}
#else
TEST_F(BrowserAutofillManagerTest,
AutofillManualFallback_UnclassifiedField_SuggestionsShown) {
base::test::ScopedFeatureList enabled_features(
features::kAutofillForUnclassifiedFieldsAvailable);
// Create a form where the first field is unclassifiable.
FormData form = CreateTestAddressFormData();
form.fields[0].label = u"unclassified";
form.fields[0].name = u"unclassified";
FormsSeen({form});
// Expect that no suggestions are returned for the first field.
const FormFieldData& first_field = form.fields[0];
GetAutofillSuggestions(form, first_field);
external_delegate()->CheckSuggestionsNotReturned(first_field.global_id());
// Expect 2 address suggestions + footer because the fixture created three
// profiles during set up (see `CreateTestAutofillProfiles()`).
GetAutofillSuggestions(
form, first_field,
AutofillSuggestionTriggerSource::kManualFallbackAddress);
external_delegate()->CheckSuggestionCount(first_field.global_id(), 4);
// Expect 3 credit card suggestions + footer because the fixture created 3
// credit cards during setup (see `CreateTestCreditCards()`).
GetAutofillSuggestions(
form, first_field,
AutofillSuggestionTriggerSource::kManualFallbackPayments);
external_delegate()->CheckSuggestionCount(first_field.global_id(), 5);
}
TEST_F(BrowserAutofillManagerTest,
AutofillManualFallback_AutocompleteUnrecognized_SuggestionsShown) {
// Create a form where the first field has ac=unrecognized.
FormData form = CreateTestAddressFormData();
form.fields[0].parsed_autocomplete =
AutocompleteParsingResult{.field_type = HtmlFieldType::kUnrecognized};
FormsSeen({form});
// Expect that no suggestions are returned for the first field by default.
const FormFieldData& first_field = form.fields[0];
GetAutofillSuggestions(form, first_field);
external_delegate()->CheckNoSuggestions(first_field.global_id());
// Expect 2 address suggestions + footer because the fixture created three
// profiles during set up, one of which is empty and cannot be suggested
// (see `CreateTestAutofillProfiles()`).
GetAutofillSuggestions(
form, first_field,
AutofillSuggestionTriggerSource::kManualFallbackAddress);
external_delegate()->CheckSuggestionCount(first_field.global_id(), 4);
// Expect 4 credit card suggestions + footer because the fixture created 3
// credit cards during setup (see `CreateTestCreditCards()`).
GetAutofillSuggestions(
form, first_field,
AutofillSuggestionTriggerSource::kManualFallbackPayments);
external_delegate()->CheckSuggestionCount(first_field.global_id(), 5);
// Expect that two address suggestions + footer are returned for all other
// fields.
for (size_t i = 1; i < form.fields.size(); i++) {
GetAutofillSuggestions(form, form.fields[i]);
external_delegate()->CheckSuggestionCount(form.fields[i].global_id(), 4);
}
}
TEST_F(BrowserAutofillManagerTest,
AutofillManualFallback_ClassifiedField_AddressForm_ShowSuggestions) {
// Create a form where all fields can be classified.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
for (const auto& field : form.fields) {
// Expect 2 address suggestions + footer because the fixture created three
// profiles during set up, one of which is empty and cannot be suggested
// (see `CreateTestAutofillProfiles()`).
GetAutofillSuggestions(
form, field, AutofillSuggestionTriggerSource::kManualFallbackAddress);
external_delegate()->CheckSuggestionCount(field.global_id(), 4);
base::ranges::all_of(
external_delegate()->suggestions(), [](const Suggestion& suggestion) {
return suggestion.popup_item_id == PopupItemId::kAddressEntry;
});
// Expect 3 credit card suggestions + footer because the fixture created 3
// credit cards during setup (see `CreateTestCreditCards()`).
GetAutofillSuggestions(
form, field, AutofillSuggestionTriggerSource::kManualFallbackPayments);
external_delegate()->CheckSuggestionCount(field.global_id(), 5);
EXPECT_TRUE(base::ranges::all_of(
external_delegate()->suggestions(), [](const Suggestion& suggestion) {
return suggestion.popup_item_id != PopupItemId::kAddressEntry ||
!suggestion.is_acceptable;
}));
}
}
TEST_F(BrowserAutofillManagerTest,
AutofillManualFallback_ClassifiedField_PaymentsForm_ShowSuggestions) {
base::test::ScopedFeatureList enabled_features(
features::kAutofillForUnclassifiedFieldsAvailable);
// Create a form where all fields can be classified.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
const FormFieldData& cc_name_field = form.fields[0];
// Expect 2 address suggestions + footer because the fixture created three
// profiles during set up (see `CreateTestAutofillProfiles()`).
GetAutofillSuggestions(
form, cc_name_field,
AutofillSuggestionTriggerSource::kManualFallbackAddress);
external_delegate()->CheckSuggestionCount(cc_name_field.global_id(), 4);
// Expect 2 credit card suggestions + footer because manual fallback flow
// triggered on a classified credit card field should generate regular
// suggestions.
GetAutofillSuggestions(
form, cc_name_field,
AutofillSuggestionTriggerSource::kManualFallbackPayments);
external_delegate()->CheckSuggestionCount(cc_name_field.global_id(), 4);
EXPECT_EQ(external_delegate()->GetMainFillingProduct(),
FillingProduct::kCreditCard);
}
#endif // BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
// Test that when small forms are disabled (min required fields enforced) no
// suggestions are returned when there are less than three fields and none of
// them have an autocomplete attribute.
TEST_F(BrowserAutofillManagerTest,
GetProfileSuggestions_MinFieldsEnforced_NoAutocomplete) {
// Set up our form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText)};
FormsSeen({form});
// Ensure that the SingleFieldFormFillRouter is called for both fields.
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.Times(2);
GetAutofillSuggestions(form, form.fields[0]);
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
GetAutofillSuggestions(form, form.fields[1]);
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
// Test that when small forms are disabled (min required fields enforced)
// for a form with two fields with one that has an autocomplete attribute,
// suggestions are only made for the one that has the attribute.
TEST_F(BrowserAutofillManagerTest,
GetProfileSuggestions_MinFieldsEnforced_WithOneAutocomplete) {
// Set up our form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText, "given-name"),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText, "")};
FormsSeen({form});
// Check that suggestions are made for the field that has the autocomplete
// attribute.
GetAutofillSuggestions(form, form.fields[0]);
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("Charles", "", Suggestion::Icon::kNoIcon,
PopupItemId::kAddressEntry),
Suggestion("Elvis", "", Suggestion::Icon::kNoIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
// Check that there are no suggestions for the field without the autocomplete
// attribute.
GetAutofillSuggestions(form, form.fields[1]);
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
// Test that for a form with two fields with autocomplete attributes,
// suggestions are made for both fields. This is true even if a minimum number
// of fields is enforced.
TEST_F(BrowserAutofillManagerTest,
GetProfileSuggestions_SmallFormWithTwoAutocomplete) {
// Set up our form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText, "given-name"),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText, "family-name")};
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("Charles", "Charles Hardin Holley", kAddressEntryIcon,
PopupItemId::kAddressEntry),
Suggestion("Elvis", "Elvis Aaron Presley", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
GetAutofillSuggestions(form, form.fields[1]);
external_delegate()->CheckSuggestions(
form.fields[1].global_id(),
{Suggestion("Holley", "Charles Hardin Holley", kAddressEntryIcon,
PopupItemId::kAddressEntry),
Suggestion("Presley", "Elvis Aaron Presley", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
}
// Tests that BrowserAutofillManager correctly returns virtual cards with usage
// data and VCN last four for a standalone cvc field.
TEST_F(BrowserAutofillManagerTest, GetVirtualCreditCardsForStandaloneCvcField) {
base::test::ScopedFeatureList feature(
features::kAutofillParseVcnCardOnFileStandaloneCvcFields);
// Set up four_digit_combinations_in_dom, essentially mocking logic in
// AutofillAgent.
std::vector<std::string> combinations = {"1234"};
test_api(*browser_autofill_manager_)
.SetFourDigitCombinationsInDOM(combinations);
// Set up virtual card usage data and credit cards.
personal_data().ClearCreditCards();
CreditCard masked_server_card = test::GetVirtualCard();
masked_server_card.set_guid("1234");
VirtualCardUsageData virtual_card_usage_data =
test::GetVirtualCardUsageData1();
masked_server_card.set_instrument_id(
*virtual_card_usage_data.instrument_id());
// Add credit card and usage data to personal data manager.
personal_data().AddVirtualCardUsageData(virtual_card_usage_data);
personal_data().AddServerCreditCard(masked_server_card);
// Call GetCreditCardsForStandaloneCvcField, should return credit card.
base::flat_map<std::string, VirtualCardUsageData::VirtualCardLastFour>
matches = test_api(*browser_autofill_manager_)
.GetVirtualCreditCardsForStandaloneCvcField(
virtual_card_usage_data.merchant_origin());
ASSERT_EQ(matches.size(), 1u);
EXPECT_EQ(matches[masked_server_card.guid()],
virtual_card_usage_data.virtual_card_last_four());
}
// Test that we return all address profile suggestions when all form fields
// are empty.
TEST_P(SuggestionMatchingTest, GetProfileSuggestions_EmptyValue) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("Charles", "123 Apple St.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
Suggestion("Elvis", "3734 Elvis Presley Blvd.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
}
// Test that we return only matching address profile suggestions when the
// selected form field has been partially filled out.
TEST_P(SuggestionMatchingTest, GetProfileSuggestions_MatchCharacter) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
FormFieldData field = CreateTestFormField("First Name", "firstname", "E",
FormControlType::kInputText);
GetAutofillSuggestions(form, field);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
field.global_id(),
{Suggestion("Elvis", "3734 Elvis Presley Blvd.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
}
// Tests that we return address profile suggestions values when the section
// is already autofilled, and that we merge identical values.
TEST_P(SuggestionMatchingTest,
GetProfileSuggestions_AlreadyAutofilledMergeValues) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// First name is already autofilled which will make the section appear as
// "already autofilled".
form.fields[0].is_autofilled = true;
// Two profiles have the same last name, and the third shares the same first
// letter for last name.
AutofillProfile profile1(i18n_model_definition::kLegacyHierarchyCountryCode);
profile1.set_guid(MakeGuid(103));
profile1.set_use_date(AutofillClock::Now() - base::Days(2));
profile1.SetInfo(NAME_FIRST, u"Robin", "en-US");
profile1.SetInfo(NAME_LAST, u"Grimes", "en-US");
profile1.SetInfo(ADDRESS_HOME_LINE1, u"1234 Smith Blvd.", "en-US");
personal_data().AddProfile(profile1);
AutofillProfile profile2(i18n_model_definition::kLegacyHierarchyCountryCode);
profile2.set_guid(MakeGuid(124));
profile2.set_use_date(AutofillClock::Now() - base::Days(1));
profile2.SetInfo(NAME_FIRST, u"Carl", "en-US");
profile2.SetInfo(NAME_LAST, u"Grimes", "en-US");
profile2.SetInfo(ADDRESS_HOME_LINE1, u"1234 Smith Blvd.", "en-US");
personal_data().AddProfile(profile2);
AutofillProfile profile3(i18n_model_definition::kLegacyHierarchyCountryCode);
profile3.set_guid(MakeGuid(126));
profile3.set_use_date(AutofillClock::Now());
profile3.SetInfo(NAME_FIRST, u"Aaron", "en-US");
profile3.SetInfo(NAME_LAST, u"Googler", "en-US");
profile3.SetInfo(ADDRESS_HOME_LINE1, u"1600 Amphitheater pkwy", "en-US");
personal_data().AddProfile(profile3);
FormFieldData field = CreateTestFormField("Last Name", "lastname", "G",
FormControlType::kInputText);
GetAutofillSuggestions(form, field);
external_delegate()->CheckSuggestions(
field.global_id(),
{Suggestion("Googler", "1600 Amphitheater pkwy", kAddressEntryIcon,
PopupItemId::kAddressEntry),
Suggestion("Grimes", "1234 Smith Blvd.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
}
// Tests that we return address profile suggestions values when the section
// is already autofilled, and that they have no label.
TEST_P(SuggestionMatchingTest,
GetProfileSuggestions_AlreadyAutofilledNoLabels) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// First name is already autofilled which will make the section appear as
// "already autofilled".
form.fields[0].is_autofilled = true;
FormFieldData field = CreateTestFormField("First Name", "firstname", "E",
FormControlType::kInputText);
GetAutofillSuggestions(form, field);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
field.global_id(),
{Suggestion("Elvis", "3734 Elvis Presley Blvd.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
}
// Test that we return no suggestions when the form has no relevant fields.
TEST_F(BrowserAutofillManagerTest, GetProfileSuggestions_UnknownFields) {
// Set up our form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
CreateTestFormField("Username", "username", "",
FormControlType::kInputText),
CreateTestFormField("Password", "password", "",
FormControlType::kInputPassword),
CreateTestFormField("Quest", "quest", "", FormControlType::kInputText),
CreateTestFormField("Color", "color", "", FormControlType::kInputText)};
FormsSeen({form});
GetAutofillSuggestions(form, form.fields.back());
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
TEST_F(BrowserAutofillManagerTest,
GetProfileSuggestions_DialogClosedByUser_NoData) {
personal_data().ClearProfiles();
FormData form = CreateTestAddressFormData();
FormsSeen({form});
GetAutofillSuggestions(form, form.fields.back(),
AutofillSuggestionTriggerSource::
kShowPromptAfterDialogClosedNonManualFallback);
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.Times(0);
external_delegate()->CheckNoSuggestions(form.fields.back().global_id());
}
// Test that single field suggestions are not queries when autofill is triggered
// manually by the user.
TEST_F(BrowserAutofillManagerTest,
GetProfileSuggestions_ManualFallback_NoData) {
personal_data().ClearProfiles();
FormData form = CreateTestAddressFormData();
FormsSeen({form});
GetAutofillSuggestions(
form, form.fields.back(),
AutofillSuggestionTriggerSource::kManualFallbackAddress);
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.Times(0);
external_delegate()->CheckNoSuggestions(form.fields.back().global_id());
}
// Test parameter data for asserting that the expected suggestion types are
// returned when triggering Autofill using manual fallback. Note that the tests
// that use this param are only about manual fallback for fields that are not
// classified as the target `FillingProduct` defined by the chosen
// `manual_fallback_option`. Therefore, manual fallbacks for `ac=unrecognized`
// fields are not covered here.
struct ManualFallbackTestParams {
const FormType form_type;
const AutofillSuggestionTriggerSource manual_fallback_option;
const FillingProduct expected_main_filling_product;
const std::string test_name;
};
// Test fixture that covers Autofill being triggered from fields that are not
// classified as the target `FillingProduct`. For example, triggering address
// manual fallback from an unclassified field.
class ManualFallbackTest
: public BrowserAutofillManagerTest,
public ::testing::WithParamInterface<ManualFallbackTestParams> {
public:
FormData GetFormDataFromTestParam() {
const FormType form_type = GetParam().form_type;
if (form_type == FormType::kAddressForm) {
return test::CreateTestAddressFormData();
} else if (form_type == FormType::kCreditCardForm) {
return CreateTestCreditCardFormData(/*is_https=*/true,
/*use_month_type=*/false);
} else {
CHECK(form_type == FormType::kUnknownFormType);
return test::GetFormData(
{.fields = {{.label = u"unclassified", .name = u"unclassified"}}});
}
}
};
TEST_P(ManualFallbackTest, ReturnsExpectedSuggestionTypes) {
base::test::ScopedFeatureList feature(
features::kAutofillForUnclassifiedFieldsAvailable);
const FormData form = GetFormDataFromTestParam();
FormsSeen({form});
const ManualFallbackTestParams& params = GetParam();
GetAutofillSuggestions(form, form.fields.back(),
params.manual_fallback_option);
EXPECT_EQ(external_delegate()->GetMainFillingProduct(),
params.expected_main_filling_product);
}
INSTANTIATE_TEST_SUITE_P(
BrowserAutofillManagerTest,
ManualFallbackTest,
::testing::ValuesIn(std::vector<ManualFallbackTestParams>(
{// Tests that address suggestions are rendered when address manual
// fallback is triggered on an unclassified field.
{.form_type = FormType::kUnknownFormType,
.manual_fallback_option =
AutofillSuggestionTriggerSource::kManualFallbackAddress,
.expected_main_filling_product = FillingProduct::kAddress,
.test_name = "_UnclassifiedField_AddressFallback"},
// Tests that address suggestions are rendered when address manual
// fallback is
// triggered on a credit card field.
{.form_type = FormType::kCreditCardForm,
.manual_fallback_option =
AutofillSuggestionTriggerSource::kManualFallbackAddress,
.expected_main_filling_product = FillingProduct::kAddress,
.test_name = "_CreditCardField_AddressFallback"},
// Tests that payments suggestions are rendered when payments manual
// fallback is triggered on an unclassified field.
{.form_type = FormType::kUnknownFormType,
.manual_fallback_option =
AutofillSuggestionTriggerSource::kManualFallbackPayments,
.expected_main_filling_product = FillingProduct::kCreditCard,
.test_name = "_UnclassifiedField_CreditCard"},
// Tests that payments suggestions are rendered when payments manual
// fallback is
// triggered on an address field.
{.form_type = FormType::kAddressForm,
.manual_fallback_option =
AutofillSuggestionTriggerSource::kManualFallbackPayments,
.expected_main_filling_product = FillingProduct::kCreditCard,
.test_name = "_AddressField_CreditCard"}})),
[](const ::testing::TestParamInfo<ManualFallbackTest::ParamType>& info) {
return info.param.test_name;
});
// Test that we call duplicate profile suggestions.
TEST_P(SuggestionMatchingTest, GetProfileSuggestions_WithDuplicates) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// Add a duplicate profile.
AutofillProfile duplicate_profile =
*personal_data().GetProfileByGUID(MakeGuid(1));
personal_data().AddProfile(duplicate_profile);
GetAutofillSuggestions(form, form.fields[0]);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("Charles", "123 Apple St.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
Suggestion("Elvis", "3734 Elvis Presley Blvd.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
}
// Test that we return no suggestions when autofill is disabled.
TEST_F(BrowserAutofillManagerTest,
GetProfileSuggestions_AutofillDisabledByUser) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// Disable Autofill.
browser_autofill_manager_->SetAutofillProfileEnabled(autofill_client_, false);
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
false);
GetAutofillSuggestions(form, form.fields[0]);
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
TEST_F(BrowserAutofillManagerTest,
OnSuggestionsReturned_CallsExternalDelegate) {
FormData form = CreateTestAddressFormData();
form.fields = {CreateTestFormField("Some Field", "somefield", "",
FormControlType::kInputText)};
FormsSeen({form});
std::vector<Suggestion> suggestions = {Suggestion(u"one"),
Suggestion(u"two")};
// Mock returning some autocomplete `suggestions`.
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.WillOnce([&](const FormFieldData& field, const AutofillClient& client,
SingleFieldFormFiller::OnSuggestionsReturnedCallback
on_suggestions_returned,
const SuggestionsContext& context) {
std::move(on_suggestions_returned).Run(field.global_id(), suggestions);
return true;
});
GetAutofillSuggestions(
form, form.fields[0],
AutofillSuggestionTriggerSource::kFormControlElementClicked);
EXPECT_EQ(external_delegate()->trigger_source(),
AutofillSuggestionTriggerSource::kFormControlElementClicked);
external_delegate()->CheckSuggestions(form.fields[0].global_id(),
{suggestions[0], suggestions[1]});
}
class BrowserAutofillManagerTestForMetadataCardSuggestions
: public BrowserAutofillManagerTest,
public testing::WithParamInterface<bool> {
public:
BrowserAutofillManagerTestForMetadataCardSuggestions() {
if (IsMetadataEnabled()) {
card_metadata_flags_.InitWithFeatures(
/*enabled_features=*/{features::kAutofillEnableVirtualCardMetadata,
features::kAutofillEnableCardProductName,
features::kAutofillEnableCardArtImage},
/*disabled_features=*/{});
} else {
card_metadata_flags_.InitWithFeatures(
/*enabled_features=*/{},
/*=disabled_features=*/{features::kAutofillEnableVirtualCardMetadata,
features::kAutofillEnableCardProductName,
features::kAutofillEnableCardArtImage});
}
}
bool IsMetadataEnabled() const { return GetParam(); }
private:
base::test::ScopedFeatureList card_metadata_flags_;
};
INSTANTIATE_TEST_SUITE_P(All,
BrowserAutofillManagerTestForMetadataCardSuggestions,
::testing::Bool());
// Test that we return all credit card profile suggestions when all form fields
// are empty.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_EmptyValue) {
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[1]);
// Test that we sent the credit card suggestions to the external delegate.
external_delegate()->CheckSuggestions(
form.fields[1].global_id(),
{GetCardSuggestion(kVisaCard), GetCardSuggestion(kMasterCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return all credit card profile suggestions when the triggering
// field has whitespace in it.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_Whitespace) {
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
FormFieldData field = form.fields[1];
field.value = u" ";
GetAutofillSuggestions(form, field);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
field.global_id(),
{GetCardSuggestion(kVisaCard), GetCardSuggestion(kMasterCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return all credit card profile suggestions when the triggering
// field has stop characters in it, which should be removed.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_StopCharsOnly) {
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
FormFieldData field = form.fields[1];
field.value = u"____-____-____-____";
GetAutofillSuggestions(form, field);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
field.global_id(),
{GetCardSuggestion(kVisaCard), GetCardSuggestion(kMasterCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return all credit card profile suggestions when the triggering
// field has some invisible unicode characters in it.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_InvisibleUnicodeOnly) {
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
FormFieldData field = form.fields[1];
field.value = std::u16string({0x200E, 0x200F});
GetAutofillSuggestions(form, field);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
field.global_id(),
{GetCardSuggestion(kVisaCard), GetCardSuggestion(kMasterCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return all credit card profile suggestions when the triggering
// field has stop characters in it and some input.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_StopCharsWithInput) {
// Add a credit card with particular numbers that we will attempt to recall.
CreditCard credit_card;
test::SetCreditCardInfo(&credit_card, "John Smith",
"5255667890168765", // Mastercard
"10", "2098", "1");
credit_card.set_guid(MakeGuid(7));
personal_data().AddCreditCard(credit_card);
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
FormFieldData field = form.fields[1];
field.value = u"5255-66__-____-____";
GetAutofillSuggestions(form, field);
// Test that we sent the right value to the external delegate.
external_delegate()->CheckSuggestions(
field.global_id(),
{GetCardSuggestion(kMasterCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return only matching credit card profile suggestions when the
// selected form field has been partially filled out.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_MatchCharacter) {
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
FormFieldData field = CreateTestFormField("Card Number", "cardnumber", "78",
FormControlType::kInputText);
GetAutofillSuggestions(form, field);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
field.global_id(),
{GetCardSuggestion(kVisaCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return credit card profile suggestions when the selected form
// field is the credit card number field.
TEST_F(CreditCardSuggestionTest, GetCreditCardSuggestions_CCNumber) {
// Set nickname with the corresponding guid of the Mastercard 8765.
personal_data().SetNicknameForCardWithGUID(MakeGuid(5), kArbitraryNickname);
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
const FormFieldData& credit_card_number_field = form.fields[1];
GetAutofillSuggestions(form, credit_card_number_field);
const std::string visa_value =
std::string("Visa ") +
test::ObfuscatedCardDigitsAsUTF8(
"3456", ObfuscationLengthForCreditCardLastFourDigits());
// Mastercard has a valid nickname. Display nickname + last four in the
// suggestion title.
const std::string master_card_value =
kArbitraryNickname + " " +
test::ObfuscatedCardDigitsAsUTF8(
"8765", ObfuscationLengthForCreditCardLastFourDigits());
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
const std::string visa_label = std::string("04/99");
const std::string master_card_label = std::string("10/98");
#else
const std::string visa_label = std::string("Expires on 04/99");
const std::string master_card_label = std::string("Expires on 10/98");
#endif
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
credit_card_number_field.global_id(),
{Suggestion(visa_value, visa_label, Suggestion::Icon::kCardVisa,
PopupItemId::kCreditCardEntry),
Suggestion(master_card_value, master_card_label,
Suggestion::Icon::kCardMasterCard,
PopupItemId::kCreditCardEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return credit card profile suggestions when the selected form
// field is not the credit card number field.
TEST_F(CreditCardSuggestionTest, GetCreditCardSuggestions_NonCCNumber) {
// Set nickname with the corresponding guid of the Mastercard 8765.
personal_data().SetNicknameForCardWithGUID(MakeGuid(5), kArbitraryNickname);
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
const FormFieldData& cardholder_name_field = form.fields[0];
GetAutofillSuggestions(form, cardholder_name_field);
const std::string obfuscated_last_four_digits1 =
test::ObfuscatedCardDigitsAsUTF8(
"3456", ObfuscationLengthForCreditCardLastFourDigits());
const std::string obfuscated_last_four_digits2 =
test::ObfuscatedCardDigitsAsUTF8(
"8765", ObfuscationLengthForCreditCardLastFourDigits());
#if BUILDFLAG(IS_ANDROID)
// For Android always show obfuscated last four.
const std::string visa_label = obfuscated_last_four_digits1;
// Mastercard has a valid nickname.
const std::string master_card_label = obfuscated_last_four_digits2;
#elif BUILDFLAG(IS_IOS)
const std::string visa_label = obfuscated_last_four_digits1;
const std::string master_card_label = obfuscated_last_four_digits2;
#else
// If no nickname available, we will show network.
const std::string visa_label = base::JoinString(
{"Visa ", obfuscated_last_four_digits1, ", expires on 04/99"}, "");
// When nickname is available, show nickname. Otherwise, show network.
const std::string master_card_label =
base::JoinString({kArbitraryNickname + " ", obfuscated_last_four_digits2,
", expires on 10/98"},
"");
#endif
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
cardholder_name_field.global_id(),
{Suggestion("Elvis Presley", visa_label, Suggestion::Icon::kCardVisa,
PopupItemId::kCreditCardEntry),
Suggestion("Buddy Holly", master_card_label,
Suggestion::Icon::kCardMasterCard,
PopupItemId::kCreditCardEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return a warning explaining that credit card profile suggestions
// are unavailable when the page is secure, but the form action URL is valid but
// not secure.
TEST_F(BrowserAutofillManagerTest,
GetCreditCardSuggestions_SecureContext_FormActionNotHTTPS) {
// Set up our form data.
FormData form = CreateTestCreditCardFormData(/*is_https=*/true, false);
// However we set the action (target URL) to be HTTP after all.
form.action = GURL("http://myform.com/submit.html");
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion(l10n_util::GetStringUTF8(IDS_AUTOFILL_WARNING_MIXED_FORM), "",
Suggestion::Icon::kNoIcon, PopupItemId::kMixedFormMessage)});
// Clear the test credit cards and try again -- we should still show the
// mixed form warning.
personal_data().ClearCreditCards();
GetAutofillSuggestions(form, form.fields[0]);
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion(l10n_util::GetStringUTF8(IDS_AUTOFILL_WARNING_MIXED_FORM), "",
Suggestion::Icon::kNoIcon, PopupItemId::kMixedFormMessage)});
}
// Test that we return credit card suggestions for secure pages that have an
// empty form action target URL.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_SecureContext_EmptyFormAction) {
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
// Clear the form action.
form.action = GURL();
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[1]);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
form.fields[1].global_id(),
{GetCardSuggestion(kVisaCard), GetCardSuggestion(kMasterCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return credit card suggestions for secure pages that have a
// form action set to "javascript:something".
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_SecureContext_JavascriptFormAction) {
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
// Have the form action be a javascript function (which is a valid URL).
form.action = GURL("javascript:alert('Hello');");
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[1]);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
form.fields[1].global_id(),
{GetCardSuggestion(kVisaCard), GetCardSuggestion(kMasterCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return all credit card suggestions in the case that two cards
// have the same obfuscated number.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_RepeatedObfuscatedNumber) {
// Add a credit card with the same obfuscated number as Elvis's.
// |credit_card| will be owned by the mock PersonalDataManager.
CreditCard credit_card;
test::SetCreditCardInfo(&credit_card, "Elvis Presley",
"5255667890168765", // Mastercard
"10", "2098", "1");
credit_card.set_guid(MakeGuid(7));
credit_card.set_use_date(AutofillClock::Now() - base::Days(15));
personal_data().AddCreditCard(credit_card);
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[1]);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
form.fields[1].global_id(),
{GetCardSuggestion(kVisaCard), GetCardSuggestion(kMasterCard),
GetCardSuggestion(kMasterCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that a masked server card is not suggested if more than six digits
// have been typed in the field.
TEST_F(BrowserAutofillManagerTest,
GetCreditCardSuggestions_MaskedCardWithMoreThan6Digits) {
// Add a masked server card.
personal_data().ClearCreditCards();
CreditCard masked_server_card;
test::SetCreditCardInfo(&masked_server_card, "Elvis Presley",
"4234567890123456", // Visa
"04", "2999", "1");
masked_server_card.set_guid(MakeGuid(7));
masked_server_card.set_record_type(CreditCard::RecordType::kMaskedServerCard);
personal_data().AddServerCreditCard(masked_server_card);
EXPECT_EQ(1U, personal_data().GetCreditCards().size());
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
FormFieldData field = form.fields[1];
field.value = u"12345678";
GetAutofillSuggestions(form, field);
external_delegate()->CheckNoSuggestions(field.global_id());
}
// Test that expired cards are ordered by their ranking score and are always
// suggested after non expired cards even if they have a higher ranking score.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_ExpiredCards) {
personal_data().ClearCreditCards();
// Add a never used non expired credit card.
CreditCard credit_card0("002149C1-EE28-4213-A3B9-DA243FFF021B",
test::kEmptyOrigin);
test::SetCreditCardInfo(&credit_card0, "Bonnie Parker",
"5105105105108765" /* Mastercard */, "10", "2098",
"1");
credit_card0.set_guid(MakeGuid(1));
personal_data().AddCreditCard(credit_card0);
// Add an expired card with a higher ranking score.
CreditCard credit_card1("287151C8-6AB1-487C-9095-28E80BE5DA15",
test::kEmptyOrigin);
test::SetCreditCardInfo(&credit_card1, "Clyde Barrow",
"378282246310005" /* American Express */, "04",
"2010", "1");
credit_card1.set_guid(MakeGuid(2));
credit_card1.set_use_count(300);
credit_card1.set_use_date(AutofillClock::Now() - base::Days(10));
personal_data().AddCreditCard(credit_card1);
// Add an expired card with a lower ranking score.
CreditCard credit_card2("1141084B-72D7-4B73-90CF-3D6AC154673B",
test::kEmptyOrigin);
credit_card2.set_use_count(3);
credit_card2.set_use_date(AutofillClock::Now() - base::Days(1));
test::SetCreditCardInfo(&credit_card2, "John Dillinger",
"4234567890123456" /* Visa */, "04", "2011", "1");
credit_card2.set_guid(MakeGuid(3));
personal_data().AddCreditCard(credit_card2);
ASSERT_EQ(3U, personal_data().GetCreditCards().size());
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[1]);
Suggestion visa_suggestion = GenerateSuggestionFromCardDetails(
kVisaCard, Suggestion::Icon::kCardVisa, "3456", "04/11");
Suggestion amex_suggestion = GetCardSuggestion(kAmericanExpressCard);
Suggestion mastercard_suggestion = GetCardSuggestion(kMasterCard);
// Test that we sent the credit card suggestions to the external delegate.
external_delegate()->CheckSuggestions(
form.fields[1].global_id(),
{mastercard_suggestion, amex_suggestion, visa_suggestion,
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test cards that are expired AND disused are suppressed when suppression is
// enabled and the input field is empty.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_SuppressDisusedCreditCardsOnEmptyField) {
personal_data().ClearCreditCards();
ASSERT_EQ(0U, personal_data().GetCreditCards().size());
// Add a never used non expired local credit card.
CreditCard credit_card0(MakeGuid(0), test::kEmptyOrigin);
test::SetCreditCardInfo(&credit_card0, "Bonnie Parker",
"5105105105105100" /* Mastercard */, "04", "2999",
"1");
personal_data().AddCreditCard(credit_card0);
auto now = AutofillClock::Now();
// Add an expired local card last used 10 days ago
CreditCard credit_card1(MakeGuid(1), test::kEmptyOrigin);
test::SetCreditCardInfo(&credit_card1, "Clyde Barrow",
"4234567890123456" /* Visa */, "04", "2010", "1");
credit_card1.set_use_date(now - base::Days(10));
personal_data().AddCreditCard(credit_card1);
// Add an expired local card last used 180 days ago.
CreditCard credit_card2(MakeGuid(2), test::kEmptyOrigin);
credit_card2.set_use_date(now - base::Days(182));
test::SetCreditCardInfo(&credit_card2, "John Dillinger",
"378282246310005" /* American Express */, "01",
"2010", "1");
personal_data().AddCreditCard(credit_card2);
ASSERT_EQ(3U, personal_data().GetCreditCards().size());
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
// Query with empty string only returns card0 and card1. Note expired
// masked card2 is not suggested on empty fields.
{
GetAutofillSuggestions(form, form.fields[0]);
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("Bonnie Parker", GenerateLabelsFromCreditCard(credit_card0),
Suggestion::Icon::kCardMasterCard,
PopupItemId::kCreditCardEntry),
Suggestion("Clyde Barrow", GenerateLabelsFromCreditCard(credit_card1),
Suggestion::Icon::kCardVisa, PopupItemId::kCreditCardEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Query with name prefix for card0 returns card0.
{
FormFieldData field = form.fields[0];
field.value = u"B";
GetAutofillSuggestions(form, field);
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("Bonnie Parker", GenerateLabelsFromCreditCard(credit_card0),
Suggestion::Icon::kCardMasterCard,
PopupItemId::kCreditCardEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Query with name prefix for card1 returns card1.
{
FormFieldData field = form.fields[0];
field.value = u"Cl";
GetAutofillSuggestions(form, field);
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("Clyde Barrow", GenerateLabelsFromCreditCard(credit_card1),
Suggestion::Icon::kCardVisa, PopupItemId::kCreditCardEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Query with name prefix for card2 returns card2.
{
FormFieldData field = form.fields[0];
field.value = u"Jo";
GetAutofillSuggestions(form, field);
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("John Dillinger",
GenerateLabelsFromCreditCard(credit_card2),
Suggestion::Icon::kCardAmericanExpress,
PopupItemId::kCreditCardEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
}
// Test that a card that doesn't have a number is not shown in the
// suggestions when querying credit cards by their number, and is shown when
// querying other fields.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_NumberMissing) {
// Create one normal credit card and one credit card with the number
// missing.
personal_data().ClearCreditCards();
ASSERT_EQ(0U, personal_data().GetCreditCards().size());
CreditCard credit_card0("287151C8-6AB1-487C-9095-28E80BE5DA15",
test::kEmptyOrigin);
test::SetCreditCardInfo(&credit_card0, "Clyde Barrow",
"378282246310005" /* American Express */, "04",
"2910", "1");
credit_card0.set_guid(MakeGuid(1));
credit_card0.set_use_date(AutofillClock::Now() - base::Days(1));
personal_data().AddCreditCard(credit_card0);
CreditCard credit_card1("1141084B-72D7-4B73-90CF-3D6AC154673B",
test::kEmptyOrigin);
test::SetCreditCardInfo(&credit_card1, "John Dillinger", "", "01", "2999",
"1");
credit_card1.set_guid(MakeGuid(2));
personal_data().AddCreditCard(credit_card1);
ASSERT_EQ(2U, personal_data().GetCreditCards().size());
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
// Query by card number field.
GetAutofillSuggestions(form, form.fields[1]);
// Sublabel is expiration date when filling card number. The second card
// doesn't have a number so it should not be included in the suggestions.
external_delegate()->CheckSuggestions(
form.fields[1].global_id(),
{GetCardSuggestion(kAmericanExpressCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
// Query by cardholder name field.
GetAutofillSuggestions(form, form.fields[0]);
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("John Dillinger", "", Suggestion::Icon::kCardGeneric,
PopupItemId::kCreditCardEntry),
Suggestion("Clyde Barrow", GenerateLabelsFromCreditCard(credit_card0),
Suggestion::Icon::kCardAmericanExpress,
PopupItemId::kCreditCardEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that we return profile and credit card suggestions for combined forms.
TEST_P(SuggestionMatchingTest, GetAddressAndCreditCardSuggestions) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
CreateTestCreditCardFormData(&form, true, false);
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("Charles", "123 Apple St.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
Suggestion("Elvis", "3734 Elvis Presley Blvd.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
FormFieldData field = CreateTestFormField("Card Number", "cardnumber", "",
FormControlType::kInputText);
GetAutofillSuggestions(form, field);
// Test that we sent the credit card suggestions to the external delegate.
external_delegate()->CheckSuggestions(
field.global_id(),
{GetCardSuggestion(kVisaCard), GetCardSuggestion(kMasterCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that for non-https forms with both address and credit card fields, we
// only return address suggestions. Instead of credit card suggestions, we
// should return a warning explaining that credit card profile suggestions are
// unavailable when the form is not https.
TEST_F(BrowserAutofillManagerTest, GetAddressAndCreditCardSuggestionsNonHttps) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
CreateTestCreditCardFormData(&form, false, false);
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
// Verify that suggestions are returned.
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
FormFieldData field = CreateTestFormField("Card Number", "cardnumber", "",
FormControlType::kInputText);
GetAutofillSuggestions(form, field);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
field.global_id(),
{Suggestion(
l10n_util::GetStringUTF8(IDS_AUTOFILL_WARNING_INSECURE_CONNECTION),
"", Suggestion::Icon::kNoIcon,
PopupItemId::kInsecureContextPaymentDisabledMessage)});
// Clear the test credit cards and try again -- we shouldn't return a warning.
personal_data().ClearCreditCards();
GetAutofillSuggestions(form, field);
external_delegate()->CheckNoSuggestions(field.global_id());
}
TEST_F(BrowserAutofillManagerTest,
ShouldShowAddressSuggestionsIfCreditCardAutofillDisabled) {
base::test::ScopedFeatureList features;
DisableAutofillViaAblation(features, /*for_addresses=*/false,
/*for_credit_cards=*/true);
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
// Verify that suggestions are returned.
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
TEST_F(BrowserAutofillManagerTest,
ShouldShowCreditCardSuggestionsIfAddressAutofillDisabled) {
base::test::ScopedFeatureList features;
DisableAutofillViaAblation(features, /*for_addresses=*/true,
/*for_credit_cards=*/false);
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
// Verify that suggestions are returned.
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
// Tests that BrowserAutofillManager ignores loss of focus events sent from the
// renderer if the renderer did not have a previously-interacted form.
// TODO(crbug.com/1140473): Remove this test when workaround is no longer
// needed.
TEST_F(BrowserAutofillManagerTest,
ShouldIgnoreLossOfFocusWithNoPreviouslyInteractedForm) {
FormData form = CreateTestAddressFormData();
browser_autofill_manager_->UpdatePendingForm(form);
ASSERT_TRUE(test_api(*browser_autofill_manager_)
.pending_form_data()
->SameFormAs(form));
// Receiving a notification that focus is no longer on the form *without* the
// renderer having a previously-interacted form should not result in
// any changes to the pending form.
browser_autofill_manager_->OnFocusNoLongerOnForm(
/*had_interacted_form=*/false);
EXPECT_TRUE(test_api(*browser_autofill_manager_)
.pending_form_data()
->SameFormAs(form));
}
TEST_F(BrowserAutofillManagerTest,
ShouldNotShowCreditCardsSuggestionsIfCreditCardAutofillDisabled) {
base::test::ScopedFeatureList scoped_feature_list;
DisableAutofillViaAblation(scoped_feature_list, /*for_addresses=*/false,
/*for_credit_cards=*/true);
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
// Check that credit card suggestions will not be available.
external_delegate()->CheckNoSuggestions(form.fields[0].global_id());
}
TEST_F(BrowserAutofillManagerTest,
ShouldNotShowAddressSuggestionsIfAddressAutofillDisabled) {
base::test::ScopedFeatureList scoped_feature_list;
DisableAutofillViaAblation(scoped_feature_list, /*for_addresses=*/true,
/*for_credit_cards=*/false);
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
// Check that credit card suggestions will not be available.
external_delegate()->CheckNoSuggestions(form.fields[0].global_id());
}
struct LogAblationTestParams {
const char* description;
// Whether any autofillable data is stored.
bool run_with_data_on_file = true;
// If true, the credit card owner name field is filled with value that is not
// a prefix of any stored credit card and then autofill suggestions are
// queried a second time.
bool second_query_for_suggestions_with_typed_prefix = false;
// Whether the form should be submitted before validating the metrics.
bool submit_form = true;
};
enum class LogAblationFormType {
kAddress,
kPayment,
kMixed, // address fields followed by payment fields
};
class BrowserAutofillManagerLogAblationTest
: public BrowserAutofillManagerTest,
public testing::WithParamInterface<
std::tuple<LogAblationTestParams, LogAblationFormType>> {
public:
BrowserAutofillManagerLogAblationTest() = default;
~BrowserAutofillManagerLogAblationTest() override = default;
};
// Validate that UMA logging works correctly for ablation studies.
INSTANTIATE_TEST_SUITE_P(
All,
BrowserAutofillManagerLogAblationTest,
testing::Combine(
testing::Values(
// Test that if autofillable data is stored and the ablation is
// enabled, we record metrics as expected.
LogAblationTestParams{.description = "Having data to fill"},
// Test that if NO autofillable data is stored and the ablation is
// enabled, we record "UnconditionalAblation" metrics but no
// "ConditionalAblation" metrics. The latter only recoded on the
// condition that we have data to fill.
LogAblationTestParams{.description = "Having NO data to fill",
.run_with_data_on_file = false},
// In this test we trigger the GetAutofillSuggestions call twice. By
// the second time the user has typed a value that is not the prefix
// of any existing autofill data. This means that autofill would not
// create any suggestions. We still want to consider this a
// conditional ablation (the condition to have fillable data on file
// is met).
LogAblationTestParams{
.description = "Typed unknown prefix",
.second_query_for_suggestions_with_typed_prefix = false},
// Test that the right events are recorded in case the user
// interacts with a form but does not submit it.
LogAblationTestParams{.description = "No form submission",
.submit_form = false}),
testing::Values(LogAblationFormType::kAddress,
LogAblationFormType::kPayment,
LogAblationFormType::kMixed)));
TEST_P(BrowserAutofillManagerLogAblationTest, TestLogging) {
const LogAblationTestParams& params = std::get<0>(GetParam());
LogAblationFormType form_type = std::get<1>(GetParam());
SCOPED_TRACE(testing::Message() << params.description << " Form type: "
<< static_cast<int>(form_type));
if (!params.run_with_data_on_file) {
personal_data().ClearAllServerDataForTesting();
personal_data().ClearAllLocalData();
}
base::test::ScopedFeatureList scoped_feature_list;
DisableAutofillViaAblation(scoped_feature_list, /*for_addresses=*/true,
/*for_credit_cards=*/true);
base::HistogramTester histogram_tester;
// Set up our form data. In the kMixed case the form will contain the fields
// of an address form followed by the fields of fields of a payment form. The
// triggering for autofill suggestions will happen on an address field in this
// case.
FormData form;
if (form_type == LogAblationFormType::kAddress ||
form_type == LogAblationFormType::kMixed) {
form = CreateTestAddressFormData();
}
if (form_type == LogAblationFormType::kPayment ||
form_type == LogAblationFormType::kMixed) {
CreateTestCreditCardFormData(&form, true, false);
}
FormsSeen({form});
// Simulate retrieving autofill suggestions with the first field as a trigger
// script. This should emit signals that lead to recorded metrics later on.
FormFieldData field = form.fields[0];
GetAutofillSuggestions(form, field);
// Simulate user typing into field (due to the ablation we would not fill).
field.value = u"Unknown User";
browser_autofill_manager_->OnTextFieldDidChange(form, field, gfx::RectF(),
base::TimeTicks::Now());
if (params.second_query_for_suggestions_with_typed_prefix) {
// Do another lookup. We won't have any suggestions because they would not
// be compatible with the "Unknown User" username.
GetAutofillSuggestions(form, field);
}
// Advance time and possibly submit the form.
base::TimeDelta time_delta = base::Seconds(42);
task_environment_.FastForwardBy(time_delta);
if (params.submit_form)
FormSubmitted(form);
// Flush FormEventLoggers.
browser_autofill_manager_->Reset();
// Validate the recorded metrics.
std::string form_type_str = (form_type == LogAblationFormType::kAddress ||
form_type == LogAblationFormType::kMixed)
? "Address"
: "CreditCard";
// If data was on file, we expect conditional ablation metrics.
if (params.run_with_data_on_file) {
histogram_tester.ExpectUniqueSample(
"Autofill.Ablation.FormSubmissionAfterInteraction." + form_type_str +
".ConditionalAblation",
/*sample=*/params.submit_form ? 1 : 0,
/*expected_bucket_count=*/1);
} else {
histogram_tester.ExpectTotalCount(
"Autofill.Ablation.FormSubmissionAfterInteraction." + form_type_str +
".ConditionalAblation",
/*expected_count=*/0);
}
// Only if data was on file an a submission happened, we can record the
// duration from interaction to submission.
if (params.run_with_data_on_file && params.submit_form) {
histogram_tester.ExpectUniqueTimeSample(
"Autofill.Ablation.FillDurationSinceInteraction." + form_type_str +
".ConditionalAblation",
time_delta,
/*expected_bucket_count=*/1);
} else {
histogram_tester.ExpectTotalCount(
"Autofill.Ablation.FillDurationSinceInteraction." + form_type_str +
".ConditionalAblation",
/*expected_count=*/0);
}
// The unconditional ablation metrics should always be logged as this the
// ablation study is always enabled.
histogram_tester.ExpectUniqueSample(
"Autofill.Ablation.FormSubmissionAfterInteraction." + form_type_str +
".UnconditionalAblation",
/*sample=*/params.submit_form ? 1 : 0,
/*expected_bucket_count=*/1);
// Expect a time from interaction to submission the form was submitted.
if (params.submit_form) {
histogram_tester.ExpectUniqueTimeSample(
"Autofill.Ablation.FillDurationSinceInteraction." + form_type_str +
".UnconditionalAblation",
time_delta,
/*expected_bucket_count=*/1);
} else {
histogram_tester.ExpectTotalCount(
"Autofill.Ablation.FillDurationSinceInteraction." + form_type_str +
".UnconditionalAblation",
/*expected_count=*/0);
}
// Ensure that no metrics are recorded for the complementary form type.
// I.e. if the trigger element is of an address form, the credit card form
// should have no metrics.
std::string complementary_form_type_str =
(form_type == LogAblationFormType::kAddress ||
form_type == LogAblationFormType::kMixed)
? "CreditCard"
: "Address";
for (const char* metric :
{"FormSubmissionAfterInteraction", "FillDurationSinceInteraction"}) {
for (const char* ablation_type :
{"UnconditionalAblation", "ConditionalAblation"}) {
histogram_tester.ExpectTotalCount(
base::StrCat({"Autofill.Ablation.", metric, ".",
complementary_form_type_str.c_str(), ".",
ablation_type}),
/*expected_count=*/0);
}
}
}
// Test that we properly match typed values to stored state data.
TEST_F(BrowserAutofillManagerTest, DetermineStateFieldTypeForUpload) {
test::ClearAlternativeStateNameMapForTesting();
test::PopulateAlternativeStateNameMapForTesting();
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile, "", "", "", "", "", "", "", "", "Bavaria", "",
"DE", "");
const char* const kValidMatches[] = {"by", "Bavaria", "Bayern",
"BY", "B.Y", "B-Y"};
for (const char* valid_match : kValidMatches) {
SCOPED_TRACE(valid_match);
FormData form;
form.fields = {CreateTestFormField("Name", "Name", "Test",
FormControlType::kInputText),
CreateTestFormField("State", "state", valid_match,
FormControlType::kInputText)};
FormStructure form_structure(form);
EXPECT_EQ(form_structure.field_count(), 2U);
test_api(*browser_autofill_manager_)
.PreProcessStateMatchingTypes({profile}, &form_structure);
EXPECT_TRUE(form_structure.field(1)->state_is_a_matching_type());
}
const char* const kInvalidMatches[] = {"Garbage", "BYA", "BYA is a state",
"Bava", "Empty", ""};
for (const char* invalid_match : kInvalidMatches) {
SCOPED_TRACE(invalid_match);
FormData form;
form.fields = {CreateTestFormField("Name", "Name", "Test",
FormControlType::kInputText),
CreateTestFormField("State", "state", invalid_match,
FormControlType::kInputText)};
FormStructure form_structure(form);
EXPECT_EQ(form_structure.field_count(), 2U);
test_api(*browser_autofill_manager_)
.PreProcessStateMatchingTypes({profile}, &form_structure);
EXPECT_FALSE(form_structure.field(1)->state_is_a_matching_type());
}
test::PopulateAlternativeStateNameMapForTesting(
"US", "California",
{{.canonical_name = "California",
.abbreviations = {"CA"},
.alternative_names = {}}});
test::SetProfileInfo(&profile, "", "", "", "", "", "", "", "", "California",
"", "US", "");
FormData form;
form.fields = {
CreateTestFormField("Name", "Name", "Test", FormControlType::kInputText),
CreateTestFormField("State", "state", "CA", FormControlType::kInputText)};
FormStructure form_structure(form);
EXPECT_EQ(form_structure.field_count(), 2U);
test_api(*browser_autofill_manager_)
.PreProcessStateMatchingTypes({profile}, &form_structure);
EXPECT_TRUE(form_structure.field(1)->state_is_a_matching_type());
}
// Ensures that if autofill is disabled but the password manager is enabled,
// Autofill still performs a lookup to the server.
TEST_F(BrowserAutofillManagerTest,
OnFormsSeen_AutofillDisabledPasswordManagerEnabled) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
// Disable autofill and the password manager.
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
false);
browser_autofill_manager_->SetAutofillProfileEnabled(autofill_client_, false);
ON_CALL(autofill_client_, IsPasswordManagerEnabled())
.WillByDefault(Return(false));
// As neither autofill nor password manager are enabled, the form should
// not be parsed.
{
base::HistogramTester histogram_tester;
FormsSeen({form});
EXPECT_EQ(0, histogram_tester.GetBucketCount("Autofill.UserHappiness",
0 /* FORMS_LOADED */));
}
// Now enable the password manager.
ON_CALL(autofill_client_, IsPasswordManagerEnabled())
.WillByDefault(Return(true));
// If the password manager is enabled, that's enough to parse the form.
{
base::HistogramTester histogram_tester;
EXPECT_CALL(*crowdsourcing_manager(), StartQueryRequest).Times(AnyNumber());
EXPECT_CALL(
*crowdsourcing_manager(),
StartQueryRequest(
ElementsAre(FormStructureHasRendererId(form.renderer_id)), _, _));
FormsSeen({form});
histogram_tester.ExpectUniqueSample("Autofill.UserHappiness",
0 /* FORMS_LOADED */, 1);
}
}
// Test that we return normal Autofill suggestions when trying to autofill
// already filled forms.
TEST_P(SuggestionMatchingTest, GetFieldSuggestionsWhenFormIsAutofilled) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// Mark one of the fields as filled.
form.fields[2].is_autofilled = true;
GetAutofillSuggestions(form, form.fields[0]);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{Suggestion("Charles", "123 Apple St.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
Suggestion("Elvis", "3734 Elvis Presley Blvd.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
}
// The method `AutofillSuggestionGenerator::GetPrefixMatchedProfiles` prevents
// that Android users see values that would override already filled fields
// due to the narrow surface and a missing preview.
#if !BUILDFLAG(IS_ANDROID)
// Test that we do not return duplicate values drawn from multiple profiles when
// filling an already filled field.
TEST_P(SuggestionMatchingTest, GetFieldSuggestionsWithDuplicateValues) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// |profile| will be owned by the mock PersonalDataManager.
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
test::SetProfileInfo(&profile, "Elvis", "", "", "", "", "", "", "", "", "",
"", "");
profile.set_guid(MakeGuid(101));
personal_data().AddProfile(profile);
FormFieldData& field = form.fields[0];
field.is_autofilled = true;
field.value = u"Elvis";
GetAutofillSuggestions(form, field);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
field.global_id(),
{Suggestion("Elvis", "3734 Elvis Presley Blvd.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateClearFormSuggestion(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
}
#endif
// Tests that we return email profile suggestions values
// when the email field with username autocomplete attribute exist.
TEST_F(BrowserAutofillManagerTest,
GetProfileSuggestions_ForEmailFieldWithUserNameAutocomplete) {
// Set up our form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
struct {
const char* const label;
const char* const name;
size_t max_length;
const char* const autocomplete_attribute;
} test_fields[] = {{"First Name", "firstname", 30, "given-name"},
{"Last Name", "lastname", 30, "family-name"},
{"Email", "email", 30, "username"},
{"Password", "password", 30, "new-password"}};
for (const auto& test_field : test_fields) {
FormControlType field_type = strcmp(test_field.name, "password") == 0
? FormControlType::kInputPassword
: FormControlType::kInputText;
form.fields.push_back(CreateTestFormField(
test_field.label, test_field.name, "", field_type,
test_field.autocomplete_attribute, test_field.max_length));
}
FormsSeen({form});
personal_data().ClearProfiles();
AutofillProfile profile(i18n_model_definition::kLegacyHierarchyCountryCode);
profile.set_guid(MakeGuid(103));
profile.SetRawInfo(NAME_FULL, u"Natty Bumppo");
profile.SetRawInfo(EMAIL_ADDRESS, u"test@example.com");
personal_data().AddProfile(profile);
GetAutofillSuggestions(form, form.fields[2]);
external_delegate()->CheckSuggestions(
form.fields[2].global_id(),
{Suggestion("test@example.com", "Natty Bumppo", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
}
// Tests that fields with unrecognized autocomplete attribute don't contribute
// to key metrics.
TEST_F(BrowserAutofillManagerTest, AutocompleteUnrecognizedFields_KeyMetrics) {
// Create an address form where field 1 has an unrecognized autocomplete
// attribute.
FormData form = CreateTestAddressFormData();
ASSERT_GE(form.fields.size(), 2u);
form.fields[1].parsed_autocomplete =
AutocompleteParsingResult{.field_type = HtmlFieldType::kUnrecognized};
// Interact with an ac != unrecognized field: Expect key metrics to be
// emitted. Note that "interacting" means querying suggestions, usually
// caused by clicking into a field.
{
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
FormSubmitted(form);
base::HistogramTester histogram_tester;
browser_autofill_manager_->Reset();
histogram_tester.ExpectTotalCount(
"Autofill.KeyMetrics.FillingAssistance.Address", 1);
}
// Interact with an ac = unrecognized field: Expect no key metric to be
// emitted.
{
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[1]);
FormSubmitted(form);
base::HistogramTester histogram_tester;
browser_autofill_manager_->Reset();
histogram_tester.ExpectTotalCount(
"Autofill.KeyMetrics.FillingAssistance.Address", 0);
}
}
TEST_F(BrowserAutofillManagerTest,
OnCreditCardFetchedSuccessfully_LocalCreditCard) {
const CreditCard local_card = test::GetCreditCard();
EXPECT_CALL(autofill_client_, OnVirtualCardDataAvailable).Times(0);
browser_autofill_manager_->OnCreditCardFetchedSuccessfully(local_card);
EXPECT_THAT(test_api(form_data_importer()).fetched_card_instrument_id(),
testing::Optional(local_card.instrument_id()));
}
TEST_F(BrowserAutofillManagerTest,
OnCreditCardFetchedSuccessfully_ServerCreditCard) {
const CreditCard server_card = test::GetMaskedServerCard();
EXPECT_CALL(autofill_client_, OnVirtualCardDataAvailable).Times(0);
browser_autofill_manager_->OnCreditCardFetchedSuccessfully(server_card);
EXPECT_THAT(test_api(form_data_importer()).fetched_card_instrument_id(),
testing::Optional(server_card.instrument_id()));
}
TEST_F(BrowserAutofillManagerTest,
OnCreditCardFetchedSuccessfully_VirtualCreditCard) {
const CreditCard virtual_card = test::WithCvc(test::GetVirtualCard());
using Options = VirtualCardManualFallbackBubbleOptions;
EXPECT_CALL(
autofill_client_,
OnVirtualCardDataAvailable(
AllOf(Field(&Options::masked_card_name,
virtual_card.CardNameForAutofillDisplay()),
Field(&Options::masked_card_number_last_four,
virtual_card.ObfuscatedNumberWithVisibleLastFourDigits()),
Field(&Options::virtual_card_cvc, virtual_card.cvc()),
Field(&Options::virtual_card, virtual_card))));
browser_autofill_manager_->OnCreditCardFetchedSuccessfully(virtual_card);
EXPECT_THAT(test_api(form_data_importer()).fetched_card_instrument_id(),
testing::Optional(virtual_card.instrument_id()));
}
// Test that the importing logic is called on form submit.
TEST_F(BrowserAutofillManagerTest, FormSubmitted_FormDataImporter) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// Fill the form.
FormData response_data =
FillAutofillFormDataAndGetResults(form, form.fields[0], MakeGuid(1));
ExpectFilledAddressFormElvis(response_data, false);
AutofillProfile filled_profile =
*personal_data().GetProfileByGUID(MakeGuid(1));
// Remove the filled profile and simulate form submission. Since the
// `personal_data()`'s auto accept imports for testing is enabled, expect
// that the profile is imported again.
personal_data().ClearAllLocalData();
ASSERT_TRUE(personal_data().GetProfiles().empty());
FormSubmitted(response_data);
// Since the imported profile has a random GUID, AutofillProfile::operator==
// cannot be used.
ASSERT_EQ(personal_data().GetProfiles().size(), 1u);
EXPECT_TRUE(personal_data().GetProfiles()[0]->Compare(filled_profile));
}
// Test that the user perception of autofill survey is triggered after a form
// submission.
TEST_F(BrowserAutofillManagerTest,
UserPerceptionOfAutofillSurvey_MinFormSizeReached_TriggerSurvey) {
base::test::ScopedFeatureList enabled_features(
features::kAutofillAddressUserPerceptionSurvey);
TestAutofillClock clock(AutofillClock::Now());
// Set up a form with 4 fields (minimum form size to trigger a survey) and
// fill them. The specific field types do not matter.
const size_t n_fields = 4;
FormData form = test::GetFormData(
{.fields = {{.role = NAME_FIRST, .autocomplete_attribute = "given-name"},
{.role = NAME_LAST, .autocomplete_attribute = "family-name"},
{.role = ADDRESS_HOME_LINE1,
.autocomplete_attribute = "address-line1"},
{.role = ADDRESS_HOME_LINE2,
.autocomplete_attribute = "address-line2"}}});
FormsSeen({form});
// Fill the form.
FormData response_data =
FillAutofillFormDataAndGetResults(form, form.fields[0], MakeGuid(1));
const std::map<std::string, std::string> expected_field_filling_stats_data = {
{"Accepted fields", base::NumberToString(n_fields)},
{"Corrected to same type", "0"},
{"Corrected to a different type", "0"},
{"Corrected to an unknown type", "0"},
{"Corrected to empty", "0"},
{"Manually filled to same type", "0"},
{"Manually filled to a different type", "0"},
{"Manually filled to an unknown type", "0"},
{"Total corrected", "0"},
{"Total filled", base::NumberToString(n_fields)},
{"Total unfilled", "0"},
{"Total manually filled", "0"},
{"Total number of fields", base::NumberToString(n_fields)}};
EXPECT_CALL(autofill_client_, TriggerUserPerceptionOfAutofillSurvey(
expected_field_filling_stats_data));
// Simulate form submission.
FormSubmitted(response_data);
}
TEST_F(
BrowserAutofillManagerTest,
UserPerceptionOfAutofillSurvey_MinFormSizeNotReached_DoNotTriggerSurvey) {
base::test::ScopedFeatureList enabled_features(
features::kAutofillAddressUserPerceptionSurvey);
TestAutofillClock clock(AutofillClock::Now());
// Set up a form with only one field and fill it.
FormData form =
test::GetFormData({.fields = {{.role = NAME_FIRST,
.autocomplete_attribute = "given-name"}}});
FormsSeen({form});
// Fill the form.
FormData response_data =
FillAutofillFormDataAndGetResults(form, form.fields[0], MakeGuid(1));
EXPECT_CALL(autofill_client_, TriggerUserPerceptionOfAutofillSurvey).Times(0);
// Simulate form submission.
FormSubmitted(response_data);
}
// Test the field log events at the form submission.
// TODO(crbug.com/1007974): Move those tests out of this file.
class BrowserAutofillManagerWithLogEventsTest
: public BrowserAutofillManagerTest {
protected:
BrowserAutofillManagerWithLogEventsTest() {
base::FieldTrialParams feature_parameters{
{features::kAutofillLogUKMEventsWithSamplingOnSessionRate.name, "100"},
};
scoped_features_.InitWithFeaturesAndParameters(
/*enabled_features=*/{{features::
kAutofillLogUKMEventsWithSamplingOnSession,
feature_parameters},
{features::kAutofillParsingPatternProvider, {}}},
/*disabled_features=*/{});
}
std::vector<AutofillField::FieldLogEventType> ToFieldTypeEvents(
FieldType heuristic_type,
FieldType overall_type,
size_t field_signature_rank = 1) {
std::vector<AutofillField::FieldLogEventType> expected_events;
#if BUILDFLAG(USE_INTERNAL_AUTOFILL_PATTERNS)
// Default pattern.
expected_events.push_back(HeuristicPredictionFieldLogEvent{
.field_type = heuristic_type,
.pattern_source = PatternSource::kDefault,
.is_active_pattern_source = true,
.rank_in_field_signature_group = field_signature_rank,
});
// Experimental pattern.
expected_events.push_back(HeuristicPredictionFieldLogEvent{
.field_type = heuristic_type,
.pattern_source = PatternSource::kExperimental,
.is_active_pattern_source = false,
.rank_in_field_signature_group = field_signature_rank,
});
#else
// Legacy pattern.
expected_events.push_back(HeuristicPredictionFieldLogEvent{
.field_type = heuristic_type,
.pattern_source = PatternSource::kLegacy,
.is_active_pattern_source = true,
.rank_in_field_signature_group = field_signature_rank,
});
#endif
// Rationalization.
expected_events.push_back(RationalizationFieldLogEvent{
.field_type = overall_type,
.section_id = 1,
.type_changed = false,
});
return expected_events;
}
// n = 1 means the first instance.
template <class T>
const T* FindNthEventOfType(
const std::vector<AutofillField::FieldLogEventType>& events,
size_t n) {
// |count| represents the number of events of type T having been seen so
// far.
size_t count = 0;
for (const auto& event : events) {
if (const T* log_event = absl::get_if<T>(&event)) {
++count;
if (count == n) {
return log_event;
}
}
}
return nullptr;
}
template <class T>
size_t CountEventOfType(
const std::vector<AutofillField::FieldLogEventType>& events) {
return base::ranges::count_if(events, [](const auto& event) {
return absl::holds_alternative<T>(event);
});
}
template <class T>
const T* FindFirstEventOfType(
const std::vector<AutofillField::FieldLogEventType>& events) {
return FindNthEventOfType<T>(events, 1);
}
private:
base::test::ScopedFeatureList scoped_features_;
};
// Test that we record TriggerFillFieldLogEvent for the field we click to show
// the autofill suggestion and FillFieldLogEvent for every field in the form.
TEST_F(BrowserAutofillManagerWithLogEventsTest, LogEventsAtFormSubmitted) {
TestAutofillClock clock(AutofillClock::Now());
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// Fill the form.
FormData response_data =
FillAutofillFormDataAndGetResults(form, form.fields[0], MakeGuid(1));
ExpectFilledAddressFormElvis(response_data, false);
// Simulate form submission.
FormSubmitted(response_data);
FormStructure* form_structure = nullptr;
AutofillField* autofill_field = nullptr;
ASSERT_TRUE(browser_autofill_manager_->GetCachedFormAndField(
form, form.fields.front(), &form_structure, &autofill_field));
ASSERT_TRUE(form_structure);
const std::vector<AutofillField::FieldLogEventType>& focus_field_log_events =
autofill_field->field_log_events();
ASSERT_EQ(u"First Name", autofill_field->parseable_label());
const TriggerFillFieldLogEvent* trigger_fill_field_log_event =
FindFirstEventOfType<TriggerFillFieldLogEvent>(focus_field_log_events);
ASSERT_TRUE(trigger_fill_field_log_event);
for (const auto& autofill_field_ptr : *form_structure) {
SCOPED_TRACE(autofill_field_ptr->parseable_label());
std::vector<AutofillField::FieldLogEventType> expected_events =
ToFieldTypeEvents(autofill_field_ptr->heuristic_type(),
autofill_field_ptr->heuristic_type());
if (autofill_field_ptr->parseable_label() == u"First Name") {
// The "First Name" field is the trigger field, so it contains the
// TriggerFillFieldLogEvent followed by a FillFieldLogEvent.
expected_events.push_back(TriggerFillFieldLogEvent{
.fill_event_id = trigger_fill_field_log_event->fill_event_id,
.data_type = FillDataType::kAutofillProfile,
.associated_country_code = "US",
.timestamp = AutofillClock::Now()});
}
// All filled fields share the same expected FillFieldLogEvent.
// The first TriggerFillFieldLogEvent determines the fill_event_id for
// all following FillFieldLogEvents.
expected_events.push_back(FillFieldLogEvent{
.fill_event_id = trigger_fill_field_log_event->fill_event_id,
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy = OptionalBoolean::kFalse,
});
EXPECT_THAT(autofill_field_ptr->field_log_events(),
ArrayEquals(expected_events));
}
}
// Test that we record FillFieldLogEvents correctly after autofill when the
// field has nothing to fill or the field contains a user typed value already.
TEST_F(BrowserAutofillManagerWithLogEventsTest,
LogEventsFillPartlyManuallyFilledForm) {
TestAutofillClock clock(AutofillClock::Now());
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// Michael will be overridden with Elvis because Autofill is triggered from
// the first field.
form.fields[0].value = u"Michael";
form.fields[0].properties_mask |= kUserTyped;
// Jackson will be preserved, only override the first field.
form.fields[2].value = u"Jackson";
form.fields[2].properties_mask |= kUserTyped;
// Fill the address data.
TestAddressFillData address_fill_data(
"Buddy", "Aaron", "Holly", "3734 Elvis Presley Blvd.", "Apt. 10",
"Memphis", "Tennessee", "38116", "United States", "US", /*phone=*/"",
/*email=*/"", "RCA");
AutofillProfile profile1 = FillDataToAutofillProfile(address_fill_data);
profile1.set_guid(MakeGuid(100));
personal_data().AddProfile(profile1);
FormData response_data = FillAutofillFormDataAndGetResults(
form, *form.fields.begin(), MakeGuid(100));
TestAddressFillData expected_address_fill_data = address_fill_data;
expected_address_fill_data.last = "Jackson";
ExpectFilledForm(response_data, expected_address_fill_data,
/*card_fill_data=*/std::nullopt);
FormStructure* form_structure = nullptr;
AutofillField* autofill_field = nullptr;
ASSERT_TRUE(browser_autofill_manager_->GetCachedFormAndField(
form, form.fields.front(), &form_structure, &autofill_field));
ASSERT_TRUE(form_structure);
const std::vector<AutofillField::FieldLogEventType>& focus_field_log_events =
autofill_field->field_log_events();
ASSERT_EQ(u"First Name", autofill_field->parseable_label());
const TriggerFillFieldLogEvent* trigger_fill_field_log_event =
FindFirstEventOfType<TriggerFillFieldLogEvent>(focus_field_log_events);
ASSERT_TRUE(trigger_fill_field_log_event);
// The first TriggerFillFieldLogEvent determines the fill_event_id for
// all following FillFieldLogEvents.
FillEventId fill_event_id = trigger_fill_field_log_event->fill_event_id;
for (const auto& autofill_field_ptr : *form_structure) {
SCOPED_TRACE(autofill_field_ptr->parseable_label());
std::vector<AutofillField::FieldLogEventType> expected_events =
ToFieldTypeEvents(autofill_field_ptr->heuristic_type(),
autofill_field_ptr->heuristic_type());
if (autofill_field_ptr->parseable_label() == u"First Name") {
// The "First Name" field is the trigger field, so it contains the
// TriggerFillFieldLogEvent followed by a FillFieldLogEvent.
expected_events.push_back(
TriggerFillFieldLogEvent{.fill_event_id = fill_event_id,
.data_type = FillDataType::kAutofillProfile,
.associated_country_code = "US",
.timestamp = AutofillClock::Now()});
expected_events.push_back(FillFieldLogEvent{
.fill_event_id = fill_event_id,
.had_value_before_filling = OptionalBoolean::kTrue,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kFalse,
});
} else if (autofill_field_ptr->parseable_label() == u"Phone Number" ||
autofill_field_ptr->parseable_label() == u"Email") {
// Not filled because the address profile contained no data to fill.
expected_events.push_back(FillFieldLogEvent{
.fill_event_id = fill_event_id,
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kFalse,
.had_value_after_filling = OptionalBoolean::kFalse,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kUndefined,
});
} else if (autofill_field_ptr->parseable_label() == u"Last Name") {
// Not filled because the field contained a user typed value already.
expected_events.push_back(FillFieldLogEvent{
.fill_event_id = fill_event_id,
.had_value_before_filling = OptionalBoolean::kTrue,
.autofill_skipped_status = FieldFillingSkipReason::kUserFilledFields,
.was_autofilled_before_security_policy = OptionalBoolean::kFalse,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kUndefined,
});
} else {
expected_events.push_back(FillFieldLogEvent{
.fill_event_id = fill_event_id,
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kFalse,
});
}
EXPECT_THAT(autofill_field_ptr->field_log_events(),
ArrayEquals(expected_events));
}
}
TEST_F(BrowserAutofillManagerWithLogEventsTest,
FillingMethod_TargetedAllFields_FullForm) {
base::test::ScopedFeatureList enabled_features(
features::kAutofillGranularFillingAvailable);
FormData form =
test::GetFormData({.fields = {{.role = NAME_FIRST,
.autocomplete_attribute = "given-name"}}});
FormsSeen({form});
FillAutofillFormDataAndGetResults(
form, form.fields[0], MakeGuid(1),
{.trigger_source = AutofillTriggerSource::kPopup,
.field_types_to_fill = kAllFieldTypes});
const std::vector<AutofillField::FieldLogEventType>& fill_field_log_events =
browser_autofill_manager_->GetAutofillField(form, form.fields[0])
->field_log_events();
ASSERT_EQ(CountEventOfType<FillFieldLogEvent>(fill_field_log_events), 1u);
EXPECT_THAT(
*FindFirstEventOfType<FillFieldLogEvent>(fill_field_log_events),
EqualsFillFieldLogEvent(FillFieldLogEvent{
.fill_event_id = FillEventId(-1),
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kFalse,
}));
}
TEST_F(BrowserAutofillManagerWithLogEventsTest,
FillingMethod_TargetedGranularFillingGroup_GroupFilling) {
base::test::ScopedFeatureList enabled_features(
features::kAutofillGranularFillingAvailable);
FormData form =
test::GetFormData({.fields = {{.role = NAME_FIRST,
.autocomplete_attribute = "given-name"}}});
FormsSeen({form});
FillAutofillFormDataAndGetResults(
form, form.fields[0], MakeGuid(1),
{.trigger_source = AutofillTriggerSource::kPopup,
.field_types_to_fill = GetFieldTypesOfGroup(FieldTypeGroup::kName)});
const std::vector<AutofillField::FieldLogEventType>& fill_field_log_events =
browser_autofill_manager_->GetAutofillField(form, form.fields[0])
->field_log_events();
ASSERT_EQ(CountEventOfType<FillFieldLogEvent>(fill_field_log_events), 1u);
EXPECT_THAT(
*FindFirstEventOfType<FillFieldLogEvent>(fill_field_log_events),
EqualsFillFieldLogEvent(FillFieldLogEvent{
.fill_event_id = FillEventId(-1),
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kGroupFilling,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kFalse,
}));
}
TEST_F(BrowserAutofillManagerWithLogEventsTest,
FillingMethod_TargetedSingleField_FieldByFieldFilling) {
base::test::ScopedFeatureList features(
features::kAutofillGranularFillingAvailable);
FormData form =
test::GetFormData({.fields = {{.role = NAME_FIRST,
.autocomplete_attribute = "given-name"}}});
FormsSeen({form});
FillAutofillFormDataAndGetResults(
form, form.fields[0], MakeGuid(1),
{.trigger_source = AutofillTriggerSource::kPopup,
.field_types_to_fill = {NAME_FIRST}});
const std::vector<AutofillField::FieldLogEventType>& fill_field_log_events =
browser_autofill_manager_->GetAutofillField(form, form.fields[0])
->field_log_events();
ASSERT_EQ(CountEventOfType<FillFieldLogEvent>(fill_field_log_events), 1u);
EXPECT_THAT(
*FindFirstEventOfType<FillFieldLogEvent>(fill_field_log_events),
EqualsFillFieldLogEvent(FillFieldLogEvent{
.fill_event_id = FillEventId(-1),
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kFieldByFieldFilling,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kFalse,
}));
}
// Test that we record FillFieldLogEvents after filling a form twice, the first
// time some field values are missing when autofilling.
TEST_F(BrowserAutofillManagerWithLogEventsTest, LogEventsAtRefillForm) {
TestAutofillClock clock(AutofillClock::Now());
// Set up our form data.
FormData form = CreateTestAddressFormData();
std::vector<FormData> forms(1, form);
FormsSeen(forms);
// First fill the address data which does not have email and phone number.
TestAddressFillData address_fill_data(
"Buddy", "Aaron", "Holly", "3734 Elvis Presley Blvd.", "Apt. 10",
"Memphis", "Tennessee", "38116", "United States", "US", /*phone=*/"",
/*email=*/"", "RCA");
AutofillProfile profile1 = FillDataToAutofillProfile(address_fill_data);
profile1.set_guid(MakeGuid(100));
personal_data().AddProfile(profile1);
FormData response_data = FillAutofillFormDataAndGetResults(
form, *form.fields.begin(), MakeGuid(100));
TestAddressFillData expected_address_fill_data = address_fill_data;
ExpectFilledForm(response_data, expected_address_fill_data,
/*card_fill_data=*/std::nullopt);
// Refill the address data with all the field values.
response_data = FillAutofillFormDataAndGetResults(
response_data, *response_data.fields.begin(), MakeGuid(1));
bool default_to_city_and_number =
base::FeatureList::IsEnabled(features::kAutofillDefaultToCityAndNumber);
expected_address_fill_data.first = "Elvis";
expected_address_fill_data.phone =
default_to_city_and_number ? "2345678901" : "12345678901";
expected_address_fill_data.email = "theking@gmail.com";
ExpectFilledForm(response_data, expected_address_fill_data,
/*card_fill_data=*/std::nullopt);
FormStructure* form_structure = nullptr;
AutofillField* autofill_field = nullptr;
ASSERT_TRUE(browser_autofill_manager_->GetCachedFormAndField(
form, form.fields.front(), &form_structure, &autofill_field));
ASSERT_TRUE(form_structure);
const std::vector<AutofillField::FieldLogEventType>& focus_field_log_events =
autofill_field->field_log_events();
ASSERT_EQ(u"First Name", autofill_field->parseable_label());
const TriggerFillFieldLogEvent* trigger_fill_field_log_event1 =
FindNthEventOfType<TriggerFillFieldLogEvent>(focus_field_log_events, 1);
ASSERT_TRUE(trigger_fill_field_log_event1);
const TriggerFillFieldLogEvent* trigger_fill_field_log_event2 =
FindNthEventOfType<TriggerFillFieldLogEvent>(focus_field_log_events, 2);
ASSERT_TRUE(trigger_fill_field_log_event2);
// The first TriggerFillFieldLogEvent determines the fill_event_id for
// all following FillFieldLogEvents.
FillFieldLogEvent expected_fill_field_log_event1{
.fill_event_id = trigger_fill_field_log_event1->fill_event_id,
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy = OptionalBoolean::kFalse,
};
for (const auto& autofill_field_ptr : *form_structure) {
SCOPED_TRACE(autofill_field_ptr->parseable_label());
std::vector<AutofillField::FieldLogEventType> expected_events =
ToFieldTypeEvents(autofill_field_ptr->heuristic_type(),
autofill_field_ptr->heuristic_type());
if (autofill_field_ptr->parseable_label() == u"First Name") {
// The "First Name" field is the trigger field, so it contains the
// TriggerFillFieldLogEvent followed by a FillFieldLogEvent.
expected_events.push_back(TriggerFillFieldLogEvent{
.fill_event_id = trigger_fill_field_log_event1->fill_event_id,
.data_type = FillDataType::kAutofillProfile,
.associated_country_code = "US",
.timestamp = AutofillClock::Now()});
expected_events.push_back(expected_fill_field_log_event1);
expected_events.push_back(TriggerFillFieldLogEvent{
.fill_event_id = trigger_fill_field_log_event2->fill_event_id,
.data_type = FillDataType::kAutofillProfile,
.associated_country_code = "US",
.timestamp = AutofillClock::Now()});
expected_events.push_back(FillFieldLogEvent{
.fill_event_id = trigger_fill_field_log_event2->fill_event_id,
.had_value_before_filling = OptionalBoolean::kTrue,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kFalse});
} else if (autofill_field_ptr->parseable_label() == u"Phone Number" ||
autofill_field_ptr->parseable_label() == u"Email") {
FillFieldLogEvent expected_event = expected_fill_field_log_event1;
expected_event.was_autofilled_before_security_policy =
OptionalBoolean::kFalse;
expected_event.had_value_after_filling = OptionalBoolean::kFalse;
expected_event.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kUndefined;
expected_events.push_back(expected_event);
expected_events.push_back(FillFieldLogEvent{
.fill_event_id = trigger_fill_field_log_event2->fill_event_id,
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kFalse});
} else {
expected_events.push_back(expected_fill_field_log_event1);
expected_events.push_back(FillFieldLogEvent{
.fill_event_id = trigger_fill_field_log_event2->fill_event_id,
.had_value_before_filling = OptionalBoolean::kTrue,
.autofill_skipped_status =
FieldFillingSkipReason::kAutofilledFieldsNotRefill,
.was_autofilled_before_security_policy = OptionalBoolean::kFalse,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kNone,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kUndefined});
}
EXPECT_THAT(autofill_field_ptr->field_log_events(),
ArrayEquals(expected_events));
}
}
// Test that we record user typing log event correctly after autofill.
TEST_F(BrowserAutofillManagerWithLogEventsTest, LogEventsAtUserTypingInField) {
TestAutofillClock clock(AutofillClock::Now());
// Set up our form data.
FormData form = CreateTestAddressFormData();
std::vector<FormData> forms(1, form);
FormsSeen(forms);
// Fill the form.
FormData response_data =
FillAutofillFormDataAndGetResults(form, form.fields[0], MakeGuid(1));
ExpectFilledAddressFormElvis(response_data, false);
FormFieldData field = form.fields[0];
// Simulate editing the first field.
field.value = u"Michael";
browser_autofill_manager_->OnTextFieldDidChange(form, field, gfx::RectF(),
base::TimeTicks::Now());
// Simulate form submission.
FormSubmitted(response_data);
FormStructure* form_structure = nullptr;
AutofillField* autofill_field = nullptr;
ASSERT_TRUE(browser_autofill_manager_->GetCachedFormAndField(
form, form.fields.front(), &form_structure, &autofill_field));
ASSERT_TRUE(form_structure);
const std::vector<AutofillField::FieldLogEventType>& focus_field_log_events =
autofill_field->field_log_events();
ASSERT_EQ(u"First Name", autofill_field->parseable_label());
const TriggerFillFieldLogEvent* trigger_fill_field_log_event =
FindFirstEventOfType<TriggerFillFieldLogEvent>(focus_field_log_events);
ASSERT_TRUE(trigger_fill_field_log_event);
// All filled fields share the same expected FillFieldLogEvent.
// The first TriggerFillFieldLogEvent determines the fill_event_id for
// all following FillFieldLogEvents.
FillFieldLogEvent expected_fill_field_log_event{
.fill_event_id = trigger_fill_field_log_event->fill_event_id,
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy = OptionalBoolean::kFalse,
};
for (const auto& autofill_field_ptr : *form_structure) {
SCOPED_TRACE(autofill_field_ptr->parseable_label());
std::vector<AutofillField::FieldLogEventType> expected_events =
ToFieldTypeEvents(autofill_field_ptr->heuristic_type(),
autofill_field_ptr->heuristic_type());
if (autofill_field_ptr->parseable_label() == u"First Name") {
// The "First Name" field is the trigger field, so it contains the
// TriggerFillFieldLogEvent followed by a FillFieldLogEvent.
expected_events.push_back(TriggerFillFieldLogEvent{
.fill_event_id = trigger_fill_field_log_event->fill_event_id,
.data_type = FillDataType::kAutofillProfile,
.associated_country_code = "US",
.timestamp = AutofillClock::Now()});
expected_events.push_back(expected_fill_field_log_event);
expected_events.push_back(TypingFieldLogEvent{
.has_value_after_typing = OptionalBoolean::kTrue,
});
} else {
expected_events.push_back(expected_fill_field_log_event);
}
EXPECT_THAT(autofill_field_ptr->field_log_events(),
ArrayEquals(expected_events));
}
}
// Test that we record field log events correctly when the user touches to fill
// and fills the credit card form with a suggestion.
TEST_F(BrowserAutofillManagerWithLogEventsTest,
LogEventsAutofillSuggestionsOrTouchToFill) {
TestAutofillClock clock(AutofillClock::Now());
FormData form;
CreateTestCreditCardFormData(&form, /*is_https=*/true,
/*use_month_type=*/false);
FormsSeen({form});
const FormFieldData& field = form.fields[0];
// Touch the field of "Name on Card" and autofill suggestion is shown.
EXPECT_CALL(touch_to_fill_delegate(), TryToShowTouchToFill)
.WillOnce(Return(false));
TryToShowTouchToFill(form, field, /*form_element_was_clicked=*/true);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
// Fill the form by triggering the suggestion from "Name on Card" field.
FormData response_data = FillAutofillFormDataAndGetResults(
form, *form.fields.begin(), MakeGuid(4));
ExpectFilledCreditCardFormElvis(response_data, /*has_address_fields=*/false);
// Simulate form submission.
FormSubmitted(response_data);
FormStructure* form_structure = nullptr;
AutofillField* autofill_field = nullptr;
ASSERT_TRUE(browser_autofill_manager_->GetCachedFormAndField(
form, form.fields.front(), &form_structure, &autofill_field));
ASSERT_TRUE(form_structure);
const std::vector<AutofillField::FieldLogEventType>& focus_field_log_events =
autofill_field->field_log_events();
ASSERT_EQ(u"Name on Card", autofill_field->parseable_label());
const TriggerFillFieldLogEvent* trigger_fill_field_log_event =
FindFirstEventOfType<TriggerFillFieldLogEvent>(focus_field_log_events);
ASSERT_TRUE(trigger_fill_field_log_event);
// All filled fields share the same expected FillFieldLogEvent.
// The first TriggerFillFieldLogEvent determines the fill_event_id for
// all following FillFieldLogEvents.
FillFieldLogEvent expected_fill_field_log_event{
.fill_event_id = trigger_fill_field_log_event->fill_event_id,
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kTrue,
.had_value_after_filling = OptionalBoolean::kTrue,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy = OptionalBoolean::kFalse,
};
for (const auto& autofill_field_ptr : *form_structure) {
SCOPED_TRACE(autofill_field_ptr->parseable_label());
std::vector<AutofillField::FieldLogEventType> expected_events =
ToFieldTypeEvents(autofill_field_ptr->heuristic_type(),
autofill_field_ptr->heuristic_type());
if (autofill_field_ptr->parseable_label() == u"Name on Card") {
// The "Name on Card" field gets focus and shows a suggestion so it
// contains the AskForValuesToFillFieldLogEvent.
expected_events.push_back(AskForValuesToFillFieldLogEvent{
.has_suggestion = OptionalBoolean::kTrue,
.suggestion_is_shown = OptionalBoolean::kTrue,
});
expected_events.push_back(TriggerFillFieldLogEvent{
.fill_event_id = trigger_fill_field_log_event->fill_event_id,
.data_type = FillDataType::kCreditCard,
.associated_country_code = "",
.timestamp = AutofillClock::Now()});
// The "Name on Card" field is the trigger field, so it contains the
// TriggerFillFieldLogEvent followed by a FillFieldLogEvent.
expected_events.push_back(expected_fill_field_log_event);
} else if (autofill_field_ptr->parseable_label() == u"CVC") {
// CVC field is not autofilled.
expected_events.push_back(FillFieldLogEvent{
.fill_event_id = trigger_fill_field_log_event->fill_event_id,
.had_value_before_filling = OptionalBoolean::kFalse,
.autofill_skipped_status = FieldFillingSkipReason::kNotSkipped,
.was_autofilled_before_security_policy = OptionalBoolean::kFalse,
.had_value_after_filling = OptionalBoolean::kFalse,
.filling_method = AutofillFillingMethod::kFullForm,
.filling_prevented_by_iframe_security_policy =
OptionalBoolean::kUndefined,
});
} else {
expected_events.push_back(expected_fill_field_log_event);
}
EXPECT_THAT(autofill_field_ptr->field_log_events(),
ArrayEquals(expected_events));
}
}
// Test that we record AutocompleteAttributeFieldLogEvent for the fields with
// autocomplete attributes in the form.
TEST_F(BrowserAutofillManagerWithLogEventsTest,
LogEventsOnAutocompleteAttributeField) {
// Set up our form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText, "given-name"),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText, "family-name"),
// Set no autocomplete attribute for the middle name.
CreateTestFormField("Middle name", "middle", "",
FormControlType::kInputText, ""),
// Set an unrecognized autocomplete attribute for the last name.
CreateTestFormField("Email", "email", "", FormControlType::kInputText,
"unrecognized")};
// Simulate having seen this form on page load.
auto form_structure_instance = std::make_unique<FormStructure>(form);
FormStructure* form_structure = form_structure_instance.get();
form_structure->DetermineHeuristicTypes(GeoIpCountryCode(""), nullptr,
nullptr);
browser_autofill_manager_->AddSeenFormStructure(
std::move(form_structure_instance));
// Simulate form submission.
FormSubmitted(form);
for (const auto& autofill_field_ptr : *form_structure) {
SCOPED_TRACE(autofill_field_ptr->parseable_label());
FieldType overall_type = autofill_field_ptr->heuristic_type();
std::vector<AutofillField::FieldLogEventType> expected_events =
ToFieldTypeEvents(autofill_field_ptr->heuristic_type(), overall_type);
if (autofill_field_ptr->parseable_label() != u"Middle name") {
expected_events.insert(expected_events.begin(),
AutocompleteAttributeFieldLogEvent{
.html_type = autofill_field_ptr->html_type(),
.html_mode = HtmlFieldMode::kNone,
.rank_in_field_signature_group = 1,
});
}
EXPECT_THAT(autofill_field_ptr->field_log_events(),
ArrayEquals(expected_events));
}
}
// Test that we record field log events correctly for autofill crowdsourced
// server prediction.
TEST_F(BrowserAutofillManagerWithLogEventsTest,
LogEventsParseQueryResponseServerPrediction) {
// Set up our form data.
FormData form;
form.host_frame = test::MakeLocalFrameToken();
form.renderer_id = test::MakeFormRendererId();
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
CreateTestFormField(/*label=*/"Name", /*name=*/"name", /*value=*/"",
/*type=*/FormControlType::kInputText),
CreateTestFormField(/*label=*/"Street", /*name=*/"Street", /*value=*/"",
/*type=*/FormControlType::kInputText),
CreateTestFormField(/*label=*/"City", /*name=*/"city", /*value=*/"",
/*type=*/FormControlType::kInputText),
CreateTestFormField(/*label=*/"State", /*name=*/"state", /*value=*/"",
/*type=*/FormControlType::kInputText),
CreateTestFormField(/*label=*/"Postal Code", /*name=*/"zipcode",
/*value=*/"", /*type=*/FormControlType::kInputText)};
// Simulate having seen this form on page load.
// |form_structure_instance| will be owned by |browser_autofill_manager_|.
auto form_structure_instance = std::make_unique<FormStructure>(form);
// This pointer is valid as long as autofill manager lives.
FormStructure* form_structure = form_structure_instance.get();
form_structure->DetermineHeuristicTypes(GeoIpCountryCode(""), nullptr,
nullptr);
browser_autofill_manager_->AddSeenFormStructure(
std::move(form_structure_instance));
// Make API response with suggestions.
AutofillQueryResponse response;
AutofillQueryResponse::FormSuggestion* form_suggestion;
// Set suggestions for form.
form_suggestion = response.add_form_suggestions();
autofill::test::AddFieldPredictionsToForm(
form.fields[0],
{test::CreateFieldPrediction(NAME_FIRST,
FieldPrediction::SOURCE_AUTOFILL_DEFAULT),
test::CreateFieldPrediction(USERNAME,
FieldPrediction::SOURCE_PASSWORDS_DEFAULT)},
form_suggestion);
autofill::test::AddFieldPredictionsToForm(
form.fields[1],
{test::CreateFieldPrediction(ADDRESS_HOME_LINE1,
FieldPrediction::SOURCE_OVERRIDE)},
form_suggestion);
autofill::test::AddFieldPredictionToForm(form.fields[2], ADDRESS_HOME_CITY,
form_suggestion);
autofill::test::AddFieldPredictionToForm(form.fields[3], ADDRESS_HOME_STATE,
form_suggestion);
autofill::test::AddFieldPredictionToForm(form.fields[4], ADDRESS_HOME_ZIP,
form_suggestion);
std::string response_string;
ASSERT_TRUE(response.SerializeToString(&response_string));
// Query autofill server for the field type prediction.
test_api(*browser_autofill_manager_)
.OnLoadedServerPredictions(base::Base64Encode(response_string),
test::GetEncodedSignatures(*form_structure));
std::vector<FieldType> types{NAME_FIRST, ADDRESS_HOME_LINE1,
ADDRESS_HOME_CITY, ADDRESS_HOME_STATE,
ADDRESS_HOME_ZIP};
for (size_t i = 0; i < types.size(); ++i) {
EXPECT_EQ(types[i], form_structure->field(i)->Type().GetStorableType());
}
// Simulate form submission.
FormSubmitted(form);
for (const auto& autofill_field_ptr : *form_structure) {
SCOPED_TRACE(autofill_field_ptr->parseable_label());
std::vector<AutofillField::FieldLogEventType> expected_events =
ToFieldTypeEvents(autofill_field_ptr->heuristic_type(),
autofill_field_ptr->heuristic_type());
// The autofill server applies two predictions on the "Name" field.
std::optional<FieldType> server_type2 =
autofill_field_ptr->parseable_label() == u"Name"
? std::optional<FieldType>(USERNAME)
: std::nullopt;
FieldPrediction::Source prediction_source2 =
autofill_field_ptr->parseable_label() == u"Name"
? FieldPrediction::SOURCE_PASSWORDS_DEFAULT
: FieldPrediction::SOURCE_UNSPECIFIED;
// The server prediction overrides the type predicted by local heuristic on
// the field of label "Street".
bool server_type_prediction_is_override =
autofill_field_ptr->parseable_label() == u"Street" ? true : false;
expected_events.push_back(ServerPredictionFieldLogEvent{
.server_type1 = autofill_field_ptr->server_type(),
.prediction_source1 =
autofill_field_ptr->server_predictions()[0].source(),
.server_type2 = server_type2,
.prediction_source2 = prediction_source2,
.server_type_prediction_is_override =
server_type_prediction_is_override,
.rank_in_field_signature_group = 1,
});
// Rationalization.
expected_events.push_back(RationalizationFieldLogEvent{
.field_type = autofill_field_ptr->server_type(),
.section_id = 1,
.type_changed = false,
});
EXPECT_THAT(autofill_field_ptr->field_log_events(),
ArrayEquals(expected_events));
}
}
// Test that we record field log events correctly for rationalization when
// there are two address fields.
TEST_F(BrowserAutofillManagerWithLogEventsTest,
LogEventsRationalizationTwoAddresses) {
// Set up our form data.
FormData form;
form.host_frame = test::MakeLocalFrameToken();
form.renderer_id = test::MakeFormRendererId();
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
CreateTestFormField(/*label=*/"Full Name", /*name=*/"fullName",
/*value=*/"", /*type=*/FormControlType::kInputText),
CreateTestFormField(/*label=*/"Address", /*name=*/"address", /*value=*/"",
/*type=*/FormControlType::kInputText),
CreateTestFormField(/*label=*/"Address", /*name=*/"address", /*value=*/"",
/*type=*/FormControlType::kInputText),
CreateTestFormField(/*label=*/"City", /*name=*/"city", /*value=*/"",
/*type=*/FormControlType::kInputText)};
// Simulate having seen this form on page load.
// |form_structure_instance| will be owned by |browser_autofill_manager_|.
auto form_structure_instance = std::make_unique<FormStructure>(form);
// This pointer is valid as long as autofill manager lives.
FormStructure* form_structure = form_structure_instance.get();
form_structure->DetermineHeuristicTypes(GeoIpCountryCode(""), nullptr,
nullptr);
browser_autofill_manager_->AddSeenFormStructure(
std::move(form_structure_instance));
// Make API response with suggestions.
AutofillQueryResponse response;
AutofillQueryResponse::FormSuggestion* form_suggestion;
// Set suggestions for form.
form_suggestion = response.add_form_suggestions();
std::vector<FieldType> server_types{NAME_FULL, ADDRESS_HOME_STREET_ADDRESS,
ADDRESS_HOME_STREET_ADDRESS,
ADDRESS_HOME_CITY};
for (size_t i = 0; i < server_types.size(); ++i) {
autofill::test::AddFieldPredictionToForm(form.fields[i], server_types[i],
form_suggestion);
}
std::string response_string;
ASSERT_TRUE(response.SerializeToString(&response_string));
// Query autofill server for the field type prediction.
test_api(*browser_autofill_manager_)
.OnLoadedServerPredictions(base::Base64Encode(response_string),
test::GetEncodedSignatures(*form_structure));
std::vector<FieldType> overall_types{NAME_FULL, ADDRESS_HOME_LINE1,
ADDRESS_HOME_LINE2, ADDRESS_HOME_CITY};
for (size_t i = 0; i < server_types.size(); ++i) {
EXPECT_EQ(overall_types[i],
form_structure->field(i)->Type().GetStorableType());
}
// Simulate form submission.
FormSubmitted(form);
for (const auto& autofill_field_ptr : *form_structure) {
SCOPED_TRACE(autofill_field_ptr->parseable_label());
size_t field_signature_rank =
autofill_field_ptr->heuristic_type() == ADDRESS_HOME_LINE2 ? 2 : 1;
std::vector<AutofillField::FieldLogEventType> expected_events =
ToFieldTypeEvents(autofill_field_ptr->heuristic_type(),
autofill_field_ptr->heuristic_type(),
field_signature_rank);
expected_events.push_back(ServerPredictionFieldLogEvent{
.server_type1 = autofill_field_ptr->server_type(),
.prediction_source1 =
autofill_field_ptr->server_predictions()[0].source(),
.server_type2 = std::nullopt,
.prediction_source2 = FieldPrediction::SOURCE_UNSPECIFIED,
.server_type_prediction_is_override = false,
.rank_in_field_signature_group = field_signature_rank,
});
// Rationalization.
bool type_changed =
autofill_field_ptr->parseable_label() == u"Address" ? true : false;
expected_events.push_back(RationalizationFieldLogEvent{
.field_type = autofill_field_ptr->Type().GetStorableType(),
.section_id = 1,
.type_changed = type_changed,
});
EXPECT_THAT(autofill_field_ptr->field_log_events(),
ArrayEquals(expected_events));
}
}
// Test that when Autocomplete is enabled and Autofill is disabled, form
// submissions are still received by the SingleFieldFormFillRouter.
TEST_F(BrowserAutofillManagerTest, FormSubmittedAutocompleteEnabled) {
browser_autofill_manager_->SetAutofillProfileEnabled(autofill_client_, false);
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
false);
// Set up our form data.
FormData form = CreateTestAddressFormData();
EXPECT_CALL(single_field_form_fill_router(), OnWillSubmitForm(_, _, true));
FormSubmitted(form);
}
// Test that the value patterns metric is reported.
TEST_F(BrowserAutofillManagerTest, ValuePatternsMetric) {
struct ValuePatternTestCase {
const char* value;
autofill::ValuePatternsMetric pattern;
} kTestCases[] = {
{"user@okaxis", autofill::ValuePatternsMetric::kUpiVpa},
{"IT60X0542811101000000123456", autofill::ValuePatternsMetric::kIban}};
for (const ValuePatternTestCase test_case : kTestCases) {
// Set up our form data.
FormData form;
form.name = u"my-form";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {CreateTestFormField("Some label", "my-field",
test_case.value,
FormControlType::kInputText)};
FormsSeen({form});
base::HistogramTester histogram_tester;
FormSubmitted(form);
histogram_tester.ExpectUniqueSample("Autofill.SubmittedValuePatterns",
test_case.pattern, 1);
}
}
// Test that when Autofill is disabled, single field form fill suggestions are
// still queried as a fallback.
TEST_F(BrowserAutofillManagerTest,
SingleFieldFormFillSuggestions_SomeWhenAutofillDisabled) {
browser_autofill_manager_->SetAutofillProfileEnabled(autofill_client_, false);
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
false);
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// Expect the SingleFieldFormFillRouter to be called for suggestions.
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions);
GetAutofillSuggestions(form, form.fields[0]);
// Single field form fill suggestions were returned, so we should not go
// through the normal autofill flow.
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
// Test that we do not query for single field form fill suggestions when there
// are Autofill suggestions available.
TEST_F(BrowserAutofillManagerTest,
SingleFieldFormFillSuggestions_NoneWhenAutofillPresent) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// SingleFieldFormFillRouter is not called for suggestions.
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.Times(0);
GetAutofillSuggestions(form, form.fields[0]);
// Verify that suggestions are returned.
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
// Test that we query for single field form fill suggestions when there are no
// Autofill suggestions available.
TEST_F(BrowserAutofillManagerTest,
SingleFieldFormFillSuggestions_SomeWhenAutofillEmpty) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// No suggestions matching "donkey".
FormFieldData field = CreateTestFormField("Email", "email", "donkey",
FormControlType::kInputEmail);
// Single field form fill manager is called for suggestions because Autofill
// is empty.
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions);
GetAutofillSuggestions(form, field);
}
// Test that when Autofill is disabled and the field is a credit card name
// field, single field form fill suggestions are queried.
TEST_F(BrowserAutofillManagerTest,
SingleFieldFormFillSuggestions_CreditCardNameFieldShouldAutocomplete) {
// Since we are testing a form that submits over HTTP, we also need to set
// the main frame to HTTP in the client, otherwise mixed form warnings will
// trigger and autofill will be disabled.
GURL::Replacements replacements;
replacements.SetSchemeStr(url::kHttpScheme);
autofill_client_.set_form_origin(
autofill_client_.form_origin().ReplaceComponents(replacements));
ResetBrowserAutofillManager();
browser_autofill_manager_->SetAutofillProfileEnabled(autofill_client_, false);
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
false);
// Set up our form data.
FormData form = CreateTestCreditCardFormData(/*is_https=*/false,
/*use_month_type=*/false);
FormsSeen({form});
// The first field is "Name on card", which should autocomplete.
FormFieldData field = form.fields[0];
field.should_autocomplete = true;
// SingleFieldFormFillRouter is called for suggestions.
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions);
GetAutofillSuggestions(form, field);
}
// Test that when Autofill is disabled and the field is a credit card number
// field, single field form fill suggestions are not queried.
TEST_F(BrowserAutofillManagerTest,
SingleFieldFormFillSuggestions_CreditCardNumberShouldNotAutocomplete) {
// Since we are testing a form that submits over HTTP, we also need to set
// the main frame to HTTP in the client, otherwise mixed form warnings will
// trigger and autofill will be disabled.
GURL::Replacements replacements;
replacements.SetSchemeStr(url::kHttpScheme);
autofill_client_.set_form_origin(
autofill_client_.form_origin().ReplaceComponents(replacements));
ResetBrowserAutofillManager();
browser_autofill_manager_->SetAutofillProfileEnabled(autofill_client_, false);
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
false);
// Set up our form data.
FormData form = CreateTestCreditCardFormData(/*is_https=*/false,
/*use_month_type=*/false);
FormsSeen({form});
// The second field is "Card Number", which should not autocomplete.
FormFieldData field = form.fields[1];
field.should_autocomplete = true;
// SingleFieldFormFillRouter is not called for suggestions.
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.Times(0);
GetAutofillSuggestions(form, field);
}
// Test that the situation where there are no Autofill suggestions available,
// and no single field form fill conditions were met is correctly handled. The
// single field form fill conditions were not met because autocomplete is set to
// off and the field is not recognized as a promo code field.
TEST_F(
BrowserAutofillManagerTest,
SingleFieldFormFillSuggestions_NoneWhenAutofillEmptyAndSingleFieldFormFillConditionsNotMet) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// No suggestions matching "donkey".
FormFieldData field = CreateTestFormField("Email", "email", "donkey",
FormControlType::kInputEmail);
field.should_autocomplete = false;
// Autocomplete is set to off, so suggestions should not get returned from
// single_field_form_fill_router()|.
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.WillRepeatedly(Return(false));
GetAutofillSuggestions(form, field);
// Single field form fill was not triggered, so go through the normal autofill
// flow.
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
// Test that the situation where no single field form fill conditions were met
// is handled correctly. The single field form fill conditions were not met
// because autocomplete is set to off and the field is not recognized as a promo
// code field.
TEST_F(
BrowserAutofillManagerTest,
SingleFieldFormFillSuggestions_NoneWhenSingleFieldFormFillConditionsNotMet) {
browser_autofill_manager_->SetAutofillProfileEnabled(autofill_client_, false);
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
false);
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
FormFieldData& field = form.fields[0];
field.should_autocomplete = false;
// Autocomplete is set to off, so suggestions should not get returned from
// |single_field_form_fill_router()|.
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.WillRepeatedly(Return(false));
GetAutofillSuggestions(form, field);
// Single field form fill was not triggered, so go through the normal autofill
// flow.
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
TEST_F(BrowserAutofillManagerTest,
DestructorCancelsSingleFieldFormFillQueries) {
EXPECT_CALL(single_field_form_fill_router(), CancelPendingQueries);
browser_autofill_manager_.reset();
}
// Test that OnLoadedServerPredictions can obtain the FormStructure with the
// signature of the queried form from the API and apply type predictions.
// What we test here:
// * The API response parser is used.
// * The query can be processed with a response from the API.
TEST_F(BrowserAutofillManagerTest, OnLoadedServerPredictionsFromApi) {
// First form on the page.
FormData form;
form.host_frame = test::MakeLocalFrameToken();
form.renderer_id = test::MakeFormRendererId();
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
CreateTestFormField(/*label=*/"City", /*name=*/"city", /*value=*/"",
/*type=*/FormControlType::kInputText),
CreateTestFormField(/*label=*/"State", /*name=*/"state", /*value=*/"",
/*type=*/FormControlType::kInputText),
CreateTestFormField(/*label=*/"Postal Code", /*name=*/"zipcode",
/*value=*/"", /*type=*/FormControlType::kInputText)};
// Simulate having seen this form on page load.
// |form_structure_instance| will be owned by |browser_autofill_manager_|.
auto form_structure_instance = std::make_unique<FormStructure>(form);
// This pointer is valid as long as autofill manager lives.
FormStructure* form_structure = form_structure_instance.get();
form_structure->DetermineHeuristicTypes(GeoIpCountryCode(""), nullptr,
nullptr);
browser_autofill_manager_->AddSeenFormStructure(
std::move(form_structure_instance));
// Second form on the page.
FormData form2;
form2.host_frame = test::MakeLocalFrameToken();
form2.renderer_id = test::MakeFormRendererId();
form2.name = u"MyForm2";
form2.url = GURL("https://myform.com/form.html");
form2.action = GURL("https://myform.com/submit.html");
form2.fields = {CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText),
CreateTestFormField("Middle Name", "middlename", "",
FormControlType::kInputText),
CreateTestFormField("Postal Code", "zipcode", "",
FormControlType::kInputText)};
auto form_structure_instance2 = std::make_unique<FormStructure>(form2);
// This pointer is valid as long as autofill manager lives.
FormStructure* form_structure2 = form_structure_instance2.get();
form_structure2->DetermineHeuristicTypes(GeoIpCountryCode(""), nullptr,
nullptr);
browser_autofill_manager_->AddSeenFormStructure(
std::move(form_structure_instance2));
// Make API response with suggestions.
AutofillQueryResponse response;
AutofillQueryResponse::FormSuggestion* form_suggestion;
// Set suggestions for form 1.
form_suggestion = response.add_form_suggestions();
autofill::test::AddFieldPredictionToForm(form.fields[0], ADDRESS_HOME_CITY,
form_suggestion);
autofill::test::AddFieldPredictionToForm(form.fields[1], ADDRESS_HOME_STATE,
form_suggestion);
autofill::test::AddFieldPredictionToForm(form.fields[2], ADDRESS_HOME_ZIP,
form_suggestion);
// Set suggestions for form 2.
form_suggestion = response.add_form_suggestions();
autofill::test::AddFieldPredictionToForm(form2.fields[0], NAME_LAST,
form_suggestion);
autofill::test::AddFieldPredictionToForm(form2.fields[1], NAME_MIDDLE,
form_suggestion);
autofill::test::AddFieldPredictionToForm(form2.fields[2], ADDRESS_HOME_ZIP,
form_suggestion);
std::string response_string;
ASSERT_TRUE(response.SerializeToString(&response_string));
std::vector<FormSignature> signatures =
test::GetEncodedSignatures({form_structure, form_structure2});
// Run method under test.
base::HistogramTester histogram_tester;
test_api(*browser_autofill_manager_)
.OnLoadedServerPredictions(base::Base64Encode(response_string),
signatures);
// Verify whether the relevant histograms were updated.
histogram_tester.ExpectBucketCount("Autofill.ServerQueryResponse",
AutofillMetrics::QUERY_RESPONSE_RECEIVED,
1);
histogram_tester.ExpectBucketCount("Autofill.ServerQueryResponse",
AutofillMetrics::QUERY_RESPONSE_PARSED, 1);
// We expect the server suggestions to have been applied to the first field of
// the first form.
EXPECT_EQ(ADDRESS_HOME_CITY,
form_structure->field(0)->Type().GetStorableType());
EXPECT_EQ(ADDRESS_HOME_STATE,
form_structure->field(1)->Type().GetStorableType());
EXPECT_EQ(ADDRESS_HOME_ZIP,
form_structure->field(2)->Type().GetStorableType());
// We expect the server suggestions to have been applied to the second form as
// well.
EXPECT_EQ(NAME_LAST, form_structure2->field(0)->Type().GetStorableType());
EXPECT_EQ(NAME_MIDDLE, form_structure2->field(1)->Type().GetStorableType());
EXPECT_EQ(ADDRESS_HOME_ZIP,
form_structure2->field(2)->Type().GetStorableType());
}
// Test that OnLoadedServerPredictions does not call ParseQueryResponse if the
// BrowserAutofillManager has been reset between the time the query was sent and
// the response received.
TEST_F(BrowserAutofillManagerTest, OnLoadedServerPredictions_ResetManager) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
// Simulate having seen this form on page load.
// |form_structure| will be owned by |browser_autofill_manager_|.
auto form_structure = std::make_unique<FormStructure>(form);
form_structure->DetermineHeuristicTypes(GeoIpCountryCode(""), nullptr,
nullptr);
std::vector<FormSignature> signatures =
test::GetEncodedSignatures(*form_structure);
browser_autofill_manager_->AddSeenFormStructure(std::move(form_structure));
AutofillQueryResponse response;
auto* form_suggestion = response.add_form_suggestions();
form_suggestion->add_field_suggestions()->add_predictions()->set_type(3);
for (int i = 0; i < 7; ++i) {
form_suggestion->add_field_suggestions()->add_predictions()->set_type(0);
}
form_suggestion->add_field_suggestions()->add_predictions()->set_type(3);
form_suggestion->add_field_suggestions()->add_predictions()->set_type(2);
form_suggestion->add_field_suggestions()->add_predictions()->set_type(61);
std::string response_string;
ASSERT_TRUE(response.SerializeToString(&response_string));
// Reset the manager (such as during a navigation).
browser_autofill_manager_->Reset();
base::HistogramTester histogram_tester;
test_api(*browser_autofill_manager_)
.OnLoadedServerPredictions(base::Base64Encode(response_string),
signatures);
// Verify that FormStructure::ParseQueryResponse was NOT called.
histogram_tester.ExpectTotalCount("Autofill.ServerQueryResponse", 0);
}
// Test that when server predictions disagree with the heuristic ones, the
// overall types and sections would be set based on the server one.
TEST_F(BrowserAutofillManagerTest, DetermineHeuristicsWithOverallPrediction) {
// Set up our form data.
FormData form;
form.url = GURL("https://www.myform.com");
form.fields = {CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText),
CreateTestFormField("Card Number", "cardnumber", "",
FormControlType::kInputText),
CreateTestFormField("Expiration Year", "exp_year", "",
FormControlType::kInputText),
CreateTestFormField("Expiration Month", "exp_month", "",
FormControlType::kInputText)};
// Simulate having seen this form on page load.
// |form_structure| will be owned by |browser_autofill_manager_|.
FormStructure* form_structure = [&] {
auto form_structure = std::make_unique<FormStructure>(form);
FormStructure* ptr = form_structure.get();
form_structure->DetermineHeuristicTypes(GeoIpCountryCode(""), nullptr,
nullptr);
browser_autofill_manager_->AddSeenFormStructure(std::move(form_structure));
return ptr;
}();
AutofillQueryResponse response;
auto* form_suggestion = response.add_form_suggestions();
autofill::test::AddFieldPredictionToForm(
form.fields[0], CREDIT_CARD_NAME_FIRST, form_suggestion);
autofill::test::AddFieldPredictionToForm(
form.fields[1], CREDIT_CARD_NAME_LAST, form_suggestion);
autofill::test::AddFieldPredictionToForm(form.fields[2], CREDIT_CARD_NUMBER,
form_suggestion);
autofill::test::AddFieldPredictionToForm(
form.fields[3], CREDIT_CARD_EXP_MONTH, form_suggestion);
autofill::test::AddFieldPredictionToForm(
form.fields[4], CREDIT_CARD_EXP_4_DIGIT_YEAR, form_suggestion);
std::string response_string;
ASSERT_TRUE(response.SerializeToString(&response_string));
base::HistogramTester histogram_tester;
test_api(*browser_autofill_manager_)
.OnLoadedServerPredictions(base::Base64Encode(response_string),
test::GetEncodedSignatures(*form_structure));
// Verify that FormStructure::ParseQueryResponse was called (here and below).
histogram_tester.ExpectBucketCount("Autofill.ServerQueryResponse",
AutofillMetrics::QUERY_RESPONSE_RECEIVED,
1);
histogram_tester.ExpectBucketCount("Autofill.ServerQueryResponse",
AutofillMetrics::QUERY_RESPONSE_PARSED, 1);
// Since the card holder name appears as the first name + last name (rather
// than the full name), and since they appears as the first fields of the
// section, the heuristics detect them as the address first/last name.
EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type());
EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type());
EXPECT_EQ(CREDIT_CARD_NUMBER, form_structure->field(2)->heuristic_type());
EXPECT_EQ(CREDIT_CARD_EXP_MONTH, form_structure->field(3)->heuristic_type());
EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR,
form_structure->field(4)->heuristic_type());
// We expect to see the server type as the overall type.
EXPECT_EQ(CREDIT_CARD_NAME_FIRST,
form_structure->field(0)->Type().GetStorableType());
EXPECT_EQ(CREDIT_CARD_NAME_LAST,
form_structure->field(1)->Type().GetStorableType());
EXPECT_EQ(CREDIT_CARD_NUMBER, form_structure->field(2)->heuristic_type());
EXPECT_EQ(CREDIT_CARD_EXP_MONTH, form_structure->field(3)->heuristic_type());
EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR,
form_structure->field(4)->heuristic_type());
// Although the heuristic types of the first two fields belongs to the address
// section, the final fields' section should be based on the overall
// prediction, therefore they should be grouped in one section.
const auto section = form_structure->field(0)->section;
EXPECT_EQ(section, form_structure->field(1)->section);
EXPECT_EQ(section, form_structure->field(2)->section);
EXPECT_EQ(section, form_structure->field(3)->section);
EXPECT_EQ(section, form_structure->field(4)->section);
}
// Test that the form signature for an uploaded form always matches the form
// signature from the query.
TEST_F(BrowserAutofillManagerTest, FormSubmittedWithDifferentFields) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// Cache the expected form signature.
std::string signature = FormStructure(form).FormSignatureAsStr();
// Change the structure of the form prior to submission.
// Websites would typically invoke JavaScript either on page load or on form
// submit to achieve this.
form.fields.pop_back();
FormFieldData field = form.fields[3];
form.fields[3] = form.fields[7];
form.fields[7] = field;
// Simulate form submission.
FormSubmitted(form);
EXPECT_EQ(signature, browser_autofill_manager_->GetSubmittedFormSignature());
}
// Test that we do not save form data when submitted fields contain default
// values.
TEST_F(BrowserAutofillManagerTest, FormSubmittedWithDefaultValues) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormFieldData* addr1_field = form.FindFieldByName(u"addr1");
ASSERT_TRUE(addr1_field != nullptr);
addr1_field->value = u"Enter your address";
FormsSeen({form});
// Fill the form.
FormData response_data =
FillAutofillFormDataAndGetResults(form, *addr1_field, kElvisProfileGuid);
// Set the address field's value back to the default value.
response_data.fields[3].value = u"Enter your address";
// Simulate form submission. The profile should not be updated with the
// meaningless default value of the street address field.
personal_data()
.GetProfileByGUID(kElvisProfileGuid)
->ClearFields({ADDRESS_HOME_STREET_ADDRESS});
FormSubmitted(response_data);
EXPECT_FALSE(personal_data()
.GetProfileByGUID(kElvisProfileGuid)
->HasInfo(ADDRESS_HOME_STREET_ADDRESS));
}
void DoTestFormSubmittedControlWithDefaultValue(
BrowserAutofillManagerTest* test,
FormControlType form_control_type) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
// Convert the state field to a <select> popup, to make sure that we only
// reject default values for text fields.
FormFieldData* state_field = form.FindFieldByName(u"state");
ASSERT_TRUE(state_field != nullptr);
state_field->form_control_type = form_control_type;
state_field->value = base::UTF8ToUTF16(GetElvisAddressFillData().state);
test->FormsSeen({form});
// Fill the form.
FormData response_data = test->FillAutofillFormDataAndGetResults(
form, form.fields[3], kElvisProfileGuid);
test->personal_data()
.GetProfileByGUID(kElvisProfileGuid)
->ClearFields({ADDRESS_HOME_STATE});
test->FormSubmitted(response_data);
// Expect that the profile was updated with the value of the state select.
EXPECT_EQ(state_field->value, test->personal_data()
.GetProfileByGUID(kElvisProfileGuid)
->GetRawInfo(ADDRESS_HOME_STATE));
}
// Test that we save form data when a <select> in the form contains the
// default value.
TEST_F(BrowserAutofillManagerTest, FormSubmittedSelectWithDefaultValue) {
DoTestFormSubmittedControlWithDefaultValue(this, FormControlType::kSelectOne);
}
// Test that we save form data when a <selectlist> in the form contains the
// default value.
TEST_F(BrowserAutofillManagerTest, FormSubmittedSelectListWithDefaultValue) {
DoTestFormSubmittedControlWithDefaultValue(this,
FormControlType::kSelectList);
}
void DoTestFormSubmittedNonAddressControlWithDefaultValue(
BrowserAutofillManagerTest* test,
FormControlType form_control_type) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
// Remove phonenumber field.
auto phonenumber_it =
base::ranges::find(form.fields, u"phonenumber", &FormFieldData::name);
ASSERT_TRUE(phonenumber_it != form.fields.end());
form.fields.erase(phonenumber_it);
// Insert country code and national phone number fields.
form.fields.push_back(CreateTestFormField("Country Code", "countrycode", "1",
form_control_type,
"tel-country-code"));
form.fields.push_back(CreateTestFormField("Phone Number", "phonenumber", "",
FormControlType::kInputText,
"tel-national"));
test->FormsSeen({form});
// Fill the form.
FormData response_data = test->FillAutofillFormDataAndGetResults(
form, form.fields[3], kElvisProfileGuid);
// Value of country code field should have been saved.
test->personal_data()
.GetProfileByGUID(kElvisProfileGuid)
->ClearFields({PHONE_HOME_WHOLE_NUMBER});
test->FormSubmitted(response_data);
std::u16string formatted_phone_number =
test->personal_data()
.GetProfileByGUID(kElvisProfileGuid)
->GetRawInfo(PHONE_HOME_WHOLE_NUMBER);
std::u16string phone_number_numbers_only;
base::RemoveChars(formatted_phone_number, u"+- ", &phone_number_numbers_only);
EXPECT_TRUE(phone_number_numbers_only.starts_with(u"1"));
}
// Test that we save form data when a non-country, non-state <select> in the
// form contains the default value.
TEST_F(BrowserAutofillManagerTest,
FormSubmittedNonAddressSelectWithDefaultValue) {
DoTestFormSubmittedNonAddressControlWithDefaultValue(
this, FormControlType::kSelectOne);
}
// Test that we save form data when a non-country, non-state <selectlist> in the
// form contains the default value.
TEST_F(BrowserAutofillManagerTest,
FormSubmittedNonAddressSelectListWithDefaultValue) {
DoTestFormSubmittedNonAddressControlWithDefaultValue(
this, FormControlType::kSelectList);
}
// Tests that DeterminePossibleFieldTypesForUpload is called when a form is
// submitted.
TEST_F(BrowserAutofillManagerTest,
DeterminePossibleFieldTypesForUpload_IsTriggered) {
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
std::vector<FieldTypeSet> expected_types;
std::vector<std::u16string> expected_values;
// These fields should all match.
FieldTypeSet types;
expected_values.push_back(u"Elvis");
types.clear();
types.insert(NAME_FIRST);
form.fields.push_back(
CreateTestFormField("", "1", "", FormControlType::kInputText));
expected_types.push_back(types);
expected_values.push_back(u"Aaron");
types.clear();
types.insert(NAME_MIDDLE);
form.fields.push_back(
CreateTestFormField("", "2", "", FormControlType::kInputText));
expected_types.push_back(types);
expected_values.push_back(u"A");
types.clear();
types.insert(NAME_MIDDLE_INITIAL);
form.fields.push_back(
CreateTestFormField("", "3", "", FormControlType::kInputText));
expected_types.push_back(types);
// Make sure the form is in the cache so that it is processed for Autofill
// upload.
FormsSeen({form});
// Once the form is cached, fill the values.
EXPECT_EQ(form.fields.size(), expected_values.size());
for (size_t i = 0; i < expected_values.size(); i++) {
form.fields[i].value = expected_values[i];
}
browser_autofill_manager_->SetExpectedSubmittedFieldTypes(expected_types);
FormSubmitted(form);
}
TEST_F(BrowserAutofillManagerTest, RemoveProfile) {
// Add and remove an Autofill profile.
AutofillProfile profile = test::GetFullProfile();
personal_data().AddProfile(profile);
EXPECT_TRUE(browser_autofill_manager_->RemoveAutofillProfileOrCreditCard(
Suggestion::Guid(profile.guid())));
EXPECT_FALSE(personal_data().GetProfileByGUID(profile.guid()));
}
TEST_F(BrowserAutofillManagerTest, RemoveLocalCreditCard) {
// Add and remove an Autofill credit card.
CreditCard local_card = test::GetCreditCard();
personal_data().AddCreditCard(local_card);
EXPECT_TRUE(browser_autofill_manager_->RemoveAutofillProfileOrCreditCard(
Suggestion::Guid(local_card.guid())));
EXPECT_FALSE(personal_data().GetCreditCardByGUID(local_card.guid()));
}
TEST_F(BrowserAutofillManagerTest, RemoveServerCreditCard) {
CreditCard server_card = test::GetMaskedServerCard();
personal_data().AddServerCreditCard(server_card);
EXPECT_FALSE(browser_autofill_manager_->RemoveAutofillProfileOrCreditCard(
Suggestion::Guid(server_card.guid())));
EXPECT_TRUE(personal_data().GetCreditCardByGUID(server_card.guid()));
}
// Test our external delegate is called at the right time.
TEST_F(BrowserAutofillManagerTest, TestExternalDelegate) {
FormData form = CreateTestAddressFormData();
FormsSeen({form});
// Should call the delegate's OnQuery().
GetAutofillSuggestions(form, form.fields[0]);
EXPECT_TRUE(external_delegate()->on_query_seen());
}
// Test that unfocusing a filled form sends an upload with types matching the
// fields.
TEST_F(BrowserAutofillManagerTest, OnTextFieldDidChangeAndUnfocus_Upload) {
// Set up our form data (it's already filled out with user data).
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
std::vector<FieldTypeSet> expected_types;
FieldTypeSet types;
form.fields.push_back(CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText));
types.insert(NAME_FIRST);
expected_types.push_back(types);
form.fields.push_back(CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText));
types.clear();
types.insert(NAME_LAST);
types.insert(NAME_LAST_SECOND);
expected_types.push_back(types);
form.fields.push_back(
CreateTestFormField("Email", "email", "", FormControlType::kInputText));
types.clear();
types.insert(EMAIL_ADDRESS);
expected_types.push_back(types);
FormsSeen({form});
// We will expect these types in the upload and no observed submission (the
// callback initiated by WaitForAsyncUploadProcess checks these expectations.)
browser_autofill_manager_->SetExpectedSubmittedFieldTypes(expected_types);
browser_autofill_manager_->SetExpectedObservedSubmission(false);
// The fields are edited after calling FormsSeen on them. This is because
// default values are not used for upload comparisons.
form.fields[0].value = u"Elvis";
form.fields[1].value = u"Presley";
form.fields[2].value = u"theking@gmail.com";
// Simulate editing a field.
browser_autofill_manager_->OnTextFieldDidChange(
form, form.fields.front(), gfx::RectF(), base::TimeTicks::Now());
// Simulate lost of focus on the form.
browser_autofill_manager_->OnFocusNoLongerOnForm(true);
}
// Test that navigating with a filled form sends an upload with types matching
// the fields.
TEST_F(BrowserAutofillManagerTest, OnTextFieldDidChangeAndNavigation_Upload) {
// Set up our form data (it's already filled out with user data).
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
std::vector<FieldTypeSet> expected_types;
FieldTypeSet types;
form.fields.push_back(CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText));
types.insert(NAME_FIRST);
expected_types.push_back(types);
form.fields.push_back(CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText));
types.clear();
types.insert(NAME_LAST_SECOND);
types.insert(NAME_LAST);
expected_types.push_back(types);
form.fields.push_back(
CreateTestFormField("Email", "email", "", FormControlType::kInputText));
types.clear();
types.insert(EMAIL_ADDRESS);
expected_types.push_back(types);
FormsSeen({form});
// We will expect these types in the upload and no observed submission. (the
// callback initiated by WaitForAsyncUploadProcess checks these expectations.)
browser_autofill_manager_->SetExpectedSubmittedFieldTypes(expected_types);
browser_autofill_manager_->SetExpectedObservedSubmission(false);
// The fields are edited after calling FormsSeen on them. This is because
// default values are not used for upload comparisons.
form.fields[0].value = u"Elvis";
form.fields[1].value = u"Presley";
form.fields[2].value = u"theking@gmail.com";
// Simulate editing a field.
browser_autofill_manager_->OnTextFieldDidChange(
form, form.fields.front(), gfx::RectF(), base::TimeTicks::Now());
// Simulate a navigation so that the pending form is uploaded.
browser_autofill_manager_->Reset();
}
// Test that unfocusing a filled form sends an upload with types matching the
// fields.
TEST_F(BrowserAutofillManagerTest, OnDidFillAutofillFormDataAndUnfocus_Upload) {
// Set up our form data (empty).
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
std::vector<FieldTypeSet> expected_types;
// These fields should all match.
FieldTypeSet types;
form.fields.push_back(CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText));
types.insert(NAME_FIRST);
expected_types.push_back(types);
form.fields.push_back(CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText));
types.clear();
types.insert(NAME_LAST_SECOND);
types.insert(NAME_LAST);
expected_types.push_back(types);
form.fields.push_back(
CreateTestFormField("Email", "email", "", FormControlType::kInputText));
types.clear();
types.insert(EMAIL_ADDRESS);
expected_types.push_back(types);
FormsSeen({form});
// We will expect these types in the upload and no observed submission. (the
// callback initiated by WaitForAsyncUploadProcess checks these expectations.)
browser_autofill_manager_->SetExpectedSubmittedFieldTypes(expected_types);
browser_autofill_manager_->SetExpectedObservedSubmission(false);
// Form was autofilled with user data.
form.fields[0].value = u"Elvis";
form.fields[1].value = u"Presley";
form.fields[2].value = u"theking@gmail.com";
browser_autofill_manager_->OnDidFillAutofillFormData(form,
base::TimeTicks::Now());
// Simulate lost of focus on the form.
browser_autofill_manager_->OnFocusNoLongerOnForm(true);
}
// Test that suggestions are returned for credit card fields with an
// unrecognized
// autocomplete attribute.
TEST_F(BrowserAutofillManagerTest,
GetCreditCardSuggestions_UnrecognizedAttribute) {
// Set up the form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
// Set a valid autocomplete attribute on the card name.
CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText, "cc-name"),
// Set no autocomplete attribute on the card number.
CreateTestFormField("Card Number", "cardnumber", "",
FormControlType::kInputText),
// Set an unrecognized autocomplete attribute on the expiration month.
CreateTestFormField("Expiration Date", "ccmonth", "",
FormControlType::kInputText, "unrecognized")};
FormsSeen({form});
// Suggestions should be returned for the first two fields
GetAutofillSuggestions(form, form.fields[0]);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
GetAutofillSuggestions(form, form.fields[1]);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
// Suggestions should still be returned for the third field because it is a
// credit card field.
GetAutofillSuggestions(form, form.fields[2]);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
// Test to verify suggestions appears for forms having credit card number split
// across fields.
TEST_P(BrowserAutofillManagerTestForMetadataCardSuggestions,
GetCreditCardSuggestions_ForNumberSplitAcrossFields) {
// Set up our form data with credit card number split across fields.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText)};
// Add new 4 |card_number_field|s to the |form|.
constexpr uint64_t kMaxLength = 4;
form.fields.push_back(CreateTestFormField("Card Number", "cardnumber_1", "",
FormControlType::kInputText, "",
kMaxLength));
form.fields.push_back(CreateTestFormField(
"", "cardnumber_2", "", FormControlType::kInputText, "", kMaxLength));
form.fields.push_back(CreateTestFormField(
"", "cardnumber_3", "", FormControlType::kInputText, "", kMaxLength));
form.fields.push_back(CreateTestFormField(
"", "cardnumber_4", "", FormControlType::kInputText, "", kMaxLength));
form.fields.push_back(CreateTestFormField("Expiration Date", "ccmonth", "",
FormControlType::kInputText));
form.fields.push_back(
CreateTestFormField("", "ccyear", "", FormControlType::kInputText));
FormsSeen({form});
// Verify whether suggestions are populated correctly for one of the middle
// credit card number fields when filled partially.
FormFieldData number_field = form.fields[3];
number_field.value = u"901";
// Get the suggestions for already filled credit card |number_field|.
GetAutofillSuggestions(form, number_field);
external_delegate()->CheckSuggestions(
form.fields[3].global_id(),
{GetCardSuggestion(kVisaCard),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Test that inputs detected to be CVC inputs are forced to
// !should_autocomplete for SingleFieldFormFillRouter::OnWillSubmitForm.
TEST_F(BrowserAutofillManagerTest, DontSaveCvcInAutocompleteHistory) {
FormData form_seen_by_ahm;
EXPECT_CALL(single_field_form_fill_router(), OnWillSubmitForm(_, _, true))
.WillOnce(SaveArg<0>(&form_seen_by_ahm));
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
struct {
const char* label;
const char* name;
const char* value;
FieldType expected_field_type;
} test_fields[] = {
{"Card number", "1", "4234-5678-9012-3456", CREDIT_CARD_NUMBER},
{"Card verification code", "2", "123", CREDIT_CARD_VERIFICATION_CODE},
{"expiration date", "3", "04/2020", CREDIT_CARD_EXP_4_DIGIT_YEAR},
};
for (const auto& test_field : test_fields) {
form.fields.push_back(CreateTestFormField(test_field.label, test_field.name,
test_field.value,
FormControlType::kInputText));
}
FormsSeen({form});
FormSubmitted(form);
EXPECT_EQ(form.fields.size(), form_seen_by_ahm.fields.size());
ASSERT_EQ(std::size(test_fields), form_seen_by_ahm.fields.size());
for (size_t i = 0; i < std::size(test_fields); ++i) {
EXPECT_EQ(
form_seen_by_ahm.fields[i].should_autocomplete,
test_fields[i].expected_field_type != CREDIT_CARD_VERIFICATION_CODE);
}
}
TEST_F(BrowserAutofillManagerTest, DontOfferToSavePaymentsCard) {
FormData form;
CreditCard card;
PrepareForRealPanResponse(&form, &card);
// Manually fill out |form| so we can use it in OnFormSubmitted.
for (auto& field : form.fields) {
if (field.name == u"cardnumber")
field.value = u"4012888888881881";
else if (field.name == u"nameoncard")
field.value = u"John H Dillinger";
else if (field.name == u"ccmonth")
field.value = u"01";
else if (field.name == u"ccyear")
field.value = u"2017";
}
CardUnmaskDelegate::UserProvidedUnmaskDetails details;
details.cvc = u"123";
full_card_unmask_delegate()->OnUnmaskPromptAccepted(details);
OnDidGetRealPan(AutofillClient::PaymentsRpcResult::kSuccess,
"4012888888881881");
browser_autofill_manager_->OnFormSubmitted(form, false,
SubmissionSource::FORM_SUBMISSION);
}
TEST_F(BrowserAutofillManagerTest, ProfileDisabledDoesNotSuggest) {
browser_autofill_manager_->SetAutofillProfileEnabled(autofill_client_, false);
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
GetAutofillSuggestions(
form,
CreateTestFormField("Email", "email", "", FormControlType::kInputEmail));
// Expect no suggestions as autofill and autocomplete are disabled for
// addresses.
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
TEST_F(BrowserAutofillManagerTest, CreditCardDisabledDoesNotSuggest) {
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
false);
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
GetAutofillSuggestions(form,
CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText));
// Expect no suggestions as autofill and autocomplete are disabled for credit
// cards.
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
TEST_F(BrowserAutofillManagerTest, ShouldUploadForm) {
// Note: The enforcement of a minimum number of required fields for upload
// is disabled by default. This tests validates both the disabled and enabled
// scenarios.
FormData form;
form.name = u"TestForm";
form.url = GURL("https://example.com/form.html");
form.action = GURL("https://example.com/submit.html");
// Empty Form.
EXPECT_FALSE(
browser_autofill_manager_->ShouldUploadForm(FormStructure(form)));
// Add a field to the form.
form.fields.push_back(
CreateTestFormField("Name", "name", "", FormControlType::kInputText));
EXPECT_TRUE(browser_autofill_manager_->ShouldUploadForm(FormStructure(form)));
// Add a second field to the form.
form.fields.push_back(
CreateTestFormField("Email", "email", "", FormControlType::kInputText));
EXPECT_TRUE(browser_autofill_manager_->ShouldUploadForm(FormStructure(form)));
// Has less than 3 fields but has autocomplete attribute.
constexpr char autocomplete[] = "given-name";
form.fields[0].autocomplete_attribute = autocomplete;
form.fields[0].parsed_autocomplete = ParseAutocompleteAttribute(autocomplete);
EXPECT_TRUE(browser_autofill_manager_->ShouldUploadForm(FormStructure(form)));
// Has more than 3 fields, no autocomplete attribute.
form.fields.push_back(CreateTestFormField("Country", "country", "",
FormControlType::kInputText, ""));
FormStructure form_structure_3(form);
EXPECT_TRUE(browser_autofill_manager_->ShouldUploadForm(FormStructure(form)));
// Has more than 3 fields and at least one autocomplete attribute.
form.fields[0].autocomplete_attribute = autocomplete;
form.fields[0].parsed_autocomplete = ParseAutocompleteAttribute(autocomplete);
EXPECT_TRUE(browser_autofill_manager_->ShouldUploadForm(FormStructure(form)));
// Is off the record.
autofill_client_.set_is_off_the_record(true);
EXPECT_FALSE(
browser_autofill_manager_->ShouldUploadForm(FormStructure(form)));
// Make sure it's reset for the next test case.
autofill_client_.set_is_off_the_record(false);
EXPECT_TRUE(browser_autofill_manager_->ShouldUploadForm(FormStructure(form)));
// Has one field which is appears to be a password field.
form.fields = {CreateTestFormField("Password", "password", "",
FormControlType::kInputPassword)};
// With min required fields disabled.
EXPECT_TRUE(browser_autofill_manager_->ShouldUploadForm(FormStructure(form)));
// Autofill disabled.
browser_autofill_manager_->SetAutofillProfileEnabled(autofill_client_, false);
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
false);
EXPECT_FALSE(
browser_autofill_manager_->ShouldUploadForm(FormStructure(form)));
}
// Verify that no suggestions are shown on desktop for non credit card related
// fields if the initiating field has the "autocomplete" attribute set to off.
TEST_F(BrowserAutofillManagerTest,
DisplaySuggestions_AutocompleteOffNotRespected_AddressField) {
// Set up an address form.
FormData mixed_form;
mixed_form.name = u"MyForm";
mixed_form.url = GURL("https://myform.com/form.html");
mixed_form.action = GURL("https://myform.com/submit.html");
mixed_form.fields.push_back(CreateTestFormField("First name", "firstname", "",
FormControlType::kInputText));
mixed_form.fields.back().should_autocomplete = false;
mixed_form.fields.push_back(CreateTestFormField("Last name", "lastname", "",
FormControlType::kInputText));
mixed_form.fields.push_back(CreateTestFormField("Address", "address", "",
FormControlType::kInputText));
FormsSeen({mixed_form});
// Suggestions should be displayed on desktop for this field in all
// circumstances.
GetAutofillSuggestions(mixed_form, mixed_form.fields[0]);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
// Suggestions should always be displayed for all the other fields.
for (size_t i = 1U; i < mixed_form.fields.size(); ++i) {
GetAutofillSuggestions(mixed_form, mixed_form.fields[i]);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
}
// Verify that suggestions are shown on desktop for credit card related fields
// even if the initiating field has the "autocomplete" attribute set to off.
TEST_F(BrowserAutofillManagerTest,
DisplaySuggestions_AutocompleteOff_CreditCardField) {
// Set up a credit card form.
FormData mixed_form;
mixed_form.name = u"MyForm";
mixed_form.url = GURL("https://myform.com/form.html");
mixed_form.action = GURL("https://myform.com/submit.html");
mixed_form.fields = {CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText)};
mixed_form.fields.back().should_autocomplete = false;
mixed_form.fields.push_back(CreateTestFormField(
"Card Number", "cardnumber", "", FormControlType::kInputText));
mixed_form.fields.push_back(CreateTestFormField(
"Expiration Month", "ccexpiresmonth", "", FormControlType::kInputText));
mixed_form.fields.back().should_autocomplete = false;
FormsSeen({mixed_form});
// Suggestions should always be displayed.
for (const FormFieldData& mixed_form_field : mixed_form.fields) {
// Single field form fill suggestions being returned are directly correlated
// to whether or not the field has autocomplete set to true or false. We
// know autocomplete must be the single field form filler in this case due
// to the field not having a type that would route to any of the other
// single field form fillers.
ON_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.WillByDefault(Return(mixed_form_field.should_autocomplete));
GetAutofillSuggestions(mixed_form, mixed_form_field);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
}
// Tests that a form with server only types is still autofillable if the form
// gets updated in cache.
TEST_F(BrowserAutofillManagerTest,
DisplaySuggestionsForUpdatedServerTypedForm) {
// Create a form with unknown heuristic fields.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
CreateTestFormField("Field 1", "field1", "", FormControlType::kInputText),
CreateTestFormField("Field 2", "field2", "", FormControlType::kInputText),
CreateTestFormField("Field 3", "field3", "",
FormControlType::kInputText)};
auto form_structure = std::make_unique<FormStructure>(form);
form_structure->DetermineHeuristicTypes(GeoIpCountryCode(""), nullptr,
nullptr);
// Make sure the form can not be autofilled now.
ASSERT_EQ(0u, form_structure->autofill_count());
for (size_t idx = 0; idx < form_structure->field_count(); ++idx) {
ASSERT_EQ(UNKNOWN_TYPE, form_structure->field(idx)->heuristic_type());
}
// Prepare and set known server fields.
const std::vector<FieldType> heuristic_types(form.fields.size(),
UNKNOWN_TYPE);
const std::vector<FieldType> server_types{NAME_FIRST, NAME_MIDDLE, NAME_LAST};
test_api(*form_structure).SetFieldTypes(heuristic_types, server_types);
browser_autofill_manager_->AddSeenFormStructure(std::move(form_structure));
// Make sure the form can be autofilled.
for (const FormFieldData& form_field : form.fields) {
GetAutofillSuggestions(form, form_field);
ASSERT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
// Modify one of the fields in the original form.
form.fields[0].css_classes += u"a";
// Expect the form still can be autofilled.
for (const FormFieldData& form_field : form.fields) {
GetAutofillSuggestions(form, form_field);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
// Modify form action URL. This can happen on in-page navigation if the form
// doesn't have an actual action (attribute is empty).
form.action = net::AppendQueryParameter(form.action, "arg", "value");
// Expect the form still can be autofilled.
for (const FormFieldData& form_field : form.fields) {
GetAutofillSuggestions(form, form_field);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
}
}
TEST_F(BrowserAutofillManagerTest, GetCreditCardSuggestions_VirtualCard) {
personal_data().ClearCreditCards();
CreditCard masked_server_card(CreditCard::RecordType::kMaskedServerCard,
/*server_id=*/"a123");
test::SetCreditCardInfo(&masked_server_card, "Elvis Presley",
"4234567890123456", // Visa
"04", "2999", "1");
masked_server_card.SetNetworkForMaskedCard(kVisaCard);
masked_server_card.set_guid(MakeGuid(7));
masked_server_card.set_virtual_card_enrollment_state(
CreditCard::VirtualCardEnrollmentState::kEnrolled);
masked_server_card.SetNickname(u"nickname");
personal_data().AddServerCreditCard(masked_server_card);
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
// Card number field.
GetAutofillSuggestions(form, form.fields[1]);
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
std::string label = std::string("04/99");
#else
std::string label = std::string("Expires on 04/99");
#endif
Suggestion virtual_card_suggestion = Suggestion(
"Virtual card",
std::string("nickname ") +
test::ObfuscatedCardDigitsAsUTF8(
"3456", ObfuscationLengthForCreditCardLastFourDigits()),
label, Suggestion::Icon::kCardVisa,
autofill::PopupItemId::kVirtualCreditCardEntry);
external_delegate()->CheckSuggestions(
form.fields[1].global_id(),
{virtual_card_suggestion,
Suggestion(
std::string("nickname ") +
test::ObfuscatedCardDigitsAsUTF8(
"3456", ObfuscationLengthForCreditCardLastFourDigits()),
label, Suggestion::Icon::kCardVisa, PopupItemId::kCreditCardEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/true)});
// Non card number field (cardholder name field).
GetAutofillSuggestions(form, form.fields[0]);
#if BUILDFLAG(IS_ANDROID) || BUILDFLAG(IS_IOS)
label = test::ObfuscatedCardDigitsAsUTF8(
"3456", ObfuscationLengthForCreditCardLastFourDigits());
#else
label = std::string("nickname ") +
test::ObfuscatedCardDigitsAsUTF8(
"3456", ObfuscationLengthForCreditCardLastFourDigits()) +
std::string(", expires on 04/99");
#endif
virtual_card_suggestion =
Suggestion("Virtual card", std::string("Elvis Presley"), label,
Suggestion::Icon::kCardVisa,
autofill::PopupItemId::kVirtualCreditCardEntry);
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{virtual_card_suggestion,
Suggestion("Elvis Presley", label, Suggestion::Icon::kCardVisa,
PopupItemId::kCreditCardEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/true)});
// Incomplete form.
GetAutofillSuggestions(form, form.fields[0]);
external_delegate()->CheckSuggestions(
form.fields[0].global_id(),
{virtual_card_suggestion,
Suggestion("Elvis Presley", label, Suggestion::Icon::kCardVisa,
PopupItemId::kCreditCardEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/true)});
}
TEST_F(BrowserAutofillManagerTest,
IbanFormProcessed_AutofillOptimizationGuidePresent) {
FormData form_data = CreateTestIbanFormData();
FormStructure form_structure{form_data};
test_api(form_structure).SetFieldTypes({IBAN_VALUE}, {IBAN_VALUE});
MockAutofillOptimizationGuide autofill_optimization_guide;
ON_CALL(autofill_client_, GetAutofillOptimizationGuide)
.WillByDefault(Return(&autofill_optimization_guide));
// We reset `browser_autofill_manager_` here so that `autofill_client_`
// initializes `autofill_optimization_guide` in `browser_autofill_manager_`.
ResetBrowserAutofillManager();
EXPECT_CALL(autofill_optimization_guide, OnDidParseForm).Times(1);
test_api(*browser_autofill_manager_)
.OnFormProcessed(form_data, form_structure);
}
TEST_F(BrowserAutofillManagerTest,
IbanFormProcessed_AutofillOptimizationGuideNotPresent) {
FormData form_data = CreateTestIbanFormData();
FormStructure form_structure{form_data};
test_api(form_structure).SetFieldTypes({IBAN_VALUE}, {IBAN_VALUE});
// Test that form processing doesn't crash when we have an IBAN form but no
// AutofillOptimizationGuide present.
test_api(*browser_autofill_manager_)
.OnFormProcessed(form_data, form_structure);
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogAutocomplete_NoHappinessMetricsEmitted) {
FormData form;
form.name = u"NothingSpecial";
form.fields = {CreateTestFormField("Something", "something", "",
FormControlType::kInputText)};
FormsSeen({form});
base::HistogramTester histogram_tester;
browser_autofill_manager_->DidShowSuggestions(
std::vector<PopupItemId>({PopupItemId::kAutocompleteEntry}), form,
form.fields.back());
// No Autofill logs.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(histograms,
Not(AnyOf(HasSubstr("Autofill.UserHappiness"),
HasSubstr("Autofill.FormEvents.Address"),
HasSubstr("Autofill.FormEvents.CreditCard"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogAutofillAddressShownMetric) {
FormData form = CreateTestAddressFormData();
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount("Autofill.UserHappiness",
AutofillMetrics::SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount("Autofill.UserHappiness.Address",
AutofillMetrics::SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.UserHappiness", AutofillMetrics::SUGGESTIONS_SHOWN_ONCE, 1);
histogram_tester.ExpectBucketCount("Autofill.UserHappiness.Address",
AutofillMetrics::SUGGESTIONS_SHOWN_ONCE,
1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// No Autocomplete or credit cards logs.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(histograms,
Not(AnyOf(HasSubstr("Autocomplete.Events2"),
HasSubstr("Autofill.FormEvents.CreditCard"))));
}
TEST_F(BrowserAutofillManagerTest, DidShowSuggestions_LogByType_AddressOnly) {
// Create a form with name and address fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText),
CreateTestFormField("Address Line 1", "addr1", "",
FormControlType::kInputText)};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressOnly",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressOnly",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(
HasSubstr("Autofill.FormEvents.Address.AddressPlusContact"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmail "),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmailPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.ContactOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogByType_AddressOnlyWithoutName) {
// Create a form with address fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {CreateTestFormField("Address Line 1", "addr1", "",
FormControlType::kInputText),
CreateTestFormField("Address Line 2", "addr2", "",
FormControlType::kInputText),
CreateTestFormField("Postal Code", "zipcode", "",
FormControlType::kInputText)};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressOnly",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressOnly",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(
HasSubstr("Autofill.FormEvents.Address.AddressPlusContact"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmail "),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmailPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.ContactOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest, DidShowSuggestions_LogByType_ContactOnly) {
// Create a form with name and contact fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {
CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText),
CreateTestFormField("Email", "email", "", FormControlType::kInputEmail)};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.ContactOnly",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.ContactOnly",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(
HasSubstr("Autofill.FormEvents.Address.AddressPlusContact"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmail "),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmailPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogByType_ContactOnlyWithoutName) {
// Create a form with contact fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {
CreateTestFormField("Phone Number", "phonenumber1", "",
FormControlType::kInputTelephone, "tel-country-code"),
CreateTestFormField("Phone Number", "phonenumber2", "",
FormControlType::kInputTelephone, "tel-national"),
CreateTestFormField("Email", "email", "", FormControlType::kInputEmail,
"email")};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.ContactOnly",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.ContactOnly",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(
HasSubstr("Autofill.FormEvents.Address.AddressPlusContact"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmail "),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmailPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest, DidShowSuggestions_LogByType_PhoneOnly) {
// Create a form with phone field.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {
CreateTestFormField("Phone Number", "phonenumber", "",
FormControlType::kInputTelephone, "tel-country-code"),
CreateTestFormField("Phone Number", "phonenumber", "",
FormControlType::kInputTelephone, "tel-area-code"),
CreateTestFormField("Phone Number", "phonenumber", "",
FormControlType::kInputTelephone, "tel-local")};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.PhoneOnly",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.PhoneOnly",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmailPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmail"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusContact"),
HasSubstr("Autofill.FormEvents.Address.AddressOnly"),
HasSubstr("Autofill.FormEvents.Address.ContactOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest, DidShowSuggestions_LogByType_Other) {
// Create a form with name fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText),
CreateTestFormField("Middle Name", "middlename", "",
FormControlType::kInputText),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText)};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.Other",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.Other",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(
HasSubstr("Autofill.FormEvents.Address.AddressPlusContact"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmail "),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmailPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.ContactOnly"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogByType_AddressPlusEmail) {
// Create a form with name, address, and email fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {
CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText),
CreateTestFormField("Address Line 1", "addr1", "",
FormControlType::kInputText),
CreateTestFormField("Email", "email", "", FormControlType::kInputEmail)};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusEmail",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusEmail",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmailPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressOnly"),
HasSubstr("Autofill.FormEvents.Address.ContactOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogByType_AddressPlusEmailWithoutName) {
// Create a form with address and email fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {
CreateTestFormField("Address Line 1", "addr1", "",
FormControlType::kInputText),
CreateTestFormField("Address Line 2", "addr2", "",
FormControlType::kInputText),
CreateTestFormField("Email", "email", "", FormControlType::kInputEmail)};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusEmail",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusEmail",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmailPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressOnly"),
HasSubstr("Autofill.FormEvents.Address.ContactOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogByType_AddressPlusPhone) {
// Create a form with name fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText),
CreateTestFormField("Address Line 1", "addr1", "",
FormControlType::kInputText),
CreateTestFormField("Phone Number", "phonenumber", "",
FormControlType::kInputTelephone)};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusPhone",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusPhone",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmailPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmail "),
HasSubstr("Autofill.FormEvents.Address.AddressOnly"),
HasSubstr("Autofill.FormEvents.Address.ContactOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogByType_AddressPlusPhoneWithoutName) {
// Create a form with name, address, and phone fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {CreateTestFormField("Address Line 1", "addr1", "",
FormControlType::kInputText),
CreateTestFormField("Address Line 2", "addr2", "",
FormControlType::kInputText),
CreateTestFormField("Phone Number", "phonenumber", "",
FormControlType::kInputTelephone)};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusPhone",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusPhone",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmailPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmail "),
HasSubstr("Autofill.FormEvents.Address.AddressOnly"),
HasSubstr("Autofill.FormEvents.Address.ContactOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogByType_AddressPlusEmailPlusPhone) {
// Create a form with name, address, phone, and email fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {
CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText),
CreateTestFormField("Address Line 1", "addr1", "",
FormControlType::kInputText),
CreateTestFormField("Phone Number", "phonenumber", "",
FormControlType::kInputTelephone),
CreateTestFormField("Email", "email", "", FormControlType::kInputEmail)};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusEmailPlusPhone",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusEmailPlusPhone",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(HasSubstr("Autofill.FormEvents.Address.AddressPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmail "),
HasSubstr("Autofill.FormEvents.Address.AddressOnly"),
HasSubstr("Autofill.FormEvents.Address.ContactOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogByType_AddressPlusEmailPlusPhoneWithoutName) {
// Create a form with address, phone, and email fields.
FormData form;
form.name = u"MyForm";
form.button_titles = {std::make_pair(
u"Submit", mojom::ButtonTitleType::BUTTON_ELEMENT_SUBMIT_TYPE)};
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.main_frame_origin =
url::Origin::Create(GURL("https://myform_root.com/form.html"));
form.submission_event = SubmissionIndicatorEvent::SAME_DOCUMENT_NAVIGATION;
form.fields = {
CreateTestFormField("Address Line 1", "addr1", "",
FormControlType::kInputText),
CreateTestFormField("Phone Number", "phonenumber", "",
FormControlType::kInputTelephone),
CreateTestFormField("Email", "email", "", FormControlType::kInputEmail)};
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusEmailPlusPhone",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusEmailPlusPhone",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.Address.AddressPlusContact",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// Logging is not done for other types of address forms.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(
histograms,
Not(AnyOf(HasSubstr("Autofill.FormEvents.Address.AddressPlusPhone"),
HasSubstr("Autofill.FormEvents.Address.AddressPlusEmail "),
HasSubstr("Autofill.FormEvents.Address.AddressOnly"),
HasSubstr("Autofill.FormEvents.Address.ContactOnly"),
HasSubstr("Autofill.FormEvents.Address.PhoneOnly"),
HasSubstr("Autofill.FormEvents.Address.Other"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_LogAutofillCreditCardShownMetric) {
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
base::HistogramTester histogram_tester;
DidShowAutofillSuggestions(form);
histogram_tester.ExpectBucketCount("Autofill.UserHappiness",
AutofillMetrics::SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount("Autofill.UserHappiness.CreditCard",
AutofillMetrics::SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.UserHappiness", AutofillMetrics::SUGGESTIONS_SHOWN_ONCE, 1);
histogram_tester.ExpectBucketCount("Autofill.UserHappiness.CreditCard",
AutofillMetrics::SUGGESTIONS_SHOWN_ONCE,
1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.CreditCard",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN, 1);
histogram_tester.ExpectBucketCount(
"Autofill.FormEvents.CreditCard",
autofill_metrics::FORM_EVENT_SUGGESTIONS_SHOWN_ONCE, 1);
// No Autocomplete or address logs.
const std::string histograms = histogram_tester.GetAllHistogramsRecorded();
EXPECT_THAT(histograms, Not(AnyOf(HasSubstr("Autocomplete.Events2"),
HasSubstr("Autofill.FormEvents.Address"))));
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_CreditCard_PreflightFetchingCall) {
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
EXPECT_CALL(cc_access_manager(), PrepareToFetchCreditCard)
.Times(IsCreditCardFidoAuthenticationEnabled() ? 1 : 0);
browser_autofill_manager_->DidShowSuggestions(
std::vector<PopupItemId>({PopupItemId::kCreditCardEntry}), form,
form.fields[0]);
}
TEST_F(BrowserAutofillManagerTest,
DidShowSuggestions_AddessSuggestion_NoPreflightFetchingCall) {
FormData form = CreateTestAddressFormData();
FormsSeen({form});
EXPECT_CALL(cc_access_manager(), PrepareToFetchCreditCard).Times(0);
browser_autofill_manager_->DidShowSuggestions(
std::vector<PopupItemId>({PopupItemId::kAddressEntry}), form,
form.fields[0]);
}
TEST_F(BrowserAutofillManagerTest, PageLanguageGetsCorrectlySet) {
FormData form = CreateTestAddressFormData();
browser_autofill_manager_->OnFormsSeen({form}, {});
FormStructure* parsed_form =
browser_autofill_manager_->FindCachedFormById(form.global_id());
ASSERT_TRUE(parsed_form);
ASSERT_EQ(LanguageCode(), parsed_form->current_page_language());
autofill_client_.GetLanguageState()->SetCurrentLanguage("zh");
browser_autofill_manager_->OnFormsSeen({form}, {});
parsed_form = browser_autofill_manager_->FindCachedFormById(form.global_id());
ASSERT_EQ(LanguageCode("zh"), parsed_form->current_page_language());
}
// Test language detection on frames depending on whether the frame is active or
// not.
class BrowserAutofillManagerTestPageLanguageDetection
: public BrowserAutofillManagerTest,
public testing::WithParamInterface<bool> {
public:
BrowserAutofillManagerTestPageLanguageDetection() {
scoped_features_.InitAndEnableFeature(
features::kAutofillPageLanguageDetection);
}
bool is_in_active_frame() const { return GetParam(); }
private:
base::test::ScopedFeatureList scoped_features_;
};
TEST_P(BrowserAutofillManagerTestPageLanguageDetection, GetsCorrectlyDetected) {
FormData form = CreateTestAddressFormData();
browser_autofill_manager_->OnFormsSeen({form}, {});
FormStructure* parsed_form =
browser_autofill_manager_->FindCachedFormById(form.global_id());
ASSERT_TRUE(parsed_form);
ASSERT_EQ(LanguageCode(), parsed_form->current_page_language());
translate::LanguageDetectionDetails language_detection_details;
language_detection_details.adopted_language = "hu";
autofill_driver_->SetIsInActiveFrame(is_in_active_frame());
browser_autofill_manager_->OnLanguageDetermined(language_detection_details);
autofill_client_.GetLanguageState()->SetCurrentLanguage("hu");
parsed_form = browser_autofill_manager_->FindCachedFormById(form.global_id());
// Language detection is used only for active frames.
auto expected_language_code =
is_in_active_frame() ? LanguageCode("hu") : LanguageCode();
ASSERT_EQ(*expected_language_code, *parsed_form->current_page_language());
}
INSTANTIATE_TEST_SUITE_P(All,
BrowserAutofillManagerTestPageLanguageDetection,
testing::Bool());
// BrowserAutofillManagerTest with different browser profile types.
class BrowserAutofillManagerProfileMetricsTest
: public BrowserAutofillManagerTest,
public testing::WithParamInterface<profile_metrics::BrowserProfileType> {
public:
BrowserAutofillManagerProfileMetricsTest() : profile_type_(GetParam()) {
EXPECT_CALL(autofill_client_, GetProfileType())
.WillRepeatedly(Return(profile_type_));
}
const profile_metrics::BrowserProfileType profile_type_;
};
// Tests if submitting a form in different browser profile types records correct
// |Autofill.FormSubmission.PerProfileType| metric.
TEST_P(BrowserAutofillManagerProfileMetricsTest,
FormSubmissionPerProfileTypeMetrics) {
// Set up our form data.
FormData form = CreateTestAddressFormData();
FormsSeen({form});
GetAutofillSuggestions(form, form.fields[0]);
base::HistogramTester histogram_tester;
FormSubmitted(form);
histogram_tester.ExpectBucketCount("Autofill.FormSubmission.PerProfileType",
profile_type_, 1);
histogram_tester.ExpectTotalCount("Autofill.FormSubmission.PerProfileType",
1);
}
INSTANTIATE_TEST_SUITE_P(
All,
BrowserAutofillManagerProfileMetricsTest,
testing::ValuesIn({profile_metrics::BrowserProfileType::kRegular,
profile_metrics::BrowserProfileType::kIncognito,
profile_metrics::BrowserProfileType::kGuest}));
// Tests that autocomplete-related metrics are emitted correctly on form
// submission.
TEST_F(BrowserAutofillManagerTest, AutocompleteMetrics) {
// `kAutocompleteValues` corresponds to empty, valid, garbage and off.
constexpr const char* kAutocompleteValues[]{"", "name", "asdf", "off"};
// The 4 possible combinations of heuristic and server type status:
// - Neither a fillable heuristic type nor a fillable server type.
// - Only a fillable server type.
// - Only a fillable heuristic type.
// - Both a fillable heuristic type and a fillable server type.
// NO_SERVER_DATA and UNKNOWN_TYPE are both unfillable types, but
// NO_SERVER_DATA is ignored in the PredictionCollisionType metric.
constexpr FieldType kTypeClasses[][2]{{UNKNOWN_TYPE, NO_SERVER_DATA},
{UNKNOWN_TYPE, EMAIL_ADDRESS},
{ADDRESS_HOME_COUNTRY, UNKNOWN_TYPE},
{ADDRESS_HOME_COUNTRY, EMAIL_ADDRESS}};
// Create a form with one field per kAutofillValue x kTypeClass combination.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
std::vector<FieldType> heuristic_types, server_types;
for (const char* autocomplete : kAutocompleteValues) {
for (const auto& types : kTypeClasses) {
form.fields.push_back(CreateTestFormField(
"", "", "", FormControlType::kInputText, autocomplete));
heuristic_types.push_back(types[0]);
server_types.push_back(types[1]);
}
}
// Override the types and simulate seeing the form on page load.
auto form_structure = std::make_unique<FormStructure>(form);
form_structure->DetermineHeuristicTypes(GeoIpCountryCode(""), nullptr,
nullptr);
test_api(*form_structure).SetFieldTypes(heuristic_types, server_types);
browser_autofill_manager_->AddSeenFormStructure(std::move(form_structure));
// Submit the form and verify that all metrics are collected correctly.
base::HistogramTester histogram_tester;
FormSubmitted(form);
// Expect one entry for each possible PredictionStateAutocompleteStatePair.
// Fields without type predictions and autocomplete attributes are ignored.
histogram_tester.ExpectTotalCount(
"Autofill.Autocomplete.PredictionCollisionState", form.fields.size() - 1);
for (int i = 0; i < 15; i++) {
histogram_tester.ExpectBucketCount(
"Autofill.Autocomplete.PredictionCollisionState", i, 1);
}
// A separate PredictionCollisionType metric exists for every prediction-type
// autocomplete status combination. Above, we created four fields per
// `kAutocompleteValues` - so we expect 4 samples in every bucket.
// The exception is the .Server metric, which is not emitted for
// NO_SERVER_DATA and hence expects only three samples.
const std::string kTypeHistogram =
"Autofill.Autocomplete.PredictionCollisionType2.";
for (const char* suffix : {"Garbage", "None", "Off", "Valid"}) {
histogram_tester.ExpectTotalCount(kTypeHistogram + "Heuristics." + suffix,
4);
histogram_tester.ExpectTotalCount(kTypeHistogram + "Server." + suffix, 3);
histogram_tester.ExpectTotalCount(
kTypeHistogram + "ServerOrHeuristics." + suffix, 4);
}
}
struct ContextMenuImpressionTestCase {
// Autocomplete attribute value.
const char* autocomplete_attribute_value;
// Heuristic type for the field in the test case.
FieldType heuristic_type;
// Server type for the field in the test case.
FieldType server_type;
// Expected autocomplete state that would be logged in the metrics.
AutofillMetrics::AutocompleteState expected_autocomplete_state;
// Expected autofill type that would be logged in the metrics.
FieldType expected_autofill_type;
};
class BrowserAutofillManagerContextMenuImpressionsTest
: public testing::WithParamInterface<ContextMenuImpressionTestCase>,
public BrowserAutofillManagerTest {};
INSTANTIATE_TEST_SUITE_P(
BrowserAutofillManagerContextMenuTests,
BrowserAutofillManagerContextMenuImpressionsTest,
testing::Values(
// Empty Autocomplete attribute
ContextMenuImpressionTestCase{"", UNKNOWN_TYPE, NO_SERVER_DATA,
AutofillMetrics::AutocompleteState::kNone,
UNKNOWN_TYPE},
// Valid Autocomplete attribute
ContextMenuImpressionTestCase{
"name", UNKNOWN_TYPE, EMAIL_ADDRESS,
AutofillMetrics::AutocompleteState::kValid, NAME_FULL},
// Garbage Autocomplete attribute
ContextMenuImpressionTestCase{
"asdf", ADDRESS_HOME_COUNTRY, UNKNOWN_TYPE,
AutofillMetrics::AutocompleteState::kGarbage, UNKNOWN_TYPE},
// Off Autocomplete attribute
ContextMenuImpressionTestCase{
"off", ADDRESS_HOME_COUNTRY, EMAIL_ADDRESS,
AutofillMetrics::AutocompleteState::kOff, EMAIL_ADDRESS}));
// Tests that metrics are emitted correctly on form submission for the fields
// from where the context menu was triggered.
TEST_P(BrowserAutofillManagerContextMenuImpressionsTest,
ContextMenuImpressionMetrics) {
auto test_case = GetParam();
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {CreateTestFormField("", "", "", FormControlType::kInputText,
test_case.autocomplete_attribute_value)};
// Override the types and simulate seeing the form on page load.
auto form_structure = std::make_unique<FormStructure>(form);
test_api(*form_structure)
.SetFieldTypes({test_case.heuristic_type}, {test_case.server_type});
browser_autofill_manager_->AddSeenFormStructure(std::move(form_structure));
// Simulate context menu trigger for all the fields.
browser_autofill_manager_->OnContextMenuShownInField(
form.global_id(), form.fields[0].global_id());
// Submit the form and verify that all metrics are collected correctly.
base::HistogramTester histogram_tester;
FormSubmitted(form);
EXPECT_THAT(
histogram_tester.GetAllSamples(
"Autofill.FieldContextMenuImpressions.ByAutocomplete"),
BucketsAre(base::Bucket(test_case.expected_autocomplete_state, 1)));
EXPECT_THAT(histogram_tester.GetAllSamples(
"Autofill.FieldContextMenuImpressions.ByAutofillType"),
BucketsAre(base::Bucket(test_case.expected_autofill_type, 1)));
EXPECT_THAT(histogram_tester.GetAllSamples(
"Autofill.FormContextMenuImpressions.ByNumberOfFields"),
BucketsAre(base::Bucket(1, 1)));
}
// Test that if a form is mixed content we show a warning instead of any
// suggestions.
TEST_F(BrowserAutofillManagerTest, GetSuggestions_MixedForm) {
// Set up our form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("http://myform.com/submit.html");
form.fields = {CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText)};
GetAutofillSuggestions(form, form.fields[0]);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
form.fields.back().global_id(),
{Suggestion(l10n_util::GetStringUTF8(IDS_AUTOFILL_WARNING_MIXED_FORM), "",
Suggestion::Icon::kNoIcon, PopupItemId::kMixedFormMessage)});
}
// Test that if a form is mixed content we do not show a warning if the opt out
// policy is set.
TEST_F(BrowserAutofillManagerTest, GetSuggestions_MixedFormOptOutPolicy) {
// Set pref to disabled.
autofill_client_.GetPrefs()->SetBoolean(::prefs::kMixedFormsWarningsEnabled,
false);
// Set up our form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("http://myform.com/submit.html");
form.fields = {CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText)};
GetAutofillSuggestions(form, form.fields[0]);
// Check there is no warning.
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
// Test that we dismiss the mixed form warning if user starts typing.
TEST_F(BrowserAutofillManagerTest, GetSuggestions_MixedFormUserTyped) {
// Set up our form data.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("http://myform.com/submit.html");
form.fields = {CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText)};
GetAutofillSuggestions(form, form.fields[0]);
// Test that we sent the right values to the external delegate.
external_delegate()->CheckSuggestions(
form.fields.back().global_id(),
{Suggestion(l10n_util::GetStringUTF8(IDS_AUTOFILL_WARNING_MIXED_FORM), "",
Suggestion::Icon::kNoIcon, PopupItemId::kMixedFormMessage)});
// Pretend user started typing and make sure we no longer set suggestions.
form.fields[0].value = u"Michael";
form.fields[0].properties_mask |= kUserTyped;
GetAutofillSuggestions(form, form.fields[0]);
external_delegate()->CheckNoSuggestions(form.fields[0].global_id());
}
// Test that we don't treat javascript scheme target URLs as mixed forms.
// Regression test for crbug.com/1135173
TEST_F(BrowserAutofillManagerTest, GetSuggestions_JavascriptUrlTarget) {
// Set up our form data, using a javascript scheme target URL.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("javascript:alert('hello');");
form.fields = {CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText)};
GetAutofillSuggestions(form, form.fields[0]);
// Check there is no warning.
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
// Test that we don't treat about:blank target URLs as mixed forms.
TEST_F(BrowserAutofillManagerTest, GetSuggestions_AboutBlankTarget) {
// Set up our form data, using a javascript scheme target URL.
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("about:blank");
form.fields = {CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText)};
GetAutofillSuggestions(form, form.fields[0]);
// Check there is no warning.
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
// Tests that both Autofill popup and TTF are hidden on renderer event.
TEST_F(BrowserAutofillManagerTest, HideAutofillPopupAndOtherPopups) {
EXPECT_CALL(autofill_client_,
HideAutofillPopup(PopupHidingReason::kRendererEvent));
EXPECT_CALL(touch_to_fill_delegate(), HideTouchToFill);
EXPECT_CALL(fast_checkout_delegate(),
HideFastCheckout(/*allow_further_runs=*/false));
browser_autofill_manager_->OnHidePopup();
}
// Tests that only Autofill popup is hidden on editing end, but not TTF or FC.
TEST_F(BrowserAutofillManagerTest, OnDidEndTextFieldEditing) {
EXPECT_CALL(autofill_client_,
HideAutofillPopup(PopupHidingReason::kEndEditing));
EXPECT_CALL(touch_to_fill_delegate(), HideTouchToFill).Times(0);
EXPECT_CALL(fast_checkout_delegate(),
HideFastCheckout(/*allow_further_runs=*/false))
.Times(0);
browser_autofill_manager_->OnDidEndTextFieldEditing();
}
// Tests that Autofill suggestions are not shown if TTF is eligible and shown.
TEST_F(BrowserAutofillManagerTest, AutofillSuggestionsOrTouchToFill) {
FormData form;
CreateTestCreditCardFormData(&form, /*is_https=*/true,
/*use_month_type=*/false);
FormsSeen({form});
const FormFieldData& field = form.fields[1];
// Not a form element click, Autofill suggestions shown.
EXPECT_CALL(touch_to_fill_delegate(), TryToShowTouchToFill).Times(0);
TryToShowTouchToFill(form, field, /*form_element_was_clicked=*/false);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
// TTF not available, Autofill suggestions shown.
EXPECT_CALL(touch_to_fill_delegate(), TryToShowTouchToFill)
.WillOnce(Return(false));
TryToShowTouchToFill(form, field, /*form_element_was_clicked=*/true);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
// A form element click and TTF available, Autofill suggestions not shown.
EXPECT_CALL(touch_to_fill_delegate(), TryToShowTouchToFill)
.WillOnce(Return(true));
TryToShowTouchToFill(form, field, /*form_element_was_clicked=*/true);
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
// Tests that neither Autofill suggestions nor TTF is triggered if TTF is
// already shown.
TEST_F(BrowserAutofillManagerTest, ShowNothingIfTouchToFillAlreadyShown) {
FormData form;
CreateTestCreditCardFormData(&form, /*is_https=*/true,
/*use_month_type=*/false);
FormsSeen({form});
const FormFieldData& field = form.fields[1];
EXPECT_CALL(touch_to_fill_delegate(), IsShowingTouchToFill)
.WillOnce(Return(true));
EXPECT_CALL(touch_to_fill_delegate(), TryToShowTouchToFill).Times(0);
TryToShowTouchToFill(form, field, /*form_element_was_clicked=*/true);
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
// Test that 'Scan New Card' suggestion is shown based on whether autofill
// credit card is enabled or disabled.
TEST_F(BrowserAutofillManagerTest, ScanCreditCardBasedOnAutofillPreference) {
ON_CALL(autofill_client_, HasCreditCardScanFeature())
.WillByDefault(Return(true));
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
const FormFieldData& card_number_field = form.fields[1];
ASSERT_EQ(card_number_field.name, u"cardnumber");
// Test case where autofill is enabled.
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
true);
EXPECT_TRUE(browser_autofill_manager_->ShouldShowScanCreditCard(
form, card_number_field));
// Test case where autofill is disabled.
browser_autofill_manager_->SetAutofillPaymentMethodsEnabled(autofill_client_,
false);
EXPECT_FALSE(browser_autofill_manager_->ShouldShowScanCreditCard(
form, card_number_field));
}
// Test that 'Scan New Card' suggestion is shown based on whether platform
// supports card scanning.
TEST_F(BrowserAutofillManagerTest, ScanCreditCardBasedOnPlatformSupport) {
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
const FormFieldData& card_number_field = form.fields[1];
ASSERT_EQ(card_number_field.name, u"cardnumber");
// Test case where device and platform support scanning credit cards.
ON_CALL(autofill_client_, HasCreditCardScanFeature())
.WillByDefault(Return(true));
EXPECT_TRUE(browser_autofill_manager_->ShouldShowScanCreditCard(
form, card_number_field));
// Test case where device and platform do not support scanning credit cards.
ON_CALL(autofill_client_, HasCreditCardScanFeature())
.WillByDefault(Return(false));
EXPECT_FALSE(browser_autofill_manager_->ShouldShowScanCreditCard(
form, card_number_field));
}
// Test that 'Scan New Card' suggestion is shown based on whether form field
// chosen is a credit card number field.
TEST_F(BrowserAutofillManagerTest, ScanCreditCardBasedOnCreditCardNumberField) {
ON_CALL(autofill_client_, HasCreditCardScanFeature())
.WillByDefault(Return(true));
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
// Test case for credit-card-number field.
const FormFieldData& card_number_field = form.fields[1];
ASSERT_EQ(card_number_field.name, u"cardnumber");
EXPECT_TRUE(browser_autofill_manager_->ShouldShowScanCreditCard(
form, card_number_field));
// Test case for non-credit-card-number field.
const FormFieldData& cvc_field = form.fields[4];
ASSERT_EQ(cvc_field.name, u"cvc");
EXPECT_FALSE(
browser_autofill_manager_->ShouldShowScanCreditCard(form, cvc_field));
}
// Test that 'Scan New Card' suggestion is shown based on whether the form is
// secure.
TEST_F(BrowserAutofillManagerTest, ScanCreditCardBasedOnIsFormSecure) {
ON_CALL(autofill_client_, HasCreditCardScanFeature())
.WillByDefault(Return(true));
// Test case for HTTP form.
FormData form_http = CreateTestCreditCardFormData(/*is_https=*/false,
/*use_month_type=*/false);
FormsSeen({form_http});
const FormFieldData& card_number_field_http = form_http.fields[1];
ASSERT_EQ(card_number_field_http.name, u"cardnumber");
EXPECT_FALSE(browser_autofill_manager_->ShouldShowScanCreditCard(
form_http, card_number_field_http));
// Test case for HTTPS form.
FormData form_https =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form_https});
const FormFieldData& card_number_field_https = form_https.fields[1];
ASSERT_EQ(card_number_field_https.name, u"cardnumber");
EXPECT_TRUE(browser_autofill_manager_->ShouldShowScanCreditCard(
form_https, card_number_field_https));
}
// Tests that compose suggestions are not queried if Autofill has suggestions
// itself.
TEST_F(BrowserAutofillManagerTest, NoComposeSuggestionsByDefault) {
MockAutofillComposeDelegate compose_delegate;
ON_CALL(autofill_client_, GetComposeDelegate)
.WillByDefault(Return(&compose_delegate));
FormData form = CreateTestAddressFormData();
form.fields[3].form_control_type = FormControlType::kTextArea;
FormsSeen({form});
// The third field is meant to correspond to address line 1. For that (unlike
// for first and last name), parsing also derives that type if it is a
// textarea.
EXPECT_CALL(compose_delegate, ShouldOfferComposePopup).Times(0);
GetAutofillSuggestions(form, form.fields[3]);
external_delegate()->CheckSuggestions(
form.fields[3].global_id(),
{Suggestion("123 Apple St., unit 6", "123 Apple St.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
Suggestion("3734 Elvis Presley Blvd., Apt. 10",
"3734 Elvis Presley Blvd.", kAddressEntryIcon,
PopupItemId::kAddressEntry),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManageAddressesEntry()});
}
// Tests that Compose suggestions are queried if the trigger source indicates
// that the focus change happened without click/tap interaction. It also
// verifies that neither Autofill nor single form fill suggestions are queried.
TEST_F(BrowserAutofillManagerTest, ComposeSuggestionsOnFocusWithoutClick) {
MockAutofillComposeDelegate compose_delegate;
ON_CALL(autofill_client_, GetComposeDelegate)
.WillByDefault(Return(&compose_delegate));
FormData form = CreateTestAddressFormData();
form.fields[3].form_control_type = FormControlType::kTextArea;
FormsSeen({form});
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.Times(0);
EXPECT_CALL(compose_delegate,
ShouldOfferComposePopup(Property(&FormFieldData::global_id,
Eq(form.fields[3].global_id()))))
.WillOnce(Return(true));
GetAutofillSuggestions(
form, form.fields[3],
AutofillSuggestionTriggerSource::kTextareaFocusedWithoutClick);
external_delegate()->CheckSuggestionCount(form.fields[3].global_id(), 1);
}
// Tests that compose suggestions are queried and shown for textareas if
// Autofill does not have suggestions of its own and the OS is not Android, iOS
// or ChromeOS.
TEST_F(BrowserAutofillManagerTest, ComposeSuggestionsAreQueriedForTextareas) {
MockAutofillComposeDelegate compose_delegate;
ON_CALL(autofill_client_, GetComposeDelegate)
.WillByDefault(Return(&compose_delegate));
FormData form = test::GetFormData(
{.fields = {{.form_control_type = FormControlType::kTextArea}}});
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
FormsSeen({form});
EXPECT_CALL(single_field_form_fill_router(), OnGetSingleFieldSuggestions)
.Times(0);
EXPECT_CALL(compose_delegate,
ShouldOfferComposePopup(Property(&FormFieldData::global_id,
Eq(form.fields[0].global_id()))))
.WillOnce(Return(true));
EXPECT_CALL(compose_delegate, HasSavedState(form.fields[0].global_id()))
.WillOnce(Return(true));
GetAutofillSuggestions(form, form.fields[0]);
external_delegate()->CheckSuggestionCount(form.fields[0].global_id(), 1);
}
// Test param indicates if there is an active screen reader.
class OnFocusOnFormFieldTest : public BrowserAutofillManagerTest,
public testing::WithParamInterface<bool> {
protected:
OnFocusOnFormFieldTest() = default;
~OnFocusOnFormFieldTest() override = default;
void SetUp() override {
BrowserAutofillManagerTest::SetUp();
has_active_screen_reader_ = GetParam();
external_delegate()->set_has_active_screen_reader(
has_active_screen_reader_);
}
void TearDown() override {
external_delegate()->set_has_active_screen_reader(false);
BrowserAutofillManagerTest::TearDown();
}
void CheckSuggestionsAvailableIfScreenReaderRunning() {
#if BUILDFLAG(IS_CHROMEOS_ASH)
// The only existing functions for determining whether ChromeVox is in use
// are in the src/chrome directory, which cannot be included in components.
// Thus, if the platform is ChromeOS, we assume that ChromeVox is in use at
// this point in the code.
EXPECT_EQ(true,
external_delegate()->has_suggestions_available_on_field_focus());
#else
EXPECT_EQ(has_active_screen_reader_,
external_delegate()->has_suggestions_available_on_field_focus());
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
}
void CheckNoSuggestionsAvailableOnFieldFocus() {
EXPECT_FALSE(
external_delegate()->has_suggestions_available_on_field_focus());
}
bool has_active_screen_reader_;
};
TEST_P(OnFocusOnFormFieldTest, AddressSuggestions) {
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
// Set a valid autocomplete attribute for the first name.
CreateTestFormField("First name", "firstname", "",
FormControlType::kInputText, "given-name"),
// Set an unrecognized autocomplete attribute for the last name.
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText, "unrecognized")};
FormsSeen({form});
// Suggestions should be returned for the first field.
browser_autofill_manager_->OnFocusOnFormFieldImpl(form, form.fields[0],
gfx::RectF());
CheckSuggestionsAvailableIfScreenReaderRunning();
// No suggestions should be provided for the second field because of its
// unrecognized autocomplete attribute.
browser_autofill_manager_->OnFocusOnFormFieldImpl(form, form.fields[1],
gfx::RectF());
CheckNoSuggestionsAvailableOnFieldFocus();
}
TEST_P(OnFocusOnFormFieldTest, AddressSuggestions_AutocompleteOffNotRespected) {
FormData form;
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {
// Set a valid autocomplete attribute for the first name.
CreateTestFormField("First name", "firstname", "",
FormControlType::kInputText, "given-name"),
// Set an autocomplete=off attribute for the last name.
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText, "given-name")};
form.fields.back().should_autocomplete = false;
FormsSeen({form});
browser_autofill_manager_->OnFocusOnFormFieldImpl(form, form.fields[1],
gfx::RectF());
CheckSuggestionsAvailableIfScreenReaderRunning();
}
TEST_P(OnFocusOnFormFieldTest, AddressSuggestions_Ablation) {
base::test::ScopedFeatureList scoped_feature_list;
DisableAutofillViaAblation(scoped_feature_list, /*for_addresses=*/true,
/*for_credit_cards=*/false);
// Set up our form data.
FormData form = CreateTestAddressFormData();
// Clear the form action.
form.action = GURL();
FormsSeen({form});
browser_autofill_manager_->OnFocusOnFormFieldImpl(form, form.fields[1],
gfx::RectF());
CheckNoSuggestionsAvailableOnFieldFocus();
}
TEST_P(OnFocusOnFormFieldTest, CreditCardSuggestions_SecureContext) {
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
// Clear the form action.
form.action = GURL();
FormsSeen({form});
browser_autofill_manager_->OnFocusOnFormFieldImpl(form, form.fields[1],
gfx::RectF());
CheckSuggestionsAvailableIfScreenReaderRunning();
}
TEST_P(OnFocusOnFormFieldTest, CreditCardSuggestions_NonSecureContext) {
// Set up our form data.
FormData form = CreateTestCreditCardFormData(/*is_https=*/false,
/*use_month_type=*/false);
// Clear the form action.
form.action = GURL();
FormsSeen({form});
browser_autofill_manager_->OnFocusOnFormFieldImpl(form, form.fields[1],
gfx::RectF());
// In a non-HTTPS context, there will be a warning indicating the page is
// insecure.
CheckSuggestionsAvailableIfScreenReaderRunning();
}
TEST_P(OnFocusOnFormFieldTest, CreditCardSuggestions_Ablation) {
base::test::ScopedFeatureList scoped_feature_list;
DisableAutofillViaAblation(scoped_feature_list, /*for_addresses=*/false,
/*for_credit_cards=*/true);
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
// Clear the form action.
form.action = GURL();
FormsSeen({form});
browser_autofill_manager_->OnFocusOnFormFieldImpl(form, form.fields[1],
gfx::RectF());
CheckNoSuggestionsAvailableOnFieldFocus();
}
INSTANTIATE_TEST_SUITE_P(All, OnFocusOnFormFieldTest, testing::Bool());
#if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
INSTANTIATE_TEST_SUITE_P(, SuggestionMatchingTest, testing::Values(false));
#else
INSTANTIATE_TEST_SUITE_P(All, SuggestionMatchingTest, testing::Bool());
#endif // BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
struct ShareNicknameTestParam {
std::string local_nickname;
std::string server_nickname;
std::string expected_nickname;
bool metadata_enabled;
};
const ShareNicknameTestParam kShareNicknameTestParam[] = {
{"", "", "", false},
{"", "server nickname", "server nickname", false},
{"local nickname", "", "local nickname", false},
{"local nickname", "server nickname", "local nickname", false},
{"local nickname", "server nickname", "local nickname", true},
};
class BrowserAutofillManagerTestForSharingNickname
: public BrowserAutofillManagerTest,
public testing::WithParamInterface<ShareNicknameTestParam> {
public:
BrowserAutofillManagerTestForSharingNickname()
: local_nickname_(GetParam().local_nickname),
server_nickname_(GetParam().server_nickname),
expected_nickname_(GetParam().expected_nickname) {
if (GetParam().metadata_enabled) {
card_metadata_flags_.InitWithFeatures(
/*enabled_features=*/{features::kAutofillEnableVirtualCardMetadata,
features::kAutofillEnableCardProductName,
features::kAutofillEnableCardArtImage},
/*disabled_features=*/{});
} else {
card_metadata_flags_.InitWithFeatures(
/*enabled_features=*/{},
/*disabled_features=*/{features::kAutofillEnableVirtualCardMetadata,
features::kAutofillEnableCardProductName,
features::kAutofillEnableCardArtImage});
}
}
CreditCard GetLocalCard() {
CreditCard local_card("287151C8-6AB1-487C-9095-28E80BE5DA15",
test::kEmptyOrigin);
test::SetCreditCardInfo(&local_card, "Clyde Barrow",
"378282246310005" /* American Express */, "04",
"2910", "1");
local_card.set_use_count(3);
local_card.set_use_date(AutofillClock::Now() - base::Days(1));
local_card.SetNickname(base::UTF8ToUTF16(local_nickname_));
local_card.set_guid(MakeGuid(1));
return local_card;
}
CreditCard GetServerCard() {
CreditCard full_server_card(CreditCard::RecordType::kFullServerCard,
"c789");
test::SetCreditCardInfo(&full_server_card, "Clyde Barrow",
"378282246310005" /* American Express */, "04",
"2910", "1");
full_server_card.SetNickname(base::UTF8ToUTF16(server_nickname_));
full_server_card.set_guid(MakeGuid(2));
return full_server_card;
}
base::test::ScopedFeatureList card_metadata_flags_;
std::string local_nickname_;
std::string server_nickname_;
std::string expected_nickname_;
};
INSTANTIATE_TEST_SUITE_P(,
BrowserAutofillManagerTestForSharingNickname,
testing::ValuesIn(kShareNicknameTestParam));
TEST_P(BrowserAutofillManagerTestForSharingNickname,
VerifySuggestion_DuplicateCards) {
personal_data().ClearCreditCards();
ASSERT_EQ(0U, personal_data().GetCreditCards().size());
CreditCard local_card = GetLocalCard();
personal_data().AddCreditCard(local_card);
personal_data().AddServerCreditCard(GetServerCard());
ASSERT_EQ(2U, personal_data().GetCreditCards().size());
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
// Query by card number field.
GetAutofillSuggestions(form, form.fields[1]);
external_delegate()->CheckSuggestions(
form.fields[1].global_id(),
{GetCardSuggestion(kAmericanExpressCard, expected_nickname_),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/true)});
}
TEST_P(BrowserAutofillManagerTestForSharingNickname,
VerifySuggestion_UnrelatedCards) {
personal_data().ClearCreditCards();
ASSERT_EQ(0U, personal_data().GetCreditCards().size());
CreditCard local_card = GetLocalCard();
personal_data().AddCreditCard(local_card);
std::vector<CreditCard> server_cards;
CreditCard server_card = GetServerCard();
// Make sure the cards are different by giving a different card number.
server_card.SetNumber(u"371449635320005");
personal_data().AddServerCreditCard(server_card);
ASSERT_EQ(2U, personal_data().GetCreditCards().size());
// Set up our form data.
FormData form =
CreateTestCreditCardFormData(/*is_https=*/true, /*use_month_type=*/false);
FormsSeen({form});
// Query by card number field.
GetAutofillSuggestions(form, form.fields[1]);
external_delegate()->CheckSuggestions(
form.fields[1].global_id(),
{GetCardSuggestion(kAmericanExpressCard, local_nickname_),
GetCardSuggestion(kAmericanExpressCard, server_nickname_),
AutofillSuggestionGenerator::CreateSeparator(),
AutofillSuggestionGenerator::CreateManagePaymentMethodsEntry(
/*with_gpay_logo=*/false)});
}
// Tests that analyze metrics logging in case JavaScript clears a field
// immediately after it was filled.
class BrowserAutofillManagerClearFieldTest : public BrowserAutofillManagerTest {
public:
void SetUp() override {
BrowserAutofillManagerTest::SetUp();
// Set up a CC form.
FormData form;
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
form.fields = {CreateTestFormField("Name on Card", "nameoncard", "",
FormControlType::kInputText),
CreateTestFormField("Card Number", "cardnumber", "",
FormControlType::kInputText),
CreateTestFormField("Expiration date", "exp_date", "",
FormControlType::kInputText)};
// Notify BrowserAutofillManager of the form.
FormsSeen({form});
// Simulate filling and store the data to be filled in `fill_data_`.
fill_data_ = FillAutofillFormDataAndGetResults(form, *form.fields.begin(),
MakeGuid(4));
ASSERT_EQ(3u, fill_data_.fields.size());
ExpectFilledField("Name on Card", "nameoncard", "Elvis Presley",
FormControlType::kInputText, fill_data_.fields[0]);
ExpectFilledField("Card Number", "cardnumber", "4234567890123456",
FormControlType::kInputText, fill_data_.fields[1]);
ExpectFilledField("Expiration date", "exp_date", "04/2999",
FormControlType::kInputText, fill_data_.fields[2]);
}
void SimulateOverrideFieldByJavaScript(size_t field_index,
const std::u16string& new_value) {
std::u16string old_value = fill_data_.fields[field_index].value;
fill_data_.fields[field_index].value = new_value;
browser_autofill_manager_->OnJavaScriptChangedAutofilledValue(
fill_data_, fill_data_.fields[field_index], old_value);
}
// Content of the form.
FormData fill_data_;
base::HistogramTester histogram_tester_;
// Shorter alias of the Autofill.FormEvents we are interested in.
const autofill_metrics::FormEvent kEvent = autofill_metrics::
FORM_EVENT_AUTOFILLED_FIELD_CLEARED_BY_JAVASCRIPT_AFTER_FILL_ONCE;
};
// Ensure that we log the appropriate Autofill.FormEvents event if an autofilled
// field is cleared by JavaScript immediately after the filling.
TEST_F(BrowserAutofillManagerClearFieldTest, OneClearedField) {
// Simulate that JavaScript clears the first field.
SimulateOverrideFieldByJavaScript(0, u"");
EXPECT_THAT(histogram_tester_.GetAllSamples("Autofill.FormEvents.CreditCard"),
base::BucketsInclude(base::Bucket(kEvent, 1)));
}
// Ensure that we log a single Autofill.FormEvents event even if *two*
// autofilled fields are cleared by JavaScript immediately after the filling.
TEST_F(BrowserAutofillManagerClearFieldTest, TwoClearedFields) {
// Simulate that JavaScript clears the first two field.
SimulateOverrideFieldByJavaScript(0, u"");
SimulateOverrideFieldByJavaScript(1, u"");
EXPECT_THAT(histogram_tester_.GetAllSamples("Autofill.FormEvents.CreditCard"),
base::BucketsInclude(base::Bucket(kEvent, 1)));
}
// Ensure that we do not log an Autofill.FormEvents event for the case that
// JavaScript modifies an autofilled field but does not clear it.
TEST_F(BrowserAutofillManagerClearFieldTest, ModifiedButDidNotClearField) {
// Simulate that JavaScript modifies the value of the field but does not clear
// the field.
SimulateOverrideFieldByJavaScript(0, u"Elvis Aaron Presley");
EXPECT_THAT(histogram_tester_.GetAllSamples("Autofill.FormEvents.CreditCard"),
base::BucketsInclude(base::Bucket(kEvent, 0)));
}
// Ensure that we do not log an appropriate Autofill.FormEvents event if an
// autofilled field is cleared by JavaScript too long after it was filled.
TEST_F(BrowserAutofillManagerClearFieldTest, NoLoggingAfterDelay) {
task_environment_.FastForwardBy(base::Seconds(5));
// Simulate that JavaScript clears the first field.
SimulateOverrideFieldByJavaScript(0, u"");
EXPECT_THAT(histogram_tester_.GetAllSamples("Autofill.FormEvents.CreditCard"),
base::BucketsInclude(base::Bucket(kEvent, 0)));
}
class BrowserAutofillManagerVotingTest : public BrowserAutofillManagerTest {
public:
void SetUp() override {
BrowserAutofillManagerTest::SetUp();
// All uploads should be expected explicitly.
EXPECT_CALL(*crowdsourcing_manager(), StartUploadRequest(_, _, _, _))
.Times(0);
form_.name = u"MyForm";
form_.url = GURL("https://myform.com/form.html");
form_.action = GURL("https://myform.com/submit.html");
form_.fields = {
CreateTestFormField("First Name", "firstname", "",
FormControlType::kInputText, "given-name"),
CreateTestFormField("Last Name", "lastname", "",
FormControlType::kInputText, "family-name")};
// Set up our form data.
FormsSeen({form_});
}
void SimulateTypingFirstNameIntoFirstField() {
form_.fields[0].value = u"Elvis";
browser_autofill_manager_->OnTextFieldDidChange(
form_, form_.fields[0], gfx::RectF(), base::TimeTicks::Now());
}
protected:
FormData form_;
};
// Ensure that a vote is submitted after a regular form submission.
TEST_F(BrowserAutofillManagerVotingTest, Submission) {
SimulateTypingFirstNameIntoFirstField();
EXPECT_CALL(*crowdsourcing_manager(),
StartUploadRequest(
FirstElementIs(AllOf(
FormSignatureIs(CalculateFormSignature(form_)),
FieldsAre(FieldAutofillTypeIs(
{FieldType::NAME_FIRST,
FieldType::CREDIT_CARD_NAME_FIRST}),
FieldAutofillTypeIs({FieldType::EMPTY_TYPE})),
ObservedSubmissionIs(true))),
_, _, _));
FormSubmitted(form_);
}
// Test that when modifying the form, a blur vote can be sent for the early
// version and a submission vote can be sent for the final version.
TEST_F(BrowserAutofillManagerVotingTest, DynamicFormSubmission) {
// 1. Simulate typing.
SimulateTypingFirstNameIntoFirstField();
// 2. Simulate removing focus from the form, which triggers a blur vote.
FormSignature first_form_signature = CalculateFormSignature(form_);
browser_autofill_manager_->OnFocusNoLongerOnForm(true);
// 3. Simulate typing into second field
form_.fields[1].value = u"Presley";
browser_autofill_manager_->OnTextFieldDidChange(
form_, form_.fields[1], gfx::RectF(), base::TimeTicks::Now());
// 4. Simulate removing the focus from the form, which generates a second blur
// vote which should be sent.
EXPECT_CALL(
*crowdsourcing_manager(),
StartUploadRequest(
FirstElementIs(AllOf(
FormSignatureIs(first_form_signature),
FieldsAre(
FieldAutofillTypeIs({FieldType::NAME_FIRST,
FieldType::CREDIT_CARD_NAME_FIRST}),
FieldAutofillTypeIs({FieldType::NAME_LAST,
FieldType::CREDIT_CARD_NAME_LAST,
FieldType::NAME_LAST_SECOND})),
ObservedSubmissionIs(false))),
_, _, _));
browser_autofill_manager_->OnFocusNoLongerOnForm(true);
// 5. Grow the form by one field, which changes the form signature.
form_.fields.push_back(CreateTestFormField(
"Zip code", "zip", "", FormControlType::kInputText, "postal-code"));
FormsSeen({form_});
// 6. Ensure that a form submission triggers votes for the new form.
// Adding a field should have changed the form signature.
FormSignature second_form_signature = CalculateFormSignature(form_);
EXPECT_NE(first_form_signature, second_form_signature);
// Because the next field after the two names is not a credit card field,
// field disambiguation removes the credit card name votes.
EXPECT_CALL(
*crowdsourcing_manager(),
StartUploadRequest(
FirstElementIs(AllOf(
FormSignatureIs(second_form_signature),
FieldsAre(FieldAutofillTypeIs({FieldType::NAME_FIRST}),
FieldAutofillTypeIs({FieldType::NAME_LAST,
FieldType::NAME_LAST_SECOND}),
FieldAutofillTypeIs({FieldType::EMPTY_TYPE})),
ObservedSubmissionIs(true))),
_, _, _));
FormSubmitted(form_);
}
// Ensure that a blur votes is sent after a navigation.
TEST_F(BrowserAutofillManagerVotingTest, BlurVoteOnNavigation) {
SimulateTypingFirstNameIntoFirstField();
// Simulate removing focus from form, which triggers a blur vote.
EXPECT_CALL(*crowdsourcing_manager(),
StartUploadRequest(
FirstElementIs(AllOf(
FormSignatureIs(CalculateFormSignature(form_)),
FieldsAre(FieldAutofillTypeIs(
{FieldType::NAME_FIRST,
FieldType::CREDIT_CARD_NAME_FIRST}),
FieldAutofillTypeIs({FieldType::EMPTY_TYPE})),
ObservedSubmissionIs(false))),
_, _, _));
browser_autofill_manager_->OnFocusNoLongerOnForm(true);
// Simulate a navigation. This is when the vote is sent.
browser_autofill_manager_->Reset();
}
// Ensure that a submission vote blocks sending a blur vote for the same form
// signature.
TEST_F(BrowserAutofillManagerVotingTest, NoBlurVoteOnSubmission) {
SimulateTypingFirstNameIntoFirstField();
// Simulate removing focus from form, which enqueues a blur vote. The blur
// vote will be ignored and only the submission will be sent.
browser_autofill_manager_->OnFocusNoLongerOnForm(true);
EXPECT_CALL(*crowdsourcing_manager(),
StartUploadRequest(
FirstElementIs(AllOf(
FormSignatureIs(CalculateFormSignature(form_)),
FieldsAre(FieldAutofillTypeIs(
{FieldType::NAME_FIRST,
FieldType::CREDIT_CARD_NAME_FIRST}),
FieldAutofillTypeIs({FieldType::EMPTY_TYPE})),
ObservedSubmissionIs(true))),
_, _, _));
FormSubmitted(form_);
}
// Test that the call is properly forwarded to its SingleFieldFormFillRouter.
TEST_F(BrowserAutofillManagerTest, OnSingleFieldSuggestionSelected) {
std::u16string test_value = u"TestValue";
FormData form = test::CreateTestAddressFormData();
FormFieldData field = form.fields[0];
EXPECT_CALL(single_field_form_fill_router(),
OnSingleFieldSuggestionSelected(test_value,
PopupItemId::kAutocompleteEntry));
browser_autofill_manager_->OnSingleFieldSuggestionSelected(
test_value, PopupItemId::kAutocompleteEntry, form, field);
EXPECT_CALL(single_field_form_fill_router(),
OnSingleFieldSuggestionSelected(test_value,
PopupItemId::kAutocompleteEntry));
browser_autofill_manager_->OnSingleFieldSuggestionSelected(
test_value, PopupItemId::kAutocompleteEntry, form, field);
EXPECT_CALL(
single_field_form_fill_router(),
OnSingleFieldSuggestionSelected(test_value, PopupItemId::kIbanEntry));
browser_autofill_manager_->OnSingleFieldSuggestionSelected(
test_value, PopupItemId::kIbanEntry, form, field);
EXPECT_CALL(single_field_form_fill_router(),
OnSingleFieldSuggestionSelected(
test_value, PopupItemId::kMerchantPromoCodeEntry));
browser_autofill_manager_->OnSingleFieldSuggestionSelected(
test_value, PopupItemId::kMerchantPromoCodeEntry, form, field);
}
// Test that we correctly fill an address form and update the used profile.
TEST_F(BrowserAutofillManagerTest, FillAddressForm_UpdateProfile) {
FormData form = test::GetFormData(
{.fields = {{.role = NAME_FULL, .autocomplete_attribute = "name"},
{.role = ADDRESS_HOME_LINE1,
.autocomplete_attribute = "address-line1"}}});
FormsSeen({form});
// Create a profile and add it to the PDM.
personal_data().ClearProfiles();
AutofillProfile profile = test::GetFullProfile();
profile.set_use_date(AutofillClock::Now());
profile.set_use_count(1u);
personal_data().AddProfile(profile);
AutofillProfile* pdm_profile =
personal_data().GetProfileByGUID(profile.guid());
ASSERT_TRUE(pdm_profile);
task_environment_.FastForwardBy(base::Hours(1));
const base::Time hour_later = AutofillClock::Now();
FillAutofillFormData(form, form.fields[0], pdm_profile->guid());
EXPECT_EQ(2U, pdm_profile->use_count());
EXPECT_LE(hour_later, pdm_profile->use_date());
}
// Tests that `ProfileTokenQuality` is correctly integrated into
// `AutofillProfile` and that on form submit, observations are collected.
TEST_F(BrowserAutofillManagerTest, FillAddressForm_CollectObservations) {
base::test::ScopedFeatureList profile_token_quality_feature{
features::kAutofillTrackProfileTokenQuality};
personal_data().ClearProfiles();
AutofillProfile profile = test::GetFullProfile();
// This is needed to not get an update prompt that would compromise the test.
profile.set_source_for_testing(AutofillProfile::Source::kAccount);
personal_data().AddProfile(profile);
AutofillProfile* pdm_profile =
personal_data().GetProfileByGUID(profile.guid());
ASSERT_TRUE(pdm_profile);
pdm_profile->token_quality().disable_randomization_for_testing();
// Create and fill an address form with profile `kElvisProfileGuid`.
FormData form = test::CreateTestAddressFormData();
FormsSeen({form});
FormData filled_form = FillAutofillFormDataAndGetResults(form, form.fields[0],
pdm_profile->guid());
// Expect that no observations for any of the form's types were collected yet.
FormStructure* form_structure =
browser_autofill_manager_->FindCachedFormById(form.global_id());
EXPECT_TRUE(base::ranges::all_of(
*form_structure,
[&pdm_profile](const std::unique_ptr<AutofillField>& field) {
return pdm_profile->token_quality()
.GetObservationTypesForFieldType(field->Type().GetStorableType())
.empty();
}));
// Submit the form and expect observations for all of the form's types. This
// updates the `profile` in `personal_data()`, invalidating the pointer.
FormSubmitted(filled_form);
pdm_profile = personal_data().GetProfileByGUID(profile.guid());
ASSERT_TRUE(pdm_profile);
EXPECT_TRUE(base::ranges::none_of(
*form_structure,
[&pdm_profile](const std::unique_ptr<AutofillField>& field) {
return pdm_profile->token_quality()
.GetObservationTypesForFieldType(field->Type().GetStorableType())
.empty();
}));
}
class BrowserAutofillManagerPlusAddressTest
: public BrowserAutofillManagerTest {
protected:
void SetUp() override {
BrowserAutofillManagerTest::SetUp();
auto plus_address_delegate =
std::make_unique<NiceMock<MockAutofillPlusAddressDelegate>>();
autofill_client_.set_plus_address_delegate(
std::move(plus_address_delegate));
}
MockAutofillPlusAddressDelegate& plus_address_delegate() {
return static_cast<MockAutofillPlusAddressDelegate&>(
*autofill_client_.GetPlusAddressDelegate());
}
};
// Ensure that plus address options aren't queried for non-email fields.
TEST_F(BrowserAutofillManagerPlusAddressTest, NoPlusAddressesWithNameFields) {
EXPECT_CALL(plus_address_delegate(), GetSuggestions).Times(0);
// Set up our form data.
FormData form = test::GetFormData(
{.fields = {{.role = NAME_FIRST, .autocomplete_attribute = "given-name"},
{.role = NAME_LAST}}});
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
FormsSeen({form});
// Check that suggestions are made for the field that has the autocomplete
// attribute. Ensure that there is no plus address option shown.
GetAutofillSuggestions(form, form.fields[0]);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
// Also check that there are no suggestions for the field without the
// autocomplete attribute, ensuring that unrecognized fields don't get plus
// address options.
GetAutofillSuggestions(form, form.fields[1]);
EXPECT_FALSE(external_delegate()->on_suggestions_returned_seen());
}
// Tests that address suggestions are queried and shown for email fields.
TEST_F(BrowserAutofillManagerPlusAddressTest,
CreatePlusAddressSuggestionShown) {
EXPECT_CALL(plus_address_delegate(), GetSuggestions)
.WillOnce(Return(std::vector<Suggestion>{
Suggestion(PopupItemId::kCreateNewPlusAddress)}));
// Set up our form data. Notably, the first field is an email address.
FormData form = test::GetFormData(
{.fields = {{.role = EMAIL_ADDRESS, .autocomplete_attribute = "email"}}});
form.name = u"MyForm";
form.url = GURL("https://myform.com/form.html");
form.action = GURL("https://myform.com/submit.html");
FormsSeen({form});
// Check that the plus address suggestion is offered.
GetAutofillSuggestions(form, form.fields[0]);
EXPECT_TRUE(external_delegate()->on_suggestions_returned_seen());
EXPECT_THAT(external_delegate()->suggestions(),
ElementsAre(EqualsSuggestion(PopupItemId::kCreateNewPlusAddress),
_, _, _, _));
}
// Test that plus address inputs are forced to !should_autocomplete
// for `SingleFieldFormFillRouter::OnWillSubmitForm()`.
TEST_F(BrowserAutofillManagerPlusAddressTest,
DontSavePlusAddressInAutocompleteHistory) {
const std::string kDummyPlusAddress = "plus+plus@plus.plus";
ON_CALL(plus_address_delegate(), IsPlusAddress)
.WillByDefault([&](const std::string& address) {
return address == kDummyPlusAddress;
});
FormData form_seen_by_autocomplete;
EXPECT_CALL(single_field_form_fill_router(),
OnWillSubmitForm(_, _, /*is_autocomplete_enabled=*/true))
.WillOnce(SaveArg<0>(&form_seen_by_autocomplete));
FormData form = test::GetFormData(
{.fields = {{.role = EMAIL_ADDRESS, .name = u"email"},
{.role = EMAIL_ADDRESS, .name = u"unfilled-email"}}});
// First, note the field with the empty value.
FormsSeen({form});
// Then fill in the dummy plus address.
form.fields[0].value = base::UTF8ToUTF16(kDummyPlusAddress);
// Submit the form, capturing it as it is passed to the autocomplete history
// manager. The first field should not be autocomplete eligible.
FormSubmitted(form);
EXPECT_EQ(form.fields.size(), form_seen_by_autocomplete.fields.size());
EXPECT_FALSE(form_seen_by_autocomplete.fields[0].should_autocomplete);
EXPECT_TRUE(form_seen_by_autocomplete.fields[1].should_autocomplete);
}
} // namespace autofill