Commit d1bd596a authored by Vidhan's avatar Vidhan Committed by Commit Bot

[Autofill] Detection and filling of the augmented phone code selection

boxes

This CL introduces the functionality for detecting and filling of the
<select> fields, having options consisting of the phone country code
along with an additional text. Also, sending votes for the server
classification.

Bug: 1081219
Change-Id: I00ee0125d457abadf4f59fb16b3850e27274006b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2198785Reviewed-by: default avatarDominic Battré <battre@chromium.org>
Reviewed-by: default avatarMatthias Körber <koerber@google.com>
Commit-Queue: Vidhan Jain <vidhanj@google.com>
Cr-Commit-Position: refs/heads/master@{#781271}
parent f99b458e
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "components/autofill/core/common/autofill_clock.h" #include "components/autofill/core/common/autofill_clock.h"
#include "components/autofill/core/common/autofill_regex_constants.h"
#include "components/autofill/core/common/autofill_regexes.h"
#include "third_party/icu/source/common/unicode/uloc.h" #include "third_party/icu/source/common/unicode/uloc.h"
#include "third_party/icu/source/i18n/unicode/dtfmtsym.h" #include "third_party/icu/source/i18n/unicode/dtfmtsym.h"
...@@ -141,6 +143,18 @@ bool SetExpirationYear(int value, int* expiration_year) { ...@@ -141,6 +143,18 @@ bool SetExpirationYear(int value, int* expiration_year) {
return true; return true;
} }
base::string16 FindPossiblePhoneCountryCode(const base::string16& text) {
base::string16 candidate;
if (text.find(base::ASCIIToUTF16("00")) != base::string16::npos ||
text.find('+') != base::string16::npos) {
if (MatchesPattern(text, base::ASCIIToUTF16(kAugmentedPhoneCountryCodeRe),
&candidate, 1))
return candidate;
}
return base::string16();
}
} // namespace data_util } // namespace data_util
} // namespace autofill } // namespace autofill
...@@ -47,6 +47,10 @@ bool SetExpirationMonth(int value, int* expiration_month); ...@@ -47,6 +47,10 @@ bool SetExpirationMonth(int value, int* expiration_month);
// made to |*expiration_year|. // made to |*expiration_year|.
bool SetExpirationYear(int value, int* expiration_year); bool SetExpirationYear(int value, int* expiration_year);
// Finds possible country code in |text| by fetching the first sub-group when
// matched with |kAugmentedPhoneCountryCodeRe| regex.
base::string16 FindPossiblePhoneCountryCode(const base::string16& text);
} // namespace data_util } // namespace data_util
} // namespace autofill } // namespace autofill
......
...@@ -13,9 +13,11 @@ ...@@ -13,9 +13,11 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/autofill_type.h" #include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h" #include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/data_model/data_model_utils.h"
#include "components/autofill/core/browser/field_types.h" #include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/geo/autofill_country.h" #include "components/autofill/core/browser/geo/autofill_country.h"
#include "components/autofill/core/browser/geo/phone_number_i18n.h" #include "components/autofill/core/browser/geo/phone_number_i18n.h"
#include "components/autofill/core/common/autofill_features.h"
namespace autofill { namespace autofill {
namespace { namespace {
...@@ -134,6 +136,18 @@ void PhoneNumber::GetMatchingTypes(const base::string16& text, ...@@ -134,6 +136,18 @@ void PhoneNumber::GetMatchingTypes(const base::string16& text,
matching_types->insert(PHONE_HOME_WHOLE_NUMBER); matching_types->insert(PHONE_HOME_WHOLE_NUMBER);
} }
} }
// |PHONE_HOME_COUNTRY_CODE| is added to the set of the |matching_types| when
// the digits extracted from the |stripped_text| match the |country_code|.
if (base::FeatureList::IsEnabled(
features::kAutofillEnableAugmentedPhoneCountryCode)) {
base::string16 candidate =
data_util::FindPossiblePhoneCountryCode(stripped_text);
base::string16 country_code =
GetInfo(AutofillType(PHONE_HOME_COUNTRY_CODE), app_locale);
if (candidate.size() > 0 && candidate == country_code)
matching_types->insert(PHONE_HOME_COUNTRY_CODE);
}
} }
// Normalize phones if |type| is a whole number: // Normalize phones if |type| is a whole number:
......
...@@ -6,10 +6,12 @@ ...@@ -6,10 +6,12 @@
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "components/autofill/core/browser/autofill_type.h" #include "components/autofill/core/browser/autofill_type.h"
#include "components/autofill/core/browser/data_model/autofill_profile.h" #include "components/autofill/core/browser/data_model/autofill_profile.h"
#include "components/autofill/core/browser/field_types.h" #include "components/autofill/core/browser/field_types.h"
#include "components/autofill/core/browser/geo/phone_number_i18n.h" #include "components/autofill/core/browser/geo/phone_number_i18n.h"
#include "components/autofill/core/common/autofill_features.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -289,4 +291,78 @@ TEST(PhoneNumberTest, InternationalPhoneHomeCityAndNumber_DE) { ...@@ -289,4 +291,78 @@ TEST(PhoneNumberTest, InternationalPhoneHomeCityAndNumber_DE) {
phone_number.GetInfo(PHONE_HOME_CITY_AND_NUMBER, "en-US")); phone_number.GetInfo(PHONE_HOME_CITY_AND_NUMBER, "en-US"));
} }
// Tests whether the |PHONE_HOME_COUNTRY_CODE| is added to the set of matching
// types.
TEST(PhoneNumberTest, CountryCodeInMatchingTypes) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
AutofillProfile profile;
profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US"));
// Set the phone number such that country_code == 1, city_code = 650,
// number = 2345678.
base::string16 phone(ASCIIToUTF16("1 [650] 234-5678"));
PhoneNumber phone_number(&profile);
phone_number.SetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), phone, "US");
std::vector<const char*> test_cases = {"+1", "1", "(+1) United States",
"US (+1)"};
for (size_t i = 0; i < test_cases.size(); i++) {
SCOPED_TRACE(testing::Message() << "i(US) = " << i);
ServerFieldTypeSet matching_types;
phone_number.GetMatchingTypes(ASCIIToUTF16(test_cases[i]), "US",
&matching_types);
EXPECT_THAT(matching_types, testing::ElementsAre(PHONE_HOME_COUNTRY_CODE));
}
profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("DE"));
base::string16 de_phone(ASCIIToUTF16("+49 0151 6679586"));
PhoneNumber phone_number_de(&profile);
phone_number_de.SetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), de_phone,
"DE");
test_cases = {"49", "+49", "(+49) DE", "(0049) DE", "0049"};
for (size_t i = 0; i < test_cases.size(); i++) {
SCOPED_TRACE(testing::Message() << "i(DE) = " << i);
ServerFieldTypeSet matching_types;
phone_number_de.GetMatchingTypes(ASCIIToUTF16(test_cases[i]), "DE",
&matching_types);
EXPECT_THAT(matching_types, testing::ElementsAre(PHONE_HOME_COUNTRY_CODE));
}
}
// Tests that the |PHONE_HOME_COUNTRY_CODE| should not be added to the set of
// matching types.
TEST(PhoneNumberTest, CountryCodeNotInMatchingTypes) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
AutofillProfile profile;
profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US"));
// Set phone number so country_code == 1, city_code = 650, number = 2345678.
base::string16 phone(ASCIIToUTF16("1 [650] 234-5678"));
PhoneNumber phone_number(&profile);
phone_number.SetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), phone, "US");
std::vector<const char*> test_cases = {
"01", "+16502", "11", "211", "0001", "++1", "+1abc2", "001abc2", "01"};
for (size_t i = 0; i < test_cases.size(); i++) {
SCOPED_TRACE(testing::Message() << "i = " << i);
ServerFieldTypeSet matching_types;
phone_number.GetMatchingTypes(ASCIIToUTF16(test_cases[i]), "US",
&matching_types);
EXPECT_THAT(matching_types, testing::IsEmpty());
}
}
} // namespace autofill } // namespace autofill
...@@ -705,6 +705,36 @@ base::string16 RemoveWhitespace(const base::string16& value) { ...@@ -705,6 +705,36 @@ base::string16 RemoveWhitespace(const base::string16& value) {
return stripped_value; return stripped_value;
} }
// Finds the best suitable option in the |field| that corresponds to the
// |country_code|.
// If the exact match is not found, extracts the digits (ignoring leading '00'
// or '+') from each option and compares them with the |country_code|.
void FillPhoneCountryCodeSelectControl(const base::string16& country_code,
FormFieldData* field,
std::string* failure_to_fill) {
if (country_code.empty())
return;
// Find the option that exactly matches the |country_code|.
if (SetSelectControlValue(country_code, field, /*best_match_index=*/nullptr,
failure_to_fill))
return;
for (size_t i = 0; i < field->option_contents.size(); i++) {
base::string16 cc_candidate_in_value =
data_util::FindPossiblePhoneCountryCode(
RemoveWhitespace(field->option_values[i]));
base::string16 cc_candidate_in_content =
data_util::FindPossiblePhoneCountryCode(
RemoveWhitespace(field->option_contents[i]));
if (cc_candidate_in_value == country_code ||
cc_candidate_in_content == country_code) {
field->value = field->option_values[i];
return;
}
}
}
} // namespace } // namespace
FieldFiller::FieldFiller(const std::string& app_locale, FieldFiller::FieldFiller(const std::string& app_locale,
...@@ -748,7 +778,16 @@ bool FieldFiller::FillFormField(const AutofillField& field, ...@@ -748,7 +778,16 @@ bool FieldFiller::FillFormField(const AutofillField& field,
} }
if (type.group() == PHONE_HOME) { if (type.group() == PHONE_HOME) {
FillPhoneNumberField(field, value, field_data); // If the |field_data| is a selection box and having the type
// |PHONE_HOME_COUNTRY_CODE|, call |FillPhoneCountryCodeSelectControl|.
if (base::FeatureList::IsEnabled(
features::kAutofillEnableAugmentedPhoneCountryCode) &&
field_data->form_control_type == "select-one" &&
type.GetStorableType() == PHONE_HOME_COUNTRY_CODE) {
FillPhoneCountryCodeSelectControl(value, field_data, failure_to_fill);
} else {
FillPhoneNumberField(field, value, field_data);
}
return true; return true;
} }
if (field_data->form_control_type == "select-one") { if (field_data->form_control_type == "select-one") {
......
...@@ -1495,4 +1495,132 @@ INSTANTIATE_TEST_SUITE_P( ...@@ -1495,4 +1495,132 @@ INSTANTIATE_TEST_SUITE_P(
FillStateTextTestCase{HTML_TYPE_ADDRESS_LEVEL1, 3, "Quebec", "", FillStateTextTestCase{HTML_TYPE_ADDRESS_LEVEL1, 3, "Quebec", "",
false})); false}));
// Tests that the correct option is chosen in the selection box when one of the
// options exactly matches the phone country code.
TEST_F(AutofillFieldFillerTest,
FillSelectControlPhoneCountryCodeWithExactMatch) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
std::vector<const char*> kPhoneCountryCode = {"91", "1", "20", "49"};
AutofillField field;
test::CreateTestSelectField(kPhoneCountryCode, &field);
field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE);
AutofillProfile address;
address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+15145554578"));
FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr);
filler.FillFormField(field, address, &field, /*cvc=*/base::string16());
EXPECT_EQ(ASCIIToUTF16("1"), field.value);
}
// Tests that the correct option is chosen in the selection box when the options
// are preceded by a plus sign and the field is of |PHONE_HOME_COUNTRY_CODE|
// type.
TEST_F(AutofillFieldFillerTest,
FillSelectControlPhoneCountryCodePrecededByPlus) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
std::vector<const char*> kPhoneCountryCode = {"+91", "+1", "+20", "+49"};
AutofillField field;
test::CreateTestSelectField(kPhoneCountryCode, &field);
field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE);
AutofillProfile address;
address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+918890888888"));
FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr);
filler.FillFormField(field, address, &field, /*cvc=*/base::string16());
EXPECT_EQ(ASCIIToUTF16("+91"), field.value);
}
// Tests that the correct option is chosen in the selection box when the options
// are preceded by a '00' and the field is of |PHONE_HOME_COUNTRY_CODE|
// type.
TEST_F(AutofillFieldFillerTest,
FillSelectControlPhoneCountryCodePrecededByDoubleZeros) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
std::vector<const char*> kPhoneCountryCode = {"0091", "001", "0020", "0049"};
AutofillField field;
test::CreateTestSelectField(kPhoneCountryCode, &field);
field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE);
AutofillProfile address;
address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+918890888888"));
FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr);
filler.FillFormField(field, address, &field, /*cvc=*/base::string16());
EXPECT_EQ(ASCIIToUTF16("0091"), field.value);
}
// Tests that the correct option is chosen in the selection box when the options
// are composed of the country code and the country name.
TEST_F(AutofillFieldFillerTest, FillSelectControlAugmentedPhoneCountryCode) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
std::vector<const char*> kPhoneCountryCode = {
"Please select an option", "+91 (India)", "+1 (United States)",
"+20 (Egypt)", "+49 (Germany)"};
AutofillField field;
test::CreateTestSelectField(kPhoneCountryCode, &field);
field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE);
AutofillProfile address;
address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+49151669087345"));
FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr);
filler.FillFormField(field, address, &field, /*cvc=*/base::string16());
EXPECT_EQ(ASCIIToUTF16("+49 (Germany)"), field.value);
}
// Tests that the correct option is chosen in the selection box when the options
// are composed of the country code having whitespace and the country name.
TEST_F(AutofillFieldFillerTest,
FillSelectControlAugmentedPhoneCountryCodeWithWhiteSpaces) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
std::vector<const char*> kPhoneCountryCode = {
"Please select an option", "(00 91) India", "(00 1) United States",
"(00 20) Egypt", "(00 49) Germany"};
AutofillField field;
test::CreateTestSelectField(kPhoneCountryCode, &field);
field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE);
AutofillProfile address;
address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+49151669087345"));
FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr);
filler.FillFormField(field, address, &field, /*cvc=*/base::string16());
EXPECT_EQ(ASCIIToUTF16("(00 49) Germany"), field.value);
}
// Tests that the correct option is chosen in the selection box when the options
// are composed of the country code that is preceded by '00' and the country
// name.
TEST_F(AutofillFieldFillerTest,
FillSelectControlAugmentedPhoneCountryCodeHavingDoubleZeros) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
std::vector<const char*> kPhoneCountryCode = {
"Please select an option", "(0091) India", "(001) United States",
"(0020) Egypt", "(0049) Germany"};
AutofillField field;
test::CreateTestSelectField(kPhoneCountryCode, &field);
field.set_heuristic_type(PHONE_HOME_COUNTRY_CODE);
AutofillProfile address;
address.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("+49151669087345"));
FieldFiller filler(/*app_locale=*/"en-US", /*address_normalizer=*/nullptr);
filler.FillFormField(field, address, &field, /*cvc=*/base::string16());
EXPECT_EQ(ASCIIToUTF16("(0049) Germany"), field.value);
}
} // namespace autofill } // namespace autofill
...@@ -17,11 +17,33 @@ ...@@ -17,11 +17,33 @@
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "components/autofill/core/browser/autofill_field.h" #include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/form_parsing/autofill_scanner.h" #include "components/autofill/core/browser/form_parsing/autofill_scanner.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/autofill_regex_constants.h" #include "components/autofill/core/common/autofill_regex_constants.h"
#include "components/autofill/core/common/autofill_regexes.h"
namespace autofill { namespace autofill {
namespace { namespace {
// Minimum limit on the number of the options of the select field for
// determining the field to be of |PHONE_HOME_COUNTRY_CODE| type.
constexpr int kMinSelectOptionsForCountryCode = 5;
// Maximum limit on the number of the options of the select field for
// determining the field to be of |PHONE_HOME_COUNTRY_CODE| type.
// Currently, there are approximately 250 countries that have been assigned a
// phone country code, therefore, 275 is taken as the upper bound.
constexpr int kMaxSelectOptionsForCountryCode = 275;
// Minimum percentage of options in select field that should look like a
// country code in order to classify the field as a |PHONE_HOME_COUNTRY_CODE|.
constexpr int kMinCandidatePercentageForCountryCode = 90;
// If a <select> element has <= |kHeuristicThresholdForCountryCode| options,
// all or all-but-one need to look like country code options. Otherwise,
// |kMinCandidatePercentageForCountryCode| is used to check for a fraction
// of country code like options.
constexpr int kHeuristicThresholdForCountryCode = 10;
// This string includes all area code separators, including NoText. // This string includes all area code separators, including NoText.
std::string GetAreaRegex() { std::string GetAreaRegex() {
std::string area_code = kAreaCodeRe; std::string area_code = kAreaCodeRe;
...@@ -130,6 +152,65 @@ const PhoneField::Parser PhoneField::kPhoneFieldGrammars[] = { ...@@ -130,6 +152,65 @@ const PhoneField::Parser PhoneField::kPhoneFieldGrammars[] = {
{REGEX_SEPARATOR, FIELD_NONE, 0}, {REGEX_SEPARATOR, FIELD_NONE, 0},
}; };
// static
bool PhoneField::LikelyAugmentedPhoneCountryCode(
AutofillScanner* scanner,
AutofillField** matched_field) {
// If the experiment |kAutofillEnableAugmentedPhoneCountryCode| is not
// enabled, return false.
if (!base::FeatureList::IsEnabled(
features::kAutofillEnableAugmentedPhoneCountryCode))
return false;
AutofillField* field = scanner->Cursor();
// Return false if the field is not a selection box.
if (!MatchesFormControlType(field->form_control_type, MATCH_SELECT))
return false;
// If the number of the options is less than the minimum limit or more than
// the maximum limit, return false.
if (field->option_contents.size() < kMinSelectOptionsForCountryCode ||
field->option_contents.size() >= kMaxSelectOptionsForCountryCode)
return false;
// |total_covered_options| stores the count of the options that are
// compared with the regex.
int total_num_options = static_cast<int>(field->option_contents.size());
// |total_positive_options| stores the count of the options that match the
// regex.
int total_positive_options = 0;
for (const auto& option : field->option_contents) {
if (MatchesPattern(option,
base::ASCIIToUTF16(kAugmentedPhoneCountryCodeRe)))
total_positive_options++;
}
// If the number of the options compared is less or equal to
// |kHeuristicThresholdForCountryCode|, then either all the options or all
// options but one should match the regex.
if (total_num_options <= kHeuristicThresholdForCountryCode &&
total_positive_options + 1 < total_num_options)
return false;
// If the number of the options compared is more than
// |kHeuristicThresholdForCountryCode|,
// |kMinCandidatePercentageForCountryCode|% of the options should match the
// regex.
if (total_num_options > kHeuristicThresholdForCountryCode &&
total_positive_options * 100 <
total_num_options * kMinCandidatePercentageForCountryCode)
return false;
// Assign the |matched_field| and advance the cursor.
if (matched_field)
*matched_field = field;
scanner->Advance();
return true;
}
// static // static
std::unique_ptr<FormField> PhoneField::Parse(AutofillScanner* scanner, std::unique_ptr<FormField> PhoneField::Parse(AutofillScanner* scanner,
LogManager* log_manager) { LogManager* log_manager) {
...@@ -149,11 +230,22 @@ std::unique_ptr<FormField> PhoneField::Parse(AutofillScanner* scanner, ...@@ -149,11 +230,22 @@ std::unique_ptr<FormField> PhoneField::Parse(AutofillScanner* scanner,
for (; i < base::size(kPhoneFieldGrammars) && for (; i < base::size(kPhoneFieldGrammars) &&
kPhoneFieldGrammars[i].regex != REGEX_SEPARATOR; kPhoneFieldGrammars[i].regex != REGEX_SEPARATOR;
++i) { ++i) {
const bool is_country_code_field =
kPhoneFieldGrammars[i].phone_part == FIELD_COUNTRY_CODE;
// The field length comparison with |kPhoneFieldGrammars[i].max_size| is
// not required in case of the selection boxes that are of phone country
// code type.
if (is_country_code_field &&
LikelyAugmentedPhoneCountryCode(scanner,
&parsed_fields[FIELD_COUNTRY_CODE]))
continue;
if (!ParsePhoneField( if (!ParsePhoneField(
scanner, GetRegExp(kPhoneFieldGrammars[i].regex), scanner, GetRegExp(kPhoneFieldGrammars[i].regex),
&parsed_fields[kPhoneFieldGrammars[i].phone_part], &parsed_fields[kPhoneFieldGrammars[i].phone_part],
{log_manager, GetRegExpName(kPhoneFieldGrammars[i].regex)}, {log_manager, GetRegExpName(kPhoneFieldGrammars[i].regex)},
(kPhoneFieldGrammars[i].phone_part == FIELD_COUNTRY_CODE))) is_country_code_field))
break; break;
if (kPhoneFieldGrammars[i].max_size && if (kPhoneFieldGrammars[i].max_size &&
(!parsed_fields[kPhoneFieldGrammars[i].phone_part]->max_length || (!parsed_fields[kPhoneFieldGrammars[i].phone_part]->max_length ||
......
...@@ -102,6 +102,13 @@ class PhoneField : public FormField { ...@@ -102,6 +102,13 @@ class PhoneField : public FormField {
const RegExLogging& logging, const RegExLogging& logging,
const bool is_country_code_field); const bool is_country_code_field);
// Returns true if |scanner| points to a <select> field that appears to be the
// phone country code by looking at its option contents.
// "Augmented" refers to the fact that we are looking for select options that
// contain not only a country code but also further text like "Germany (+49)".
static bool LikelyAugmentedPhoneCountryCode(AutofillScanner* scanner,
AutofillField** match);
// FIELD_PHONE is always present; holds suffix if prefix is present. // FIELD_PHONE is always present; holds suffix if prefix is present.
// The rest could be NULL. // The rest could be NULL.
AutofillField* parsed_phone_fields_[FIELD_MAX]; AutofillField* parsed_phone_fields_[FIELD_MAX];
......
...@@ -13,8 +13,10 @@ ...@@ -13,8 +13,10 @@
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "components/autofill/core/browser/autofill_field.h" #include "components/autofill/core/browser/autofill_field.h"
#include "components/autofill/core/browser/form_parsing/autofill_scanner.h" #include "components/autofill/core/browser/form_parsing/autofill_scanner.h"
#include "components/autofill/core/common/autofill_features.h"
#include "components/autofill/core/common/form_field_data.h" #include "components/autofill/core/common/form_field_data.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -59,6 +61,22 @@ class PhoneFieldTest : public testing::Test { ...@@ -59,6 +61,22 @@ class PhoneFieldTest : public testing::Test {
EXPECT_EQ(expected_type, it->second.BestHeuristicType()) << name; EXPECT_EQ(expected_type, it->second.BestHeuristicType()) << name;
} }
// Populates a select |field| with the |label|, the |name| and the |contents|.
void CreateTestSelectField(const char* label,
const char* name,
const std::vector<const char*>& contents,
FormFieldData* field) {
field->label = ASCIIToUTF16(label);
field->name = ASCIIToUTF16(name);
field->form_control_type = "select-one";
std::vector<base::string16> contents16;
for (auto* const element : contents)
contents16.push_back(base::UTF8ToUTF16(element));
field->option_contents = contents16;
}
std::vector<std::unique_ptr<AutofillField>> list_; std::vector<std::unique_ptr<AutofillField>> list_;
std::unique_ptr<PhoneField> field_; std::unique_ptr<PhoneField> field_;
FieldCandidatesMap field_candidates_map_; FieldCandidatesMap field_candidates_map_;
...@@ -335,4 +353,317 @@ TEST_F(PhoneFieldTest, CountryCodeIsSelectElement) { ...@@ -335,4 +353,317 @@ TEST_F(PhoneFieldTest, CountryCodeIsSelectElement) {
CheckField("phoneNumber", PHONE_HOME_NUMBER); CheckField("phoneNumber", PHONE_HOME_NUMBER);
} }
// Tests if the country code, city code and phone number fields are correctly
// classified by the heuristic when the phone code field is a select element
// consisting of valid options.
TEST_F(PhoneFieldTest, CountryCodeWithOptions) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
FormFieldData field;
// Options consisting of the country code followed by the country names.
std::vector<const char*> augmented_field_options_list = {
"(+91) India", "(+49) Germany", "(+1) United States", "(+20) Egypt",
"(+1242) Bahamas", "(+593) Ecuador", "(+7) Russia"};
CreateTestSelectField("PC", "PC", augmented_field_options_list, &field);
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("countryCode")));
field.label = ASCIIToUTF16("Phone City Code");
field.name = ASCIIToUTF16("areacode");
field.form_control_type = "text";
field.max_length = 3;
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("cityCode")));
field.label = ASCIIToUTF16("Phone Number");
field.name = ASCIIToUTF16("phonenumber");
field.max_length = 0;
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber")));
AutofillScanner scanner(list_);
field_ = Parse(&scanner);
ASSERT_NE(nullptr, field_.get());
field_->AddClassificationsForTesting(&field_candidates_map_);
CheckField("countryCode", PHONE_HOME_COUNTRY_CODE);
CheckField("cityCode", PHONE_HOME_CITY_CODE);
CheckField("phoneNumber", PHONE_HOME_NUMBER);
}
// Tests if the country code field is correctly classified by the heuristic when
// the phone code is a select element and consists of valid options.
TEST_F(PhoneFieldTest, IsPhoneCountryCodeField) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
FormFieldData field;
std::vector<std::vector<const char*>> augmented_field_options_list = {
// Options with the country name followed by the country code in brackets.
{"India(+91) ", "Germany(+49)", "United States(+1)", "Egypt(+20)",
"Bahamas(+1242)", "Ecuador(+593)", "Russia(+7)"},
// Options consisting of the country code totaling more than 20.
{"+91", "+49", "+1", "+20", "+1242", "+593", "+7",
"+1441", "+211", "+212", "+30", "+31", "+32", "+33",
"+34", "+51", "52", "+673", "+674", "+81", "+82"},
// Options consisting of the country code totaling more than 20 with an
// additional placeholder option.
{"+91", "+49",
"+1", "+20",
"+1242", "+593",
"+7", "+1441",
"+211", "+212",
"+30", "+31",
"+32", "+33",
"+34", "+51",
"52", "+673",
"+674", "+81",
"+82", "Please select an option"},
// Options with the country name followed by the country code in brackets
// along with a placeholder option.
{"Please select an option", "(+91) India", "(+49) Germany",
"(+1) United States", "(+20) Egypt", "(+1242) Bahamas", "(+593) Ecuador",
"(+7) Russia"},
// Options with the phone country code followed by the country
// abbreviation.
{"91 IN", "49 DE", "1 US", "20 E", "1242 B", "593 EQ", "7 R"},
// Options with the phone country code that are preceded by '00' and
// followed by the country abbreviation.
{"(0091) IN", "(0049) DE", "(001) US", "(0020) E", "(001242) B",
"(00593) EQ", "(007) R"},
// Options with the phone country code that are preceded by '00' and
// followed by the country abbreviation with single space in between.
{"(00 91) IN", "(00 49) DE", "(00 1) US", "(00 20) E", "(00 1242) B",
"(00 593) EQ", "(00 7) R"},
// Options with the phone country code preceded by '00' with multiple
// spaces in between to align them.
{"00 91", "00 49", "00 1", "00 20", "001242", "00 593", "00 7"},
// Options with the phone country code preceded by '00'.
{"0091", "0049", "001", "0020", "001242", "00593", "007"}};
for (size_t i = 0; i < augmented_field_options_list.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
const auto& options_list = augmented_field_options_list[i];
CreateTestSelectField("PC", "PC", options_list, &field);
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("countryCode")));
field.label = ASCIIToUTF16("Phone Number");
field.name = ASCIIToUTF16("phonenumber");
field.max_length = 14;
field.form_control_type = "text";
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber")));
AutofillScanner scanner(list_);
field_ = Parse(&scanner);
ASSERT_NE(nullptr, field_.get());
field_->AddClassificationsForTesting(&field_candidates_map_);
CheckField("countryCode", PHONE_HOME_COUNTRY_CODE);
}
} // namespace autofill
// Tests that the month field is not classified as |PHONE_HOME_COUNTRY_CODE|.
TEST_F(PhoneFieldTest, IsMonthField) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
FormFieldData field;
std::vector<std::vector<const char*>> augmented_field_options_list = {
// Month options in numeric.
{"01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12"},
// Month options in numeric followed by the respective text.
{"(01) Jan", "(02) Feb", "(03) March", "(04) April", "(05) May",
"(06) June", "(07) July", "(08) August", "(09) Sept", "(10) Oct",
"(11) Nov", "(12) Dec"}};
for (size_t i = 0; i < augmented_field_options_list.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
const auto& options_list = augmented_field_options_list[i];
CreateTestSelectField("Month", "Month", options_list, &field);
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("months")));
field.label = ASCIIToUTF16("Phone Number");
field.name = ASCIIToUTF16("phonenumber");
field.max_length = 14;
field.form_control_type = "text";
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber")));
AutofillScanner scanner(list_);
field_ = Parse(&scanner);
ASSERT_EQ(nullptr, field_.get());
}
}
// Tests that the day field is not classified as |PHONE_HOME_COUNTRY_CODE|.
TEST_F(PhoneFieldTest, IsDayField) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
FormFieldData field;
std::vector<std::vector<const char*>> augmented_field_options_list = {
// Numeric day options.
{"01", "02", "03", "04", "05", "06", "07", "08", "09", "10",
"11", "12", "13", "14", "15", "16", "17", "18", "19", "20",
"21", "22", "23", "24", "25", "26", "27", "28", "29", "30"},
// Numeric day options with a select option placeholder.
{"Please select an option",
"01",
"02",
"03",
"04",
"05",
"06",
"07",
"08",
"09",
"10",
"11",
"12",
"13",
"14",
"15",
"16",
"17",
"18",
"19",
"20",
"21",
"22",
"23",
"24",
"25",
"26",
"27",
"28",
"29",
"30"}};
for (size_t i = 0; i < augmented_field_options_list.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
const auto& options_list = augmented_field_options_list[i];
CreateTestSelectField("Field", "Field", options_list, &field);
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("day")));
field.label = ASCIIToUTF16("Phone Number");
field.name = ASCIIToUTF16("phonenumber");
field.max_length = 14;
field.form_control_type = "text";
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber")));
AutofillScanner scanner(list_);
field_ = Parse(&scanner);
ASSERT_EQ(nullptr, field_.get());
}
}
// Tests that the field is not classified as |PHONE_HOME_COUNTRY_CODE|.
TEST_F(PhoneFieldTest, IsYearField) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
FormFieldData field;
std::vector<std::vector<const char*>> augmented_field_options_list = {
// Numeric four digit year options.
{"1990", "1991", "1992", "1993", "1994", "1995", "1996",
"1997", "1998", "1999", "2000", "2001", "2002", "2003",
"2004", "2005", "2006", "2007", "2008", "2009", "2010"},
// Numeric four digit year options less than 10 in total.
{"1990", "1991", "1992", "1993", "1994"},
// Numeric four digit year options in decreasing order.
{"2025", "2024", "2023", "2022", "2021", "2020"},
// Numeric two digit year options.
{"90", "91", "92", "93", "94", "95", "96", "97", "98", "99", "00", "01",
"02", "03", "04", "05", "06"},
// Numeric two digit year options along with an additional placeholder
// option.
{"Please select an option", "90", "91", "92", "93", "94", "95", "96",
"97", "98", "99", "00", "01", "02", "03", "04", "05", "06"},
// Numeric two digit year options along with an additional placeholder
// option less than 10 in total.
{"Please select an option", "90", "91", "92", "93", "94"}};
for (size_t i = 0; i < augmented_field_options_list.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
const auto& options_list = augmented_field_options_list[i];
CreateTestSelectField("Field", "Field", options_list, &field);
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("year")));
field.label = ASCIIToUTF16("Phone Number");
field.name = ASCIIToUTF16("phonenumber");
field.max_length = 14;
field.form_control_type = "text";
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber")));
AutofillScanner scanner(list_);
field_ = Parse(&scanner);
ASSERT_EQ(nullptr, field_.get());
}
}
// Tests that the timezone field is not classified as |PHONE_HOME_COUNTRY_CODE|.
TEST_F(PhoneFieldTest, IsTimeZoneField) {
base::test::ScopedFeatureList enabled;
enabled.InitAndEnableFeature(
features::kAutofillEnableAugmentedPhoneCountryCode);
FormFieldData field;
std::vector<std::vector<const char*>> augmented_field_options_list = {
// Time Zone options.
{"Yemen (UTC+03:00)", "Uruguay (UTC−03:00)", "UAE (UTC+04:00)",
"Uganda (UTC+03:00)", "Turkey (UTC+03:00)", "Taiwan (UTC+08:00)",
"Sweden (UTC+01:00)"},
// Time Zone options with a placeholder select element.
{"Please select an option", "Yemen (UTC+03:00)", "Uruguay (UTC−03:00)",
"UAE (UTC+04:00)", "Uganda (UTC+03:00)", "Turkey (UTC+03:00)",
"Taiwan (UTC+08:00)", "Sweden (UTC+01:00)"}};
for (size_t i = 0; i < augmented_field_options_list.size(); ++i) {
SCOPED_TRACE(testing::Message() << "i = " << i);
const auto& options_list = augmented_field_options_list[i];
CreateTestSelectField("Time Zone", "TimeZone", options_list, &field);
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("timeZone")));
field.label = ASCIIToUTF16("Phone Number");
field.name = ASCIIToUTF16("phonenumber");
field.max_length = 14;
field.form_control_type = "text";
list_.push_back(
std::make_unique<AutofillField>(field, ASCIIToUTF16("phoneNumber")));
AutofillScanner scanner(list_);
field_ = Parse(&scanner);
ASSERT_EQ(nullptr, field_.get());
}
}
} // namespace autofill } // namespace autofill
...@@ -65,6 +65,12 @@ const base::Feature kAutofillEnableAccountWalletStorage { ...@@ -65,6 +65,12 @@ const base::Feature kAutofillEnableAccountWalletStorage {
#endif #endif
}; };
// Controls whether to detect and fill the augmented phone country code field
// when enabled.
const base::Feature kAutofillEnableAugmentedPhoneCountryCode{
"AutofillEnableAugmentedPhoneCountryCode",
base::FEATURE_DISABLED_BY_DEFAULT};
// Controls whether we use COMPANY as part of Autofill // Controls whether we use COMPANY as part of Autofill
const base::Feature kAutofillEnableCompanyName{ const base::Feature kAutofillEnableCompanyName{
"AutofillEnableCompanyName", base::FEATURE_ENABLED_BY_DEFAULT}; "AutofillEnableCompanyName", base::FEATURE_ENABLED_BY_DEFAULT};
......
...@@ -29,6 +29,7 @@ extern const base::Feature kAutofillCacheQueryResponses; ...@@ -29,6 +29,7 @@ extern const base::Feature kAutofillCacheQueryResponses;
extern const base::Feature kAutofillCreateDataForTest; extern const base::Feature kAutofillCreateDataForTest;
extern const base::Feature kAutofillCreditCardAssist; extern const base::Feature kAutofillCreditCardAssist;
extern const base::Feature kAutofillEnableAccountWalletStorage; extern const base::Feature kAutofillEnableAccountWalletStorage;
extern const base::Feature kAutofillEnableAugmentedPhoneCountryCode;
extern const base::Feature kAutofillEnableCompanyName; extern const base::Feature kAutofillEnableCompanyName;
extern const base::Feature kAutofillEnableHideSuggestionsUI; extern const base::Feature kAutofillEnableHideSuggestionsUI;
extern const base::Feature kAutofillEnforceMinRequiredFieldsForHeuristics; extern const base::Feature kAutofillEnforceMinRequiredFieldsForHeuristics;
......
...@@ -351,6 +351,8 @@ const char kPhoneRe[] = ...@@ -351,6 +351,8 @@ const char kPhoneRe[] =
"|电话" // zh-CN "|电话" // zh-CN
"|മൊബൈല്‍" // ml for mobile "|മൊബൈല്‍" // ml for mobile
"|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?"; // ko-KR "|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?"; // ko-KR
const char kAugmentedPhoneCountryCodeRe[] =
"^[^0-9+]*(?:\\+|00)\\s*([1-9]\\d{0,3})\\D*$";
const char kCountryCodeRe[] = const char kCountryCodeRe[] =
"country.*code|ccode|_cc|phone.*code|user.*phone.*code"; "country.*code|ccode|_cc|phone.*code|user.*phone.*code";
const char kAreaCodeNotextRe[] = "^\\($"; const char kAreaCodeNotextRe[] = "^\\($";
......
...@@ -47,6 +47,7 @@ extern const char kMiddleInitialRe[]; ...@@ -47,6 +47,7 @@ extern const char kMiddleInitialRe[];
extern const char kMiddleNameRe[]; extern const char kMiddleNameRe[];
extern const char kLastNameRe[]; extern const char kLastNameRe[];
extern const char kPhoneRe[]; extern const char kPhoneRe[];
extern const char kAugmentedPhoneCountryCodeRe[];
extern const char kCountryCodeRe[]; extern const char kCountryCodeRe[];
extern const char kAreaCodeNotextRe[]; extern const char kAreaCodeNotextRe[];
extern const char kAreaCodeRe[]; extern const char kAreaCodeRe[];
......
...@@ -64,7 +64,8 @@ namespace autofill { ...@@ -64,7 +64,8 @@ namespace autofill {
bool MatchesPattern(const base::string16& input, bool MatchesPattern(const base::string16& input,
const base::string16& pattern, const base::string16& pattern,
base::string16* match) { base::string16* match,
int32_t group_to_be_captured) {
static base::NoDestructor<AutofillRegexes> g_autofill_regexes; static base::NoDestructor<AutofillRegexes> g_autofill_regexes;
static base::NoDestructor<base::Lock> g_lock; static base::NoDestructor<base::Lock> g_lock;
base::AutoLock lock(*g_lock); base::AutoLock lock(*g_lock);
...@@ -78,7 +79,8 @@ bool MatchesPattern(const base::string16& input, ...@@ -78,7 +79,8 @@ bool MatchesPattern(const base::string16& input,
DCHECK(U_SUCCESS(status)); DCHECK(U_SUCCESS(status));
if (matched == TRUE && match) { if (matched == TRUE && match) {
icu::UnicodeString match_unicode = matcher->group(0, status); icu::UnicodeString match_unicode =
matcher->group(group_to_be_captured, status);
DCHECK(U_SUCCESS(status)); DCHECK(U_SUCCESS(status));
*match = base::i18n::UnicodeStringToString16(match_unicode); *match = base::i18n::UnicodeStringToString16(match_unicode);
} }
......
...@@ -12,10 +12,11 @@ namespace autofill { ...@@ -12,10 +12,11 @@ namespace autofill {
// Case-insensitive regular expression matching. // Case-insensitive regular expression matching.
// Returns true if |pattern| is found in |input|. // Returns true if |pattern| is found in |input|.
// If |match| is not nullptr, the matched part is assigned to it. // The |group_to_be_captured| numbered group is captured into |match|.
bool MatchesPattern(const base::string16& input, bool MatchesPattern(const base::string16& input,
const base::string16& pattern, const base::string16& pattern,
base::string16* match = nullptr); base::string16* match = nullptr,
int32_t group_to_be_captured = 0);
} // namespace autofill } // namespace autofill
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment