diff options
author | joi@chromium.org <joi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-09 22:56:56 +0000 |
---|---|---|
committer | joi@chromium.org <joi@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-03-09 22:56:56 +0000 |
commit | e4b2fa31a1f25cfc80a422bcf1c76317c9e00cf2 (patch) | |
tree | f2b18dd29be63dddf88dfd728b8ebcf0ab87834b /components | |
parent | 3a8a6d92816b0e5b4cd67a74516fbda36fdc692d (diff) | |
download | chromium_src-e4b2fa31a1f25cfc80a422bcf1c76317c9e00cf2.zip chromium_src-e4b2fa31a1f25cfc80a422bcf1c76317c9e00cf2.tar.gz chromium_src-e4b2fa31a1f25cfc80a422bcf1c76317c9e00cf2.tar.bz2 |
Move remaining Autofill code to //components/autofill.
This leaves behind all browser tests, the AutofillCCInfobarDelegate
and PersonalDataManagerFactory, which remain Chrome-specific.
All source files were moved using //tools/git/move_source_file.py,
which updates includes of moved files, sorts include order, and
updates header guards.
The only manual bits of this change were:
- Move .proto file
- Move .cc.utf8 file
- Update .gypi files
- Update DEPS files
- Remove an unnecessary include from autofill_manager.cc
TBR=ben@chromium.org
BUG=140037
Review URL: https://chromiumcodereview.appspot.com/12434004
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@187173 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components')
168 files changed, 43906 insertions, 1 deletions
diff --git a/components/autofill.gypi b/components/autofill.gypi index c5d2611..2afd3e9 100644 --- a/components/autofill.gypi +++ b/components/autofill.gypi @@ -3,6 +3,38 @@ # found in the LICENSE file. { + 'targets': [ + { + 'target_name': 'autofill_regexes', + 'type': 'none', + 'actions': [{ + 'action_name': 'autofill_regexes', + 'inputs': [ + '<(DEPTH)/build/escape_unicode.py', + 'autofill/browser/autofill_regex_constants.cc.utf8', + ], + 'outputs': [ + '<(SHARED_INTERMEDIATE_DIR)/autofill_regex_constants.cc', + ], + 'action': ['python', '<(DEPTH)/build/escape_unicode.py', + '-o', '<(SHARED_INTERMEDIATE_DIR)', + 'autofill/browser/autofill_regex_constants.cc.utf8'], + }], + }, + { + # Protobuf compiler / generate rule for Autofill's risk integration. + 'target_name': 'autofill_risk_proto', + 'type': 'static_library', + 'sources': [ + 'autofill/browser/risk/proto/fingerprint.proto', + ], + 'variables': { + 'proto_in_dir': 'autofill/browser/risk/proto', + 'proto_out_dir': 'components/autofill/browser/risk/proto', + }, + 'includes': [ '../build/protoc.gypi' ] + }, + ], 'conditions': [ ['OS != "ios"', { 'targets': [ diff --git a/components/autofill/DEPS b/components/autofill/DEPS index 9d52b11..cd724eb 100644 --- a/components/autofill/DEPS +++ b/components/autofill/DEPS @@ -1,4 +1,9 @@ include_rules = [ - "+third_party/WebKit/Source/WebKit/chromium/public", + "+google_apis/gaia/gaia_urls.h", + "+grit", # For generated headers + "+net", + # Allow inclusion of WebKit API files. + "+third_party/WebKit/Source/Platform/chromium", + "+third_party/WebKit/Source/WebKit/chromium", "+ui", ] diff --git a/components/autofill/browser/DEPS b/components/autofill/browser/DEPS new file mode 100644 index 0000000..f1032f5 --- /dev/null +++ b/components/autofill/browser/DEPS @@ -0,0 +1,60 @@ +include_rules = [ + "+content/public/browser", + "+crypto/random.h", + "+google_apis/google_api_keys.h", + "+net", + "+third_party/libjingle", + "+third_party/libphonenumber", # For phone number i18n. + "+webkit/plugins/webplugininfo.h", + + # TODO(joi): This is "ok" temporarily (it's just a header file with + # a large enum, and doesn't bring in any other dependencies on + # //chrome). The plan of record to get rid of this is to change + # Autofill and WebData to not use NotificationService, and instead + # use typed callback interfaces or allow registering base::Callback + # callbacks for each event. + "!chrome/common/chrome_notification_types.h", + + # TODO(joi, kaiwang): Bring this list to zero. + "!chrome/browser/api/webdata", + + # TODO(akalin): Remove this dependency. + "!sync/util/data_encryption_win.h", +] + +specific_include_rules = { + '.*_[a-z]*test\.cc': [ + "+content/public/test", + + # TODO(joi, kaiwang): Bring this list to zero. + # + # Do not add to the list of temporarily-allowed dependencies below, + # and please do not introduce more #includes of these files. + "!chrome/browser/autofill/autofill_cc_infobar_delegate.h", + "!chrome/browser/autofill/personal_data_manager_factory.h", + "!chrome/browser/api/infobars/confirm_infobar_delegate.h", + "!chrome/browser/api/infobars/infobar_service.h", + "!chrome/browser/password_manager/encryptor.h", + "!chrome/browser/password_manager/password_manager.h", + "!chrome/browser/password_manager/password_manager_delegate_impl.h", + "!chrome/browser/profiles/profile.h", + "!chrome/browser/sync/profile_sync_service.h", + "!chrome/browser/sync/profile_sync_service_factory.h", + "!chrome/browser/translate/translate_infobar_delegate.h", + "!chrome/browser/translate/translate_manager.h", + "!chrome/browser/webdata/autofill_web_data_service_impl.h", + "!chrome/browser/webdata/web_data_service.h", + "!chrome/browser/webdata/web_data_service_factory.h", + "!chrome/browser/ui/autofill/tab_autofill_manager_delegate.h", + "!chrome/browser/ui/browser.h", + "!chrome/browser/ui/browser_tabstrip.h", + "!chrome/browser/ui/browser_window.h", + "!chrome/browser/ui/tabs/tab_strip_model.h", + "!chrome/common/chrome_constants.h", + "!chrome/common/chrome_paths.h", + "!chrome/common/pref_names.h", + "!chrome/common/render_messages.h", + "!chrome/common/url_constants.h", + "!chrome/test/base", + ], +} diff --git a/components/autofill/browser/address.cc b/components/autofill/browser/address.cc new file mode 100644 index 0000000..7e430f8 --- /dev/null +++ b/components/autofill/browser/address.cc @@ -0,0 +1,114 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/address.h" + +#include <stddef.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/field_types.h" + +namespace { + +const char16 kAddressSplitChars[] = {'-', ',', '#', '.', ' ', 0}; + +} // namespace + +Address::Address() {} + +Address::Address(const Address& address) : FormGroup() { + *this = address; +} + +Address::~Address() {} + +Address& Address::operator=(const Address& address) { + if (this == &address) + return *this; + + line1_ = address.line1_; + line2_ = address.line2_; + city_ = address.city_; + state_ = address.state_; + country_code_ = address.country_code_; + zip_code_ = address.zip_code_; + return *this; +} + +void Address::GetSupportedTypes(FieldTypeSet* supported_types) const { + supported_types->insert(ADDRESS_HOME_LINE1); + supported_types->insert(ADDRESS_HOME_LINE2); + supported_types->insert(ADDRESS_HOME_CITY); + supported_types->insert(ADDRESS_HOME_STATE); + supported_types->insert(ADDRESS_HOME_ZIP); + supported_types->insert(ADDRESS_HOME_COUNTRY); +} + +string16 Address::GetRawInfo(AutofillFieldType type) const { + if (type == ADDRESS_HOME_LINE1) + return line1_; + + if (type == ADDRESS_HOME_LINE2) + return line2_; + + if (type == ADDRESS_HOME_CITY) + return city_; + + if (type == ADDRESS_HOME_STATE) + return state_; + + if (type == ADDRESS_HOME_ZIP) + return zip_code_; + + if (type == ADDRESS_HOME_COUNTRY) + return Country(); + + return string16(); +} + +void Address::SetRawInfo(AutofillFieldType type, const string16& value) { + type = AutofillType::GetEquivalentFieldType(type); + if (type == ADDRESS_HOME_LINE1) + line1_ = value; + else if (type == ADDRESS_HOME_LINE2) + line2_ = value; + else if (type == ADDRESS_HOME_CITY) + city_ = value; + else if (type == ADDRESS_HOME_STATE) + state_ = value; + else if (type == ADDRESS_HOME_COUNTRY) + // TODO(isherman): When setting the country, it should only be possible to + // call this with a country code, which means we should be able to drop the + // call to GetCountryCode() below. + country_code_ = + AutofillCountry::GetCountryCode(value, + AutofillCountry::ApplicationLocale()); + else if (type == ADDRESS_HOME_ZIP) + zip_code_ = value; + else + NOTREACHED(); +} + +void Address::GetMatchingTypes(const string16& text, + const std::string& app_locale, + FieldTypeSet* matching_types) const { + FormGroup::GetMatchingTypes(text, app_locale, matching_types); + + // Check to see if the |text| canonicalized as a country name is a match. + std::string country_code = AutofillCountry::GetCountryCode(text, app_locale); + if (!country_code.empty() && country_code_ == country_code) + matching_types->insert(ADDRESS_HOME_COUNTRY); +} + +string16 Address::Country() const { + if (country_code().empty()) + return string16(); + + std::string app_locale = AutofillCountry::ApplicationLocale(); + return AutofillCountry(country_code(), app_locale).name(); +} diff --git a/components/autofill/browser/address.h b/components/autofill/browser/address.h new file mode 100644 index 0000000..5a3127d --- /dev/null +++ b/components/autofill/browser/address.h @@ -0,0 +1,54 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_ADDRESS_H_ +#define COMPONENTS_AUTOFILL_BROWSER_ADDRESS_H_ + +#include <string> +#include <vector> + +#include "base/string16.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/form_group.h" + +// A form group that stores address information. +class Address : public FormGroup { + public: + Address(); + Address(const Address& address); + virtual ~Address(); + + Address& operator=(const Address& address); + + // FormGroup: + virtual string16 GetRawInfo(AutofillFieldType type) const OVERRIDE; + virtual void SetRawInfo(AutofillFieldType type, + const string16& value) OVERRIDE; + virtual void GetMatchingTypes(const string16& text, + const std::string& app_locale, + FieldTypeSet* matching_types) const OVERRIDE; + + const std::string& country_code() const { return country_code_; } + void set_country_code(const std::string& country_code) { + country_code_ = country_code; + } + + private: + // FormGroup: + virtual void GetSupportedTypes(FieldTypeSet* supported_types) const OVERRIDE; + + // Returns the localized country name corresponding to |country_code_|. + string16 Country() const; + + // The address. + string16 line1_; + string16 line2_; + string16 city_; + string16 state_; + std::string country_code_; + string16 zip_code_; +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_ADDRESS_H_ diff --git a/components/autofill/browser/address_field.cc b/components/autofill/browser/address_field.cc new file mode 100644 index 0000000..a8f614d --- /dev/null +++ b/components/autofill/browser/address_field.cc @@ -0,0 +1,335 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/address_field.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_regex_constants.h" +#include "components/autofill/browser/autofill_scanner.h" +#include "components/autofill/browser/field_types.h" +#include "ui/base/l10n/l10n_util.h" + +FormField* AddressField::Parse(AutofillScanner* scanner) { + if (scanner->IsEnd()) + return NULL; + + scoped_ptr<AddressField> address_field(new AddressField); + const AutofillField* const initial_field = scanner->Cursor(); + size_t saved_cursor = scanner->SaveCursor(); + + string16 attention_ignored = UTF8ToUTF16(autofill::kAttentionIgnoredRe); + string16 region_ignored = UTF8ToUTF16(autofill::kRegionIgnoredRe); + + // Allow address fields to appear in any order. + size_t begin_trailing_non_labeled_fields = 0; + bool has_trailing_non_labeled_fields = false; + while (!scanner->IsEnd()) { + const size_t cursor = scanner->SaveCursor(); + if (ParseAddressLines(scanner, address_field.get()) || + ParseCity(scanner, address_field.get()) || + ParseState(scanner, address_field.get()) || + ParseZipCode(scanner, address_field.get()) || + ParseCountry(scanner, address_field.get()) || + ParseCompany(scanner, address_field.get())) { + has_trailing_non_labeled_fields = false; + continue; + } else if (ParseField(scanner, attention_ignored, NULL) || + ParseField(scanner, region_ignored, NULL)) { + // We ignore the following: + // * Attention. + // * Province/Region/Other. + continue; + } else if (scanner->Cursor() != initial_field && + ParseEmptyLabel(scanner, NULL)) { + // Ignore non-labeled fields within an address; the page + // MapQuest Driving Directions North America.html contains such a field. + // We only ignore such fields after we've parsed at least one other field; + // otherwise we'd effectively parse address fields before other field + // types after any non-labeled fields, and we want email address fields to + // have precedence since some pages contain fields labeled + // "Email address". + if (!has_trailing_non_labeled_fields) { + has_trailing_non_labeled_fields = true; + begin_trailing_non_labeled_fields = cursor; + } + + continue; + } else { + // No field found. + break; + } + } + + // If we have identified any address fields in this field then it should be + // added to the list of fields. + if (address_field->company_ != NULL || + address_field->address1_ != NULL || address_field->address2_ != NULL || + address_field->city_ != NULL || address_field->state_ != NULL || + address_field->zip_ != NULL || address_field->zip4_ || + address_field->country_ != NULL) { + // Don't slurp non-labeled fields at the end into the address. + if (has_trailing_non_labeled_fields) + scanner->RewindTo(begin_trailing_non_labeled_fields); + + address_field->type_ = address_field->FindType(); + return address_field.release(); + } + + scanner->RewindTo(saved_cursor); + return NULL; +} + +AddressField::AddressType AddressField::FindType() const { + // First look at the field name, which itself will sometimes contain + // "bill" or "ship". + if (company_) { + string16 name = StringToLowerASCII(company_->name); + return AddressTypeFromText(name); + } + if (address1_) { + string16 name = StringToLowerASCII(address1_->name); + return AddressTypeFromText(name); + } + if (address2_) { + string16 name = StringToLowerASCII(address2_->name); + return AddressTypeFromText(name); + } + if (city_) { + string16 name = StringToLowerASCII(city_->name); + return AddressTypeFromText(name); + } + if (zip_) { + string16 name = StringToLowerASCII(zip_->name); + return AddressTypeFromText(name); + } + if (state_) { + string16 name = StringToLowerASCII(state_->name); + return AddressTypeFromText(name); + } + if (country_) { + string16 name = StringToLowerASCII(country_->name); + return AddressTypeFromText(name); + } + + return kGenericAddress; +} + +AddressField::AddressField() + : company_(NULL), + address1_(NULL), + address2_(NULL), + city_(NULL), + state_(NULL), + zip_(NULL), + zip4_(NULL), + country_(NULL), + type_(kGenericAddress) { +} + +bool AddressField::ClassifyField(FieldTypeMap* map) const { + AutofillFieldType address_company; + AutofillFieldType address_line1; + AutofillFieldType address_line2; + AutofillFieldType address_city; + AutofillFieldType address_state; + AutofillFieldType address_zip; + AutofillFieldType address_country; + + switch (type_) { + case kShippingAddress: + // Fall through. Autofill does not support shipping addresses. + case kGenericAddress: + address_company = COMPANY_NAME; + address_line1 = ADDRESS_HOME_LINE1; + address_line2 = ADDRESS_HOME_LINE2; + address_city = ADDRESS_HOME_CITY; + address_state = ADDRESS_HOME_STATE; + address_zip = ADDRESS_HOME_ZIP; + address_country = ADDRESS_HOME_COUNTRY; + break; + + case kBillingAddress: + address_company = COMPANY_NAME; + address_line1 = ADDRESS_BILLING_LINE1; + address_line2 = ADDRESS_BILLING_LINE2; + address_city = ADDRESS_BILLING_CITY; + address_state = ADDRESS_BILLING_STATE; + address_zip = ADDRESS_BILLING_ZIP; + address_country = ADDRESS_BILLING_COUNTRY; + break; + + default: + NOTREACHED(); + return false; + } + + bool ok = AddClassification(company_, address_company, map); + ok = ok && AddClassification(address1_, address_line1, map); + ok = ok && AddClassification(address2_, address_line2, map); + ok = ok && AddClassification(city_, address_city, map); + ok = ok && AddClassification(state_, address_state, map); + ok = ok && AddClassification(zip_, address_zip, map); + ok = ok && AddClassification(country_, address_country, map); + return ok; +} + +// static +bool AddressField::ParseCompany(AutofillScanner* scanner, + AddressField* address_field) { + if (address_field->company_ && !address_field->company_->IsEmpty()) + return false; + + return ParseField(scanner, UTF8ToUTF16(autofill::kCompanyRe), + &address_field->company_); +} + +// static +bool AddressField::ParseAddressLines(AutofillScanner* scanner, + AddressField* address_field) { + // We only match the string "address" in page text, not in element names, + // because sometimes every element in a group of address fields will have + // a name containing the string "address"; for example, on the page + // Kohl's - Register Billing Address.html the text element labeled "city" + // has the name "BILL_TO_ADDRESS<>city". We do match address labels + // such as "address1", which appear as element names on various pages (eg + // AmericanGirl-Registration.html, BloomingdalesBilling.html, + // EBay Registration Enter Information.html). + if (address_field->address1_) + return false; + + string16 pattern = UTF8ToUTF16(autofill::kAddressLine1Re); + string16 label_pattern = UTF8ToUTF16(autofill::kAddressLine1LabelRe); + + if (!ParseField(scanner, pattern, &address_field->address1_) && + !ParseFieldSpecifics(scanner, label_pattern, MATCH_LABEL | MATCH_TEXT, + &address_field->address1_)) { + return false; + } + + // Optionally parse more address lines, which may have empty labels. + // Some pages have 3 address lines (eg SharperImageModifyAccount.html) + // Some pages even have 4 address lines (e.g. uk/ShoesDirect2.html)! + pattern = UTF8ToUTF16(autofill::kAddressLine2Re); + label_pattern = UTF8ToUTF16(autofill::kAddressLine2LabelRe); + if (!ParseEmptyLabel(scanner, &address_field->address2_) && + !ParseField(scanner, pattern, &address_field->address2_)) { + ParseFieldSpecifics(scanner, label_pattern, MATCH_LABEL | MATCH_TEXT, + &address_field->address2_); + } + + // Try for a third line, which we will promptly discard. + if (address_field->address2_ != NULL) { + pattern = UTF8ToUTF16(autofill::kAddressLine3Re); + ParseField(scanner, pattern, NULL); + } + + return true; +} + +// static +bool AddressField::ParseCountry(AutofillScanner* scanner, + AddressField* address_field) { + // Parse a country. The occasional page (e.g. + // Travelocity_New Member Information1.html) calls this a "location". + if (address_field->country_ && !address_field->country_->IsEmpty()) + return false; + + return ParseFieldSpecifics(scanner, + UTF8ToUTF16(autofill::kCountryRe), + MATCH_DEFAULT | MATCH_SELECT, + &address_field->country_); +} + +// static +bool AddressField::ParseZipCode(AutofillScanner* scanner, + AddressField* address_field) { + // Parse a zip code. On some UK pages (e.g. The China Shop2.html) this + // is called a "post code". + // + // HACK: Just for the MapQuest driving directions page we match the + // exact name "1z", which MapQuest uses to label its zip code field. + // Hopefully before long we'll be smart enough to find the zip code + // on that page automatically. + if (address_field->zip_) + return false; + + string16 pattern = UTF8ToUTF16(autofill::kZipCodeRe); + if (!ParseField(scanner, pattern, &address_field->zip_)) + return false; + + address_field->type_ = kGenericAddress; + // Look for a zip+4, whose field name will also often contain + // the substring "zip". + ParseField(scanner, + UTF8ToUTF16(autofill::kZip4Re), + &address_field->zip4_); + + return true; +} + +// static +bool AddressField::ParseCity(AutofillScanner* scanner, + AddressField* address_field) { + // Parse a city name. Some UK pages (e.g. The China Shop2.html) use + // the term "town". + if (address_field->city_) + return false; + + // Select fields are allowed here. This occurs on top-100 site rediff.com. + return ParseFieldSpecifics(scanner, + UTF8ToUTF16(autofill::kCityRe), + MATCH_DEFAULT | MATCH_SELECT, + &address_field->city_); +} + +// static +bool AddressField::ParseState(AutofillScanner* scanner, + AddressField* address_field) { + if (address_field->state_) + return false; + + return ParseFieldSpecifics(scanner, + UTF8ToUTF16(autofill::kStateRe), + MATCH_DEFAULT | MATCH_SELECT, + &address_field->state_); +} + +AddressField::AddressType AddressField::AddressTypeFromText( + const string16 &text) { + size_t same_as = text.find(UTF8ToUTF16(autofill::kAddressTypeSameAsRe)); + size_t use_shipping = text.find(UTF8ToUTF16(autofill::kAddressTypeUseMyRe)); + if (same_as != string16::npos || use_shipping != string16::npos) + // This text could be a checkbox label such as "same as my billing + // address" or "use my shipping address". + // ++ It would help if we generally skipped all text that appears + // after a check box. + return kGenericAddress; + + // Not all pages say "billing address" and "shipping address" explicitly; + // for example, Craft Catalog1.html has "Bill-to Address" and + // "Ship-to Address". + size_t bill = text.rfind(UTF8ToUTF16(autofill::kBillingDesignatorRe)); + size_t ship = text.rfind(UTF8ToUTF16(autofill::kShippingDesignatorRe)); + + if (bill == string16::npos && ship == string16::npos) + return kGenericAddress; + + if (bill != string16::npos && ship == string16::npos) + return kBillingAddress; + + if (bill == string16::npos && ship != string16::npos) + return kShippingAddress; + + if (bill > ship) + return kBillingAddress; + + return kShippingAddress; +} diff --git a/components/autofill/browser/address_field.h b/components/autofill/browser/address_field.h new file mode 100644 index 0000000..debdf53 --- /dev/null +++ b/components/autofill/browser/address_field.h @@ -0,0 +1,84 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_ADDRESS_FIELD_H_ +#define COMPONENTS_AUTOFILL_BROWSER_ADDRESS_FIELD_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/string16.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/form_field.h" + +class AutofillField; +class AutofillScanner; + +class AddressField : public FormField { + public: + static FormField* Parse(AutofillScanner* scanner); + + protected: + // FormField: + virtual bool ClassifyField(FieldTypeMap* map) const OVERRIDE; + + private: + enum AddressType { + kGenericAddress = 0, + kBillingAddress, + kShippingAddress + }; + + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseOneLineAddress); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseOneLineAddressBilling); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseOneLineAddressShipping); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseTwoLineAddress); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseThreeLineAddress); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseCity); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseState); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseZip); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseStateAndZipOneLabel); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseCountry); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseTwoLineAddressMissingLabel); + FRIEND_TEST_ALL_PREFIXES(AddressFieldTest, ParseCompany); + + AddressField(); + + static bool ParseCompany(AutofillScanner* scanner, + AddressField* address_field); + static bool ParseAddressLines(AutofillScanner* scanner, + AddressField* address_field); + static bool ParseCountry(AutofillScanner* scanner, + AddressField* address_field); + static bool ParseZipCode(AutofillScanner* scanner, + AddressField* address_field); + static bool ParseCity(AutofillScanner* scanner, + AddressField* address_field); + static bool ParseState(AutofillScanner* scanner, + AddressField* address_field); + + // Looks for an address type in the given text, which the caller must + // convert to lowercase. + static AddressType AddressTypeFromText(const string16& text); + + // Tries to determine the billing/shipping type of this address. + AddressType FindType() const; + + const AutofillField* company_; // optional + const AutofillField* address1_; + const AutofillField* address2_; // optional + const AutofillField* city_; + const AutofillField* state_; // optional + const AutofillField* zip_; + const AutofillField* zip4_; // optional ZIP+4; we don't fill this yet + const AutofillField* country_; // optional + + AddressType type_; + + DISALLOW_COPY_AND_ASSIGN(AddressField); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_ADDRESS_FIELD_H_ diff --git a/components/autofill/browser/address_field_unittest.cc b/components/autofill/browser/address_field_unittest.cc new file mode 100644 index 0000000..2798f28 --- /dev/null +++ b/components/autofill/browser/address_field_unittest.cc @@ -0,0 +1,294 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/address_field.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_scanner.h" +#include "components/autofill/common/form_field_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +class AddressFieldTest : public testing::Test { + public: + AddressFieldTest() {} + + protected: + ScopedVector<const AutofillField> list_; + scoped_ptr<AddressField> field_; + FieldTypeMap field_type_map_; + + // Downcast for tests. + static AddressField* Parse(AutofillScanner* scanner) { + return static_cast<AddressField*>(AddressField::Parse(scanner)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(AddressFieldTest); +}; + +TEST_F(AddressFieldTest, Empty) { + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_EQ(static_cast<AddressField*>(NULL), field_.get()); +} + +TEST_F(AddressFieldTest, NonParse) { + list_.push_back(new AutofillField); + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_EQ(static_cast<AddressField*>(NULL), field_.get()); +} + +TEST_F(AddressFieldTest, ParseOneLineAddress) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("address"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("addr1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kGenericAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("addr1")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_LINE1, field_type_map_[ASCIIToUTF16("addr1")]); +} + +TEST_F(AddressFieldTest, ParseOneLineAddressBilling) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("billingAddress"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("addr1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kBillingAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("addr1")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_BILLING_LINE1, field_type_map_[ASCIIToUTF16("addr1")]); +} + +TEST_F(AddressFieldTest, ParseOneLineAddressShipping) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("shippingAddress"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("addr1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kShippingAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("addr1")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_LINE1, field_type_map_[ASCIIToUTF16("addr1")]); +} + +TEST_F(AddressFieldTest, ParseTwoLineAddress) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("address"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("addr1"))); + + field.label = string16(); + field.name = string16(); + list_.push_back(new AutofillField(field, ASCIIToUTF16("addr2"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kGenericAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("addr1")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_LINE1, field_type_map_[ASCIIToUTF16("addr1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("addr2")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_LINE2, field_type_map_[ASCIIToUTF16("addr2")]); +} + +TEST_F(AddressFieldTest, ParseThreeLineAddress) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Address Line1"); + field.name = ASCIIToUTF16("Address1"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("addr1"))); + + field.label = ASCIIToUTF16("Address Line2"); + field.name = ASCIIToUTF16("Address2"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("addr2"))); + + field.label = ASCIIToUTF16("Address Line3"); + field.name = ASCIIToUTF16("Address3"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("addr3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kGenericAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("addr1")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_LINE1, field_type_map_[ASCIIToUTF16("addr1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("addr2")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_LINE2, field_type_map_[ASCIIToUTF16("addr2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("addr3")) == field_type_map_.end()); +} + +TEST_F(AddressFieldTest, ParseCity) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("City"); + field.name = ASCIIToUTF16("city"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("city1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kGenericAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("city1")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_CITY, field_type_map_[ASCIIToUTF16("city1")]); +} + +TEST_F(AddressFieldTest, ParseState) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("State"); + field.name = ASCIIToUTF16("state"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("state1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kGenericAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("state1")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_STATE, field_type_map_[ASCIIToUTF16("state1")]); +} + +TEST_F(AddressFieldTest, ParseZip) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Zip"); + field.name = ASCIIToUTF16("zip"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("zip1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kGenericAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("zip1")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_ZIP, field_type_map_[ASCIIToUTF16("zip1")]); +} + +TEST_F(AddressFieldTest, ParseStateAndZipOneLabel) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("State/Province, Zip/Postal Code"); + field.name = ASCIIToUTF16("state"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("state"))); + + field.label = ASCIIToUTF16("State/Province, Zip/Postal Code"); + field.name = ASCIIToUTF16("zip"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("zip"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kGenericAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("state")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_STATE, field_type_map_[ASCIIToUTF16("state")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("zip")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_ZIP, field_type_map_[ASCIIToUTF16("zip")]); +} + +TEST_F(AddressFieldTest, ParseCountry) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Country"); + field.name = ASCIIToUTF16("country"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("country1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kGenericAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("country1")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_COUNTRY, field_type_map_[ASCIIToUTF16("country1")]); +} + +TEST_F(AddressFieldTest, ParseTwoLineAddressMissingLabel) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("address"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("addr1"))); + + field.label = string16(); + field.name = ASCIIToUTF16("bogus"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("addr2"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kGenericAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("addr1")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_LINE1, field_type_map_[ASCIIToUTF16("addr1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("addr2")) != field_type_map_.end()); + EXPECT_EQ(ADDRESS_HOME_LINE2, field_type_map_[ASCIIToUTF16("addr2")]); +} + +TEST_F(AddressFieldTest, ParseCompany) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Company"); + field.name = ASCIIToUTF16("company"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("company1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<AddressField*>(NULL), field_.get()); + EXPECT_EQ(AddressField::kGenericAddress, field_->FindType()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("company1")) != field_type_map_.end()); + EXPECT_EQ(COMPANY_NAME, field_type_map_[ASCIIToUTF16("company1")]); +} diff --git a/components/autofill/browser/address_unittest.cc b/components/autofill/browser/address_unittest.cc new file mode 100644 index 0000000..0925146 --- /dev/null +++ b/components/autofill/browser/address_unittest.cc @@ -0,0 +1,134 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/message_loop.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/address.h" +#include "components/autofill/browser/autofill_type.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; + +class AddressTest : public testing::Test { + public: + // In order to access the application locale -- which the tested functions do + // internally -- this test must run on the UI thread. + AddressTest() : ui_thread_(BrowserThread::UI, &message_loop_) {} + + private: + MessageLoopForUI message_loop_; + content::TestBrowserThread ui_thread_; + + DISALLOW_COPY_AND_ASSIGN(AddressTest); +}; + +// Test that the getters and setters for country code are working. +TEST_F(AddressTest, CountryCode) { + Address address; + EXPECT_EQ(std::string(), address.country_code()); + + address.set_country_code("US"); + EXPECT_EQ("US", address.country_code()); + + address.set_country_code("CA"); + EXPECT_EQ("CA", address.country_code()); +} + +// Test that country codes are properly decoded as country names. +TEST_F(AddressTest, GetCountry) { + Address address; + EXPECT_EQ(std::string(), address.country_code()); + + // Make sure that nothing breaks when the country code is missing. + string16 country = address.GetInfo(ADDRESS_HOME_COUNTRY, "en-US"); + EXPECT_EQ(string16(), country); + + address.set_country_code("US"); + country = address.GetInfo(ADDRESS_HOME_COUNTRY, "en-US"); + EXPECT_EQ(ASCIIToUTF16("United States"), country); + + address.set_country_code("CA"); + country = address.GetInfo(ADDRESS_HOME_COUNTRY, "en-US"); + EXPECT_EQ(ASCIIToUTF16("Canada"), country); +} + +// Test that we properly detect country codes appropriate for each country. +TEST_F(AddressTest, SetCountry) { + Address address; + EXPECT_EQ(std::string(), address.country_code()); + + // Test basic conversion. + address.SetInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("United States"), "en-US"); + string16 country = address.GetInfo(ADDRESS_HOME_COUNTRY, "en-US"); + EXPECT_EQ("US", address.country_code()); + EXPECT_EQ(ASCIIToUTF16("United States"), country); + + // Test basic synonym detection. + address.SetInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("USA"), "en-US"); + country = address.GetInfo(ADDRESS_HOME_COUNTRY, "en-US"); + EXPECT_EQ("US", address.country_code()); + EXPECT_EQ(ASCIIToUTF16("United States"), country); + + // Test case-insensitivity. + address.SetInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("canADA"), "en-US"); + country = address.GetInfo(ADDRESS_HOME_COUNTRY, "en-US"); + EXPECT_EQ("CA", address.country_code()); + EXPECT_EQ(ASCIIToUTF16("Canada"), country); + + // Test country code detection. + address.SetInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("JP"), "en-US"); + country = address.GetInfo(ADDRESS_HOME_COUNTRY, "en-US"); + EXPECT_EQ("JP", address.country_code()); + EXPECT_EQ(ASCIIToUTF16("Japan"), country); + + // Test that we ignore unknown countries. + address.SetInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("Unknown"), "en-US"); + country = address.GetInfo(ADDRESS_HOME_COUNTRY, "en-US"); + EXPECT_EQ(std::string(), address.country_code()); + EXPECT_EQ(string16(), country); +} + +// Test that we properly match typed values to stored country data. +TEST_F(AddressTest, IsCountry) { + Address address; + address.set_country_code("US"); + + const char* const kValidMatches[] = { + "United States", + "USA", + "US", + "United states", + "us" + }; + for (size_t i = 0; i < arraysize(kValidMatches); ++i) { + SCOPED_TRACE(kValidMatches[i]); + FieldTypeSet matching_types; + address.GetMatchingTypes(ASCIIToUTF16(kValidMatches[i]), "US", + &matching_types); + ASSERT_EQ(1U, matching_types.size()); + EXPECT_EQ(ADDRESS_HOME_COUNTRY, *matching_types.begin()); + } + + const char* const kInvalidMatches[] = { + "United", + "Garbage" + }; + for (size_t i = 0; i < arraysize(kInvalidMatches); ++i) { + FieldTypeSet matching_types; + address.GetMatchingTypes(ASCIIToUTF16(kInvalidMatches[i]), "US", + &matching_types); + EXPECT_EQ(0U, matching_types.size()); + } + + // Make sure that garbage values don't match when the country code is empty. + address.set_country_code(""); + EXPECT_EQ(std::string(), address.country_code()); + FieldTypeSet matching_types; + address.GetMatchingTypes(ASCIIToUTF16("Garbage"), "US", &matching_types); + EXPECT_EQ(0U, matching_types.size()); +} diff --git a/components/autofill/browser/autocheckout/whitelist_manager.cc b/components/autofill/browser/autocheckout/whitelist_manager.cc new file mode 100644 index 0000000..410ef7d --- /dev/null +++ b/components/autofill/browser/autocheckout/whitelist_manager.cc @@ -0,0 +1,158 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autocheckout/whitelist_manager.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "base/strings/string_split.h" +#include "components/autofill/common/autofill_switches.h" +#include "content/public/browser/browser_context.h" +#include "googleurl/src/gurl.h" +#include "net/base/load_flags.h" +#include "net/http/http_status_code.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_request_context_getter.h" + +namespace { + +// Back off in seconds after each whitelist download is attempted. +const int kDownloadIntervalSeconds = 86400; // 1 day + +// The delay in seconds after startup before download whitelist. This helps +// to reduce contention at startup time. +const int kInitialDownloadDelaySeconds = 3; + +const char kWhitelistUrl[] = + "http://www.gstatic.com/commerce/autocheckout/whitelist.csv"; + +const char kWhiteListKeyName[] = "autocheckout_whitelist_manager"; + +} // namespace + + +namespace autofill { +namespace autocheckout { + +// static +WhitelistManager* WhitelistManager::GetForBrowserContext( + content::BrowserContext* context) { + WhitelistManager* whitelist_manager = static_cast<WhitelistManager*>( + context->GetUserData(kWhiteListKeyName)); + if (!whitelist_manager) { + whitelist_manager = + new WhitelistManager(context->GetRequestContext()); + whitelist_manager->ScheduleDownload(kInitialDownloadDelaySeconds); + context->SetUserData(kWhiteListKeyName, whitelist_manager); + } + return whitelist_manager; +} + +WhitelistManager::WhitelistManager( + net::URLRequestContextGetter* context_getter) + : callback_is_pending_(false), + context_getter_(context_getter), + experimental_form_filling_enabled_( + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableExperimentalFormFilling)){ + DCHECK(context_getter); +} + +WhitelistManager::~WhitelistManager() {} + +void WhitelistManager::ScheduleDownload(size_t interval_seconds) { + if (!experimental_form_filling_enabled_) { + // The feature is not enabled: do not do the request. + return; + } + if (download_timer_.IsRunning() || callback_is_pending_) { + // A download activity is already scheduled or happening. + return; + } + StartDownloadTimer(interval_seconds); +} + +void WhitelistManager::StartDownloadTimer(size_t interval_seconds) { + download_timer_.Start(FROM_HERE, + base::TimeDelta::FromSeconds(interval_seconds), + this, + &WhitelistManager::TriggerDownload); +} + +void WhitelistManager::TriggerDownload() { + callback_is_pending_ = true; + + request_.reset(net::URLFetcher::Create( + 0, GURL(kWhitelistUrl), net::URLFetcher::GET, this)); + request_->SetRequestContext(context_getter_); + request_->SetAutomaticallyRetryOn5xx(false); + request_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES | + net::LOAD_DO_NOT_SEND_COOKIES); + request_->Start(); +} + +void WhitelistManager::StopDownloadTimer() { + download_timer_.Stop(); + callback_is_pending_ = false; +} + +void WhitelistManager::OnURLFetchComplete( + const net::URLFetcher* source) { + DCHECK(callback_is_pending_); + callback_is_pending_ = false; + scoped_ptr<net::URLFetcher> old_request = request_.Pass(); + DCHECK_EQ(source, old_request.get()); + + if (source->GetResponseCode() == net::HTTP_OK) { + std::string data; + source->GetResponseAsString(&data); + BuildWhitelist(data); + } + + ScheduleDownload(kDownloadIntervalSeconds); +} + +std::string WhitelistManager::GetMatchedURLPrefix(const GURL& url) const { + if (!experimental_form_filling_enabled_ || url.is_empty()) + return std::string(); + + for (std::vector<std::string>::const_iterator it = url_prefixes_.begin(); + it != url_prefixes_.end(); ++it) { + // This is only for ~20 sites initially, liner search is sufficient. + // TODO(benquan): Look for optimization options when we support + // more sites. + if (StartsWithASCII(url.spec(), *it, true)) { + DVLOG(1) << "WhitelistManager matched URLPrefix: " << *it; + return *it; + } + } + return std::string(); +} + +void WhitelistManager::BuildWhitelist(const std::string& data) { + std::vector<std::string> new_url_prefixes; + + std::vector<std::string> lines; + base::SplitString(data, '\n', &lines); + + for (std::vector<std::string>::const_iterator line = lines.begin(); + line != lines.end(); ++line) { + if (!line->empty()) { + std::vector<std::string> fields; + base::SplitString(*line, ',', &fields); + // Currently we have only one column in the whitelist file, if we decide + // to add more metadata as additional columns, previous versions of + // Chrome can ignore them and continue to work. + if (!fields[0].empty()) + new_url_prefixes.push_back(fields[0]); + } + } + url_prefixes_ = new_url_prefixes; +} + +} // namespace autocheckout +} // namespace autofill + diff --git a/components/autofill/browser/autocheckout/whitelist_manager.h b/components/autofill/browser/autocheckout/whitelist_manager.h new file mode 100644 index 0000000..6e28941 --- /dev/null +++ b/components/autofill/browser/autocheckout/whitelist_manager.h @@ -0,0 +1,93 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_WHITELIST_MANAGER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_WHITELIST_MANAGER_H_ + +#include <string> +#include <vector> + +#include "base/supports_user_data.h" +#include "base/timer.h" +#include "net/url_request/url_fetcher_delegate.h" + +class GURL; + +namespace content { +class BrowserContext; +} + +namespace net { +class URLFetcher; +class URLRequestContextGetter; +} + +namespace autofill { +namespace autocheckout { + +// Downloads and caches the list of URL prefixes whitelisted for use with +// Autocheckout. +class WhitelistManager : public net::URLFetcherDelegate, + public base::SupportsUserData::Data { + public: + static WhitelistManager* GetForBrowserContext( + content::BrowserContext* context); + + // Matches the url with whitelist and return the matched url prefix. + // Returns empty string when it is not matched. + std::string GetMatchedURLPrefix(const GURL& url) const; + + protected: + explicit WhitelistManager(net::URLRequestContextGetter* context_getter); + virtual ~WhitelistManager(); + + // Schedules a future call to TriggerDownload if one isn't already pending. + virtual void ScheduleDownload(size_t interval_seconds); + + // Start the download timer. It is called by ScheduleDownload(), and exposed + // as a separate method for mocking out in tests. + virtual void StartDownloadTimer(size_t interval_seconds); + + // Timer callback indicating it's time to download whitelist from server. + void TriggerDownload(); + + // Used by tests only. + void StopDownloadTimer(); + + const std::vector<std::string>& url_prefixes() const { + return url_prefixes_; + } + + private: + // Implements net::URLFetcherDelegate. + virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; + + // Parse whitelist data and build whitelist. + void BuildWhitelist(const std::string& data); + + // A list of whitelisted url prefixes. + std::vector<std::string> url_prefixes_; + + base::OneShotTimer<WhitelistManager> download_timer_; + + // Indicates that the last triggered download hasn't resolved yet. + bool callback_is_pending_; + + // The context for the request. + net::URLRequestContextGetter* const context_getter_; // WEAK + + // State of the kEnableExperimentalFormFilling flag. + const bool experimental_form_filling_enabled_; + + // The request object. + scoped_ptr<net::URLFetcher> request_; + + DISALLOW_COPY_AND_ASSIGN(WhitelistManager); +}; + +} // namespace autocheckout +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_WHITELIST_MANAGER_H_ + diff --git a/components/autofill/browser/autocheckout/whitelist_manager_unittest.cc b/components/autofill/browser/autocheckout/whitelist_manager_unittest.cc new file mode 100644 index 0000000..9d23c13 --- /dev/null +++ b/components/autofill/browser/autocheckout/whitelist_manager_unittest.cc @@ -0,0 +1,242 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/command_line.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/autocheckout/whitelist_manager.h" +#include "components/autofill/common/autofill_switches.h" +#include "content/public/test/test_browser_thread.h" +#include "googleurl/src/gurl.h" +#include "net/base/net_errors.h" +#include "net/http/http_status_code.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_status.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const size_t kTestDownloadInterval = 3; // 3 seconds + +const char kDownloadWhitelistResponse[] = + "https://www.merchant1.com/checkout/\n" + "https://cart.merchant2.com/"; + +} // namespace + +namespace autofill { +namespace autocheckout { + +class WhitelistManagerTest; + +class TestWhitelistManager : public WhitelistManager { + public: + explicit TestWhitelistManager(net::URLRequestContextGetter* context_getter) + : WhitelistManager(context_getter), + did_start_download_timer_(false) {} + + virtual void ScheduleDownload(size_t interval_seconds) OVERRIDE { + did_start_download_timer_ = false; + return WhitelistManager::ScheduleDownload(interval_seconds); + } + + virtual void StartDownloadTimer(size_t interval_seconds) OVERRIDE { + WhitelistManager::StartDownloadTimer(interval_seconds); + did_start_download_timer_ = true; + } + + bool did_start_download_timer() const { + return did_start_download_timer_; + } + + void TriggerDownload() { + WhitelistManager::TriggerDownload(); + } + + void StopDownloadTimer() { + WhitelistManager::StopDownloadTimer(); + } + + const std::vector<std::string>& url_prefixes() const { + return WhitelistManager::url_prefixes(); + } + + private: + bool did_start_download_timer_; + + DISALLOW_COPY_AND_ASSIGN(TestWhitelistManager); +}; + +class WhitelistManagerTest : public testing::Test { + public: + WhitelistManagerTest() : io_thread_(content::BrowserThread::IO) {} + + virtual void SetUp() { + io_thread_.StartIOThread(); + profile_.CreateRequestContext(); + } + + virtual void TearDown() { + profile_.ResetRequestContext(); + io_thread_.Stop(); + } + + protected: + void CreateWhitelistManager() { + if (!whitelist_manager_.get()) { + whitelist_manager_.reset(new TestWhitelistManager( + profile_.GetRequestContext())); + } + } + + void DownloadWhitelist(int response_code, const std::string& response) { + // Create and register factory. + net::TestURLFetcherFactory factory; + + CreateWhitelistManager(); + + whitelist_manager_->TriggerDownload(); + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response); + fetcher->delegate()->OnURLFetchComplete(fetcher); + } + + void ResetBackOff() { + whitelist_manager_->StopDownloadTimer(); + } + + const std::vector<std::string>& get_url_prefixes() const { + return whitelist_manager_->url_prefixes(); + } + + protected: + TestingProfile profile_; + scoped_ptr<TestWhitelistManager> whitelist_manager_; + + private: + MessageLoopForIO message_loop_; + // The profile's request context must be released on the IO thread. + content::TestBrowserThread io_thread_; +}; + +TEST_F(WhitelistManagerTest, DownloadWhitelist) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalFormFilling); + DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); + ASSERT_EQ(2U, get_url_prefixes().size()); + EXPECT_EQ("https://www.merchant1.com/checkout/", + get_url_prefixes()[0]); + EXPECT_EQ("https://cart.merchant2.com/", + get_url_prefixes()[1]); +} + +TEST_F(WhitelistManagerTest, DoNotDownloadWhitelistWhenSwitchIsOff) { + CreateWhitelistManager(); + whitelist_manager_->ScheduleDownload(kTestDownloadInterval); + EXPECT_FALSE(whitelist_manager_->did_start_download_timer()); +} + +TEST_F(WhitelistManagerTest, DoNotDownloadWhitelistWhenBackOff) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalFormFilling); + CreateWhitelistManager(); + // First attempt should schedule a download. + whitelist_manager_->ScheduleDownload(kTestDownloadInterval); + EXPECT_TRUE(whitelist_manager_->did_start_download_timer()); + // Second attempt should NOT schedule a download while there is already one. + whitelist_manager_->ScheduleDownload(kTestDownloadInterval); + EXPECT_FALSE(whitelist_manager_->did_start_download_timer()); + // It should schedule a new download when not in backoff mode. + ResetBackOff(); + whitelist_manager_->ScheduleDownload(kTestDownloadInterval); + EXPECT_TRUE(whitelist_manager_->did_start_download_timer()); +} + +TEST_F(WhitelistManagerTest, DownloadWhitelistFailed) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalFormFilling); + DownloadWhitelist(net::HTTP_INTERNAL_SERVER_ERROR, + kDownloadWhitelistResponse); + EXPECT_EQ(0U, get_url_prefixes().size()); + + ResetBackOff(); + DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); + EXPECT_EQ(2U, get_url_prefixes().size()); + + ResetBackOff(); + DownloadWhitelist(net::HTTP_INTERNAL_SERVER_ERROR, + kDownloadWhitelistResponse); + EXPECT_EQ(2U, get_url_prefixes().size()); +} + +TEST_F(WhitelistManagerTest, GetMatchedURLPrefix) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalFormFilling); + DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); + EXPECT_EQ(2U, get_url_prefixes().size()); + + // Empty url. + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix(GURL(std::string()))); + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix(GURL())); + + // Positive tests. + EXPECT_EQ("https://www.merchant1.com/checkout/", + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://www.merchant1.com/checkout/"))); + EXPECT_EQ("https://www.merchant1.com/checkout/", + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://www.merchant1.com/checkout/Shipping"))); + EXPECT_EQ("https://www.merchant1.com/checkout/", + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://www.merchant1.com/checkout/?a=b&c=d"))); + EXPECT_EQ("https://cart.merchant2.com/", + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://cart.merchant2.com/"))); + EXPECT_EQ("https://cart.merchant2.com/", + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://cart.merchant2.com/ShippingInfo"))); + EXPECT_EQ("https://cart.merchant2.com/", + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://cart.merchant2.com/ShippingInfo?a=b&c=d"))); + + // Negative tests. + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://www.merchant1.com/checkout"))); + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://www.merchant1.com/"))); + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://www.merchant1.com/Building"))); + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://www.merchant2.com/cart"))); + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix( + GURL("a random string"))); + + // Test different cases in schema, host and path. + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix( + GURL("http://www.merchant1.com/checkout/"))); + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix( + GURL("www.merchant1.com/checkout/"))); + EXPECT_EQ("https://www.merchant1.com/checkout/", + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://www.Merchant1.com/checkout/"))); + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://www.merchant1.com/CheckOut/"))); +} + +} // namespace autocheckout +} // namespace autofill + diff --git a/components/autofill/browser/autocheckout_manager.cc b/components/autofill/browser/autocheckout_manager.cc new file mode 100644 index 0000000..d9724cf --- /dev/null +++ b/components/autofill/browser/autocheckout_manager.cc @@ -0,0 +1,285 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autocheckout_manager.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_manager.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/credit_card.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/common/autocheckout_status.h" +#include "components/autofill/common/autofill_messages.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_field_data.h" +#include "components/autofill/common/web_element_descriptor.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/ssl_status.h" +#include "googleurl/src/gurl.h" +#include "ui/gfx/rect.h" + +using content::RenderViewHost; +using content::SSLStatus; +using content::WebContents; + +namespace { + +// Build FormFieldData based on the supplied |autocomplete_attribute|. Will +// fill rest of properties with default values. +FormFieldData BuildField(const std::string& autocomplete_attribute) { + FormFieldData field; + field.name = string16(); + field.value = string16(); + field.autocomplete_attribute = autocomplete_attribute; + field.form_control_type = "text"; + return field; +} + +// Build Autocheckout specific form data to be consumed by +// AutofillDialogController to show the Autocheckout specific UI. +FormData BuildAutocheckoutFormData() { + FormData formdata; + formdata.fields.push_back(BuildField("name")); + formdata.fields.push_back(BuildField("tel")); + formdata.fields.push_back(BuildField("email")); + formdata.fields.push_back(BuildField("cc-name")); + formdata.fields.push_back(BuildField("cc-number")); + formdata.fields.push_back(BuildField("cc-exp-month")); + formdata.fields.push_back(BuildField("cc-exp-year")); + formdata.fields.push_back(BuildField("cc-csc")); + formdata.fields.push_back(BuildField("billing street-address")); + formdata.fields.push_back(BuildField("billing locality")); + formdata.fields.push_back(BuildField("billing region")); + formdata.fields.push_back(BuildField("billing country")); + formdata.fields.push_back(BuildField("billing postal-code")); + formdata.fields.push_back(BuildField("shipping street-address")); + formdata.fields.push_back(BuildField("shipping locality")); + formdata.fields.push_back(BuildField("shipping region")); + formdata.fields.push_back(BuildField("shipping country")); + formdata.fields.push_back(BuildField("shipping postal-code")); + return formdata; +} + +} // namespace + +namespace autofill { + +AutocheckoutManager::AutocheckoutManager(AutofillManager* autofill_manager) + : autofill_manager_(autofill_manager), + autocheckout_bubble_shown_(false), + in_autocheckout_flow_(false), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { +} + +AutocheckoutManager::~AutocheckoutManager() { +} + +void AutocheckoutManager::FillForms() { + // |page_meta_data_| should have been set by OnLoadedPageMetaData. + DCHECK(page_meta_data_); + + // Fill the forms on the page with data given by user. + std::vector<FormData> filled_forms; + const std::vector<FormStructure*>& form_structures = + autofill_manager_->GetFormStructures(); + for (std::vector<FormStructure*>::const_iterator iter = + form_structures.begin(); iter != form_structures.end(); ++iter) { + const FormStructure& form_structure = **iter; + FormData form = form_structure.ToFormData(); + DCHECK_EQ(form_structure.field_count(), form.fields.size()); + + for (size_t i = 0; i < form_structure.field_count(); ++i) { + const AutofillField* field = form_structure.field(i); + SetValue(*field, &form.fields[i]); + } + + filled_forms.push_back(form); + } + + // Send filled forms along with proceed descriptor to renderer. + RenderViewHost* host = + autofill_manager_->GetWebContents()->GetRenderViewHost(); + if (!host) + return; + + host->Send(new AutofillMsg_FillFormsAndClick( + host->GetRoutingID(), + filled_forms, + page_meta_data_->IsEndOfAutofillableFlow() ? + WebElementDescriptor() : + *page_meta_data_->proceed_element_descriptor)); +} + +void AutocheckoutManager::OnLoadedPageMetaData( + scoped_ptr<AutocheckoutPageMetaData> page_meta_data) { + scoped_ptr<AutocheckoutPageMetaData> old_meta_data = + page_meta_data_.Pass(); + page_meta_data_ = page_meta_data.Pass(); + + // On the first page of an Autocheckout flow, when this function is called the + // user won't have opted into the flow yet. + if (!in_autocheckout_flow_) + return; + + // Missing Autofill server results. + if (!page_meta_data_) { + in_autocheckout_flow_ = false; + } else if (page_meta_data_->IsStartOfAutofillableFlow()) { + // Not possible unless Autocheckout failed to proceed. + in_autocheckout_flow_ = false; + } else if (!page_meta_data_->IsInAutofillableFlow()) { + // Missing Autocheckout meta data in the Autofill server results. + in_autocheckout_flow_ = false; + } else if (!page_meta_data_->proceed_element_descriptor && + !page_meta_data_->IsEndOfAutofillableFlow()) { + // Missing Autocheckout proceed data in meta data in the Autofill server + // results. + in_autocheckout_flow_ = false; + } else if (page_meta_data_->current_page_number <= + old_meta_data->current_page_number) { + // Not possible unless Autocheckout failed to proceed. + in_autocheckout_flow_ = false; + } + + // Encountered an error during the Autocheckout flow. + if (!in_autocheckout_flow_) { + // TODO(ahutter): SendAutocheckoutStatus of the error. + autofill_manager_->delegate()->OnAutocheckoutError(); + return; + } + + // Add 1.0 since page numbers are 0-indexed. + autofill_manager_->delegate()->UpdateProgressBar( + (1.0 + page_meta_data_->current_page_number) / + page_meta_data_->total_pages); + FillForms(); + // If the current page is the last page in the flow, close the dialog. + if (page_meta_data_->IsEndOfAutofillableFlow()) { + // TODO(ahutter): SendAutocheckoutStatus of SUCCESS. + autofill_manager_->delegate()->HideRequestAutocompleteDialog(); + in_autocheckout_flow_ = false; + } +} + +void AutocheckoutManager::OnFormsSeen() { + autocheckout_bubble_shown_ = false; +} + +bool AutocheckoutManager::MaybeShowAutocheckoutBubble( + const GURL& frame_url, + const content::SSLStatus& ssl_status, + const gfx::NativeView& native_view, + const gfx::RectF& bounding_box) { + if (autocheckout_bubble_shown_) + return false; + + base::Closure callback = base::Bind( + &AutocheckoutManager::ShowAutocheckoutDialog, + weak_ptr_factory_.GetWeakPtr(), + frame_url, + ssl_status); + autofill_manager_->delegate()->ShowAutocheckoutBubble( + bounding_box, + native_view, + callback); + autocheckout_bubble_shown_ = true; + return true; +} + +void AutocheckoutManager::ShowAutocheckoutDialog( + const GURL& frame_url, + const SSLStatus& ssl_status) { + base::Callback<void(const FormStructure*)> callback = + base::Bind(&AutocheckoutManager::ReturnAutocheckoutData, + weak_ptr_factory_.GetWeakPtr()); + autofill_manager_->ShowRequestAutocompleteDialog( + BuildAutocheckoutFormData(), frame_url, ssl_status, + DIALOG_TYPE_AUTOCHECKOUT, callback); +} + +bool AutocheckoutManager::IsStartOfAutofillableFlow() const { + return page_meta_data_ && page_meta_data_->IsStartOfAutofillableFlow(); +} + +bool AutocheckoutManager::IsInAutofillableFlow() const { + return page_meta_data_ && page_meta_data_->IsInAutofillableFlow(); +} + +void AutocheckoutManager::ReturnAutocheckoutData(const FormStructure* result) { + if (!result) + return; + + in_autocheckout_flow_ = true; + + profile_.reset(new AutofillProfile()); + credit_card_.reset(new CreditCard()); + + for (size_t i = 0; i < result->field_count(); ++i) { + AutofillFieldType type = result->field(i)->type(); + if (type == CREDIT_CARD_VERIFICATION_CODE) { + // TODO(ramankk): CVV is not handled by CreditCard, not sure how to + // handle it yet. + cvv_ = result->field(i)->value; + continue; + } + if (AutofillType(type).group() == AutofillType::CREDIT_CARD) { + credit_card_->SetRawInfo(result->field(i)->type(), + result->field(i)->value); + } else { + profile_->SetRawInfo(result->field(i)->type(), result->field(i)->value); + } + } + + // Add 1.0 since page numbers are 0-indexed. + autofill_manager_->delegate()->UpdateProgressBar( + (1.0 + page_meta_data_->current_page_number) / + page_meta_data_->total_pages); + FillForms(); +} + +void AutocheckoutManager::SetValue(const AutofillField& field, + FormFieldData* field_to_fill) { + AutofillFieldType type = field.type(); + + if (type == FIELD_WITH_DEFAULT_VALUE) { + DCHECK(field.is_checkable); + // For a form with radio buttons, like: + // <form> + // <input type="radio" name="sex" value="male">Male<br> + // <input type="radio" name="sex" value="female">Female + // </form> + // If the default value specified at the server is "female", then + // Autofill server responds back with following field mappings + // (fieldtype: FIELD_WITH_DEFAULT_VALUE, value: "female") + // (fieldtype: FIELD_WITH_DEFAULT_VALUE, value: "female") + // Note that, the field mapping is repeated twice to respond to both the + // input elements with the same name/signature in the form. + string16 default_value = UTF8ToUTF16(field.default_value()); + // Mark the field checked if server says the default value of the field + // to be this field's value. + field_to_fill->is_checked = (field.value == default_value); + return; + } + + // Handle verification code directly. + if (type == CREDIT_CARD_VERIFICATION_CODE) { + field_to_fill->value = cvv_; + return; + } + + // TODO(ramankk): Handle variants in a better fashion, need to distinguish + // between shipping and billing address. + if (AutofillType(type).group() == AutofillType::CREDIT_CARD) + credit_card_->FillFormField(field, 0, field_to_fill); + else + profile_->FillFormField(field, 0, field_to_fill); +} + +} // namespace autofill diff --git a/components/autofill/browser/autocheckout_manager.h b/components/autofill/browser/autocheckout_manager.h new file mode 100644 index 0000000..8383a31 --- /dev/null +++ b/components/autofill/browser/autocheckout_manager.h @@ -0,0 +1,117 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_MANAGER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_MANAGER_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/string16.h" +#include "components/autofill/browser/autocheckout_page_meta_data.h" +#include "ui/gfx/native_widget_types.h" + +class AutofillField; +class AutofillManager; +class AutofillProfile; +class CreditCard; +class FormStructure; +class GURL; + +struct FormData; +struct FormFieldData; + +namespace content { +struct SSLStatus; +} + +namespace gfx { +class RectF; +} + +namespace autofill { + +class AutocheckoutManager { + public: + explicit AutocheckoutManager(AutofillManager* autofill_manager); + virtual ~AutocheckoutManager(); + + // Fill all the forms seen by the Autofill manager with the information + // gathered from the requestAutocomplete dialog. + void FillForms(); + + // Sets |page_meta_data_| with the meta data for the current page. + void OnLoadedPageMetaData( + scoped_ptr<AutocheckoutPageMetaData> page_meta_data); + + // Called when a page containing forms is loaded. + void OnFormsSeen(); + + // Causes the Autocheckout bubble to be displayed if the user hasn't seen it + // yet for the current page. |frame_url| is the page where Autocheckout is + // being initiated. |ssl_status| is the SSL status of the page. |native_view| + // is the parent view of the bubble. |bounding_box| is the bounding box of the + // input field in focus. Returns true if the bubble was shown and false + // otherwise. + virtual bool MaybeShowAutocheckoutBubble(const GURL& frame_url, + const content::SSLStatus& ssl_status, + const gfx::NativeView& native_view, + const gfx::RectF& bounding_box); + + // Show the requestAutocomplete dialog. + virtual void ShowAutocheckoutDialog(const GURL& frame_url, + const content::SSLStatus& ssl_status); + + // Whether or not the current page is the start of a multipage Autofill flow. + bool IsStartOfAutofillableFlow() const; + + // Whether or not the current page is part of a multipage Autofill flow. + bool IsInAutofillableFlow() const; + + protected: + // Exposed for testing. + bool in_autocheckout_flow() const { return in_autocheckout_flow_; } + + // Exposed for testing. + bool autocheckout_bubble_shown() const { return autocheckout_bubble_shown_; } + + private: + // Callback called from AutofillDialogController on filling up the UI form. + void ReturnAutocheckoutData(const FormStructure* result); + + // Sets value of form field data |field_to_fill| based on the Autofill + // field type specified by |field|. + void SetValue(const AutofillField& field, FormFieldData* field_to_fill); + + AutofillManager* autofill_manager_; // WEAK; owns us + + // Credit card verification code. + string16 cvv_; + + // Profile built using the data supplied by requestAutocomplete dialog. + scoped_ptr<AutofillProfile> profile_; + + // Credit card built using the data supplied by requestAutocomplete dialog. + scoped_ptr<CreditCard> credit_card_; + + // Autocheckout specific page meta data. + scoped_ptr<AutocheckoutPageMetaData> page_meta_data_; + + // Whether or not the Autocheckout bubble has been displayed to the user for + // the current forms. Ensures the Autocheckout bubble is only shown to a + // user once per pageview. + bool autocheckout_bubble_shown_; + + // Whether or not the user is in an Autocheckout flow. + bool in_autocheckout_flow_; + + base::WeakPtrFactory<AutocheckoutManager> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(AutocheckoutManager); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_MANAGER_H_ diff --git a/components/autofill/browser/autocheckout_manager_unittest.cc b/components/autofill/browser/autocheckout_manager_unittest.cc new file mode 100644 index 0000000..28a6c57 --- /dev/null +++ b/components/autofill/browser/autocheckout_manager_unittest.cc @@ -0,0 +1,529 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/tuple.h" +#include "base/utf_string_conversions.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "components/autofill/browser/autocheckout_manager.h" +#include "components/autofill/browser/autofill_common_test.h" +#include "components/autofill/browser/autofill_manager.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/browser/test_autofill_manager_delegate.h" +#include "components/autofill/common/autofill_messages.h" +#include "components/autofill/common/form_data.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/mock_render_process_host.h" +#include "content/public/test/test_browser_thread.h" +#include "ipc/ipc_test_sink.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + + +using content::BrowserThread; + +namespace autofill { + +namespace { + +typedef Tuple2<std::vector<FormData>, WebElementDescriptor> AutofillParam; + +FormFieldData BuildFieldWithValue( + const std::string& autocomplete_attribute, + const std::string& value) { + FormFieldData field; + field.name = ASCIIToUTF16(autocomplete_attribute); + field.value = ASCIIToUTF16(value); + field.autocomplete_attribute = autocomplete_attribute; + field.form_control_type = "text"; + return field; +} + +FormFieldData BuildField(const std::string& autocomplete_attribute) { + return BuildFieldWithValue(autocomplete_attribute, autocomplete_attribute); +} + +scoped_ptr<FormStructure> CreateTestFormStructure( + const std::vector<AutofillFieldType>& autofill_types) { + FormData form; + form.name = ASCIIToUTF16("MyForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("https://myform.com/form.html"); + form.action = GURL("https://myform.com/submit.html"); + form.user_submitted = true; + + // Add some fields, autocomplete_attribute is not important and we + // fake that server sends authoritative field mappings. + for (size_t i = 0; i < autofill_types.size(); ++i) + form.fields.push_back(BuildField("SomeField")); + + scoped_ptr<FormStructure> form_structure( + new FormStructure(form, std::string())); + + // Set mocked Autofill server field types. + for (size_t i = 0; i < autofill_types.size(); ++i) + form_structure->field(i)->set_server_type(autofill_types[i]); + + return form_structure.Pass(); +} + +scoped_ptr<FormStructure> CreateTestAddressFormStructure() { + std::vector<AutofillFieldType> autofill_types; + autofill_types.push_back(NAME_FULL); + autofill_types.push_back(PHONE_HOME_WHOLE_NUMBER); + autofill_types.push_back(EMAIL_ADDRESS); + + return CreateTestFormStructure(autofill_types); +} + +scoped_ptr<FormStructure> CreateTestCreditCardFormStructure() { + std::vector<AutofillFieldType> autofill_types; + autofill_types.push_back(CREDIT_CARD_NAME); + autofill_types.push_back(CREDIT_CARD_NUMBER); + autofill_types.push_back(CREDIT_CARD_EXP_MONTH); + autofill_types.push_back(CREDIT_CARD_EXP_4_DIGIT_YEAR); + autofill_types.push_back(CREDIT_CARD_VERIFICATION_CODE); + autofill_types.push_back(ADDRESS_HOME_LINE1); + autofill_types.push_back(ADDRESS_HOME_CITY); + autofill_types.push_back(ADDRESS_HOME_STATE); + autofill_types.push_back(ADDRESS_HOME_COUNTRY); + autofill_types.push_back(ADDRESS_HOME_ZIP); + return CreateTestFormStructure(autofill_types); +} + +scoped_ptr<FormStructure> CreateTestFormStructureWithDefaultValues() { + FormData form; + form.name = ASCIIToUTF16("MyForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("https://myform.com/form.html"); + form.action = GURL("https://myform.com/submit.html"); + form.user_submitted = true; + + // Add two radio button fields. + FormFieldData male = BuildFieldWithValue("sex", "male"); + male.is_checkable = true; + form.fields.push_back(male); + FormFieldData female = BuildFieldWithValue("sex", "female"); + female.is_checkable = true; + form.fields.push_back(female); + + scoped_ptr<FormStructure> form_structure( + new FormStructure(form, std::string())); + + // Fake server response. Set all fields as fields with default value. + form_structure->field(0)->set_server_type(FIELD_WITH_DEFAULT_VALUE); + form_structure->field(0)->set_default_value("female"); + form_structure->field(1)->set_server_type(FIELD_WITH_DEFAULT_VALUE); + form_structure->field(1)->set_default_value("female"); + + return form_structure.Pass(); +} + +scoped_ptr<WebElementDescriptor> CreateProceedElement() { + scoped_ptr<WebElementDescriptor> proceed_element(new WebElementDescriptor()); + proceed_element->descriptor = "#foo"; + proceed_element->retrieval_method = WebElementDescriptor::ID; + return proceed_element.Pass(); +} + +scoped_ptr<AutocheckoutPageMetaData> CreateStartOfFlowMetaData() { + scoped_ptr<AutocheckoutPageMetaData> start_of_flow( + new AutocheckoutPageMetaData()); + start_of_flow->current_page_number = 0; + start_of_flow->total_pages = 3; + start_of_flow->proceed_element_descriptor = CreateProceedElement().Pass(); + return start_of_flow.Pass(); +} + +scoped_ptr<AutocheckoutPageMetaData> CreateInFlowMetaData() { + scoped_ptr<AutocheckoutPageMetaData> in_flow(new AutocheckoutPageMetaData()); + in_flow->current_page_number = 1; + in_flow->total_pages = 3; + in_flow->proceed_element_descriptor = CreateProceedElement().Pass(); + return in_flow.Pass(); +} + +scoped_ptr<AutocheckoutPageMetaData> CreateEndOfFlowMetaData() { + scoped_ptr<AutocheckoutPageMetaData> end_of_flow( + new AutocheckoutPageMetaData()); + end_of_flow->current_page_number = 2; + end_of_flow->total_pages = 3; + return end_of_flow.Pass(); +} + +scoped_ptr<AutocheckoutPageMetaData> CreateMissingProceedMetaData() { + scoped_ptr<AutocheckoutPageMetaData> missing_proceed( + new AutocheckoutPageMetaData()); + missing_proceed->current_page_number = 1; + missing_proceed->total_pages = 3; + return missing_proceed.Pass(); +} + +struct TestField { + const char* const field_type; + const char* const field_value; + AutofillFieldType autofill_type; +}; + +const TestField kTestFields[] = { + {"name", "Test User", NAME_FULL}, + {"tel", "650-123-9909", PHONE_HOME_WHOLE_NUMBER}, + {"email", "blah@blah.com", EMAIL_ADDRESS}, + {"cc-name", "Test User", CREDIT_CARD_NAME}, + {"cc-number", "4444444444444448", CREDIT_CARD_NUMBER}, + {"cc-exp-month", "10", CREDIT_CARD_EXP_MONTH}, + {"cc-exp-year", "2020", CREDIT_CARD_EXP_4_DIGIT_YEAR}, + {"cc-csc", "123", CREDIT_CARD_VERIFICATION_CODE}, + {"street-address", "Fake Street", ADDRESS_HOME_LINE1}, + {"locality", "Mocked City", ADDRESS_HOME_CITY}, + {"region", "California", ADDRESS_HOME_STATE}, + {"country", "USA", ADDRESS_HOME_COUNTRY}, + {"postal-code", "49012", ADDRESS_HOME_ZIP} +}; + +// Build Autocheckout specific form data to be consumed by +// AutofillDialogController to show the Autocheckout specific UI. +scoped_ptr<FormStructure> FakeUserSubmittedFormStructure() { + FormData formdata; + for (size_t i = 0; i < arraysize(kTestFields); i++) { + formdata.fields.push_back( + BuildFieldWithValue(kTestFields[i].field_type, + kTestFields[i].field_value)); + } + scoped_ptr<FormStructure> form_structure; + form_structure.reset(new FormStructure(formdata, std::string())); + for (size_t i = 0; i < arraysize(kTestFields); ++i) + form_structure->field(i)->set_server_type(kTestFields[i].autofill_type); + + return form_structure.Pass(); +} + +class MockAutofillManagerDelegate : public TestAutofillManagerDelegate { + public: + MockAutofillManagerDelegate() + : request_autocomplete_dialog_open_(false), + autocheckout_bubble_shown_(false) {} + + virtual ~MockAutofillManagerDelegate() {} + + virtual void HideRequestAutocompleteDialog() OVERRIDE { + request_autocomplete_dialog_open_ = false; + } + + MOCK_METHOD0(OnAutocheckoutError, void()); + + virtual void ShowAutocheckoutBubble( + const gfx::RectF& bounds, + const gfx::NativeView& native_view, + const base::Closure& callback) OVERRIDE { + autocheckout_bubble_shown_ = true; + callback.Run(); + } + + virtual void ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + const content::SSLStatus& ssl_status, + const AutofillMetrics& metric_logger, + DialogType dialog_type, + const base::Callback<void(const FormStructure*)>& callback) OVERRIDE { + request_autocomplete_dialog_open_ = true; + callback.Run(user_supplied_data_.get()); + } + + MOCK_METHOD1(UpdateProgressBar, void(double value)); + + void SetUserSuppliedData(scoped_ptr<FormStructure> user_supplied_data) { + user_supplied_data_.reset(user_supplied_data.release()); + } + + bool autocheckout_bubble_shown() const { + return autocheckout_bubble_shown_; + } + + void set_autocheckout_bubble_shown(bool autocheckout_bubble_shown) { + autocheckout_bubble_shown_ = autocheckout_bubble_shown; + } + + bool request_autocomplete_dialog_open() const { + return request_autocomplete_dialog_open_; + } + + private: + bool request_autocomplete_dialog_open_; + bool autocheckout_bubble_shown_; + scoped_ptr<FormStructure> user_supplied_data_; +}; + +class TestAutofillManager : public AutofillManager { + public: + explicit TestAutofillManager(content::WebContents* contents, + AutofillManagerDelegate* delegate) + : AutofillManager(contents, delegate, NULL) { + } + virtual ~TestAutofillManager() {} + + void SetFormStructure(scoped_ptr<FormStructure> form_structure) { + form_structures()->clear(); + form_structures()->push_back(form_structure.release()); + } +}; + + +class TestAutocheckoutManager: public AutocheckoutManager { + public: + explicit TestAutocheckoutManager(AutofillManager* autofill_manager) + : AutocheckoutManager(autofill_manager) {} + + using AutocheckoutManager::in_autocheckout_flow; + using AutocheckoutManager::autocheckout_bubble_shown; +}; + +} // namespace + +class AutocheckoutManagerTest : public ChromeRenderViewHostTestHarness { + public: + AutocheckoutManagerTest() + : ChromeRenderViewHostTestHarness(), + ui_thread_(BrowserThread::UI, &message_loop_) { + } + + std::vector<FormData> ReadFilledForms() { + uint32 kMsgID = AutofillMsg_FillFormsAndClick::ID; + const IPC::Message* message = + process()->sink().GetFirstMessageMatching(kMsgID); + AutofillParam autofill_param; + AutofillMsg_FillFormsAndClick::Read(message, &autofill_param); + return autofill_param.a; + } + + void CheckIpcMessageSent() { + EXPECT_EQ(1U, process()->sink().message_count()); + uint32 kMsgID = AutofillMsg_FillFormsAndClick::ID; + const IPC::Message* message = + process()->sink().GetFirstMessageMatching(kMsgID); + EXPECT_TRUE(message); + ClearIpcSink(); + } + + void ClearIpcSink() { + process()->sink().ClearMessages(); + } + + void OpenRequestAutocompleteDialog() { + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_FALSE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); + // Simulate the user submitting some data via the requestAutocomplete UI. + autofill_manager_delegate_->SetUserSuppliedData( + FakeUserSubmittedFormStructure()); + GURL frame_url; + content::SSLStatus ssl_status; + EXPECT_CALL(*autofill_manager_delegate_, + UpdateProgressBar(testing::DoubleEq(1.0/3.0))).Times(1); + autocheckout_manager_->ShowAutocheckoutDialog(frame_url, ssl_status); + CheckIpcMessageSent(); + EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_TRUE(autofill_manager_delegate_->request_autocomplete_dialog_open()); + } + + void HideRequestAutocompleteDialog() { + EXPECT_TRUE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + autofill_manager_delegate_->HideRequestAutocompleteDialog(); + EXPECT_FALSE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + } + + protected: + content::TestBrowserThread ui_thread_; + scoped_ptr<TestAutofillManager> autofill_manager_; + scoped_ptr<TestAutocheckoutManager> autocheckout_manager_; + scoped_ptr<MockAutofillManagerDelegate> autofill_manager_delegate_; + + private: + virtual void SetUp() OVERRIDE { + ChromeRenderViewHostTestHarness::SetUp(); + autofill_manager_delegate_.reset(new MockAutofillManagerDelegate()); + autofill_manager_.reset(new TestAutofillManager( + web_contents(), + autofill_manager_delegate_.get())); + autocheckout_manager_.reset( + new TestAutocheckoutManager(autofill_manager_.get())); + } + + virtual void TearDown() OVERRIDE { + autocheckout_manager_.reset(); + autofill_manager_delegate_.reset(); + autofill_manager_.reset(); + ChromeRenderViewHostTestHarness::TearDown(); + } + + DISALLOW_COPY_AND_ASSIGN(AutocheckoutManagerTest); +}; + +TEST_F(AutocheckoutManagerTest, TestFillForms) { + OpenRequestAutocompleteDialog(); + + // Test if autocheckout manager can fill the first page. + autofill_manager_->SetFormStructure(CreateTestAddressFormStructure()); + + autocheckout_manager_->FillForms(); + + std::vector<FormData> filled_forms = ReadFilledForms(); + ASSERT_EQ(1U, filled_forms.size()); + ASSERT_EQ(3U, filled_forms[0].fields.size()); + EXPECT_EQ(ASCIIToUTF16("Test User"), filled_forms[0].fields[0].value); + EXPECT_EQ(ASCIIToUTF16("650-123-9909"), filled_forms[0].fields[1].value); + EXPECT_EQ(ASCIIToUTF16("blah@blah.com"), filled_forms[0].fields[2].value); + + filled_forms.clear(); + ClearIpcSink(); + + // Test if autocheckout manager can fill form on second page. + autofill_manager_->SetFormStructure(CreateTestCreditCardFormStructure()); + + autocheckout_manager_->FillForms(); + + filled_forms = ReadFilledForms(); + ASSERT_EQ(1U, filled_forms.size()); + ASSERT_EQ(10U, filled_forms[0].fields.size()); + EXPECT_EQ(ASCIIToUTF16("Test User"), filled_forms[0].fields[0].value); + EXPECT_EQ(ASCIIToUTF16("4444444444444448"), filled_forms[0].fields[1].value); + EXPECT_EQ(ASCIIToUTF16("10"), filled_forms[0].fields[2].value); + EXPECT_EQ(ASCIIToUTF16("2020"), filled_forms[0].fields[3].value); + EXPECT_EQ(ASCIIToUTF16("123"), filled_forms[0].fields[4].value); + EXPECT_EQ(ASCIIToUTF16("Fake Street"), filled_forms[0].fields[5].value); + EXPECT_EQ(ASCIIToUTF16("Mocked City"), filled_forms[0].fields[6].value); + EXPECT_EQ(ASCIIToUTF16("California"), filled_forms[0].fields[7].value); + EXPECT_EQ(ASCIIToUTF16("United States"), filled_forms[0].fields[8].value); + EXPECT_EQ(ASCIIToUTF16("49012"), filled_forms[0].fields[9].value); + + filled_forms.clear(); + ClearIpcSink(); + + // Test form with default values. + autofill_manager_->SetFormStructure( + CreateTestFormStructureWithDefaultValues()); + + autocheckout_manager_->FillForms(); + + filled_forms = ReadFilledForms(); + ASSERT_EQ(1U, filled_forms.size()); + ASSERT_EQ(2U, filled_forms[0].fields.size()); + EXPECT_FALSE(filled_forms[0].fields[0].is_checked); + EXPECT_EQ(ASCIIToUTF16("male"), filled_forms[0].fields[0].value); + EXPECT_TRUE(filled_forms[0].fields[1].is_checked); + EXPECT_EQ(ASCIIToUTF16("female"), filled_forms[0].fields[1].value); +} + +TEST_F(AutocheckoutManagerTest, OnFormsSeenTest) { + GURL frame_url; + content::SSLStatus ssl_status; + gfx::NativeView native_view; + gfx::RectF bounding_box; + EXPECT_TRUE(autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, + ssl_status, + native_view, + bounding_box)); + EXPECT_TRUE(autocheckout_manager_->autocheckout_bubble_shown()); + // OnFormsSeen resets whether or not the bubble was shown. + autocheckout_manager_->OnFormsSeen(); + EXPECT_FALSE(autocheckout_manager_->autocheckout_bubble_shown()); +} + +TEST_F(AutocheckoutManagerTest, MaybeShowAutocheckoutBubbleTest) { + GURL frame_url; + content::SSLStatus ssl_status; + gfx::NativeView native_view; + gfx::RectF bounding_box; + // MaybeShowAutocheckoutBubble shows bubble if it has not been shown. + EXPECT_TRUE(autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, + ssl_status, + native_view, + bounding_box)); + EXPECT_TRUE(autocheckout_manager_->autocheckout_bubble_shown()); + EXPECT_TRUE(autofill_manager_delegate_->autocheckout_bubble_shown()); + + // Reset |autofill_manager_delegate_|. + HideRequestAutocompleteDialog(); + autofill_manager_delegate_->set_autocheckout_bubble_shown(false); + + // MaybeShowAutocheckoutBubble does nothing if the bubble was already shown + // for the current page. + EXPECT_FALSE(autocheckout_manager_->MaybeShowAutocheckoutBubble( + frame_url, + ssl_status, + native_view, + bounding_box)); + EXPECT_TRUE(autocheckout_manager_->autocheckout_bubble_shown()); + EXPECT_FALSE(autofill_manager_delegate_->autocheckout_bubble_shown()); + EXPECT_FALSE(autofill_manager_delegate_->request_autocomplete_dialog_open()); +} + +TEST_F(AutocheckoutManagerTest, OnLoadedPageMetaDataTest) { + // Gettting no meta data after any autocheckout page is an error. + OpenRequestAutocompleteDialog(); + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + autocheckout_manager_->OnLoadedPageMetaData( + scoped_ptr<AutocheckoutPageMetaData>()); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_EQ(0U, process()->sink().message_count()); + HideRequestAutocompleteDialog(); + + // Getting start page twice in a row is an error. + OpenRequestAutocompleteDialog(); + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_EQ(0U, process()->sink().message_count()); + HideRequestAutocompleteDialog(); + + // A missing proceed element when not at the end of a flow is an error. + OpenRequestAutocompleteDialog(); + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateMissingProceedMetaData()); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_EQ(0U, process()->sink().message_count()); + HideRequestAutocompleteDialog(); + + // Repeating a page is an error. + OpenRequestAutocompleteDialog(); + // Go to second page. + EXPECT_CALL(*autofill_manager_delegate_, + UpdateProgressBar(testing::DoubleEq(2.0/3.0))).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); + EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); + CheckIpcMessageSent(); + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_EQ(0U, process()->sink().message_count()); + HideRequestAutocompleteDialog(); + + // If not in flow, OnLoadedPageMetaData does not fill forms. + autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); + // Go to second page. + EXPECT_CALL(*autofill_manager_delegate_, + UpdateProgressBar(testing::_)).Times(0); + autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); + EXPECT_EQ(0U, process()->sink().message_count()); + + // Test for progression through last page. + OpenRequestAutocompleteDialog(); + // Go to second page. + EXPECT_CALL(*autofill_manager_delegate_, + UpdateProgressBar(testing::DoubleEq(2.0/3.0))).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); + EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); + CheckIpcMessageSent(); + // Go to third page. + EXPECT_CALL(*autofill_manager_delegate_, UpdateProgressBar(1)).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateEndOfFlowMetaData()); + CheckIpcMessageSent(); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_FALSE(autofill_manager_delegate_->request_autocomplete_dialog_open()); +} + +} // namespace autofill diff --git a/components/autofill/browser/autocheckout_page_meta_data.cc b/components/autofill/browser/autocheckout_page_meta_data.cc new file mode 100644 index 0000000..aa48ca7 --- /dev/null +++ b/components/autofill/browser/autocheckout_page_meta_data.cc @@ -0,0 +1,27 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autocheckout_page_meta_data.h" + +namespace autofill { + +AutocheckoutPageMetaData::AutocheckoutPageMetaData() + : current_page_number(-1), + total_pages(-1) {} + +AutocheckoutPageMetaData::~AutocheckoutPageMetaData() {} + +bool AutocheckoutPageMetaData::IsStartOfAutofillableFlow() const { + return current_page_number == 0 && total_pages > 0; +} + +bool AutocheckoutPageMetaData::IsInAutofillableFlow() const { + return current_page_number >= 0 && current_page_number < total_pages; +} + +bool AutocheckoutPageMetaData::IsEndOfAutofillableFlow() const { + return total_pages > 0 && current_page_number == total_pages - 1; +} + +} // namesapce autofill diff --git a/components/autofill/browser/autocheckout_page_meta_data.h b/components/autofill/browser/autocheckout_page_meta_data.h new file mode 100644 index 0000000..20b0883 --- /dev/null +++ b/components/autofill/browser/autocheckout_page_meta_data.h @@ -0,0 +1,49 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_PAGE_META_DATA_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_PAGE_META_DATA_H_ + +#include "base/memory/scoped_ptr.h" +#include "components/autofill/common/web_element_descriptor.h" + +namespace autofill { + +// Container for multipage Autocheckout data. +struct AutocheckoutPageMetaData { + AutocheckoutPageMetaData(); + ~AutocheckoutPageMetaData(); + + // Returns true if the Autofill server says that the current page is start of + // a multipage Autofill flow. + bool IsStartOfAutofillableFlow() const; + + // Returns true if the Autofill server says that the current page is in a + // multipage Autofill flow. + bool IsInAutofillableFlow() const; + + // Returns true if the Autofill server says that the current page is the end + // of a multipage Autofill flow. + bool IsEndOfAutofillableFlow() const; + + // Page number of the multipage Autofill flow this form belongs to + // (zero-indexed). If this form doesn't belong to any autofill flow, it is set + // to -1. + int current_page_number; + + // Total number of pages in the multipage Autofill flow. If this form doesn't + // belong to any autofill flow, it is set to -1. + int total_pages; + + // The proceed element of the multipage Autofill flow. Can be null if the + // current page is the last page of a flow or isn't a member of a flow. + scoped_ptr<WebElementDescriptor> proceed_element_descriptor; + + private: + DISALLOW_COPY_AND_ASSIGN(AutocheckoutPageMetaData); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_PAGE_META_DATA_H_ diff --git a/components/autofill/browser/autocheckout_page_meta_data_unittest.cc b/components/autofill/browser/autocheckout_page_meta_data_unittest.cc new file mode 100644 index 0000000..bb3f2da --- /dev/null +++ b/components/autofill/browser/autocheckout_page_meta_data_unittest.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autocheckout_page_meta_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +void SetPageDetails(autofill::AutocheckoutPageMetaData* meta_data, + int current_page, + int total) { + meta_data->current_page_number = current_page; + meta_data->total_pages = total; +} + +} // namespace + +namespace autofill { + +TEST(AutocheckoutPageMetaDataTest, AutofillableFlow) { + + AutocheckoutPageMetaData page_meta_data; + EXPECT_FALSE(page_meta_data.IsStartOfAutofillableFlow()); + EXPECT_FALSE(page_meta_data.IsInAutofillableFlow()); + EXPECT_FALSE(page_meta_data.IsEndOfAutofillableFlow()); + + SetPageDetails(&page_meta_data, -1, 0); + EXPECT_FALSE(page_meta_data.IsStartOfAutofillableFlow()); + EXPECT_FALSE(page_meta_data.IsInAutofillableFlow()); + EXPECT_FALSE(page_meta_data.IsEndOfAutofillableFlow()); + + SetPageDetails(&page_meta_data, 0, 0); + EXPECT_FALSE(page_meta_data.IsStartOfAutofillableFlow()); + EXPECT_FALSE(page_meta_data.IsInAutofillableFlow()); + EXPECT_FALSE(page_meta_data.IsEndOfAutofillableFlow()); + + SetPageDetails(&page_meta_data, 0, 1); + EXPECT_TRUE(page_meta_data.IsStartOfAutofillableFlow()); + EXPECT_TRUE(page_meta_data.IsInAutofillableFlow()); + EXPECT_TRUE(page_meta_data.IsEndOfAutofillableFlow()); + + SetPageDetails(&page_meta_data, 1, 2); + EXPECT_FALSE(page_meta_data.IsStartOfAutofillableFlow()); + EXPECT_TRUE(page_meta_data.IsInAutofillableFlow()); + EXPECT_TRUE(page_meta_data.IsEndOfAutofillableFlow()); + + SetPageDetails(&page_meta_data, 2, 2); + EXPECT_FALSE(page_meta_data.IsStartOfAutofillableFlow()); + EXPECT_FALSE(page_meta_data.IsInAutofillableFlow()); + EXPECT_FALSE(page_meta_data.IsEndOfAutofillableFlow()); +} + +} // namespace autofill diff --git a/components/autofill/browser/autocomplete_history_manager.cc b/components/autofill/browser/autocomplete_history_manager.cc new file mode 100644 index 0000000..a38b0d6 --- /dev/null +++ b/components/autofill/browser/autocomplete_history_manager.cc @@ -0,0 +1,289 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autocomplete_history_manager.h" + +#include <vector> + +#include "base/prefs/pref_service.h" +#include "base/string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_external_delegate.h" +#include "components/autofill/browser/validation.h" +#include "components/autofill/common/autofill_messages.h" +#include "components/autofill/common/autofill_pref_names.h" +#include "components/autofill/common/form_data.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" + +using base::StringPiece16; +using content::BrowserContext; +using content::WebContents; + +namespace { + +// Limit on the number of suggestions to appear in the pop-up menu under an +// text input element in a form. +const int kMaxAutocompleteMenuItems = 6; + +// The separator characters for SSNs. +const char16 kSSNSeparators[] = {' ', '-', 0}; + +bool IsSSN(const string16& text) { + string16 number_string; + RemoveChars(text, kSSNSeparators, &number_string); + + // A SSN is of the form AAA-GG-SSSS (A = area number, G = group number, S = + // serial number). The validation we do here is simply checking if the area, + // group, and serial numbers are valid. + // + // Historically, the area number was assigned per state, with the group number + // ascending in an alternating even/odd sequence. With that scheme it was + // possible to check for validity by referencing a table that had the highest + // group number assigned for a given area number. (This was something that + // Chromium never did though, because the "high group" values were constantly + // changing.) + // + // However, starting on 25 June 2011 the SSA began issuing SSNs randomly from + // all areas and groups. Group numbers and serial numbers of zero remain + // invalid, and areas 000, 666, and 900-999 remain invalid. + // + // References for current practices: + // http://www.socialsecurity.gov/employer/randomization.html + // http://www.socialsecurity.gov/employer/randomizationfaqs.html + // + // References for historic practices: + // http://www.socialsecurity.gov/history/ssn/geocard.html + // http://www.socialsecurity.gov/employer/stateweb.htm + // http://www.socialsecurity.gov/employer/ssnvhighgroup.htm + + if (number_string.length() != 9 || !IsStringASCII(number_string)) + return false; + + int area; + if (!base::StringToInt(StringPiece16(number_string.begin(), + number_string.begin() + 3), + &area)) { + return false; + } + if (area < 1 || + area == 666 || + area >= 900) { + return false; + } + + int group; + if (!base::StringToInt(StringPiece16(number_string.begin() + 3, + number_string.begin() + 5), + &group) + || group == 0) { + return false; + } + + int serial; + if (!base::StringToInt(StringPiece16(number_string.begin() + 5, + number_string.begin() + 9), + &serial) + || serial == 0) { + return false; + } + + return true; +} + +bool IsTextField(const FormFieldData& field) { + return + field.form_control_type == "text" || + field.form_control_type == "search" || + field.form_control_type == "tel" || + field.form_control_type == "url" || + field.form_control_type == "email" || + field.form_control_type == "text"; +} + +} // namespace + +AutocompleteHistoryManager::AutocompleteHistoryManager( + WebContents* web_contents) + : content::WebContentsObserver(web_contents), + browser_context_(web_contents->GetBrowserContext()), + autofill_data_( + AutofillWebDataService::FromBrowserContext(browser_context_)), + pending_query_handle_(0), + query_id_(0), + external_delegate_(NULL) { + autofill_enabled_.Init( + prefs::kAutofillEnabled, + components::UserPrefs::Get(browser_context_)); +} + +AutocompleteHistoryManager::~AutocompleteHistoryManager() { + CancelPendingQuery(); +} + +bool AutocompleteHistoryManager::OnMessageReceived( + const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AutocompleteHistoryManager, message) + IPC_MESSAGE_HANDLER(AutofillHostMsg_RemoveAutocompleteEntry, + OnRemoveAutocompleteEntry) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void AutocompleteHistoryManager::OnWebDataServiceRequestDone( + WebDataServiceBase::Handle h, + const WDTypedResult* result) { + DCHECK(pending_query_handle_); + pending_query_handle_ = 0; + + if (!*autofill_enabled_) { + SendSuggestions(NULL); + return; + } + + DCHECK(result); + // Returning early here if |result| is NULL. We've seen this happen on + // Linux due to NFS dismounting and causing sql failures. + // See http://crbug.com/68783. + if (!result) { + SendSuggestions(NULL); + return; + } + + DCHECK_EQ(AUTOFILL_VALUE_RESULT, result->GetType()); + const WDResult<std::vector<string16> >* autofill_result = + static_cast<const WDResult<std::vector<string16> >*>(result); + std::vector<string16> suggestions = autofill_result->GetValue(); + SendSuggestions(&suggestions); +} + +void AutocompleteHistoryManager::OnGetAutocompleteSuggestions( + int query_id, + const string16& name, + const string16& prefix, + const std::vector<string16>& autofill_values, + const std::vector<string16>& autofill_labels, + const std::vector<string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids) { + CancelPendingQuery(); + + query_id_ = query_id; + autofill_values_ = autofill_values; + autofill_labels_ = autofill_labels; + autofill_icons_ = autofill_icons; + autofill_unique_ids_ = autofill_unique_ids; + if (!*autofill_enabled_) { + SendSuggestions(NULL); + return; + } + + if (autofill_data_.get()) { + pending_query_handle_ = autofill_data_->GetFormValuesForElementName( + name, prefix, kMaxAutocompleteMenuItems, this); + } +} + +void AutocompleteHistoryManager::OnFormSubmitted(const FormData& form) { + if (!*autofill_enabled_) + return; + + if (browser_context_->IsOffTheRecord()) + return; + + // Don't save data that was submitted through JavaScript. + if (!form.user_submitted) + return; + + // We put the following restriction on stored FormFields: + // - non-empty name + // - non-empty value + // - text field + // - value is not a credit card number + // - value is not a SSN + std::vector<FormFieldData> values; + for (std::vector<FormFieldData>::const_iterator iter = + form.fields.begin(); + iter != form.fields.end(); ++iter) { + if (!iter->value.empty() && + !iter->name.empty() && + IsTextField(*iter) && + !autofill::IsValidCreditCardNumber(iter->value) && + !IsSSN(iter->value)) { + values.push_back(*iter); + } + } + + if (!values.empty() && autofill_data_.get()) + autofill_data_->AddFormFields(values); +} + +void AutocompleteHistoryManager::OnRemoveAutocompleteEntry( + const string16& name, const string16& value) { + if (autofill_data_.get()) + autofill_data_->RemoveFormValueForElementName(name, value); +} + +void AutocompleteHistoryManager::SetExternalDelegate( + AutofillExternalDelegate* delegate) { + external_delegate_ = delegate; +} + +void AutocompleteHistoryManager::CancelPendingQuery() { + if (pending_query_handle_) { + if (autofill_data_) + autofill_data_->CancelRequest(pending_query_handle_); + pending_query_handle_ = 0; + } +} + +void AutocompleteHistoryManager::SendSuggestions( + const std::vector<string16>* suggestions) { + if (suggestions) { + // Combine Autofill and Autocomplete values into values and labels. + for (size_t i = 0; i < suggestions->size(); ++i) { + bool unique = true; + for (size_t j = 0; j < autofill_values_.size(); ++j) { + // Don't add duplicate values. + if (autofill_values_[j] == (*suggestions)[i]) { + unique = false; + break; + } + } + + if (unique) { + autofill_values_.push_back((*suggestions)[i]); + autofill_labels_.push_back(string16()); + autofill_icons_.push_back(string16()); + autofill_unique_ids_.push_back(0); // 0 means no profile. + } + } + } + + if (external_delegate_) { + external_delegate_->OnSuggestionsReturned( + query_id_, + autofill_values_, + autofill_labels_, + autofill_icons_, + autofill_unique_ids_); + } else { + Send(new AutofillMsg_SuggestionsReturned(routing_id(), + query_id_, + autofill_values_, + autofill_labels_, + autofill_icons_, + autofill_unique_ids_)); + } + + query_id_ = 0; + autofill_values_.clear(); + autofill_labels_.clear(); + autofill_icons_.clear(); + autofill_unique_ids_.clear(); +} diff --git a/components/autofill/browser/autocomplete_history_manager.h b/components/autofill/browser/autocomplete_history_manager.h new file mode 100644 index 0000000..e503087 --- /dev/null +++ b/components/autofill/browser/autocomplete_history_manager.h @@ -0,0 +1,91 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOCOMPLETE_HISTORY_MANAGER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOCOMPLETE_HISTORY_MANAGER_H_ + +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/prefs/public/pref_member.h" +#include "chrome/browser/api/webdata/autofill_web_data_service.h" +#include "chrome/browser/api/webdata/web_data_service_consumer.h" +#include "content/public/browser/web_contents_observer.h" + +struct FormData; + +namespace content { +class BrowserContext; +} + +class AutofillExternalDelegate; + +// Per-tab Autocomplete history manager. Handles receiving form data +// from the renderer and the storing and retrieving of form data +// through WebDataServiceBase. +class AutocompleteHistoryManager : public content::WebContentsObserver, + public WebDataServiceConsumer { + public: + explicit AutocompleteHistoryManager(content::WebContents* web_contents); + virtual ~AutocompleteHistoryManager(); + + // content::WebContentsObserver implementation. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + // WebDataServiceConsumer implementation. + virtual void OnWebDataServiceRequestDone( + WebDataServiceBase::Handle h, + const WDTypedResult* result) OVERRIDE; + + // Pass-through functions that are called by AutofillManager, after it has + // dispatched a message. + void OnGetAutocompleteSuggestions( + int query_id, + const string16& name, + const string16& prefix, + const std::vector<string16>& autofill_values, + const std::vector<string16>& autofill_labels, + const std::vector<string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids); + void OnFormSubmitted(const FormData& form); + + // Must be public for the external delegate to use. + void OnRemoveAutocompleteEntry(const string16& name, const string16& value); + + // Sets our external delegate. + void SetExternalDelegate(AutofillExternalDelegate* delegate); + + protected: + friend class AutofillManagerTest; + + // Sends the given |suggestions| for display in the Autofill popup. + void SendSuggestions(const std::vector<string16>* suggestions); + + private: + // Cancels the currently pending WebDataService query, if there is one. + void CancelPendingQuery(); + + content::BrowserContext* browser_context_; + scoped_ptr<AutofillWebDataService> autofill_data_; + + BooleanPrefMember autofill_enabled_; + + // When the manager makes a request from WebDataServiceBase, the database is + // queried on another thread, we record the query handle until we get called + // back. We also store the autofill results so we can send them together. + WebDataServiceBase::Handle pending_query_handle_; + int query_id_; + std::vector<string16> autofill_values_; + std::vector<string16> autofill_labels_; + std::vector<string16> autofill_icons_; + std::vector<int> autofill_unique_ids_; + + // Delegate to perform external processing (display, selection) on + // our behalf. Weak. + AutofillExternalDelegate* external_delegate_; + + DISALLOW_COPY_AND_ASSIGN(AutocompleteHistoryManager); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOCOMPLETE_HISTORY_MANAGER_H_ diff --git a/components/autofill/browser/autocomplete_history_manager_unittest.cc b/components/autofill/browser/autocomplete_history_manager_unittest.cc new file mode 100644 index 0000000..e2becff --- /dev/null +++ b/components/autofill/browser/autocomplete_history_manager_unittest.cc @@ -0,0 +1,239 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/prefs/testing_pref_service.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/webdata/autofill_web_data_service_impl.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "chrome/browser/webdata/web_data_service_factory.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/autocomplete_history_manager.h" +#include "components/autofill/browser/autofill_external_delegate.h" +#include "components/autofill/browser/autofill_manager.h" +#include "components/autofill/browser/test_autofill_manager_delegate.h" +#include "components/autofill/common/form_data.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" + +using content::BrowserThread; +using content::WebContents; +using testing::_; + +namespace { + +class MockWebDataService : public WebDataService { + public: + MockWebDataService() + : WebDataService(NULL) { + current_mock_web_data_service_ = this; + } + + static scoped_refptr<RefcountedProfileKeyedService> Build(Profile* profile) { + return current_mock_web_data_service_; + } + + virtual void ShutdownOnUIThread() OVERRIDE {} + + MOCK_METHOD1(AddFormFields, void(const std::vector<FormFieldData>&)); + + protected: + virtual ~MockWebDataService() {} + + private: + // Keep track of the most recently created instance, so that it can be + // associated with the current profile when Build() is called. + static MockWebDataService* current_mock_web_data_service_; +}; + +MockWebDataService* MockWebDataService::current_mock_web_data_service_ = NULL; + +class MockAutofillManagerDelegate + : public autofill::TestAutofillManagerDelegate { + public: + MockAutofillManagerDelegate() {} + virtual ~MockAutofillManagerDelegate() {} + virtual PrefService* GetPrefs() { return &prefs_; } + + private: + TestingPrefServiceSimple prefs_; + + DISALLOW_COPY_AND_ASSIGN(MockAutofillManagerDelegate); +}; + +} // namespace + +class AutocompleteHistoryManagerTest : public ChromeRenderViewHostTestHarness { + protected: + AutocompleteHistoryManagerTest() + : db_thread_(BrowserThread::DB) { + } + + virtual void SetUp() OVERRIDE { + ChromeRenderViewHostTestHarness::SetUp(); + web_data_service_ = new MockWebDataService(); + WebDataServiceFactory::GetInstance()->SetTestingFactory( + profile(), MockWebDataService::Build); + autocomplete_manager_.reset(new AutocompleteHistoryManager(web_contents())); + } + + virtual void TearDown() OVERRIDE { + autocomplete_manager_.reset(); + web_data_service_ = NULL; + ChromeRenderViewHostTestHarness::TearDown(); + } + + content::TestBrowserThread db_thread_; + scoped_refptr<MockWebDataService> web_data_service_; + scoped_ptr<AutocompleteHistoryManager> autocomplete_manager_; + MockAutofillManagerDelegate manager_delegate; +}; + +// Tests that credit card numbers are not sent to the WebDatabase to be saved. +TEST_F(AutocompleteHistoryManagerTest, CreditCardNumberValue) { + FormData form; + form.name = ASCIIToUTF16("MyForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://myform.com/form.html"); + form.action = GURL("http://myform.com/submit.html"); + form.user_submitted = true; + + // Valid Visa credit card number pulled from the paypal help site. + FormFieldData valid_cc; + valid_cc.label = ASCIIToUTF16("Credit Card"); + valid_cc.name = ASCIIToUTF16("ccnum"); + valid_cc.value = ASCIIToUTF16("4012888888881881"); + valid_cc.form_control_type = "text"; + form.fields.push_back(valid_cc); + + EXPECT_CALL(*web_data_service_, AddFormFields(_)).Times(0); + autocomplete_manager_->OnFormSubmitted(form); +} + +// Contrary test to AutocompleteHistoryManagerTest.CreditCardNumberValue. The +// value being submitted is not a valid credit card number, so it will be sent +// to the WebDatabase to be saved. +TEST_F(AutocompleteHistoryManagerTest, NonCreditCardNumberValue) { + FormData form; + form.name = ASCIIToUTF16("MyForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://myform.com/form.html"); + form.action = GURL("http://myform.com/submit.html"); + form.user_submitted = true; + + // Invalid credit card number. + FormFieldData invalid_cc; + invalid_cc.label = ASCIIToUTF16("Credit Card"); + invalid_cc.name = ASCIIToUTF16("ccnum"); + invalid_cc.value = ASCIIToUTF16("4580123456789012"); + invalid_cc.form_control_type = "text"; + form.fields.push_back(invalid_cc); + + EXPECT_CALL(*(web_data_service_.get()), AddFormFields(_)).Times(1); + autocomplete_manager_->OnFormSubmitted(form); +} + +// Tests that SSNs are not sent to the WebDatabase to be saved. +TEST_F(AutocompleteHistoryManagerTest, SSNValue) { + FormData form; + form.name = ASCIIToUTF16("MyForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://myform.com/form.html"); + form.action = GURL("http://myform.com/submit.html"); + form.user_submitted = true; + + FormFieldData ssn; + ssn.label = ASCIIToUTF16("Social Security Number"); + ssn.name = ASCIIToUTF16("ssn"); + ssn.value = ASCIIToUTF16("078-05-1120"); + ssn.form_control_type = "text"; + form.fields.push_back(ssn); + + EXPECT_CALL(*web_data_service_, AddFormFields(_)).Times(0); + autocomplete_manager_->OnFormSubmitted(form); +} + +// Verify that autocomplete text is saved for search fields. +TEST_F(AutocompleteHistoryManagerTest, SearchField) { + FormData form; + form.name = ASCIIToUTF16("MyForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://myform.com/form.html"); + form.action = GURL("http://myform.com/submit.html"); + form.user_submitted = true; + + // Search field. + FormFieldData search_field; + search_field.label = ASCIIToUTF16("Search"); + search_field.name = ASCIIToUTF16("search"); + search_field.value = ASCIIToUTF16("my favorite query"); + search_field.form_control_type = "search"; + form.fields.push_back(search_field); + + EXPECT_CALL(*(web_data_service_.get()), AddFormFields(_)).Times(1); + autocomplete_manager_->OnFormSubmitted(form); +} + +namespace { + +class MockAutofillExternalDelegate : public AutofillExternalDelegate { + public: + explicit MockAutofillExternalDelegate(content::WebContents* web_contents) + : AutofillExternalDelegate( + web_contents, AutofillManager::FromWebContents(web_contents)) {} + virtual ~MockAutofillExternalDelegate() {} + + MOCK_METHOD5(OnSuggestionsReturned, + void(int query_id, + const std::vector<string16>& autofill_values, + const std::vector<string16>& autofill_labels, + const std::vector<string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillExternalDelegate); +}; + +class AutocompleteHistoryManagerStubSend : public AutocompleteHistoryManager { + public: + explicit AutocompleteHistoryManagerStubSend(WebContents* web_contents) + : AutocompleteHistoryManager(web_contents) {} + + // Increase visibility for testing. + void SendSuggestions(const std::vector<string16>* suggestions) { + AutocompleteHistoryManager::SendSuggestions(suggestions); + } + + // Intentionally swallow the message. + virtual bool Send(IPC::Message* message) OVERRIDE { + delete message; + return true; + } +}; + +} // namespace + +// Make sure our external delegate is called at the right time. +TEST_F(AutocompleteHistoryManagerTest, ExternalDelegate) { + // Local version with a stubbed out Send() + AutocompleteHistoryManagerStubSend autocomplete_history_manager( + web_contents()); + + AutofillManager::CreateForWebContentsAndDelegate( + web_contents(), &manager_delegate); + + MockAutofillExternalDelegate external_delegate(web_contents()); + autocomplete_history_manager.SetExternalDelegate(&external_delegate); + + // Should trigger a call to OnSuggestionsReturned, verified by the mock. + EXPECT_CALL(external_delegate, OnSuggestionsReturned(_, _, _, _, _)); + autocomplete_history_manager.SendSuggestions(NULL); +} diff --git a/components/autofill/browser/autofill-inl.h b/components/autofill/browser/autofill-inl.h new file mode 100644 index 0000000..726d443 --- /dev/null +++ b/components/autofill/browser/autofill-inl.h @@ -0,0 +1,35 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_INL_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_INL_H_ + +template<typename T> +class FormGroupMatchesByCompareFunctor { + public: + explicit FormGroupMatchesByCompareFunctor(const T& form_group) + : form_group_(form_group) { + } + + bool operator()(const T* form_group) { + return form_group->Compare(form_group_) == 0; + } + + bool operator()(const T& form_group) { + return form_group.Compare(form_group_) == 0; + } + + private: + const T& form_group_; +}; + +template<typename C, typename T> +bool FindByContents(const C& container, const T& form_group) { + return std::find_if( + container.begin(), + container.end(), + FormGroupMatchesByCompareFunctor<T>(form_group)) != container.end(); +} + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_INL_H_ diff --git a/components/autofill/browser/autofill_common_test.cc b/components/autofill/browser/autofill_common_test.cc new file mode 100644 index 0000000..447c12d --- /dev/null +++ b/components/autofill/browser/autofill_common_test.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_common_test.h" + +#include "base/guid.h" +#include "base/prefs/pref_service.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/password_manager/encryptor.h" +#include "chrome/browser/profiles/profile.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/credit_card.h" +#include "components/autofill/common/autofill_pref_names.h" +#include "components/autofill/common/form_field_data.h" +#include "components/user_prefs/user_prefs.h" + +namespace autofill_test { + +void CreateTestFormField(const char* label, + const char* name, + const char* value, + const char* type, + FormFieldData* field) { + field->label = ASCIIToUTF16(label); + field->name = ASCIIToUTF16(name); + field->value = ASCIIToUTF16(value); + field->form_control_type = type; +} + +inline void check_and_set( + FormGroup* profile, AutofillFieldType type, const char* value) { + if (value) + profile->SetRawInfo(type, UTF8ToUTF16(value)); +} + +AutofillProfile GetFullProfile() { + AutofillProfile profile(base::GenerateGUID()); + SetProfileInfo(&profile, + "John", + "H.", + "Doe", + "johndoe@hades.com", + "Underworld", + "666 Erebus St.", + "Apt 8", + "Elysium", "CA", + "91111", + "US", + "16502111111"); + return profile; +} + +void SetProfileInfo(AutofillProfile* profile, + const char* first_name, const char* middle_name, + const char* last_name, const char* email, const char* company, + const char* address1, const char* address2, const char* city, + const char* state, const char* zipcode, const char* country, + const char* phone) { + check_and_set(profile, NAME_FIRST, first_name); + check_and_set(profile, NAME_MIDDLE, middle_name); + check_and_set(profile, NAME_LAST, last_name); + check_and_set(profile, EMAIL_ADDRESS, email); + check_and_set(profile, COMPANY_NAME, company); + check_and_set(profile, ADDRESS_HOME_LINE1, address1); + check_and_set(profile, ADDRESS_HOME_LINE2, address2); + check_and_set(profile, ADDRESS_HOME_CITY, city); + check_and_set(profile, ADDRESS_HOME_STATE, state); + check_and_set(profile, ADDRESS_HOME_ZIP, zipcode); + check_and_set(profile, ADDRESS_HOME_COUNTRY, country); + check_and_set(profile, PHONE_HOME_WHOLE_NUMBER, phone); +} + +void SetProfileInfoWithGuid(AutofillProfile* profile, + const char* guid, const char* first_name, const char* middle_name, + const char* last_name, const char* email, const char* company, + const char* address1, const char* address2, const char* city, + const char* state, const char* zipcode, const char* country, + const char* phone) { + if (guid) + profile->set_guid(guid); + SetProfileInfo(profile, first_name, middle_name, last_name, email, + company, address1, address2, city, state, zipcode, country, + phone); +} + +void SetCreditCardInfo(CreditCard* credit_card, + const char* name_on_card, const char* card_number, + const char* expiration_month, const char* expiration_year) { + check_and_set(credit_card, CREDIT_CARD_NAME, name_on_card); + check_and_set(credit_card, CREDIT_CARD_NUMBER, card_number); + check_and_set(credit_card, CREDIT_CARD_EXP_MONTH, expiration_month); + check_and_set(credit_card, CREDIT_CARD_EXP_4_DIGIT_YEAR, expiration_year); +} + +void DisableSystemServices(Profile* profile) { + // Use a mock Keychain rather than the OS one to store credit card data. +#if defined(OS_MACOSX) + Encryptor::UseMockKeychain(true); +#endif + + // Disable auxiliary profiles for unit testing. These reach out to system + // services on the Mac. + if (profile) { + components::UserPrefs::Get(profile)->SetBoolean( + prefs::kAutofillAuxiliaryProfilesEnabled, false); + } +} + +} // namespace autofill_test diff --git a/components/autofill/browser/autofill_common_test.h b/components/autofill/browser/autofill_common_test.h new file mode 100644 index 0000000..858d8da --- /dev/null +++ b/components/autofill/browser/autofill_common_test.h @@ -0,0 +1,58 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_COMMON_TEST_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_COMMON_TEST_H_ + +class AutofillProfile; +class CreditCard; +class Profile; + +struct FormFieldData; + +// Common utilities shared amongst Autofill tests. +namespace autofill_test { + +// Provides a quick way to populate a FormField with c-strings. +void CreateTestFormField(const char* label, + const char* name, + const char* value, + const char* type, + FormFieldData* field); + +// Returns a profile full of dummy info. +AutofillProfile GetFullProfile(); + +// A unit testing utility that is common to a number of the Autofill unit +// tests. |SetProfileInfo| provides a quick way to populate a profile with +// c-strings. +void SetProfileInfo(AutofillProfile* profile, + const char* first_name, const char* middle_name, + const char* last_name, const char* email, const char* company, + const char* address1, const char* address2, const char* city, + const char* state, const char* zipcode, const char* country, + const char* phone); + +void SetProfileInfoWithGuid(AutofillProfile* profile, + const char* guid, const char* first_name, const char* middle_name, + const char* last_name, const char* email, const char* company, + const char* address1, const char* address2, const char* city, + const char* state, const char* zipcode, const char* country, + const char* phone); + +// A unit testing utility that is common to a number of the Autofill unit +// tests. |SetCreditCardInfo| provides a quick way to populate a credit card +// with c-strings. +void SetCreditCardInfo(CreditCard* credit_card, + const char* name_on_card, const char* card_number, + const char* expiration_month, const char* expiration_year); + +// TODO(isherman): We should do this automatically for all tests, not manually +// on a per-test basis: http://crbug.com/57221 +// Disables or mocks out code that would otherwise reach out to system services. +void DisableSystemServices(Profile* profile); + +} // namespace autofill_test + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_COMMON_TEST_H_ diff --git a/components/autofill/browser/autofill_country.cc b/components/autofill/browser/autofill_country.cc new file mode 100644 index 0000000..e8a8f9e --- /dev/null +++ b/components/autofill/browser/autofill_country.cc @@ -0,0 +1,896 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_country.h" + +#include <stddef.h> +#include <stdint.h> +#include <map> +#include <utility> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "base/threading/thread_checker.h" +#include "base/utf_string_conversions.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/content_browser_client.h" +#include "grit/generated_resources.h" +#include "third_party/icu/public/common/unicode/locid.h" +#include "third_party/icu/public/common/unicode/uloc.h" +#include "third_party/icu/public/common/unicode/unistr.h" +#include "third_party/icu/public/common/unicode/urename.h" +#include "third_party/icu/public/common/unicode/utypes.h" +#include "third_party/icu/public/i18n/unicode/coll.h" +#include "third_party/icu/public/i18n/unicode/ucol.h" +#include "ui/base/l10n/l10n_util.h" + +using content::BrowserThread; + +namespace { + +// The maximum capacity needed to store a locale up to the country code. +const size_t kLocaleCapacity = + ULOC_LANG_CAPACITY + ULOC_SCRIPT_CAPACITY + ULOC_COUNTRY_CAPACITY + 1; + +struct CountryData { + int postal_code_label_id; + int state_label_id; +}; + +struct StaticCountryData { + char country_code[3]; + CountryData country_data; +}; + +// Maps country codes to localized label string identifiers. +const StaticCountryData kCountryData[] = { + { "AD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PARISH } }, + { "AE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_EMIRATE } }, + { "AF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AQ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "AS", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "AT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "AW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AX", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "AZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BB", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PARISH } }, + { "BD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BJ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "BS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND } }, + { "BT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "BZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "CM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND } }, + { "CX", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "CZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "DE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "DJ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "DK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "DM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "DO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "DZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "EC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "EE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "EG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "EH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "ER", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "ES", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "ET", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "FI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "FJ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "FK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "FM", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "FO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "FR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GB", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_COUNTY } }, + { "GD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GP", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GQ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GU", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "GW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "GY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "HK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_AREA } }, + { "HM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "HN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "HR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "HT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "HU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "ID", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "IE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_COUNTY } }, + { "IL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "IM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "IN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "IO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "IQ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "IS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "IT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "JE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "JM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PARISH } }, + { "JO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "JP", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PREFECTURE } }, + { "KE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "KG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "KH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "KI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND } }, + { "KM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "KN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND } }, + { "KP", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "KR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "KW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "KY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND } }, + { "KZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LB", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "LY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "ME", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MH", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "MK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "ML", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MP", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "MQ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "MX", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "MY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "MZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "NA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "NC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "NE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "NF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "NG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "NI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_DEPARTMENT } }, + { "NL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "NO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "NP", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "NR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_DISTRICT } }, + { "NU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "NZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "OM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND } }, + { "PG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PR", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "PW", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "PY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "QA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "RE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "RO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "RS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "RU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "RW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SB", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND } }, + { "SE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SJ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "ST", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "SZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TJ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "TV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND } }, + { "TW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_COUNTY } }, + { "TZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "UA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "UG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "UM", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "US", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "UY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "UZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "VA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "VC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "VE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "VG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "VI", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE } }, + { "VN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "VU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "WF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "WS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "YE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "YT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "ZA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "ZM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, + { "ZW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE } }, +}; + +// A singleton class that encapsulates a map from country codes to country data. +class CountryDataMap { + public: + // A const iterator over the wrapped map data. + typedef std::map<std::string, CountryData>::const_iterator Iterator; + + static CountryDataMap* GetInstance(); + static const Iterator Begin(); + static const Iterator End(); + static const Iterator Find(const std::string& country_code); + + private: + CountryDataMap(); + friend struct DefaultSingletonTraits<CountryDataMap>; + + std::map<std::string, CountryData> country_data_; + + DISALLOW_COPY_AND_ASSIGN(CountryDataMap); +}; + +// static +CountryDataMap* CountryDataMap::GetInstance() { + return Singleton<CountryDataMap>::get(); +} + +CountryDataMap::CountryDataMap() { + // Add all the countries we have explicit data for. + for (size_t i = 0; i < arraysize(kCountryData); ++i) { + const StaticCountryData& static_data = kCountryData[i]; + country_data_.insert(std::make_pair(static_data.country_code, + static_data.country_data)); + } + + // Add any other countries that ICU knows about, falling back to default data + // values. + for (const char* const* country_pointer = icu::Locale::getISOCountries(); + *country_pointer; + ++country_pointer) { + std::string country_code = *country_pointer; + if (!country_data_.count(country_code)) { + CountryData data = { + IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE + }; + country_data_.insert(std::make_pair(country_code, data)); + } + } +} + +const CountryDataMap::Iterator CountryDataMap::Begin() { + return GetInstance()->country_data_.begin(); +} + +const CountryDataMap::Iterator CountryDataMap::End() { + return GetInstance()->country_data_.end(); +} + +const CountryDataMap::Iterator CountryDataMap::Find( + const std::string& country_code) { + return GetInstance()->country_data_.find(country_code); +} + +// A singleton class that encapsulates mappings from country names to their +// corresponding country codes. +class CountryNames { + public: + static CountryNames* GetInstance(); + + // Returns the application locale. + const std::string ApplicationLocale(); + + // Returns the country code corresponding to |country|, which should be a + // country code or country name localized to |locale|. + const std::string GetCountryCode(const string16& country, + const std::string& locale); + + private: + CountryNames(); + ~CountryNames(); + friend struct DefaultSingletonTraits<CountryNames>; + + // Populates |locales_to_localized_names_| with the mapping of country names + // localized to |locale| to their corresponding country codes. + void AddLocalizedNamesForLocale(const std::string& locale); + + // Interprets |country_name| as a full country name localized to the given + // |locale| and returns the corresponding country code stored in + // |locales_to_localized_names_|, or an empty string if there is none. + const std::string GetCountryCodeForLocalizedName(const string16& country_name, + const std::string& locale); + + // Returns an ICU collator -- i.e. string comparator -- appropriate for the + // given |locale|. + icu::Collator* GetCollatorForLocale(const std::string& locale); + + // Returns the ICU sort key corresponding to |str| for the given |collator|. + // Uses |buffer| as temporary storage, and might resize |buffer| as a side- + // effect. |buffer_size| should specify the |buffer|'s size, and is updated if + // the |buffer| is resized. + const std::string GetSortKey(const icu::Collator& collator, + const string16& str, + scoped_array<uint8_t>* buffer, + int32_t* buffer_size) const; + + // Maps from common country names, including 2- and 3-letter country codes, + // to the corresponding 2-letter country codes. The keys are uppercase ASCII + // strings. + std::map<std::string, std::string> common_names_; + + // The outer map keys are ICU locale identifiers. + // The inner maps map from localized country names to their corresponding + // country codes. The inner map keys are ICU collation sort keys corresponding + // to the target localized country name. + std::map<std::string, std::map<std::string, std::string> > + locales_to_localized_names_; + + // Maps ICU locale names to their corresponding collators. + std::map<std::string, icu::Collator*> collators_; + + // Verifies thread-safety of accesses to the application locale. + base::ThreadChecker thread_checker_; + + // Caches the application locale, for thread-safe access. + std::string application_locale_; + + DISALLOW_COPY_AND_ASSIGN(CountryNames); +}; + +// static +CountryNames* CountryNames::GetInstance() { + return Singleton<CountryNames>::get(); +} + +const std::string CountryNames::ApplicationLocale() { + if (application_locale_.empty()) { + // In production code, this class is always constructed on the UI thread, so + // the two conditions in the below DCHECK are identical. In test code, + // sometimes there is a UI thread, and sometimes there is just the unnamed + // main thread. Since this class is a singleton, it needs to support both + // cases. Hence, the somewhat strange looking DCHECK below. + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI) || + thread_checker_.CalledOnValidThread()); + application_locale_ = + content::GetContentClient()->browser()->GetApplicationLocale(); + } + + return application_locale_; +} + +CountryNames::CountryNames() { + // Add 2- and 3-letter ISO country codes. + for (CountryDataMap::Iterator it = CountryDataMap::Begin(); + it != CountryDataMap::End(); + ++it) { + const std::string& country_code = it->first; + std::string iso3_country_code = + icu::Locale(NULL, country_code.c_str()).getISO3Country(); + + common_names_.insert(std::make_pair(country_code, country_code)); + common_names_.insert(std::make_pair(iso3_country_code, country_code)); + } + + // Add a few other common synonyms. + common_names_.insert(std::make_pair("UNITED STATES OF AMERICA", "US")); + common_names_.insert(std::make_pair("U.S.A.", "US")); + common_names_.insert(std::make_pair("GREAT BRITAIN", "GB")); + common_names_.insert(std::make_pair("UK", "GB")); + common_names_.insert(std::make_pair("BRASIL", "BR")); + common_names_.insert(std::make_pair("DEUTSCHLAND", "DE")); +} + +CountryNames::~CountryNames() { + STLDeleteContainerPairSecondPointers(collators_.begin(), + collators_.end()); +} + +const std::string CountryNames::GetCountryCode(const string16& country, + const std::string& locale) { + // First, check common country names, including 2- and 3-letter country codes. + std::string country_utf8 = UTF16ToUTF8(StringToUpperASCII(country)); + std::map<std::string, std::string>::const_iterator result = + common_names_.find(country_utf8); + if (result != common_names_.end()) + return result->second; + + // Next, check country names localized to |locale|. + std::string country_code = GetCountryCodeForLocalizedName(country, locale); + if (!country_code.empty()) + return country_code; + + // Finally, check country names localized to US English. + return GetCountryCodeForLocalizedName(country, "en_US"); +} + +void CountryNames::AddLocalizedNamesForLocale(const std::string& locale) { + // Nothing to do if we've previously added the localized names for the given + // |locale|. + if (locales_to_localized_names_.count(locale)) + return; + + std::map<std::string, std::string> localized_names; + const icu::Collator* collator = GetCollatorForLocale(locale); + int32_t buffer_size = 1000; + scoped_array<uint8_t> buffer(new uint8_t[buffer_size]); + + for (CountryDataMap::Iterator it = CountryDataMap::Begin(); + it != CountryDataMap::End(); + ++it) { + const std::string& country_code = it->first; + string16 country_name = l10n_util::GetDisplayNameForCountry(country_code, + locale); + std::string sort_key = GetSortKey(*collator, + country_name, + &buffer, + &buffer_size); + + localized_names.insert(std::make_pair(sort_key, country_code)); + } + + locales_to_localized_names_.insert(std::make_pair(locale, localized_names)); +} + +const std::string CountryNames::GetCountryCodeForLocalizedName( + const string16& country_name, + const std::string& locale) { + AddLocalizedNamesForLocale(locale); + + icu::Collator* collator = GetCollatorForLocale(locale); + + // As recommended[1] by ICU, initialize the buffer size to four times the + // source string length. + // [1] http://userguide.icu-project.org/collation/api#TOC-Examples + int32_t buffer_size = country_name.size() * 4; + scoped_array<uint8_t> buffer(new uint8_t[buffer_size]); + std::string sort_key = GetSortKey(*collator, + country_name, + &buffer, + &buffer_size); + + const std::map<std::string, std::string>& localized_names = + locales_to_localized_names_[locale]; + std::map<std::string, std::string>::const_iterator result = + localized_names.find(sort_key); + + if (result != localized_names.end()) + return result->second; + + return std::string(); +} + +icu::Collator* CountryNames::GetCollatorForLocale(const std::string& locale) { + if (!collators_.count(locale)) { + icu::Locale icu_locale(locale.c_str()); + UErrorCode ignored = U_ZERO_ERROR; + icu::Collator* collator(icu::Collator::createInstance(icu_locale, ignored)); + + // Compare case-insensitively and ignoring punctuation. + ignored = U_ZERO_ERROR; + collator->setAttribute(UCOL_STRENGTH, UCOL_SECONDARY, ignored); + ignored = U_ZERO_ERROR; + collator->setAttribute(UCOL_ALTERNATE_HANDLING, UCOL_SHIFTED, ignored); + + collators_.insert(std::make_pair(locale, collator)); + } + + return collators_[locale]; +} + +const std::string CountryNames::GetSortKey(const icu::Collator& collator, + const string16& str, + scoped_array<uint8_t>* buffer, + int32_t* buffer_size) const { + DCHECK(buffer); + DCHECK(buffer_size); + + icu::UnicodeString icu_str(str.c_str(), str.length()); + int32_t expected_size = collator.getSortKey(icu_str, buffer->get(), + *buffer_size); + if (expected_size > *buffer_size) { + // If there wasn't enough space, grow the buffer and try again. + *buffer_size = expected_size; + buffer->reset(new uint8_t[*buffer_size]); + DCHECK(buffer->get()); + + expected_size = collator.getSortKey(icu_str, buffer->get(), *buffer_size); + DCHECK_EQ(*buffer_size, expected_size); + } + + return std::string(reinterpret_cast<const char*>(buffer->get())); +} + +} // namespace + +AutofillCountry::AutofillCountry(const std::string& country_code, + const std::string& locale) { + const CountryDataMap::Iterator result = CountryDataMap::Find(country_code); + DCHECK(result != CountryDataMap::End()); + const CountryData& data = result->second; + + country_code_ = country_code; + name_ = l10n_util::GetDisplayNameForCountry(country_code, locale); + postal_code_label_ = l10n_util::GetStringUTF16(data.postal_code_label_id); + state_label_ = l10n_util::GetStringUTF16(data.state_label_id); +} + +AutofillCountry::~AutofillCountry() { +} + +// static +void AutofillCountry::GetAvailableCountries( + std::vector<std::string>* country_codes) { + DCHECK(country_codes); + + for (CountryDataMap::Iterator it = CountryDataMap::Begin(); + it != CountryDataMap::End(); + ++it) { + country_codes->push_back(it->first); + } +} + +// static +const std::string AutofillCountry::CountryCodeForLocale( + const std::string& locale) { + // Add likely subtags to the locale. In particular, add any likely country + // subtags -- e.g. for locales like "ru" that only include the language. + std::string likely_locale; + UErrorCode error_ignored = U_ZERO_ERROR; + uloc_addLikelySubtags(locale.c_str(), + WriteInto(&likely_locale, kLocaleCapacity), + kLocaleCapacity, + &error_ignored); + + // Extract the country code. + std::string country_code = icu::Locale(likely_locale.c_str()).getCountry(); + + // Default to the United States if we have no better guess. + if (CountryDataMap::Find(country_code) == CountryDataMap::End()) + return "US"; + + return country_code; +} + +// static +const std::string AutofillCountry::GetCountryCode(const string16& country, + const std::string& locale) { + return CountryNames::GetInstance()->GetCountryCode(country, locale); +} + +// static +const std::string AutofillCountry::ApplicationLocale() { + return CountryNames::GetInstance()->ApplicationLocale(); +} + +AutofillCountry::AutofillCountry(const std::string& country_code, + const string16& name, + const string16& postal_code_label, + const string16& state_label) + : country_code_(country_code), + name_(name), + postal_code_label_(postal_code_label), + state_label_(state_label) { +} diff --git a/components/autofill/browser/autofill_country.h b/components/autofill/browser/autofill_country.h new file mode 100644 index 0000000..2dcaef6 --- /dev/null +++ b/components/autofill/browser/autofill_country.h @@ -0,0 +1,69 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_COUNTRY_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_COUNTRY_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/string16.h" + +// Stores data associated with a country. Strings are localized to the app +// locale. +class AutofillCountry { + public: + // Returns country data corresponding to the two-letter ISO code + // |country_code|. + AutofillCountry(const std::string& country_code, const std::string& locale); + ~AutofillCountry(); + + // Fills |country_codes| with a list of the available countries' codes. + static void GetAvailableCountries( + std::vector<std::string>* country_codes); + + // Returns the likely country code for |locale|, or "US" as a fallback if no + // mapping from the locale is available. + static const std::string CountryCodeForLocale(const std::string& locale); + + // Returns the country code corresponding to |country|, which should be a + // country code or country name localized to |locale|. This function can + // be expensive so use judiciously. + static const std::string GetCountryCode(const string16& country, + const std::string& locale); + + // Returns the application locale. + // The first time this is called, it should be called from the UI thread. + // Once [ http://crbug.com/100845 ] is fixed, this method should *only* be + // called from the UI thread. + static const std::string ApplicationLocale(); + + const std::string country_code() const { return country_code_; } + const string16 name() const { return name_; } + const string16 postal_code_label() const { return postal_code_label_; } + const string16 state_label() const { return state_label_; } + + private: + AutofillCountry(const std::string& country_code, + const string16& name, + const string16& postal_code_label, + const string16& state_label); + + // The two-letter ISO-3166 country code. + std::string country_code_; + + // The country's name, localized to the app locale. + string16 name_; + + // The localized label for the postal code (or zip code) field. + string16 postal_code_label_; + + // The localized label for the state (or province, district, etc.) field. + string16 state_label_; + + DISALLOW_COPY_AND_ASSIGN(AutofillCountry); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_COUNTRY_H_ diff --git a/components/autofill/browser/autofill_country_unittest.cc b/components/autofill/browser/autofill_country_unittest.cc new file mode 100644 index 0000000..3615129 --- /dev/null +++ b/components/autofill/browser/autofill_country_unittest.cc @@ -0,0 +1,86 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_country.h" +#include "testing/gtest/include/gtest/gtest.h" + +// Test the constructor and accessors +TEST(AutofillCountryTest, AutofillCountry) { + AutofillCountry united_states_en("US", "en_US"); + EXPECT_EQ("US", united_states_en.country_code()); + EXPECT_EQ(ASCIIToUTF16("United States"), united_states_en.name()); + EXPECT_EQ(ASCIIToUTF16("ZIP code"), united_states_en.postal_code_label()); + EXPECT_EQ(ASCIIToUTF16("State"), united_states_en.state_label()); + + AutofillCountry united_states_es("US", "es"); + EXPECT_EQ("US", united_states_es.country_code()); + EXPECT_EQ(ASCIIToUTF16("Estados Unidos"), united_states_es.name()); + + AutofillCountry canada_en("CA", "en_US"); + EXPECT_EQ("CA", canada_en.country_code()); + EXPECT_EQ(ASCIIToUTF16("Canada"), canada_en.name()); + EXPECT_EQ(ASCIIToUTF16("Postal code"), canada_en.postal_code_label()); + EXPECT_EQ(ASCIIToUTF16("Province"), canada_en.state_label()); + + AutofillCountry canada_hu("CA", "hu"); + EXPECT_EQ("CA", canada_hu.country_code()); + EXPECT_EQ(ASCIIToUTF16("Kanada"), canada_hu.name()); +} + +// Test locale to country code mapping. +TEST(AutofillCountryTest, CountryCodeForLocale) { + EXPECT_EQ("US", AutofillCountry::CountryCodeForLocale("en_US")); + EXPECT_EQ("CA", AutofillCountry::CountryCodeForLocale("fr_CA")); + EXPECT_EQ("FR", AutofillCountry::CountryCodeForLocale("fr")); + EXPECT_EQ("US", AutofillCountry::CountryCodeForLocale("Unknown")); + // "es-419" isn't associated with a country. See base/l10n/l10n_util.cc + // for details about this locale. Default to US. + EXPECT_EQ("US", AutofillCountry::CountryCodeForLocale("es-419")); +} + +// Test mapping of localized country names to country codes. +TEST(AutofillCountryTest, GetCountryCode) { + // Basic mapping + EXPECT_EQ("US", AutofillCountry::GetCountryCode(ASCIIToUTF16("United States"), + "en_US")); + EXPECT_EQ("CA", AutofillCountry::GetCountryCode(ASCIIToUTF16("Canada"), + "en_US")); + + // Case-insensitive mapping + EXPECT_EQ("US", AutofillCountry::GetCountryCode(ASCIIToUTF16("united states"), + "en_US")); + + // Country codes should map to themselves, independent of locale. + EXPECT_EQ("US", AutofillCountry::GetCountryCode(ASCIIToUTF16("US"), "en_US")); + EXPECT_EQ("HU", AutofillCountry::GetCountryCode(ASCIIToUTF16("hu"), "en_US")); + EXPECT_EQ("CA", AutofillCountry::GetCountryCode(ASCIIToUTF16("CA"), "fr_CA")); + EXPECT_EQ("MX", AutofillCountry::GetCountryCode(ASCIIToUTF16("mx"), "fr_CA")); + + // Basic synonyms + EXPECT_EQ("US", + AutofillCountry::GetCountryCode( + ASCIIToUTF16("United States of America"), "en_US")); + EXPECT_EQ("US", AutofillCountry::GetCountryCode(ASCIIToUTF16("USA"), + "en_US")); + + // Other locales + EXPECT_EQ("US", + AutofillCountry::GetCountryCode(ASCIIToUTF16("Estados Unidos"), + "es")); + EXPECT_EQ("IT", AutofillCountry::GetCountryCode(ASCIIToUTF16("Italia"), + "it")); + EXPECT_EQ("DE", AutofillCountry::GetCountryCode(ASCIIToUTF16("duitsland"), + "nl")); + + // Should fall back to "en_US" locale if all else fails. + EXPECT_EQ("US", AutofillCountry::GetCountryCode(ASCIIToUTF16("United States"), + "es")); + EXPECT_EQ("US", AutofillCountry::GetCountryCode(ASCIIToUTF16("united states"), + "es")); + EXPECT_EQ("US", AutofillCountry::GetCountryCode(ASCIIToUTF16("USA"), "es")); +} diff --git a/components/autofill/browser/autofill_download.cc b/components/autofill/browser/autofill_download.cc new file mode 100644 index 0000000..6377184 --- /dev/null +++ b/components/autofill/browser/autofill_download.cc @@ -0,0 +1,340 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_download.h" + +#include <algorithm> +#include <ostream> +#include <vector> + +#include "base/logging.h" +#include "base/prefs/pref_service.h" +#include "base/rand_util.h" +#include "base/stl_util.h" +#include "base/string_util.h" +#include "components/autofill/browser/autofill_download_url.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/browser/autofill_xml_parser.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/common/autofill_pref_names.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" +#include "googleurl/src/gurl.h" +#include "net/base/load_flags.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_fetcher.h" +#include "third_party/libjingle/source/talk/xmllite/xmlparser.h" + +using content::BrowserContext; + +namespace { +const char kAutofillQueryServerNameStartInHeader[] = "GFE/"; + +const size_t kMaxFormCacheSize = 16; + +// Log the contents of the upload request +static void LogUploadRequest(const GURL& url, const std::string& signature, + const std::string& form_xml) { + VLOG(2) << url; + VLOG(2) << signature; + VLOG(2) << form_xml; +} + +}; + +// static +std::string AutofillDownloadManager::AutofillRequestTypeToString( + const AutofillRequestType type) { + switch (type) { + case AutofillDownloadManager::REQUEST_QUERY: + return "query"; + case AutofillDownloadManager::REQUEST_UPLOAD: + return "upload"; + } + return std::string(); +} + +struct AutofillDownloadManager::FormRequestData { + std::vector<std::string> form_signatures; + AutofillRequestType request_type; +}; + +AutofillDownloadManager::AutofillDownloadManager(BrowserContext* context, + Observer* observer) + : browser_context_(context), + observer_(observer), + max_form_cache_size_(kMaxFormCacheSize), + next_query_request_(base::Time::Now()), + next_upload_request_(base::Time::Now()), + positive_upload_rate_(0), + negative_upload_rate_(0), + fetcher_id_for_unittest_(0) { + DCHECK(observer_); + PrefService* preferences = components::UserPrefs::Get(browser_context_); + positive_upload_rate_ = + preferences->GetDouble(prefs::kAutofillPositiveUploadRate); + negative_upload_rate_ = + preferences->GetDouble(prefs::kAutofillNegativeUploadRate); +} + +AutofillDownloadManager::~AutofillDownloadManager() { + STLDeleteContainerPairFirstPointers(url_fetchers_.begin(), + url_fetchers_.end()); +} + +bool AutofillDownloadManager::StartQueryRequest( + const std::vector<FormStructure*>& forms, + const AutofillMetrics& metric_logger) { + if (next_query_request_ > base::Time::Now()) { + // We are in back-off mode: do not do the request. + return false; + } + std::string form_xml; + FormRequestData request_data; + if (!FormStructure::EncodeQueryRequest(forms, &request_data.form_signatures, + &form_xml)) { + return false; + } + + request_data.request_type = AutofillDownloadManager::REQUEST_QUERY; + metric_logger.LogServerQueryMetric(AutofillMetrics::QUERY_SENT); + + std::string query_data; + if (CheckCacheForQueryRequest(request_data.form_signatures, &query_data)) { + DVLOG(1) << "AutofillDownloadManager: query request has been retrieved from" + << "the cache"; + observer_->OnLoadedServerPredictions(query_data); + return true; + } + + return StartRequest(form_xml, request_data); +} + +bool AutofillDownloadManager::StartUploadRequest( + const FormStructure& form, + bool form_was_autofilled, + const FieldTypeSet& available_field_types) { + std::string form_xml; + if (!form.EncodeUploadRequest(available_field_types, form_was_autofilled, + &form_xml)) + return false; + + LogUploadRequest(form.source_url(), form.FormSignature(), form_xml); + + if (next_upload_request_ > base::Time::Now()) { + // We are in back-off mode: do not do the request. + DVLOG(1) << "AutofillDownloadManager: Upload request is throttled."; + return false; + } + + // Flip a coin to see if we should upload this form. + double upload_rate = form_was_autofilled ? GetPositiveUploadRate() : + GetNegativeUploadRate(); + if (form.upload_required() == UPLOAD_NOT_REQUIRED || + (form.upload_required() == USE_UPLOAD_RATES && + base::RandDouble() > upload_rate)) { + DVLOG(1) << "AutofillDownloadManager: Upload request is ignored."; + // If we ever need notification that upload was skipped, add it here. + return false; + } + + FormRequestData request_data; + request_data.form_signatures.push_back(form.FormSignature()); + request_data.request_type = AutofillDownloadManager::REQUEST_UPLOAD; + + return StartRequest(form_xml, request_data); +} + +double AutofillDownloadManager::GetPositiveUploadRate() const { + return positive_upload_rate_; +} + +double AutofillDownloadManager::GetNegativeUploadRate() const { + return negative_upload_rate_; +} + +void AutofillDownloadManager::SetPositiveUploadRate(double rate) { + if (rate == positive_upload_rate_) + return; + positive_upload_rate_ = rate; + DCHECK_GE(rate, 0.0); + DCHECK_LE(rate, 1.0); + PrefService* preferences = components::UserPrefs::Get(browser_context_); + preferences->SetDouble(prefs::kAutofillPositiveUploadRate, rate); +} + +void AutofillDownloadManager::SetNegativeUploadRate(double rate) { + if (rate == negative_upload_rate_) + return; + negative_upload_rate_ = rate; + DCHECK_GE(rate, 0.0); + DCHECK_LE(rate, 1.0); + PrefService* preferences = components::UserPrefs::Get(browser_context_); + preferences->SetDouble(prefs::kAutofillNegativeUploadRate, rate); +} + +bool AutofillDownloadManager::StartRequest( + const std::string& form_xml, + const FormRequestData& request_data) { + net::URLRequestContextGetter* request_context = + browser_context_->GetRequestContext(); + DCHECK(request_context); + GURL request_url; + if (request_data.request_type == AutofillDownloadManager::REQUEST_QUERY) + request_url = autofill::GetAutofillQueryUrl(); + else + request_url = autofill::GetAutofillUploadUrl(); + + // Id is ignored for regular chrome, in unit test id's for fake fetcher + // factory will be 0, 1, 2, ... + net::URLFetcher* fetcher = net::URLFetcher::Create( + fetcher_id_for_unittest_++, request_url, net::URLFetcher::POST, + this); + url_fetchers_[fetcher] = request_data; + fetcher->SetAutomaticallyRetryOn5xx(false); + fetcher->SetRequestContext(request_context); + fetcher->SetUploadData("text/plain", form_xml); + fetcher->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES | + net::LOAD_DO_NOT_SEND_COOKIES); + fetcher->Start(); + + DVLOG(1) << "Sending AutofillDownloadManager " + << AutofillRequestTypeToString(request_data.request_type) + << " request: " << form_xml; + + return true; +} + +void AutofillDownloadManager::CacheQueryRequest( + const std::vector<std::string>& forms_in_query, + const std::string& query_data) { + std::string signature = GetCombinedSignature(forms_in_query); + for (QueryRequestCache::iterator it = cached_forms_.begin(); + it != cached_forms_.end(); ++it) { + if (it->first == signature) { + // We hit the cache, move to the first position and return. + std::pair<std::string, std::string> data = *it; + cached_forms_.erase(it); + cached_forms_.push_front(data); + return; + } + } + std::pair<std::string, std::string> data; + data.first = signature; + data.second = query_data; + cached_forms_.push_front(data); + while (cached_forms_.size() > max_form_cache_size_) + cached_forms_.pop_back(); +} + +bool AutofillDownloadManager::CheckCacheForQueryRequest( + const std::vector<std::string>& forms_in_query, + std::string* query_data) const { + std::string signature = GetCombinedSignature(forms_in_query); + for (QueryRequestCache::const_iterator it = cached_forms_.begin(); + it != cached_forms_.end(); ++it) { + if (it->first == signature) { + // We hit the cache, fill the data and return. + *query_data = it->second; + return true; + } + } + return false; +} + +std::string AutofillDownloadManager::GetCombinedSignature( + const std::vector<std::string>& forms_in_query) const { + size_t total_size = forms_in_query.size(); + for (size_t i = 0; i < forms_in_query.size(); ++i) + total_size += forms_in_query[i].length(); + std::string signature; + + signature.reserve(total_size); + + for (size_t i = 0; i < forms_in_query.size(); ++i) { + if (i) + signature.append(","); + signature.append(forms_in_query[i]); + } + return signature; +} + +void AutofillDownloadManager::OnURLFetchComplete( + const net::URLFetcher* source) { + std::map<net::URLFetcher *, FormRequestData>::iterator it = + url_fetchers_.find(const_cast<net::URLFetcher*>(source)); + if (it == url_fetchers_.end()) { + // Looks like crash on Mac is possibly caused with callback entering here + // with unknown fetcher when network is refreshed. + return; + } + std::string type_of_request( + AutofillRequestTypeToString(it->second.request_type)); + const int kHttpResponseOk = 200; + const int kHttpInternalServerError = 500; + const int kHttpBadGateway = 502; + const int kHttpServiceUnavailable = 503; + + CHECK(it->second.form_signatures.size()); + if (source->GetResponseCode() != kHttpResponseOk) { + bool back_off = false; + std::string server_header; + switch (source->GetResponseCode()) { + case kHttpBadGateway: + if (!source->GetResponseHeaders()->EnumerateHeader(NULL, "server", + &server_header) || + StartsWithASCII(server_header.c_str(), + kAutofillQueryServerNameStartInHeader, + false) != 0) + break; + // Bad gateway was received from Autofill servers. Fall through to back + // off. + case kHttpInternalServerError: + case kHttpServiceUnavailable: + back_off = true; + break; + } + + if (back_off) { + base::Time back_off_time(base::Time::Now() + source->GetBackoffDelay()); + if (it->second.request_type == AutofillDownloadManager::REQUEST_QUERY) { + next_query_request_ = back_off_time; + } else { + next_upload_request_ = back_off_time; + } + } + + DVLOG(1) << "AutofillDownloadManager: " << type_of_request + << " request has failed with response " + << source->GetResponseCode(); + observer_->OnServerRequestError(it->second.form_signatures[0], + it->second.request_type, + source->GetResponseCode()); + } else { + std::string response_body; + source->GetResponseAsString(&response_body); + DVLOG(1) << "AutofillDownloadManager: " << type_of_request + << " request has succeeded with response body: " + << response_body; + if (it->second.request_type == AutofillDownloadManager::REQUEST_QUERY) { + CacheQueryRequest(it->second.form_signatures, response_body); + observer_->OnLoadedServerPredictions(response_body); + } else { + double new_positive_upload_rate = 0; + double new_negative_upload_rate = 0; + AutofillUploadXmlParser parse_handler(&new_positive_upload_rate, + &new_negative_upload_rate); + buzz::XmlParser parser(&parse_handler); + parser.Parse(response_body.data(), response_body.length(), true); + if (parse_handler.succeeded()) { + SetPositiveUploadRate(new_positive_upload_rate); + SetNegativeUploadRate(new_negative_upload_rate); + } + + observer_->OnUploadedPossibleFieldTypes(); + } + } + delete it->first; + url_fetchers_.erase(it); +} diff --git a/components/autofill/browser/autofill_download.h b/components/autofill/browser/autofill_download.h new file mode 100644 index 0000000..18a237c --- /dev/null +++ b/components/autofill/browser/autofill_download.h @@ -0,0 +1,166 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_DOWNLOAD_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_DOWNLOAD_H_ + +#include <stddef.h> +#include <list> +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/time.h" +#include "components/autofill/browser/autofill_type.h" +#include "net/url_request/url_fetcher_delegate.h" + +class AutofillMetrics; +class FormStructure; + +namespace content { +class BrowserContext; +} // namespace content + +namespace net { +class URLFetcher; +} // namespace net + +// Handles getting and updating Autofill heuristics. +class AutofillDownloadManager : public net::URLFetcherDelegate { + public: + enum AutofillRequestType { + REQUEST_QUERY, + REQUEST_UPLOAD, + }; + + // An interface used to notify clients of AutofillDownloadManager. + class Observer { + public: + // Called when field type predictions are successfully received from the + // server. |response_xml| contains the server response. + virtual void OnLoadedServerPredictions(const std::string& response_xml) = 0; + + // These notifications are used to help with testing. + // Called when heuristic either successfully considered for upload and + // not send or uploaded. + virtual void OnUploadedPossibleFieldTypes() {} + // Called when there was an error during the request. + // |form_signature| - the signature of the requesting form. + // |request_type| - type of request that failed. + // |http_error| - HTTP error code. + virtual void OnServerRequestError(const std::string& form_signature, + AutofillRequestType request_type, + int http_error) {} + + protected: + virtual ~Observer() {} + }; + + // |observer| - observer to notify on successful completion or error. + AutofillDownloadManager(content::BrowserContext* context, + Observer* observer); + virtual ~AutofillDownloadManager(); + + // Starts a query request to Autofill servers. The observer is called with the + // list of the fields of all requested forms. + // |forms| - array of forms aggregated in this request. + bool StartQueryRequest(const std::vector<FormStructure*>& forms, + const AutofillMetrics& metric_logger); + + // Starts an upload request for the given |form|, unless throttled by the + // server. The probability of the request going over the wire is + // GetPositiveUploadRate() if |form_was_autofilled| is true, or + // GetNegativeUploadRate() otherwise. The observer will be called even if + // there was no actual trip over the wire. + // |available_field_types| should contain the types for which we have data + // stored on the local client. + bool StartUploadRequest(const FormStructure& form, + bool form_was_autofilled, + const FieldTypeSet& available_field_types); + + private: + friend class AutofillDownloadTest; + FRIEND_TEST_ALL_PREFIXES(AutofillDownloadTest, QueryAndUploadTest); + + static std::string AutofillRequestTypeToString(const AutofillRequestType); + + struct FormRequestData; + typedef std::list<std::pair<std::string, std::string> > QueryRequestCache; + + // Initiates request to Autofill servers to download/upload heuristics. + // |form_xml| - form structure XML to upload/download. + // |request_data| - form signature hash(es) and indicator if it was a query. + // |request_data.query| - if true the data is queried and observer notified + // with new data, if available. If false heuristic data is uploaded to our + // servers. + bool StartRequest(const std::string& form_xml, + const FormRequestData& request_data); + + // Each request is page visited. We store last |max_form_cache_size| + // request, to avoid going over the wire. Set to 16 in constructor. Warning: + // the search is linear (newest first), so do not make the constant very big. + void set_max_form_cache_size(size_t max_form_cache_size) { + max_form_cache_size_ = max_form_cache_size; + } + + // Caches query request. |forms_in_query| is a vector of form signatures in + // the query. |query_data| is the successful data returned over the wire. + void CacheQueryRequest(const std::vector<std::string>& forms_in_query, + const std::string& query_data); + // Returns true if query is in the cache, while filling |query_data|, false + // otherwise. |forms_in_query| is a vector of form signatures in the query. + bool CheckCacheForQueryRequest(const std::vector<std::string>& forms_in_query, + std::string* query_data) const; + // Concatenates |forms_in_query| into one signature. + std::string GetCombinedSignature( + const std::vector<std::string>& forms_in_query) const; + + // net::URLFetcherDelegate implementation: + virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; + + // Probability of the form upload. Between 0 (no upload) and 1 (upload all). + // GetPositiveUploadRate() is for matched forms, + // GetNegativeUploadRate() for non-matched. + double GetPositiveUploadRate() const; + double GetNegativeUploadRate() const; + void SetPositiveUploadRate(double rate); + void SetNegativeUploadRate(double rate); + + // The pointer value is const, so this can only be set in the + // constructor. Must not be null. + content::BrowserContext* const browser_context_; // WEAK + + // The observer to notify when server predictions are successfully received. + // The pointer value is const, so this can only be set in the constructor. + // Must not be null. + AutofillDownloadManager::Observer* const observer_; // WEAK + + // For each requested form for both query and upload we create a separate + // request and save its info. As url fetcher is identified by its address + // we use a map between fetchers and info. + std::map<net::URLFetcher*, FormRequestData> url_fetchers_; + + // Cached QUERY requests. + QueryRequestCache cached_forms_; + size_t max_form_cache_size_; + + // Time when next query/upload requests are allowed. If 50x HTTP received, + // exponential back off is initiated, so this times will be in the future + // for awhile. + base::Time next_query_request_; + base::Time next_upload_request_; + + // |positive_upload_rate_| is for matched forms, + // |negative_upload_rate_| for non matched. + double positive_upload_rate_; + double negative_upload_rate_; + + // Needed for unit-test. + int fetcher_id_for_unittest_; +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_DOWNLOAD_H_ diff --git a/components/autofill/browser/autofill_download_unittest.cc b/components/autofill/browser/autofill_download_unittest.cc new file mode 100644 index 0000000..3218bcc --- /dev/null +++ b/components/autofill/browser/autofill_download_unittest.cc @@ -0,0 +1,505 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <list> + +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/test/test_timeouts.h" +#include "base/utf_string_conversions.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/autofill_download.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/common/form_data.h" +#include "content/public/test/test_browser_thread.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_request_status.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" + +using content::BrowserThread; +using WebKit::WebInputElement; + +namespace { + +class MockAutofillMetrics : public AutofillMetrics { + public: + MockAutofillMetrics() {} + MOCK_CONST_METHOD1(LogServerQueryMetric, void(ServerQueryMetric metric)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillMetrics); +}; + +// Call |fetcher->OnURLFetchComplete()| as the URLFetcher would when +// a response is received. Params allow caller to set fake status. +void FakeOnURLFetchComplete(net::TestURLFetcher* fetcher, + int response_code, + const std::string& response_body) { + fetcher->set_url(GURL()); + fetcher->set_status(net::URLRequestStatus()); + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response_body); + + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +} // namespace + +// This tests AutofillDownloadManager. AutofillDownloadTest implements +// AutofillDownloadManager::Observer and creates an instance of +// AutofillDownloadManager. Then it records responses to different initiated +// requests, which are verified later. To mock network requests +// TestURLFetcherFactory is used, which creates URLFetchers that do not +// go over the wire, but allow calling back HTTP responses directly. +// The responses in test are out of order and verify: successful query request, +// successful upload request, failed upload request. +class AutofillDownloadTest : public AutofillDownloadManager::Observer, + public testing::Test { + public: + AutofillDownloadTest() + : download_manager_(&profile_, this), + io_thread_(BrowserThread::IO) { + } + + virtual void SetUp() { + io_thread_.StartIOThread(); + profile_.CreateRequestContext(); + } + + virtual void TearDown() { + profile_.ResetRequestContext(); + io_thread_.Stop(); + } + + void LimitCache(size_t cache_size) { + download_manager_.set_max_form_cache_size(cache_size); + } + + // AutofillDownloadManager::Observer implementation. + virtual void OnLoadedServerPredictions( + const std::string& response_xml) OVERRIDE { + ResponseData response; + response.response = response_xml; + response.type_of_response = QUERY_SUCCESSFULL; + responses_.push_back(response); + } + + virtual void OnUploadedPossibleFieldTypes() OVERRIDE { + ResponseData response; + response.type_of_response = UPLOAD_SUCCESSFULL; + responses_.push_back(response); + } + + virtual void OnServerRequestError( + const std::string& form_signature, + AutofillDownloadManager::AutofillRequestType request_type, + int http_error) OVERRIDE { + ResponseData response; + response.signature = form_signature; + response.error = http_error; + response.type_of_response = + request_type == AutofillDownloadManager::REQUEST_QUERY ? + REQUEST_QUERY_FAILED : REQUEST_UPLOAD_FAILED; + responses_.push_back(response); + } + + enum ResponseType { + QUERY_SUCCESSFULL, + UPLOAD_SUCCESSFULL, + REQUEST_QUERY_FAILED, + REQUEST_UPLOAD_FAILED, + }; + + struct ResponseData { + ResponseType type_of_response; + int error; + std::string signature; + std::string response; + + ResponseData() : type_of_response(REQUEST_QUERY_FAILED), error(0) {} + }; + std::list<ResponseData> responses_; + + TestingProfile profile_; + AutofillDownloadManager download_manager_; + + private: + // The profile's request context must be released on the IO thread. + content::TestBrowserThread io_thread_; +}; + +TEST_F(AutofillDownloadTest, QueryAndUploadTest) { + MessageLoopForUI message_loop; + // Create and register factory. + net::TestURLFetcherFactory factory; + + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.label = ASCIIToUTF16("username"); + field.name = ASCIIToUTF16("username"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("firstname"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("lastname"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("email"); + field.name = ASCIIToUTF16("email"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("email2"); + field.name = ASCIIToUTF16("email2"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("password"); + field.name = ASCIIToUTF16("password"); + field.form_control_type = "password"; + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + FormStructure *form_structure = new FormStructure(form, std::string()); + ScopedVector<FormStructure> form_structures; + form_structures.push_back(form_structure); + + form.fields.clear(); + + field.label = ASCIIToUTF16("address"); + field.name = ASCIIToUTF16("address"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("address2"); + field.name = ASCIIToUTF16("address2"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("city"); + field.name = ASCIIToUTF16("city"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + form_structure = new FormStructure(form, std::string()); + form_structures.push_back(form_structure); + + // Request with id 0. + MockAutofillMetrics mock_metric_logger; + EXPECT_CALL(mock_metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(download_manager_.StartQueryRequest(form_structures.get(), + mock_metric_logger)); + // Set upload to 100% so requests happen. + download_manager_.SetPositiveUploadRate(1.0); + download_manager_.SetNegativeUploadRate(1.0); + // Request with id 1. + EXPECT_TRUE(download_manager_.StartUploadRequest( + *(form_structures[0]), true, FieldTypeSet())); + // Request with id 2. + EXPECT_TRUE(download_manager_.StartUploadRequest( + *(form_structures[1]), false, FieldTypeSet())); + + const char *responses[] = { + "<autofillqueryresponse>" + "<field autofilltype=\"0\" />" + "<field autofilltype=\"3\" />" + "<field autofilltype=\"5\" />" + "<field autofilltype=\"9\" />" + "<field autofilltype=\"0\" />" + "<field autofilltype=\"30\" />" + "<field autofilltype=\"31\" />" + "<field autofilltype=\"33\" />" + "</autofillqueryresponse>", + "<autofilluploadresponse positiveuploadrate=\"0.5\" " + "negativeuploadrate=\"0.3\"/>", + "<html></html>", + }; + + // Return them out of sequence. + net::TestURLFetcher* fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(fetcher); + FakeOnURLFetchComplete(fetcher, 200, std::string(responses[1])); + + // After that upload rates would be adjusted to 0.5/0.3 + EXPECT_DOUBLE_EQ(0.5, download_manager_.GetPositiveUploadRate()); + EXPECT_DOUBLE_EQ(0.3, download_manager_.GetNegativeUploadRate()); + + fetcher = factory.GetFetcherByID(2); + ASSERT_TRUE(fetcher); + FakeOnURLFetchComplete(fetcher, 404, std::string(responses[2])); + + fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + FakeOnURLFetchComplete(fetcher, 200, std::string(responses[0])); + EXPECT_EQ(static_cast<size_t>(3), responses_.size()); + + EXPECT_EQ(AutofillDownloadTest::UPLOAD_SUCCESSFULL, + responses_.front().type_of_response); + EXPECT_EQ(0, responses_.front().error); + EXPECT_EQ(std::string(), responses_.front().signature); + // Expected response on non-query request is an empty string. + EXPECT_EQ(std::string(), responses_.front().response); + responses_.pop_front(); + + EXPECT_EQ(AutofillDownloadTest::REQUEST_UPLOAD_FAILED, + responses_.front().type_of_response); + EXPECT_EQ(404, responses_.front().error); + EXPECT_EQ(form_structures[1]->FormSignature(), + responses_.front().signature); + // Expected response on non-query request is an empty string. + EXPECT_EQ(std::string(), responses_.front().response); + responses_.pop_front(); + + EXPECT_EQ(responses_.front().type_of_response, + AutofillDownloadTest::QUERY_SUCCESSFULL); + EXPECT_EQ(0, responses_.front().error); + EXPECT_EQ(std::string(), responses_.front().signature); + EXPECT_EQ(responses[0], responses_.front().response); + responses_.pop_front(); + + // Set upload to 0% so no new requests happen. + download_manager_.SetPositiveUploadRate(0.0); + download_manager_.SetNegativeUploadRate(0.0); + // No actual requests for the next two calls, as we set upload rate to 0%. + EXPECT_FALSE(download_manager_.StartUploadRequest( + *(form_structures[0]), true, FieldTypeSet())); + EXPECT_FALSE(download_manager_.StartUploadRequest( + *(form_structures[1]), false, FieldTypeSet())); + fetcher = factory.GetFetcherByID(3); + EXPECT_EQ(NULL, fetcher); + + // Modify form structures to miss the cache. + field.label = ASCIIToUTF16("Address line 2"); + field.name = ASCIIToUTF16("address2"); + field.form_control_type = "text"; + form.fields.push_back(field); + form_structure = new FormStructure(form, std::string()); + form_structures.push_back(form_structure); + + // Request with id 3. + EXPECT_CALL(mock_metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(download_manager_.StartQueryRequest(form_structures.get(), + mock_metric_logger)); + fetcher = factory.GetFetcherByID(3); + ASSERT_TRUE(fetcher); + fetcher->set_backoff_delay(TestTimeouts::action_max_timeout()); + FakeOnURLFetchComplete(fetcher, 500, std::string(responses[0])); + + EXPECT_EQ(AutofillDownloadTest::REQUEST_QUERY_FAILED, + responses_.front().type_of_response); + EXPECT_EQ(500, responses_.front().error); + // Expected response on non-query request is an empty string. + EXPECT_EQ(std::string(), responses_.front().response); + responses_.pop_front(); + + // Query requests should be ignored for the next 10 seconds. + EXPECT_CALL(mock_metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_SENT)).Times(0); + EXPECT_FALSE(download_manager_.StartQueryRequest(form_structures.get(), + mock_metric_logger)); + fetcher = factory.GetFetcherByID(4); + EXPECT_EQ(NULL, fetcher); + + // Set upload required to true so requests happen. + form_structures[0]->upload_required_ = UPLOAD_REQUIRED; + // Request with id 4. + EXPECT_TRUE(download_manager_.StartUploadRequest( + *(form_structures[0]), true, FieldTypeSet())); + fetcher = factory.GetFetcherByID(4); + ASSERT_TRUE(fetcher); + fetcher->set_backoff_delay(TestTimeouts::action_max_timeout()); + FakeOnURLFetchComplete(fetcher, 503, std::string(responses[2])); + EXPECT_EQ(AutofillDownloadTest::REQUEST_UPLOAD_FAILED, + responses_.front().type_of_response); + EXPECT_EQ(503, responses_.front().error); + responses_.pop_front(); + + // Upload requests should be ignored for the next 10 seconds. + EXPECT_FALSE(download_manager_.StartUploadRequest( + *(form_structures[0]), true, FieldTypeSet())); + fetcher = factory.GetFetcherByID(5); + EXPECT_EQ(NULL, fetcher); +} + +TEST_F(AutofillDownloadTest, CacheQueryTest) { + MessageLoopForUI message_loop; + // Create and register factory. + net::TestURLFetcherFactory factory; + + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("username"); + field.name = ASCIIToUTF16("username"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("firstname"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("lastname"); + form.fields.push_back(field); + + FormStructure *form_structure = new FormStructure(form, std::string()); + ScopedVector<FormStructure> form_structures0; + form_structures0.push_back(form_structure); + + // Add a slightly different form, which should result in a different request. + field.label = ASCIIToUTF16("email"); + field.name = ASCIIToUTF16("email"); + form.fields.push_back(field); + form_structure = new FormStructure(form, std::string()); + ScopedVector<FormStructure> form_structures1; + form_structures1.push_back(form_structure); + + // Add another slightly different form, which should also result in a + // different request. + field.label = ASCIIToUTF16("email2"); + field.name = ASCIIToUTF16("email2"); + form.fields.push_back(field); + form_structure = new FormStructure(form, std::string()); + ScopedVector<FormStructure> form_structures2; + form_structures2.push_back(form_structure); + + // Limit cache to two forms. + LimitCache(2); + + const char *responses[] = { + "<autofillqueryresponse>" + "<field autofilltype=\"0\" />" + "<field autofilltype=\"3\" />" + "<field autofilltype=\"5\" />" + "</autofillqueryresponse>", + "<autofillqueryresponse>" + "<field autofilltype=\"0\" />" + "<field autofilltype=\"3\" />" + "<field autofilltype=\"5\" />" + "<field autofilltype=\"9\" />" + "</autofillqueryresponse>", + "<autofillqueryresponse>" + "<field autofilltype=\"0\" />" + "<field autofilltype=\"3\" />" + "<field autofilltype=\"5\" />" + "<field autofilltype=\"9\" />" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>", + }; + + // Request with id 0. + MockAutofillMetrics mock_metric_logger; + EXPECT_CALL(mock_metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(download_manager_.StartQueryRequest(form_structures0.get(), + mock_metric_logger)); + // No responses yet + EXPECT_EQ(static_cast<size_t>(0), responses_.size()); + + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + FakeOnURLFetchComplete(fetcher, 200, std::string(responses[0])); + ASSERT_EQ(static_cast<size_t>(1), responses_.size()); + EXPECT_EQ(responses[0], responses_.front().response); + + responses_.clear(); + + // No actual request - should be a cache hit. + EXPECT_CALL(mock_metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(download_manager_.StartQueryRequest(form_structures0.get(), + mock_metric_logger)); + // Data is available immediately from cache - no over-the-wire trip. + ASSERT_EQ(static_cast<size_t>(1), responses_.size()); + EXPECT_EQ(responses[0], responses_.front().response); + responses_.clear(); + + // Request with id 1. + EXPECT_CALL(mock_metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(download_manager_.StartQueryRequest(form_structures1.get(), + mock_metric_logger)); + // No responses yet + EXPECT_EQ(static_cast<size_t>(0), responses_.size()); + + fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(fetcher); + FakeOnURLFetchComplete(fetcher, 200, std::string(responses[1])); + ASSERT_EQ(static_cast<size_t>(1), responses_.size()); + EXPECT_EQ(responses[1], responses_.front().response); + + responses_.clear(); + + // Request with id 2. + EXPECT_CALL(mock_metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(download_manager_.StartQueryRequest(form_structures2.get(), + mock_metric_logger)); + + fetcher = factory.GetFetcherByID(2); + ASSERT_TRUE(fetcher); + FakeOnURLFetchComplete(fetcher, 200, std::string(responses[2])); + ASSERT_EQ(static_cast<size_t>(1), responses_.size()); + EXPECT_EQ(responses[2], responses_.front().response); + + responses_.clear(); + + // No actual requests - should be a cache hit. + EXPECT_CALL(mock_metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(download_manager_.StartQueryRequest(form_structures1.get(), + mock_metric_logger)); + + EXPECT_CALL(mock_metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(download_manager_.StartQueryRequest(form_structures2.get(), + mock_metric_logger)); + + ASSERT_EQ(static_cast<size_t>(2), responses_.size()); + EXPECT_EQ(responses[1], responses_.front().response); + EXPECT_EQ(responses[2], responses_.back().response); + responses_.clear(); + + // The first structure should've expired. + // Request with id 3. + EXPECT_CALL(mock_metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_SENT)).Times(1); + EXPECT_TRUE(download_manager_.StartQueryRequest(form_structures0.get(), + mock_metric_logger)); + // No responses yet + EXPECT_EQ(static_cast<size_t>(0), responses_.size()); + + fetcher = factory.GetFetcherByID(3); + ASSERT_TRUE(fetcher); + FakeOnURLFetchComplete(fetcher, 200, std::string(responses[0])); + ASSERT_EQ(static_cast<size_t>(1), responses_.size()); + EXPECT_EQ(responses[0], responses_.front().response); +} diff --git a/components/autofill/browser/autofill_download_url.cc b/components/autofill/browser/autofill_download_url.cc new file mode 100644 index 0000000..8c8251b --- /dev/null +++ b/components/autofill/browser/autofill_download_url.cc @@ -0,0 +1,45 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_download_url.h" + +#include <string> + +#include "base/command_line.h" +#include "components/autofill/common/autofill_switches.h" +#include "googleurl/src/gurl.h" + +namespace { + +const char kDefaultAutofillServiceUrl[] = + "https://clients1.google.com/tbproxy/af/"; + +#if defined(GOOGLE_CHROME_BUILD) +const char kClientName[] = "Google Chrome"; +#else +const char kClientName[] = "Chromium"; +#endif // defined(GOOGLE_CHROME_BUILD) + +std::string GetBaseAutofillUrl() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + std::string baseAutofillServiceUrl = command_line.GetSwitchValueASCII( + switches::kAutofillServiceUrl); + if (baseAutofillServiceUrl.empty()) + return kDefaultAutofillServiceUrl; + + return baseAutofillServiceUrl; +} + +} // anonymous namespace + +GURL autofill::GetAutofillQueryUrl() { + std::string baseAutofillServiceUrl = GetBaseAutofillUrl(); + return GURL(baseAutofillServiceUrl + "query?client=" + kClientName); +} + +GURL autofill::GetAutofillUploadUrl() { + std::string baseAutofillServiceUrl = GetBaseAutofillUrl(); + return GURL(baseAutofillServiceUrl + "upload?client=" + kClientName); +} + diff --git a/components/autofill/browser/autofill_download_url.h b/components/autofill/browser/autofill_download_url.h new file mode 100644 index 0000000..427fec3 --- /dev/null +++ b/components/autofill/browser/autofill_download_url.h @@ -0,0 +1,18 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_DOWNLOAD_URL_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_DOWNLOAD_URL_H_ + +class GURL; + +namespace autofill { + +GURL GetAutofillQueryUrl(); +GURL GetAutofillUploadUrl(); + +} + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_DOWNLOAD_URL_H_ + diff --git a/components/autofill/browser/autofill_download_url_unittest.cc b/components/autofill/browser/autofill_download_url_unittest.cc new file mode 100644 index 0000000..7ae3449 --- /dev/null +++ b/components/autofill/browser/autofill_download_url_unittest.cc @@ -0,0 +1,23 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_download_url.h" +#include "googleurl/src/gurl.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::StartsWith; + +TEST(AutofillDownloadUrlTest, CheckDefaultUrls) { + std::string query_url = + autofill::GetAutofillQueryUrl().spec(); + EXPECT_THAT(query_url, + StartsWith("https://clients1.google.com/tbproxy/af/query?client=")); + + std::string upload_url = + autofill::GetAutofillUploadUrl().spec(); + EXPECT_THAT(upload_url, + StartsWith("https://clients1.google.com/tbproxy/af/upload?client=")); +} + diff --git a/components/autofill/browser/autofill_external_delegate.cc b/components/autofill/browser/autofill_external_delegate.cc new file mode 100644 index 0000000..128beb4 --- /dev/null +++ b/components/autofill/browser/autofill_external_delegate.cc @@ -0,0 +1,382 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autocomplete_history_manager.h" +#include "components/autofill/browser/autofill_external_delegate.h" +#include "components/autofill/browser/autofill_manager.h" +#include "components/autofill/common/autofill_messages.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h" +#include "ui/base/l10n/l10n_util.h" + +#if defined(OS_ANDROID) +#include "content/public/browser/android/content_view_core.h" +#endif + +using content::RenderViewHost; +using WebKit::WebAutofillClient; + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(AutofillExternalDelegate); + +void AutofillExternalDelegate::CreateForWebContentsAndManager( + content::WebContents* web_contents, + AutofillManager* autofill_manager) { + if (FromWebContents(web_contents)) + return; + + web_contents->SetUserData( + UserDataKey(), + new AutofillExternalDelegate(web_contents, autofill_manager)); +} + +AutofillExternalDelegate::AutofillExternalDelegate( + content::WebContents* web_contents, + AutofillManager* autofill_manager) + : web_contents_(web_contents), + autofill_manager_(autofill_manager), + password_autofill_manager_(web_contents), + autofill_query_id_(0), + display_warning_if_disabled_(false), + has_autofill_suggestion_(false), + has_shown_autofill_popup_for_current_edit_(false), + registered_keyboard_listener_with_(NULL) { + DCHECK(autofill_manager); + + registrar_.Add(this, + content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, + content::Source<content::WebContents>(web_contents)); + registrar_.Add( + this, + content::NOTIFICATION_NAV_ENTRY_COMMITTED, + content::Source<content::NavigationController>( + &(web_contents->GetController()))); +} + +AutofillExternalDelegate::~AutofillExternalDelegate() {} + +void AutofillExternalDelegate::OnQuery(int query_id, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& element_bounds, + bool display_warning_if_disabled) { + autofill_query_form_ = form; + autofill_query_field_ = field; + display_warning_if_disabled_ = display_warning_if_disabled; + autofill_query_id_ = query_id; + element_bounds_ = element_bounds; +} + +void AutofillExternalDelegate::OnSuggestionsReturned( + int query_id, + const std::vector<string16>& autofill_values, + const std::vector<string16>& autofill_labels, + const std::vector<string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids) { + if (query_id != autofill_query_id_) + return; + + std::vector<string16> values(autofill_values); + std::vector<string16> labels(autofill_labels); + std::vector<string16> icons(autofill_icons); + std::vector<int> ids(autofill_unique_ids); + + // Add a separator to go between the values and menu items. + values.push_back(string16()); + labels.push_back(string16()); + icons.push_back(string16()); + ids.push_back(WebAutofillClient::MenuItemIDSeparator); + + ApplyAutofillWarnings(&values, &labels, &icons, &ids); + + // Only include "Autofill Options" special menu item if we have Autofill + // suggestions. + has_autofill_suggestion_ = false; + for (size_t i = 0; i < ids.size(); ++i) { + if (ids[i] > 0) { + has_autofill_suggestion_ = true; + break; + } + } + + if (has_autofill_suggestion_) + ApplyAutofillOptions(&values, &labels, &icons, &ids); + + // Remove the separator if it is the last element. + if (ids.back() == WebAutofillClient::MenuItemIDSeparator) { + values.pop_back(); + labels.pop_back(); + icons.pop_back(); + ids.pop_back(); + } + + InsertDataListValues(&values, &labels, &icons, &ids); + + if (values.empty()) { + // No suggestions, any popup currently showing is obsolete. + autofill_manager_->delegate()->HideAutofillPopup(); + return; + } + + // Send to display. + if (autofill_query_field_.is_focusable) { + autofill_manager_->delegate()->ShowAutofillPopup( + element_bounds_, values, labels, icons, ids, this); + } +} + +void AutofillExternalDelegate::OnShowPasswordSuggestions( + const std::vector<string16>& suggestions, + const FormFieldData& field, + const gfx::RectF& element_bounds) { + autofill_query_field_ = field; + element_bounds_ = element_bounds; + + if (suggestions.empty()) { + autofill_manager_->delegate()->HideAutofillPopup(); + return; + } + + std::vector<string16> empty(suggestions.size()); + std::vector<int> password_ids(suggestions.size(), + WebAutofillClient::MenuItemIDPasswordEntry); + autofill_manager_->delegate()->ShowAutofillPopup( + element_bounds_, suggestions, empty, empty, password_ids, this); +} + +void AutofillExternalDelegate::SetCurrentDataListValues( + const std::vector<string16>& data_list_values, + const std::vector<string16>& data_list_labels, + const std::vector<string16>& data_list_icons, + const std::vector<int>& data_list_unique_ids) { + data_list_values_ = data_list_values; + data_list_labels_ = data_list_labels; + data_list_icons_ = data_list_icons; + data_list_unique_ids_ = data_list_unique_ids; +} + +void AutofillExternalDelegate::OnPopupShown( + content::KeyboardListener* listener) { + if (!registered_keyboard_listener_with_) { + registered_keyboard_listener_with_ = web_contents_->GetRenderViewHost(); + registered_keyboard_listener_with_->AddKeyboardListener(listener); + } + + autofill_manager_->OnDidShowAutofillSuggestions( + has_autofill_suggestion_ && !has_shown_autofill_popup_for_current_edit_); + has_shown_autofill_popup_for_current_edit_ |= has_autofill_suggestion_; +} + +void AutofillExternalDelegate::OnPopupHidden( + content::KeyboardListener* listener) { + if (registered_keyboard_listener_with_ == web_contents_->GetRenderViewHost()) + web_contents_->GetRenderViewHost()->RemoveKeyboardListener(listener); + + registered_keyboard_listener_with_ = NULL; +} + +void AutofillExternalDelegate::DidSelectSuggestion(int identifier) { + ClearPreviewedForm(); + + // Only preview the data if it is a profile. + if (identifier > 0) + FillAutofillFormData(identifier, true); +} + +void AutofillExternalDelegate::DidAcceptSuggestion(const string16& value, + int identifier) { + RenderViewHost* host = web_contents_->GetRenderViewHost(); + + if (identifier == WebAutofillClient::MenuItemIDAutofillOptions) { + // User selected 'Autofill Options'. + autofill_manager_->OnShowAutofillDialog(); + } else if (identifier == WebAutofillClient::MenuItemIDClearForm) { + // User selected 'Clear form'. + host->Send(new AutofillMsg_ClearForm(host->GetRoutingID())); + } else if (identifier == WebAutofillClient::MenuItemIDPasswordEntry && + password_autofill_manager_.DidAcceptAutofillSuggestion( + autofill_query_field_, value)) { + // DidAcceptAutofillSuggestion has already handled the work to fill in + // the page as required. + } else if (identifier == WebAutofillClient::MenuItemIDDataListEntry) { + host->Send(new AutofillMsg_AcceptDataListSuggestion(host->GetRoutingID(), + value)); + } else if (identifier == WebAutofillClient::MenuItemIDAutocompleteEntry) { + // User selected an Autocomplete, so we fill directly. + host->Send(new AutofillMsg_SetNodeText(host->GetRoutingID(), value)); + } else { + FillAutofillFormData(identifier, false); + } + + autofill_manager_->delegate()->HideAutofillPopup(); +} + +void AutofillExternalDelegate::RemoveSuggestion(const string16& value, + int identifier) { + if (identifier > 0) { + autofill_manager_->RemoveAutofillProfileOrCreditCard(identifier); + } else { + autofill_manager_->RemoveAutocompleteEntry(autofill_query_field_.name, + value); + } +} + +void AutofillExternalDelegate::DidEndTextFieldEditing() { + autofill_manager_->delegate()->HideAutofillPopup(); + + has_shown_autofill_popup_for_current_edit_ = false; +} + +void AutofillExternalDelegate::ClearPreviewedForm() { + RenderViewHost* host = web_contents_->GetRenderViewHost(); + if (host) + host->Send(new AutofillMsg_ClearPreviewedForm(host->GetRoutingID())); +} + +void AutofillExternalDelegate::Reset() { + autofill_manager_->delegate()->HideAutofillPopup(); + + password_autofill_manager_.Reset(); +} + +void AutofillExternalDelegate::AddPasswordFormMapping( + const FormFieldData& form, + const PasswordFormFillData& fill_data) { + password_autofill_manager_.AddPasswordFormMapping(form, fill_data); +} + +void AutofillExternalDelegate::FillAutofillFormData(int unique_id, + bool is_preview) { + // If the selected element is a warning we don't want to do anything. + if (unique_id == WebAutofillClient::MenuItemIDWarningMessage) + return; + + RenderViewHost* host = web_contents_->GetRenderViewHost(); + + if (is_preview) { + host->Send(new AutofillMsg_SetAutofillActionPreview( + host->GetRoutingID())); + } else { + host->Send(new AutofillMsg_SetAutofillActionFill( + host->GetRoutingID())); + } + + // Fill the values for the whole form. + autofill_manager_->OnFillAutofillFormData(autofill_query_id_, + autofill_query_form_, + autofill_query_field_, + unique_id); +} + +void AutofillExternalDelegate::ApplyAutofillWarnings( + std::vector<string16>* autofill_values, + std::vector<string16>* autofill_labels, + std::vector<string16>* autofill_icons, + std::vector<int>* autofill_unique_ids) { + if (!autofill_query_field_.should_autocomplete) { + // If autofill is disabled and we had suggestions, show a warning instead. + autofill_values->assign( + 1, l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_FORM_DISABLED)); + autofill_labels->assign(1, string16()); + autofill_icons->assign(1, string16()); + autofill_unique_ids->assign(1, WebAutofillClient::MenuItemIDWarningMessage); + } else if (autofill_unique_ids->size() > 1 && + (*autofill_unique_ids)[0] == + WebAutofillClient::MenuItemIDWarningMessage) { + // If we received a warning instead of suggestions from autofill but regular + // suggestions from autocomplete, don't show the autofill warning. + autofill_values->erase(autofill_values->begin()); + autofill_labels->erase(autofill_labels->begin()); + autofill_icons->erase(autofill_icons->begin()); + autofill_unique_ids->erase(autofill_unique_ids->begin()); + } + + // If we were about to show a warning and we shouldn't, don't. + if (!autofill_unique_ids->empty() && + (*autofill_unique_ids)[0] == + WebAutofillClient::MenuItemIDWarningMessage && + !display_warning_if_disabled_) { + autofill_values->clear(); + autofill_labels->clear(); + autofill_icons->clear(); + autofill_unique_ids->clear(); + } +} + +void AutofillExternalDelegate::ApplyAutofillOptions( + std::vector<string16>* autofill_values, + std::vector<string16>* autofill_labels, + std::vector<string16>* autofill_icons, + std::vector<int>* autofill_unique_ids) { + // The form has been auto-filled, so give the user the chance to clear the + // form. Append the 'Clear form' menu item. + if (autofill_query_field_.is_autofilled) { + autofill_values->push_back( + l10n_util::GetStringUTF16(IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM)); + autofill_labels->push_back(string16()); + autofill_icons->push_back(string16()); + autofill_unique_ids->push_back(WebAutofillClient::MenuItemIDClearForm); + } + + // Append the 'Chrome Autofill options' menu item; + autofill_values->push_back( + l10n_util::GetStringUTF16(IDS_AUTOFILL_OPTIONS_POPUP)); + autofill_labels->push_back(string16()); + autofill_icons->push_back(string16()); + autofill_unique_ids->push_back(WebAutofillClient::MenuItemIDAutofillOptions); +} + +void AutofillExternalDelegate::InsertDataListValues( + std::vector<string16>* autofill_values, + std::vector<string16>* autofill_labels, + std::vector<string16>* autofill_icons, + std::vector<int>* autofill_unique_ids) { + if (data_list_values_.empty()) + return; + + // Insert the separator between the datalist and Autofill values (if there + // are any). + if (!autofill_values->empty()) { + autofill_values->insert(autofill_values->begin(), string16()); + autofill_labels->insert(autofill_labels->begin(), string16()); + autofill_icons->insert(autofill_icons->begin(), string16()); + autofill_unique_ids->insert(autofill_unique_ids->begin(), + WebAutofillClient::MenuItemIDSeparator); + } + + // Insert the datalist elements. + autofill_values->insert(autofill_values->begin(), + data_list_values_.begin(), + data_list_values_.end()); + autofill_labels->insert(autofill_labels->begin(), + data_list_labels_.begin(), + data_list_labels_.end()); + autofill_icons->insert(autofill_icons->begin(), + data_list_icons_.begin(), + data_list_icons_.end()); + autofill_unique_ids->insert(autofill_unique_ids->begin(), + data_list_unique_ids_.begin(), + data_list_unique_ids_.end()); +} + +void AutofillExternalDelegate::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + if (type == content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED) { + if (!*content::Details<bool>(details).ptr()) + autofill_manager_->delegate()->HideAutofillPopup(); + } else if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) { + autofill_manager_->delegate()->HideAutofillPopup(); + } else { + NOTREACHED(); + } +} diff --git a/components/autofill/browser/autofill_external_delegate.h b/components/autofill/browser/autofill_external_delegate.h new file mode 100644 index 0000000..48a5236 --- /dev/null +++ b/components/autofill/browser/autofill_external_delegate.h @@ -0,0 +1,189 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_EXTERNAL_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_EXTERNAL_DELEGATE_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/string16.h" +#include "components/autofill/browser/autofill_popup_delegate.h" +#include "components/autofill/browser/password_autofill_manager.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_field_data.h" +#include "components/autofill/common/password_form_fill_data.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/web_contents_user_data.h" +#include "ui/gfx/rect.h" + +class AutofillManager; + +namespace gfx { +class Rect; +} + +namespace content { +class WebContents; +} + +// TODO(csharp): A lot of the logic in this class is copied from autofillagent. +// Once Autofill is moved out of WebKit this class should be the only home for +// this logic. See http://crbug.com/51644 + +// Delegate for in-browser Autocomplete and Autofill display and selection. +class AutofillExternalDelegate + : public content::WebContentsUserData<AutofillExternalDelegate>, + public content::NotificationObserver, + public AutofillPopupDelegate { + public: + // Creates an AutofillExternalDelegate and attaches it to the specified + // contents; the second argument is an AutofillManager managing Autofill for + // that WebContents. + static void CreateForWebContentsAndManager(content::WebContents* web_contents, + AutofillManager* autofill_manager); + + // AutofillPopupDelegate implementation. + virtual void OnPopupShown(content::KeyboardListener* listener) OVERRIDE; + virtual void OnPopupHidden(content::KeyboardListener* listener) OVERRIDE; + virtual void DidSelectSuggestion(int identifier) OVERRIDE; + virtual void DidAcceptSuggestion(const string16& value, + int identifier) OVERRIDE; + virtual void RemoveSuggestion(const string16& value, int identifier) OVERRIDE; + virtual void ClearPreviewedForm() OVERRIDE; + + // Records and associates a query_id with web form data. Called + // when the renderer posts an Autofill query to the browser. |bounds| + // is window relative. |display_warning_if_disabled| tells us if we should + // display warnings (such as autofill is disabled, but had suggestions). + // We might not want to display the warning if a website has disabled + // Autocomplete because they have their own popup, and showing our popup + // on to of theirs would be a poor user experience. + virtual void OnQuery(int query_id, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& element_bounds, + bool display_warning_if_disabled); + + // Records query results and correctly formats them before sending them off + // to be displayed. Called when an Autofill query result is available. + virtual void OnSuggestionsReturned( + int query_id, + const std::vector<string16>& autofill_values, + const std::vector<string16>& autofill_labels, + const std::vector<string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids); + + // Show password suggestions in the popup. + void OnShowPasswordSuggestions(const std::vector<string16>& suggestions, + const FormFieldData& field, + const gfx::RectF& bounds); + + // Set the data list value associated with the current field. + void SetCurrentDataListValues(const std::vector<string16>& autofill_values, + const std::vector<string16>& autofill_labels, + const std::vector<string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids); + + // Inform the delegate that the text field editing has ended. This is + // used to help record the metrics of when a new popup is shown. + void DidEndTextFieldEditing(); + + // Returns the delegate to its starting state by removing any page specific + // values or settings. + void Reset(); + + // Inform the Password Manager of a filled form. + void AddPasswordFormMapping( + const FormFieldData& form, + const PasswordFormFillData& fill_data); + + protected: + friend class content::WebContentsUserData<AutofillExternalDelegate>; + AutofillExternalDelegate(content::WebContents* web_contents, + AutofillManager* autofill_manager); + virtual ~AutofillExternalDelegate(); + + content::WebContents* web_contents() { return web_contents_; } + + private: + // Fills the form with the Autofill data corresponding to |unique_id|. + // If |is_preview| is true then this is just a preview to show the user what + // would be selected and if |is_preview| is false then the user has selected + // this data. + void FillAutofillFormData(int unique_id, bool is_preview); + + // Handle applying any Autofill warnings to the Autofill popup. + void ApplyAutofillWarnings(std::vector<string16>* autofill_values, + std::vector<string16>* autofill_labels, + std::vector<string16>* autofill_icons, + std::vector<int>* autofill_unique_ids); + + // Handle applying any Autofill option listings to the Autofill popup. + // This function should only get called when there is at least one + // multi-field suggestion in the list of suggestions. + void ApplyAutofillOptions(std::vector<string16>* autofill_values, + std::vector<string16>* autofill_labels, + std::vector<string16>* autofill_icons, + std::vector<int>* autofill_unique_ids); + + // Insert the data list values at the start of the given list, including + // any required separators. + void InsertDataListValues(std::vector<string16>* autofill_values, + std::vector<string16>* autofill_labels, + std::vector<string16>* autofill_icons, + std::vector<int>* autofill_unique_ids); + + // content::NotificationObserver method override. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // The web_contents associated with this delegate. + content::WebContents* web_contents_; // weak; owns me. + AutofillManager* autofill_manager_; // weak. + + // Password Autofill manager, handles all password-related Autofilling. + PasswordAutofillManager password_autofill_manager_; + + // The ID of the last request sent for form field Autofill. Used to ignore + // out of date responses. + int autofill_query_id_; + + // A scoped container for notification registries. + content::NotificationRegistrar registrar_; + + // The current form and field selected by Autofill. + FormData autofill_query_form_; + FormFieldData autofill_query_field_; + + // The bounds of the form field that user is interacting with. + gfx::RectF element_bounds_; + + // Should we display a warning if Autofill is disabled? + bool display_warning_if_disabled_; + + // Does the popup include any Autofill profile or credit card suggestions? + bool has_autofill_suggestion_; + + // Have we already shown Autofill suggestions for the field the user is + // currently editing? Used to keep track of state for metrics logging. + bool has_shown_autofill_popup_for_current_edit_; + + // The RenderViewHost that this object has been registered with as a + // keyboard listener. + content::RenderViewHost* registered_keyboard_listener_with_; + + // The current data list values. + std::vector<string16> data_list_values_; + std::vector<string16> data_list_labels_; + std::vector<string16> data_list_icons_; + std::vector<int> data_list_unique_ids_; + + DISALLOW_COPY_AND_ASSIGN(AutofillExternalDelegate); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_EXTERNAL_DELEGATE_H_ diff --git a/components/autofill/browser/autofill_external_delegate_unittest.cc b/components/autofill/browser/autofill_external_delegate_unittest.cc new file mode 100644 index 0000000..e6b5cb5 --- /dev/null +++ b/components/autofill/browser/autofill_external_delegate_unittest.cc @@ -0,0 +1,308 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/string16.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/autofill_manager.h" +#include "components/autofill/browser/test_autofill_external_delegate.h" +#include "components/autofill/browser/test_autofill_manager_delegate.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_field_data.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h" +#include "ui/gfx/rect.h" + +using content::BrowserThread; +using testing::_; +using WebKit::WebAutofillClient; + +namespace { + +// A constant value to use as the Autofill query ID. +const int kQueryId = 5; + +// A constant value to use as an Autofill profile ID. +const int kAutofillProfileId = 1; + +class MockAutofillExternalDelegate : public AutofillExternalDelegate { + public: + MockAutofillExternalDelegate(content::WebContents* web_contents, + AutofillManager* autofill_manger) + : AutofillExternalDelegate(web_contents, autofill_manger) {} + + ~MockAutofillExternalDelegate() {} + + MOCK_METHOD0(ClearPreviewedForm, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillExternalDelegate); +}; + +class MockAutofillManagerDelegate + : public autofill::TestAutofillManagerDelegate { + public: + MockAutofillManagerDelegate() {} + + MOCK_METHOD6(ShowAutofillPopup, + void(const gfx::RectF& element_bounds, + const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& identifiers, + AutofillPopupDelegate* delegate)); + + MOCK_METHOD0(HideAutofillPopup, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillManagerDelegate); +}; + +class MockAutofillManager : public AutofillManager { + public: + MockAutofillManager(content::WebContents* web_contents, + MockAutofillManagerDelegate* delegate) + // Force to use the constructor designated for unit test, but we don't + // really need personal_data in this test so we pass a NULL pointer. + : AutofillManager(web_contents, delegate, NULL) { + } + virtual ~MockAutofillManager() {} + + MOCK_METHOD4(OnFillAutofillFormData, + void(int query_id, + const FormData& form, + const FormFieldData& field, + int unique_id)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillManager); +}; + +} // namespace + +class AutofillExternalDelegateUnitTest + : public ChromeRenderViewHostTestHarness { + public: + AutofillExternalDelegateUnitTest() + : ui_thread_(BrowserThread::UI, &message_loop_) {} + virtual ~AutofillExternalDelegateUnitTest() {} + + protected: + // Issue an OnQuery call with the given |query_id|. + void IssueOnQuery(int query_id) { + const FormData form; + FormFieldData field; + field.is_focusable = true; + field.should_autocomplete = true; + const gfx::RectF element_bounds; + + external_delegate_->OnQuery(query_id, form, field, element_bounds, false); + } + + MockAutofillManagerDelegate manager_delegate_; + scoped_ptr<MockAutofillManager> autofill_manager_; + scoped_ptr<testing::NiceMock<MockAutofillExternalDelegate> > + external_delegate_; + + private: + virtual void SetUp() OVERRIDE { + ChromeRenderViewHostTestHarness::SetUp(); + autofill_manager_.reset( + new MockAutofillManager(web_contents(), &manager_delegate_)); + external_delegate_.reset( + new testing::NiceMock<MockAutofillExternalDelegate>( + web_contents(), + autofill_manager_.get())); + } + + virtual void TearDown() OVERRIDE { + // Order of destruction is important as AutofillManager relies on + // PersonalDataManager to be around when it gets destroyed. Also, a real + // AutofillManager is tied to the lifetime of the WebContents, so it must + // be destroyed at the destruction of the WebContents. + autofill_manager_.reset(); + external_delegate_.reset(); + ChromeRenderViewHostTestHarness::TearDown(); + } + + content::TestBrowserThread ui_thread_; + + DISALLOW_COPY_AND_ASSIGN(AutofillExternalDelegateUnitTest); +}; + +// Test that our external delegate called the virtual methods at the right time. +TEST_F(AutofillExternalDelegateUnitTest, TestExternalDelegateVirtualCalls) { + IssueOnQuery(kQueryId); + + // The enums must be cast to ints to prevent compile errors on linux_rel. + EXPECT_CALL(manager_delegate_, + ShowAutofillPopup( + _, _, _, _, + testing::ElementsAre( + kAutofillProfileId, + static_cast<int>(WebAutofillClient::MenuItemIDSeparator), + static_cast<int>( + WebAutofillClient::MenuItemIDAutofillOptions)), + external_delegate_.get())); + + // This should call ShowAutofillPopup. + std::vector<string16> autofill_item; + autofill_item.push_back(string16()); + std::vector<int> autofill_ids; + autofill_ids.push_back(kAutofillProfileId); + external_delegate_->OnSuggestionsReturned(kQueryId, + autofill_item, + autofill_item, + autofill_item, + autofill_ids); + + // Called by DidAutofillSuggestions, add expectation to remove warning. + EXPECT_CALL(*autofill_manager_, OnFillAutofillFormData(_, _, _, _)); + + EXPECT_CALL(manager_delegate_, HideAutofillPopup()); + + // This should trigger a call to hide the popup since we've selected an + // option. + external_delegate_->DidAcceptSuggestion(autofill_item[0], autofill_ids[0]); +} + +// Test that data list elements for a node will appear in the Autofill popup. +TEST_F(AutofillExternalDelegateUnitTest, ExternalDelegateDataList) { + IssueOnQuery(kQueryId); + + std::vector<string16> data_list_items; + data_list_items.push_back(string16()); + std::vector<int> data_list_ids; + data_list_ids.push_back(WebAutofillClient::MenuItemIDDataListEntry); + + external_delegate_->SetCurrentDataListValues(data_list_items, + data_list_items, + data_list_items, + data_list_ids); + + // The enums must be cast to ints to prevent compile errors on linux_rel. + EXPECT_CALL(manager_delegate_, + ShowAutofillPopup( + _, _, _, _, + testing::ElementsAre( + static_cast<int>( + WebAutofillClient::MenuItemIDDataListEntry), + static_cast<int>(WebAutofillClient::MenuItemIDSeparator), + kAutofillProfileId, + static_cast<int>(WebAutofillClient::MenuItemIDSeparator), + static_cast<int>( + WebAutofillClient::MenuItemIDAutofillOptions)), + external_delegate_.get())); + + // This should call ShowAutofillPopup. + std::vector<string16> autofill_item; + autofill_item.push_back(string16()); + std::vector<int> autofill_ids; + autofill_ids.push_back(kAutofillProfileId); + external_delegate_->OnSuggestionsReturned(kQueryId, + autofill_item, + autofill_item, + autofill_item, + autofill_ids); + + // Try calling OnSuggestionsReturned with no Autofill values and ensure + // the datalist items are still shown. + // The enum must be cast to an int to prevent compile errors on linux_rel. + EXPECT_CALL(manager_delegate_, + ShowAutofillPopup( + _, _, _, _, + testing::ElementsAre( + static_cast<int>( + WebAutofillClient::MenuItemIDDataListEntry)), + external_delegate_.get())); + + autofill_item = std::vector<string16>(); + autofill_ids = std::vector<int>(); + external_delegate_->OnSuggestionsReturned(kQueryId, + autofill_item, + autofill_item, + autofill_item, + autofill_ids); +} + +// Test that the Autofill delegate doesn't try and fill a form with a +// negative unique id. +TEST_F(AutofillExternalDelegateUnitTest, ExternalDelegateInvalidUniqueId) { + // Ensure it doesn't try to preview the negative id. + EXPECT_CALL(*autofill_manager_, OnFillAutofillFormData(_, _, _, _)).Times(0); + EXPECT_CALL(*external_delegate_, ClearPreviewedForm()).Times(1); + external_delegate_->DidSelectSuggestion(-1); + + // Ensure it doesn't try to fill the form in with the negative id. + EXPECT_CALL(manager_delegate_, HideAutofillPopup()); + EXPECT_CALL(*autofill_manager_, OnFillAutofillFormData(_, _, _, _)).Times(0); + external_delegate_->DidAcceptSuggestion(string16(), -1); +} + +// Test that the ClearPreview IPC is only sent the form was being previewed +// (i.e. it isn't autofilling a password). +TEST_F(AutofillExternalDelegateUnitTest, ExternalDelegateClearPreviewedForm) { + // Called by DidSelectSuggestion, add expectation to remove warning. + EXPECT_CALL(*autofill_manager_, OnFillAutofillFormData(_, _, _, _)); + + // Ensure selecting a new password entries or Autofill entries will + // cause any previews to get cleared. + EXPECT_CALL(*external_delegate_, ClearPreviewedForm()).Times(1); + external_delegate_->DidSelectSuggestion( + WebAutofillClient::MenuItemIDPasswordEntry); + + EXPECT_CALL(*external_delegate_, ClearPreviewedForm()).Times(1); + external_delegate_->DidSelectSuggestion(1); +} + +// Test that the popup is hidden once we are done editing the autofill field. +TEST_F(AutofillExternalDelegateUnitTest, + ExternalDelegateHidePopupAfterEditing) { + EXPECT_CALL(manager_delegate_, ShowAutofillPopup(_, _, _, _, _, _)); + autofill::GenerateTestAutofillPopup(external_delegate_.get()); + + EXPECT_CALL(manager_delegate_, HideAutofillPopup()); + external_delegate_->DidEndTextFieldEditing(); +} + +// Test that the popup is marked as visible after recieving password +// suggestions. +TEST_F(AutofillExternalDelegateUnitTest, ExternalDelegatePasswordSuggestions) { + std::vector<string16> suggestions; + suggestions.push_back(string16()); + + FormFieldData field; + field.is_focusable = true; + field.should_autocomplete = true; + const gfx::RectF element_bounds; + + // The enums must be cast to ints to prevent compile errors on linux_rel. + EXPECT_CALL(manager_delegate_, + ShowAutofillPopup( + _, _, _, _, + testing::ElementsAre( + static_cast<int>( + WebAutofillClient::MenuItemIDPasswordEntry)), + external_delegate_.get())); + + external_delegate_->OnShowPasswordSuggestions(suggestions, + field, + element_bounds); + + // Called by DidAutofillSuggestions, add expectation to remove warning. + EXPECT_CALL(*autofill_manager_, OnFillAutofillFormData(_, _, _, _)); + + EXPECT_CALL(manager_delegate_, HideAutofillPopup()); + + // This should trigger a call to hide the popup since + // we've selected an option. + external_delegate_->DidAcceptSuggestion( + suggestions[0], + WebAutofillClient::MenuItemIDPasswordEntry); +} diff --git a/components/autofill/browser/autofill_field.cc b/components/autofill/browser/autofill_field.cc new file mode 100644 index 0000000..ba3817e --- /dev/null +++ b/components/autofill/browser/autofill_field.cc @@ -0,0 +1,84 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_field.h" + +#include "base/logging.h" +#include "base/sha1.h" +#include "base/strings/string_number_conversions.h" +#include "base/utf_string_conversions.h" + +namespace { + +static std::string Hash32Bit(const std::string& str) { + std::string hash_bin = base::SHA1HashString(str); + DCHECK_EQ(20U, hash_bin.length()); + + uint32 hash32 = ((hash_bin[0] & 0xFF) << 24) | + ((hash_bin[1] & 0xFF) << 16) | + ((hash_bin[2] & 0xFF) << 8) | + (hash_bin[3] & 0xFF); + + return base::UintToString(hash32); +} + +} // namespace + +AutofillField::AutofillField() + : server_type_(NO_SERVER_DATA), + heuristic_type_(UNKNOWN_TYPE), + phone_part_(IGNORED) { +} + +AutofillField::AutofillField(const FormFieldData& field, + const string16& unique_name) + : FormFieldData(field), + unique_name_(unique_name), + server_type_(NO_SERVER_DATA), + heuristic_type_(UNKNOWN_TYPE), + phone_part_(IGNORED) { +} + +AutofillField::~AutofillField() {} + +void AutofillField::set_heuristic_type(AutofillFieldType type) { + if (type >= 0 && type < MAX_VALID_FIELD_TYPE && + type != FIELD_WITH_DEFAULT_VALUE) { + heuristic_type_ = type; + } else { + NOTREACHED(); + // This case should not be reachable; but since this has potential + // implications on data uploaded to the server, better safe than sorry. + heuristic_type_ = UNKNOWN_TYPE; + } +} + +void AutofillField::set_server_type(AutofillFieldType type) { + // Chrome no longer supports fax numbers, but the server still does. + if (type >= PHONE_FAX_NUMBER && type <= PHONE_FAX_WHOLE_NUMBER) + return; + + server_type_ = type; +} + +AutofillFieldType AutofillField::type() const { + if (server_type_ != NO_SERVER_DATA) + return server_type_; + + return heuristic_type_; +} + +bool AutofillField::IsEmpty() const { + return value.empty(); +} + +std::string AutofillField::FieldSignature() const { + std::string field_name = UTF16ToUTF8(name); + std::string field_string = field_name + "&" + form_control_type; + return Hash32Bit(field_string); +} + +bool AutofillField::IsFieldFillable() const { + return type() != UNKNOWN_TYPE; +} diff --git a/components/autofill/browser/autofill_field.h b/components/autofill/browser/autofill_field.h new file mode 100644 index 0000000..aa43091 --- /dev/null +++ b/components/autofill/browser/autofill_field.h @@ -0,0 +1,88 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_FIELD_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_FIELD_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/string16.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/common/form_field_data.h" + +class AutofillField : public FormFieldData { + public: + enum PhonePart { + IGNORED = 0, + PHONE_PREFIX = 1, + PHONE_SUFFIX = 2, + }; + + AutofillField(); + AutofillField(const FormFieldData& field, const string16& unique_name); + virtual ~AutofillField(); + + const string16& unique_name() const { return unique_name_; } + + const std::string& section() const { return section_; } + AutofillFieldType heuristic_type() const { return heuristic_type_; } + AutofillFieldType server_type() const { return server_type_; } + const FieldTypeSet& possible_types() const { return possible_types_; } + PhonePart phone_part() const { return phone_part_; } + + // Sets the heuristic type of this field, validating the input. + void set_section(const std::string& section) { section_ = section; } + void set_heuristic_type(AutofillFieldType type); + void set_server_type(AutofillFieldType type); + void set_possible_types(const FieldTypeSet& possible_types) { + possible_types_ = possible_types; + } + void set_phone_part(PhonePart part) { phone_part_ = part; } + + // This function automatically chooses between server and heuristic autofill + // type, depending on the data available. + AutofillFieldType type() const; + + // Returns true if the value of this field is empty. + bool IsEmpty() const; + + // The unique signature of this field, composed of the field name and the html + // input type in a 32-bit hash. + std::string FieldSignature() const; + + // Returns true if the field type has been determined (without the text in the + // field). + bool IsFieldFillable() const; + + void set_default_value(const std::string& value) { default_value_ = value; } + const std::string& default_value() const { return default_value_; } + + private: + // The unique name of this field, generated by Autofill. + string16 unique_name_; + + // The unique identifier for the section (e.g. billing vs. shipping address) + // that this field belongs to. + std::string section_; + + // The type of the field, as determined by the Autofill server. + AutofillFieldType server_type_; + + // The type of the field, as determined by the local heuristics. + AutofillFieldType heuristic_type_; + + // The set of possible types for this field. + FieldTypeSet possible_types_; + + // Used to track whether this field is a phone prefix or suffix. + PhonePart phone_part_; + + // The default value returned by the Autofill server. + std::string default_value_; + + DISALLOW_COPY_AND_ASSIGN(AutofillField); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_FIELD_H_ diff --git a/components/autofill/browser/autofill_field_unittest.cc b/components/autofill/browser/autofill_field_unittest.cc new file mode 100644 index 0000000..745f6fc --- /dev/null +++ b/components/autofill/browser/autofill_field_unittest.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/field_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +TEST(AutofillFieldTest, Type) { + AutofillField field; + ASSERT_EQ(NO_SERVER_DATA, field.server_type()); + ASSERT_EQ(UNKNOWN_TYPE, field.heuristic_type()); + + // |server_type_| is NO_SERVER_DATA, so |heuristic_type_| is returned. + EXPECT_EQ(UNKNOWN_TYPE, field.type()); + + // Set the heuristic type and check it. + field.set_heuristic_type(NAME_FIRST); + EXPECT_EQ(NAME_FIRST, field.type()); + + // Set the server type and check it. + field.set_server_type(ADDRESS_BILLING_LINE1); + EXPECT_EQ(ADDRESS_BILLING_LINE1, field.type()); + + // Remove the server type to make sure the heuristic type is preserved. + field.set_server_type(NO_SERVER_DATA); + EXPECT_EQ(NAME_FIRST, field.type()); +} + +TEST(AutofillFieldTest, IsEmpty) { + AutofillField field; + ASSERT_EQ(string16(), field.value); + + // Field value is empty. + EXPECT_TRUE(field.IsEmpty()); + + // Field value is non-empty. + field.value = ASCIIToUTF16("Value"); + EXPECT_FALSE(field.IsEmpty()); +} + +TEST(AutofillFieldTest, FieldSignature) { + AutofillField field; + ASSERT_EQ(string16(), field.name); + ASSERT_EQ(std::string(), field.form_control_type); + + // Signature is empty. + EXPECT_EQ("2085434232", field.FieldSignature()); + + // Field name is set. + field.name = ASCIIToUTF16("Name"); + EXPECT_EQ("1606968241", field.FieldSignature()); + + // Field form control type is set. + field.form_control_type = "text"; + EXPECT_EQ("502192749", field.FieldSignature()); + + // Heuristic type does not affect FieldSignature. + field.set_heuristic_type(NAME_FIRST); + EXPECT_EQ("502192749", field.FieldSignature()); + + // Server type does not affect FieldSignature. + field.set_server_type(NAME_LAST); + EXPECT_EQ("502192749", field.FieldSignature()); +} + +TEST(AutofillFieldTest, IsFieldFillable) { + AutofillField field; + ASSERT_EQ(UNKNOWN_TYPE, field.type()); + + // Type is unknown. + EXPECT_FALSE(field.IsFieldFillable()); + + // Only heuristic type is set. + field.set_heuristic_type(NAME_FIRST); + EXPECT_TRUE(field.IsFieldFillable()); + + // Only server type is set. + field.set_heuristic_type(UNKNOWN_TYPE); + field.set_server_type(NAME_LAST); + EXPECT_TRUE(field.IsFieldFillable()); + + // Both types set. + field.set_heuristic_type(NAME_FIRST); + field.set_server_type(NAME_LAST); + EXPECT_TRUE(field.IsFieldFillable()); +} + +} // namespace diff --git a/components/autofill/browser/autofill_ie_toolbar_import_win.cc b/components/autofill/browser/autofill_ie_toolbar_import_win.cc new file mode 100644 index 0000000..29ba67b --- /dev/null +++ b/components/autofill/browser/autofill_ie_toolbar_import_win.cc @@ -0,0 +1,295 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_ie_toolbar_import_win.h" + +#include <stddef.h> +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/string16.h" +#include "base/win/registry.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/credit_card.h" +#include "components/autofill/browser/crypto/rc4_decryptor.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/form_group.h" +#include "components/autofill/browser/personal_data_manager.h" +#include "components/autofill/browser/personal_data_manager_observer.h" +#include "components/autofill/browser/phone_number.h" +#include "components/autofill/browser/phone_number_i18n.h" +#include "sync/util/data_encryption_win.h" + +using base::win::RegKey; + +// Forward declaration. This function is not in unnamed namespace as it +// is referenced in the unittest. +bool ImportCurrentUserProfiles(std::vector<AutofillProfile>* profiles, + std::vector<CreditCard>* credit_cards); +namespace { + +const wchar_t* const kProfileKey = + L"Software\\Google\\Google Toolbar\\4.0\\Autofill\\Profiles"; +const wchar_t* const kCreditCardKey = + L"Software\\Google\\Google Toolbar\\4.0\\Autofill\\Credit Cards"; +const wchar_t* const kPasswordHashValue = L"password_hash"; +const wchar_t* const kSaltValue = L"salt"; + +// This is RC4 decryption for Toolbar credit card data. This is necessary +// because it is not standard, so Crypto API cannot be used. +std::wstring DecryptCCNumber(const std::wstring& data) { + const wchar_t* kEmptyKey = + L"\x3605\xCEE5\xCE49\x44F7\xCF4E\xF6CC\x604B\xFCBE\xC70A\x08FD"; + const size_t kMacLen = 10; + + if (data.length() <= kMacLen) + return std::wstring(); + + RC4Decryptor rc4_algorithm(kEmptyKey); + return rc4_algorithm.Run(data.substr(kMacLen)); +} + +bool IsEmptySalt(std::wstring const& salt) { + // Empty salt in IE Toolbar is \x1\x2...\x14 + if (salt.length() != 20) + return false; + for (size_t i = 0; i < salt.length(); ++i) { + if (salt[i] != i + 1) + return false; + } + return true; +} + +string16 ReadAndDecryptValue(const RegKey& key, const wchar_t* value_name) { + DWORD data_type = REG_BINARY; + DWORD data_size = 0; + LONG result = key.ReadValue(value_name, NULL, &data_size, &data_type); + if ((result != ERROR_SUCCESS) || !data_size || data_type != REG_BINARY) + return string16(); + std::vector<uint8> data; + data.resize(data_size); + result = key.ReadValue(value_name, &(data[0]), &data_size, &data_type); + if (result == ERROR_SUCCESS) { + std::string out_data; + if (syncer::DecryptData(data, &out_data)) { + // The actual data is in UTF16 already. + if (!(out_data.size() & 1) && (out_data.size() > 2) && + !out_data[out_data.size() - 1] && !out_data[out_data.size() - 2]) { + return string16( + reinterpret_cast<const wchar_t *>(out_data.c_str())); + } + } + } + return string16(); +} + +struct { + AutofillFieldType field_type; + const wchar_t *reg_value_name; +} profile_reg_values[] = { + { NAME_FIRST, L"name_first" }, + { NAME_MIDDLE, L"name_middle" }, + { NAME_LAST, L"name_last" }, + { NAME_SUFFIX, L"name_suffix" }, + { EMAIL_ADDRESS, L"email" }, + { COMPANY_NAME, L"company_name" }, + { PHONE_HOME_NUMBER, L"phone_home_number" }, + { PHONE_HOME_CITY_CODE, L"phone_home_city_code" }, + { PHONE_HOME_COUNTRY_CODE, L"phone_home_country_code" }, + { ADDRESS_HOME_LINE1, L"address_home_line1" }, + { ADDRESS_HOME_LINE2, L"address_home_line2" }, + { ADDRESS_HOME_CITY, L"address_home_city" }, + { ADDRESS_HOME_STATE, L"address_home_state" }, + { ADDRESS_HOME_ZIP, L"address_home_zip" }, + { ADDRESS_HOME_COUNTRY, L"address_home_country" }, + { ADDRESS_BILLING_LINE1, L"address_billing_line1" }, + { ADDRESS_BILLING_LINE2, L"address_billing_line2" }, + { ADDRESS_BILLING_CITY, L"address_billing_city" }, + { ADDRESS_BILLING_STATE, L"address_billing_state" }, + { ADDRESS_BILLING_ZIP, L"address_billing_zip" }, + { ADDRESS_BILLING_COUNTRY, L"address_billing_country" }, + { CREDIT_CARD_NAME, L"credit_card_name" }, + { CREDIT_CARD_NUMBER, L"credit_card_number" }, + { CREDIT_CARD_EXP_MONTH, L"credit_card_exp_month" }, + { CREDIT_CARD_EXP_4_DIGIT_YEAR, L"credit_card_exp_4_digit_year" }, + { CREDIT_CARD_TYPE, L"credit_card_type" }, + // We do not import verification code. +}; + +typedef std::map<std::wstring, AutofillFieldType> RegToFieldMap; + +// Imports address or credit card data from the given registry |key| into the +// given |form_group|, with the help of |reg_to_field|. When importing address +// data, writes the phone data into |phone|; otherwise, |phone| should be null. +// Returns true if any fields were set, false otherwise. +bool ImportSingleFormGroup(const RegKey& key, + const RegToFieldMap& reg_to_field, + FormGroup* form_group, + PhoneNumber::PhoneCombineHelper* phone) { + if (!key.Valid()) + return false; + + bool has_non_empty_fields = false; + + const std::string app_locale = AutofillCountry::ApplicationLocale(); + for (uint32 i = 0; i < key.GetValueCount(); ++i) { + std::wstring value_name; + if (key.GetValueNameAt(i, &value_name) != ERROR_SUCCESS) + continue; + + RegToFieldMap::const_iterator it = reg_to_field.find(value_name); + if (it == reg_to_field.end()) + continue; // This field is not imported. + + string16 field_value = ReadAndDecryptValue(key, value_name.c_str()); + if (!field_value.empty()) { + if (it->second == CREDIT_CARD_NUMBER) + field_value = DecryptCCNumber(field_value); + + // Phone numbers are stored piece-by-piece, and then reconstructed from + // the pieces. The rest of the fields are set "as is". + if (!phone || !phone->SetInfo(it->second, field_value)) { + has_non_empty_fields = true; + form_group->SetInfo(it->second, field_value, app_locale); + } + } + } + + return has_non_empty_fields; +} + +// Imports address data from the given registry |key| into the given |profile|, +// with the help of |reg_to_field|. Returns true if any fields were set, false +// otherwise. +bool ImportSingleProfile(const RegKey& key, + const RegToFieldMap& reg_to_field, + AutofillProfile* profile) { + PhoneNumber::PhoneCombineHelper phone; + bool has_non_empty_fields = + ImportSingleFormGroup(key, reg_to_field, profile, &phone); + + // Now re-construct the phones if needed. + string16 constructed_number; + const std::string app_locale = AutofillCountry::ApplicationLocale(); + if (phone.ParseNumber(*profile, app_locale, &constructed_number)) { + has_non_empty_fields = true; + profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, constructed_number); + } + + return has_non_empty_fields; +} + +// Imports profiles from the IE toolbar and stores them. Asynchronous +// if PersonalDataManager has not been loaded yet. Deletes itself on completion. +class AutofillImporter : public PersonalDataManagerObserver { + public: + explicit AutofillImporter(PersonalDataManager* personal_data_manager) + : personal_data_manager_(personal_data_manager) { + personal_data_manager_->AddObserver(this); + } + + bool ImportProfiles() { + if (!ImportCurrentUserProfiles(&profiles_, &credit_cards_)) { + delete this; + return false; + } + if (personal_data_manager_->IsDataLoaded()) + OnPersonalDataChanged(); + return true; + } + + // PersonalDataManagerObserver: + virtual void OnPersonalDataChanged() OVERRIDE { + for (std::vector<AutofillProfile>::const_iterator iter = profiles_.begin(); + iter != profiles_.end(); ++iter) { + personal_data_manager_->AddProfile(*iter); + } + for (std::vector<CreditCard>::const_iterator iter = credit_cards_.begin(); + iter != credit_cards_.end(); ++iter) { + personal_data_manager_->AddCreditCard(*iter); + } + delete this; + } + + private: + ~AutofillImporter() { + personal_data_manager_->RemoveObserver(this); + } + + PersonalDataManager* personal_data_manager_; + std::vector<AutofillProfile> profiles_; + std::vector<CreditCard> credit_cards_; +}; + +} // namespace + +// Imports Autofill profiles and credit cards from IE Toolbar if present and not +// password protected. Returns true if data is successfully retrieved. False if +// there is no data, data is password protected or error occurred. +bool ImportCurrentUserProfiles(std::vector<AutofillProfile>* profiles, + std::vector<CreditCard>* credit_cards) { + DCHECK(profiles); + DCHECK(credit_cards); + + // Create a map of possible fields for a quick access. + RegToFieldMap reg_to_field; + for (size_t i = 0; i < arraysize(profile_reg_values); ++i) { + reg_to_field[std::wstring(profile_reg_values[i].reg_value_name)] = + profile_reg_values[i].field_type; + } + + base::win::RegistryKeyIterator iterator_profiles(HKEY_CURRENT_USER, + kProfileKey); + for (; iterator_profiles.Valid(); ++iterator_profiles) { + std::wstring key_name(kProfileKey); + key_name.append(L"\\"); + key_name.append(iterator_profiles.Name()); + RegKey key(HKEY_CURRENT_USER, key_name.c_str(), KEY_READ); + AutofillProfile profile; + if (ImportSingleProfile(key, reg_to_field, &profile)) { + // Combine phones into whole phone #. + profiles->push_back(profile); + } + } + string16 password_hash; + string16 salt; + RegKey cc_key(HKEY_CURRENT_USER, kCreditCardKey, KEY_READ); + if (cc_key.Valid()) { + password_hash = ReadAndDecryptValue(cc_key, kPasswordHashValue); + salt = ReadAndDecryptValue(cc_key, kSaltValue); + } + + // We import CC profiles only if they are not password protected. + if (password_hash.empty() && IsEmptySalt(salt)) { + base::win::RegistryKeyIterator iterator_cc(HKEY_CURRENT_USER, + kCreditCardKey); + for (; iterator_cc.Valid(); ++iterator_cc) { + std::wstring key_name(kCreditCardKey); + key_name.append(L"\\"); + key_name.append(iterator_cc.Name()); + RegKey key(HKEY_CURRENT_USER, key_name.c_str(), KEY_READ); + CreditCard credit_card; + if (ImportSingleFormGroup(key, reg_to_field, &credit_card, NULL)) { + string16 cc_number = credit_card.GetRawInfo(CREDIT_CARD_NUMBER); + if (!cc_number.empty()) + credit_cards->push_back(credit_card); + } + } + } + return (profiles->size() + credit_cards->size()) > 0; +} + +bool ImportAutofillDataWin(PersonalDataManager* pdm) { + // In incognito mode we do not have PDM - and we should not import anything. + if (!pdm) + return false; + AutofillImporter *importer = new AutofillImporter(pdm); + // importer will self delete. + return importer->ImportProfiles(); +} diff --git a/components/autofill/browser/autofill_ie_toolbar_import_win.h b/components/autofill/browser/autofill_ie_toolbar_import_win.h new file mode 100644 index 0000000..d89604d --- /dev/null +++ b/components/autofill/browser/autofill_ie_toolbar_import_win.h @@ -0,0 +1,20 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_IE_TOOLBAR_IMPORT_WIN_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_IE_TOOLBAR_IMPORT_WIN_H_ + +// This importer is here and not in chrome/browser/importer/toolbar_importer.cc +// because of the following: +// 1. The data is not saved in profile, but rather in registry, thus it is +// accessed without going through toolbar front end. +// 2. This applies to IE (thus Windows) toolbar only. +// 3. The functionality relevant only to and completely encapsulated in the +// autofill. +// 4. This is completely automated as opposed to Importers, which are explicit. +class PersonalDataManager; + +bool ImportAutofillDataWin(PersonalDataManager* pdm); + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_IE_TOOLBAR_IMPORT_WIN_H_ diff --git a/components/autofill/browser/autofill_ie_toolbar_import_win_unittest.cc b/components/autofill/browser/autofill_ie_toolbar_import_win_unittest.cc new file mode 100644 index 0000000..b81a833 --- /dev/null +++ b/components/autofill/browser/autofill_ie_toolbar_import_win_unittest.cc @@ -0,0 +1,204 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_ie_toolbar_import_win.h" + +#include "base/basictypes.h" +#include "base/string16.h" +#include "base/win/registry.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/credit_card.h" +#include "components/autofill/browser/field_types.h" +#include "sync/util/data_encryption_win.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::win::RegKey; + +// Defined in autofill_ie_toolbar_import_win.cc. Not exposed in the header file. +bool ImportCurrentUserProfiles(std::vector<AutofillProfile>* profiles, + std::vector<CreditCard>* credit_cards); + +namespace { + +const wchar_t kUnitTestRegistrySubKey[] = L"SOFTWARE\\Chromium Unit Tests"; +const wchar_t kUnitTestUserOverrideSubKey[] = + L"SOFTWARE\\Chromium Unit Tests\\HKCU Override"; + +const wchar_t* const kProfileKey = + L"Software\\Google\\Google Toolbar\\4.0\\Autofill\\Profiles"; +const wchar_t* const kCreditCardKey = + L"Software\\Google\\Google Toolbar\\4.0\\Autofill\\Credit Cards"; +const wchar_t* const kPasswordHashValue = L"password_hash"; +const wchar_t* const kSaltValue = L"salt"; + +struct ValueDescription { + wchar_t const* const value_name; + wchar_t const* const value; +}; + +ValueDescription profile1[] = { + { L"name_first", L"John" }, + { L"name_middle", L"Herman" }, + { L"name_last", L"Doe" }, + { L"email", L"jdoe@test.com" }, + { L"company_name", L"Testcompany" }, + { L"phone_home_number", L"555-5555" }, + { L"phone_home_city_code", L"650" }, + { L"phone_home_country_code", L"1" }, +}; + +ValueDescription profile2[] = { + { L"name_first", L"Jane" }, + { L"name_last", L"Doe" }, + { L"email", L"janedoe@test.com" }, + { L"company_name", L"Testcompany" }, +}; + +ValueDescription credit_card[] = { + { L"credit_card_name", L"Tommy Gun" }, + // "4111111111111111" encrypted: + { L"credit_card_number", L"\xE53F\x19AB\xC1BF\xC9EB\xECCC\x9BDA\x8515" + L"\xE14D\x6852\x80A8\x50A3\x4375\xFD9F\x1E07" + L"\x790E\x7336\xB773\xAF33\x93EA\xB846\xEC89" + L"\x265C\xD0E6\x4E23\xB75F\x7983" }, + { L"credit_card_exp_month", L"11" }, + { L"credit_card_exp_4_digit_year", L"2011" }, +}; + +ValueDescription empty_salt = { + L"salt", L"\x1\x2\x3\x4\x5\x6\x7\x8\x9\xA\xB\xC\xD\xE\xF\x10\x11\x12\x13\x14" +}; + +ValueDescription empty_password = { + L"password_hash", L"" +}; + +ValueDescription protected_salt = { + L"salt", L"\x4854\xB906\x9C7C\x50A6\x4376\xFD9D\x1E02" +}; + +ValueDescription protected_password = { + L"password_hash", L"\x18B7\xE586\x459B\x7457\xA066\x3842\x71DA" +}; + +void EncryptAndWrite(RegKey* key, const ValueDescription* value) { + std::string data; + size_t data_size = (lstrlen(value->value) + 1) * sizeof(wchar_t); + data.resize(data_size); + memcpy(&data[0], value->value, data_size); + + std::vector<uint8> encrypted_data = syncer::EncryptData(data); + EXPECT_EQ(ERROR_SUCCESS, key->WriteValue(value->value_name, + &encrypted_data[0], encrypted_data.size(), REG_BINARY)); +} + +void CreateSubkey(RegKey* key, wchar_t const* subkey_name, + const ValueDescription* values, size_t values_size) { + RegKey subkey; + subkey.Create(key->Handle(), subkey_name, KEY_ALL_ACCESS); + EXPECT_TRUE(subkey.Valid()); + for (size_t i = 0; i < values_size; ++i) + EncryptAndWrite(&subkey, values + i); +} + +} // namespace + +class AutofillIeToolbarImportTest : public testing::Test { + public: + AutofillIeToolbarImportTest(); + + // testing::Test method overrides: + virtual void SetUp(); + virtual void TearDown(); + + private: + RegKey temp_hkcu_hive_key_; + + DISALLOW_COPY_AND_ASSIGN(AutofillIeToolbarImportTest); +}; + +AutofillIeToolbarImportTest::AutofillIeToolbarImportTest() { +} + +void AutofillIeToolbarImportTest::SetUp() { + temp_hkcu_hive_key_.Create(HKEY_CURRENT_USER, + kUnitTestUserOverrideSubKey, + KEY_ALL_ACCESS); + EXPECT_TRUE(temp_hkcu_hive_key_.Valid()); + EXPECT_EQ(ERROR_SUCCESS, RegOverridePredefKey(HKEY_CURRENT_USER, + temp_hkcu_hive_key_.Handle())); +} + +void AutofillIeToolbarImportTest::TearDown() { + EXPECT_EQ(ERROR_SUCCESS, RegOverridePredefKey(HKEY_CURRENT_USER, NULL)); + temp_hkcu_hive_key_.Close(); + RegKey key(HKEY_CURRENT_USER, kUnitTestRegistrySubKey, KEY_ALL_ACCESS); + key.DeleteKey(L""); +} + +TEST_F(AutofillIeToolbarImportTest, TestAutofillImport) { + RegKey profile_key; + profile_key.Create(HKEY_CURRENT_USER, kProfileKey, KEY_ALL_ACCESS); + EXPECT_TRUE(profile_key.Valid()); + + CreateSubkey(&profile_key, L"0", profile1, arraysize(profile1)); + CreateSubkey(&profile_key, L"1", profile2, arraysize(profile2)); + + RegKey cc_key; + cc_key.Create(HKEY_CURRENT_USER, kCreditCardKey, KEY_ALL_ACCESS); + EXPECT_TRUE(cc_key.Valid()); + CreateSubkey(&cc_key, L"0", credit_card, arraysize(credit_card)); + EncryptAndWrite(&cc_key, &empty_password); + EncryptAndWrite(&cc_key, &empty_salt); + + profile_key.Close(); + cc_key.Close(); + + std::vector<AutofillProfile> profiles; + std::vector<CreditCard> credit_cards; + EXPECT_TRUE(ImportCurrentUserProfiles(&profiles, &credit_cards)); + ASSERT_EQ(2U, profiles.size()); + // The profiles are read in reverse order. + EXPECT_EQ(profile1[0].value, profiles[1].GetRawInfo(NAME_FIRST)); + EXPECT_EQ(profile1[1].value, profiles[1].GetRawInfo(NAME_MIDDLE)); + EXPECT_EQ(profile1[2].value, profiles[1].GetRawInfo(NAME_LAST)); + EXPECT_EQ(profile1[3].value, profiles[1].GetRawInfo(EMAIL_ADDRESS)); + EXPECT_EQ(profile1[4].value, profiles[1].GetRawInfo(COMPANY_NAME)); + EXPECT_EQ(profile1[7].value, + profiles[1].GetInfo(PHONE_HOME_COUNTRY_CODE, "US")); + EXPECT_EQ(profile1[6].value, profiles[1].GetInfo(PHONE_HOME_CITY_CODE, "US")); + EXPECT_EQ(L"5555555", profiles[1].GetInfo(PHONE_HOME_NUMBER, "US")); + EXPECT_EQ(L"+1 650-555-5555", + profiles[1].GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); + + EXPECT_EQ(profile2[0].value, profiles[0].GetRawInfo(NAME_FIRST)); + EXPECT_EQ(profile2[1].value, profiles[0].GetRawInfo(NAME_LAST)); + EXPECT_EQ(profile2[2].value, profiles[0].GetRawInfo(EMAIL_ADDRESS)); + EXPECT_EQ(profile2[3].value, profiles[0].GetRawInfo(COMPANY_NAME)); + + ASSERT_EQ(1U, credit_cards.size()); + EXPECT_EQ(credit_card[0].value, credit_cards[0].GetRawInfo(CREDIT_CARD_NAME)); + EXPECT_EQ(L"4111111111111111", + credit_cards[0].GetRawInfo(CREDIT_CARD_NUMBER)); + EXPECT_EQ(credit_card[2].value, + credit_cards[0].GetRawInfo(CREDIT_CARD_EXP_MONTH)); + EXPECT_EQ(credit_card[3].value, + credit_cards[0].GetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR)); + + // Mock password encrypted cc. + cc_key.Open(HKEY_CURRENT_USER, kCreditCardKey, KEY_ALL_ACCESS); + EXPECT_TRUE(cc_key.Valid()); + EncryptAndWrite(&cc_key, &protected_password); + EncryptAndWrite(&cc_key, &protected_salt); + cc_key.Close(); + + profiles.clear(); + credit_cards.clear(); + EXPECT_TRUE(ImportCurrentUserProfiles(&profiles, &credit_cards)); + // Profiles are not protected. + EXPECT_EQ(2U, profiles.size()); + // Credit cards are. + EXPECT_EQ(0U, credit_cards.size()); +} + diff --git a/components/autofill/browser/autofill_manager.cc b/components/autofill/browser/autofill_manager.cc new file mode 100644 index 0000000..5d9f5ac --- /dev/null +++ b/components/autofill/browser/autofill_manager.cc @@ -0,0 +1,1321 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_manager.h" + +#include <stddef.h> + +#include <limits> +#include <map> +#include <set> +#include <utility> + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/guid.h" +#include "base/logging.h" +#include "base/prefs/pref_service.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/threading/sequenced_worker_pool.h" +#include "base/utf_string_conversions.h" +#include "chrome/common/chrome_notification_types.h" +#include "components/autofill/browser/autocheckout/whitelist_manager.h" +#include "components/autofill/browser/autocheckout_manager.h" +#include "components/autofill/browser/autocomplete_history_manager.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_external_delegate.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_manager_delegate.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/credit_card.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/browser/password_generator.h" +#include "components/autofill/browser/personal_data_manager.h" +#include "components/autofill/browser/phone_number.h" +#include "components/autofill/browser/phone_number_i18n.h" +#include "components/autofill/common/autofill_messages.h" +#include "components/autofill/common/autofill_pref_names.h" +#include "components/autofill/common/autofill_switches.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_data_predictions.h" +#include "components/autofill/common/form_field_data.h" +#include "components/autofill/common/password_form_fill_data.h" +#include "components/user_prefs/pref_registry_syncable.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_view.h" +#include "content/public/common/url_constants.h" +#include "googleurl/src/gurl.h" +#include "grit/generated_resources.h" +#include "ipc/ipc_message_macros.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/rect.h" + +typedef PersonalDataManager::GUIDPair GUIDPair; + +using base::TimeTicks; +using content::BrowserThread; +using content::RenderViewHost; +using WebKit::WebFormElement; + +namespace { + +const char* kAutofillManagerWebContentsUserDataKey = "web_contents_autofill"; + +// We only send a fraction of the forms to upload server. +// The rate for positive/negative matches potentially could be different. +const double kAutofillPositiveUploadRateDefaultValue = 0.20; +const double kAutofillNegativeUploadRateDefaultValue = 0.20; + +const size_t kMaxRecentFormSignaturesToRemember = 3; + +// Set a conservative upper bound on the number of forms we are willing to +// cache, simply to prevent unbounded memory consumption. +const size_t kMaxFormCacheSize = 100; + +// Removes duplicate suggestions whilst preserving their original order. +void RemoveDuplicateSuggestions(std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<int>* unique_ids) { + DCHECK_EQ(values->size(), labels->size()); + DCHECK_EQ(values->size(), icons->size()); + DCHECK_EQ(values->size(), unique_ids->size()); + + std::set<std::pair<string16, string16> > seen_suggestions; + std::vector<string16> values_copy; + std::vector<string16> labels_copy; + std::vector<string16> icons_copy; + std::vector<int> unique_ids_copy; + + for (size_t i = 0; i < values->size(); ++i) { + const std::pair<string16, string16> suggestion((*values)[i], (*labels)[i]); + if (seen_suggestions.insert(suggestion).second) { + values_copy.push_back((*values)[i]); + labels_copy.push_back((*labels)[i]); + icons_copy.push_back((*icons)[i]); + unique_ids_copy.push_back((*unique_ids)[i]); + } + } + + values->swap(values_copy); + labels->swap(labels_copy); + icons->swap(icons_copy); + unique_ids->swap(unique_ids_copy); +} + +// Precondition: |form_structure| and |form| should correspond to the same +// logical form. Returns true if any field in the given |section| within |form| +// is auto-filled. +bool SectionIsAutofilled(const FormStructure& form_structure, + const FormData& form, + const std::string& section) { + DCHECK_EQ(form_structure.field_count(), form.fields.size()); + for (size_t i = 0; i < form_structure.field_count(); ++i) { + if (form_structure.field(i)->section() == section && + form.fields[i].is_autofilled) { + return true; + } + } + + return false; +} + +bool FormIsHTTPS(const FormStructure& form) { + return form.source_url().SchemeIs(chrome::kHttpsScheme); +} + +// Uses the existing personal data in |profiles| and |credit_cards| to determine +// possible field types for the |submitted_form|. This is potentially +// expensive -- on the order of 50ms even for a small set of |stored_data|. +// Hence, it should not run on the UI thread -- to avoid locking up the UI -- +// nor on the IO thread -- to avoid blocking IPC calls. +void DeterminePossibleFieldTypesForUpload( + const std::vector<AutofillProfile>& profiles, + const std::vector<CreditCard>& credit_cards, + const std::string& app_locale, + FormStructure* submitted_form) { + DCHECK(BrowserThread::GetBlockingPool()->RunsTasksOnCurrentThread()); + + // For each field in the |submitted_form|, extract the value. Then for each + // profile or credit card, identify any stored types that match the value. + for (size_t i = 0; i < submitted_form->field_count(); ++i) { + AutofillField* field = submitted_form->field(i); + string16 value = CollapseWhitespace(field->value, false); + + FieldTypeSet matching_types; + for (std::vector<AutofillProfile>::const_iterator it = profiles.begin(); + it != profiles.end(); ++it) { + it->GetMatchingTypes(value, app_locale, &matching_types); + } + for (std::vector<CreditCard>::const_iterator it = credit_cards.begin(); + it != credit_cards.end(); ++it) { + it->GetMatchingTypes(value, app_locale, &matching_types); + } + + if (matching_types.empty()) + matching_types.insert(UNKNOWN_TYPE); + + field->set_possible_types(matching_types); + } +} + +} // namespace + +// static +void AutofillManager::CreateForWebContentsAndDelegate( + content::WebContents* contents, + autofill::AutofillManagerDelegate* delegate) { + if (FromWebContents(contents)) + return; + + contents->SetUserData(kAutofillManagerWebContentsUserDataKey, + new AutofillManager(contents, delegate)); +} + +// static +AutofillManager* AutofillManager::FromWebContents( + content::WebContents* contents) { + return static_cast<AutofillManager*>( + contents->GetUserData(kAutofillManagerWebContentsUserDataKey)); +} + +AutofillManager::AutofillManager(content::WebContents* web_contents, + autofill::AutofillManagerDelegate* delegate) + : content::WebContentsObserver(web_contents), + manager_delegate_(delegate), + personal_data_(delegate->GetPersonalDataManager()), + download_manager_(web_contents->GetBrowserContext(), this), + disable_download_manager_requests_(false), + autocomplete_history_manager_(web_contents), + autocheckout_manager_(this), + metric_logger_(new AutofillMetrics), + has_logged_autofill_enabled_(false), + has_logged_address_suggestions_count_(false), + did_show_suggestions_(false), + user_did_type_(false), + user_did_autofill_(false), + user_did_edit_autofilled_field_(false), + password_generation_enabled_(false), + external_delegate_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { + RegisterWithSyncService(); + registrar_.Init(manager_delegate_->GetPrefs()); + registrar_.Add( + prefs::kPasswordGenerationEnabled, + base::Bind(&AutofillManager::OnPasswordGenerationEnabledChanged, + base::Unretained(this))); +} + +AutofillManager::~AutofillManager() { +} + +// static +void AutofillManager::RegisterUserPrefs(PrefRegistrySyncable* registry) { + registry->RegisterBooleanPref(prefs::kAutofillEnabled, + true, + PrefRegistrySyncable::SYNCABLE_PREF); + registry->RegisterBooleanPref(prefs::kPasswordGenerationEnabled, + true, + PrefRegistrySyncable::SYNCABLE_PREF); +#if defined(OS_MACOSX) + registry->RegisterBooleanPref(prefs::kAutofillAuxiliaryProfilesEnabled, + true, + PrefRegistrySyncable::SYNCABLE_PREF); +#else + registry->RegisterBooleanPref(prefs::kAutofillAuxiliaryProfilesEnabled, + false, + PrefRegistrySyncable::UNSYNCABLE_PREF); +#endif + registry->RegisterDoublePref(prefs::kAutofillPositiveUploadRate, + kAutofillPositiveUploadRateDefaultValue, + PrefRegistrySyncable::UNSYNCABLE_PREF); + registry->RegisterDoublePref(prefs::kAutofillNegativeUploadRate, + kAutofillNegativeUploadRateDefaultValue, + PrefRegistrySyncable::UNSYNCABLE_PREF); +} + +void AutofillManager::RegisterWithSyncService() { + // TODO(joi): If/when SupportsWebData supports structured + // destruction ordering, we could use a base::Unretained here and + // just unsubscribe in our destructor. As is, we can't guarantee + // that the delegate doesn't get destroyed (by WebContent's + // SupportsUserData) right before the AutofillManager. + manager_delegate_->SetSyncStateChangedCallback(base::Bind( + &AutofillManager::OnSyncStateChanged, weak_ptr_factory_.GetWeakPtr())); +} + +void AutofillManager::SendPasswordGenerationStateToRenderer( + content::RenderViewHost* host, bool enabled) { + host->Send(new AutofillMsg_PasswordGenerationEnabled(host->GetRoutingID(), + enabled)); +} + +// In order for password generation to be enabled, we need to make sure: +// (1) Password sync is enabled, +// (2) Password manager is enabled, and +// (3) Password generation preference check box is checked. +void AutofillManager::UpdatePasswordGenerationState( + content::RenderViewHost* host, + bool new_renderer) { + bool saving_passwords_enabled = manager_delegate_->IsSavingPasswordsEnabled(); + bool preference_checked = manager_delegate_->GetPrefs()->GetBoolean( + prefs::kPasswordGenerationEnabled); + + bool new_password_generation_enabled = + manager_delegate_->IsPasswordSyncEnabled() && + saving_passwords_enabled && + preference_checked; + + if (new_password_generation_enabled != password_generation_enabled_ || + new_renderer) { + password_generation_enabled_ = new_password_generation_enabled; + SendPasswordGenerationStateToRenderer(host, password_generation_enabled_); + } +} + +void AutofillManager::RenderViewCreated(content::RenderViewHost* host) { + UpdatePasswordGenerationState(host, true); +} + +void AutofillManager::OnPasswordGenerationEnabledChanged() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + UpdatePasswordGenerationState(web_contents()->GetRenderViewHost(), false); +} + +void AutofillManager::OnSyncStateChanged() { + // It is possible for sync state to change during tab contents destruction. + // In this case, we don't need to update the renderer since it's going away. + if (web_contents() && web_contents()->GetRenderViewHost()) { + UpdatePasswordGenerationState(web_contents()->GetRenderViewHost(), + false); + } +} + +void AutofillManager::DidNavigateMainFrame( + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params) { + Reset(); +} + +void AutofillManager::SetExternalDelegate(AutofillExternalDelegate* delegate) { + // TODO(jrg): consider passing delegate into the ctor. That won't + // work if the delegate has a pointer to the AutofillManager, but + // future directions may not need such a pointer. + external_delegate_ = delegate; + autocomplete_history_manager_.SetExternalDelegate(delegate); +} + +bool AutofillManager::IsNativeUiEnabled() { + return external_delegate_ != NULL; +} + +bool AutofillManager::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AutofillManager, message) + IPC_MESSAGE_HANDLER(AutofillHostMsg_FormsSeen, OnFormsSeen) + IPC_MESSAGE_HANDLER(AutofillHostMsg_FormSubmitted, OnFormSubmitted) + IPC_MESSAGE_HANDLER(AutofillHostMsg_TextFieldDidChange, + OnTextFieldDidChange) + IPC_MESSAGE_HANDLER(AutofillHostMsg_QueryFormFieldAutofill, + OnQueryFormFieldAutofill) + IPC_MESSAGE_HANDLER(AutofillHostMsg_ShowAutofillDialog, + OnShowAutofillDialog) + IPC_MESSAGE_HANDLER(AutofillHostMsg_FillAutofillFormData, + OnFillAutofillFormData) + IPC_MESSAGE_HANDLER(AutofillHostMsg_DidPreviewAutofillFormData, + OnDidPreviewAutofillFormData) + IPC_MESSAGE_HANDLER(AutofillHostMsg_DidFillAutofillFormData, + OnDidFillAutofillFormData) + IPC_MESSAGE_HANDLER(AutofillHostMsg_DidShowAutofillSuggestions, + OnDidShowAutofillSuggestions) + IPC_MESSAGE_HANDLER(AutofillHostMsg_DidEndTextFieldEditing, + OnDidEndTextFieldEditing) + IPC_MESSAGE_HANDLER(AutofillHostMsg_HideAutofillPopup, + OnHideAutofillPopup) + IPC_MESSAGE_HANDLER(AutofillHostMsg_ShowPasswordGenerationPopup, + OnShowPasswordGenerationPopup) + IPC_MESSAGE_HANDLER(AutofillHostMsg_AddPasswordFormMapping, + OnAddPasswordFormMapping) + IPC_MESSAGE_HANDLER(AutofillHostMsg_ShowPasswordSuggestions, + OnShowPasswordSuggestions) + IPC_MESSAGE_HANDLER(AutofillHostMsg_SetDataList, + OnSetDataList) + IPC_MESSAGE_HANDLER(AutofillHostMsg_RequestAutocomplete, + OnRequestAutocomplete) + IPC_MESSAGE_HANDLER(AutofillHostMsg_ClickFailed, + OnClickFailed) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + return handled; +} + +void AutofillManager::WebContentsDestroyed(content::WebContents* web_contents) { + // Unsubscribe. + manager_delegate_->SetSyncStateChangedCallback(base::Closure()); +} + +bool AutofillManager::OnFormSubmitted(const FormData& form, + const TimeTicks& timestamp) { + // Let AutoComplete know as well. + autocomplete_history_manager_.OnFormSubmitted(form); + + if (!IsAutofillEnabled()) + return false; + + if (web_contents()->GetBrowserContext()->IsOffTheRecord()) + return false; + + // Don't save data that was submitted through JavaScript. + if (!form.user_submitted) + return false; + + // Grab a copy of the form data. + scoped_ptr<FormStructure> submitted_form( + new FormStructure(form, GetAutocheckoutURLPrefix())); + + // Disregard forms that we wouldn't ever autofill in the first place. + if (!submitted_form->ShouldBeParsed(true)) + return false; + + // Ignore forms not present in our cache. These are typically forms with + // wonky JavaScript that also makes them not auto-fillable. + FormStructure* cached_submitted_form; + if (!FindCachedForm(form, &cached_submitted_form)) + return false; + + submitted_form->UpdateFromCache(*cached_submitted_form); + if (submitted_form->IsAutofillable(true)) + ImportFormData(*submitted_form); + + // Only upload server statistics and UMA metrics if at least some local data + // is available to use as a baseline. + const std::vector<AutofillProfile*>& profiles = personal_data_->GetProfiles(); + const std::vector<CreditCard*>& credit_cards = personal_data_->credit_cards(); + if (!profiles.empty() || !credit_cards.empty()) { + // Copy the profile and credit card data, so that it can be accessed on a + // separate thread. + std::vector<AutofillProfile> copied_profiles; + copied_profiles.reserve(profiles.size()); + for (std::vector<AutofillProfile*>::const_iterator it = profiles.begin(); + it != profiles.end(); ++it) { + copied_profiles.push_back(**it); + } + + std::vector<CreditCard> copied_credit_cards; + copied_credit_cards.reserve(credit_cards.size()); + for (std::vector<CreditCard*>::const_iterator it = credit_cards.begin(); + it != credit_cards.end(); ++it) { + copied_credit_cards.push_back(**it); + } + + // Note that ownership of |submitted_form| is passed to the second task, + // using |base::Owned|. + FormStructure* raw_submitted_form = submitted_form.get(); + BrowserThread::GetBlockingPool()->PostTaskAndReply( + FROM_HERE, + base::Bind(&DeterminePossibleFieldTypesForUpload, + copied_profiles, + copied_credit_cards, + AutofillCountry::ApplicationLocale(), + raw_submitted_form), + base::Bind(&AutofillManager::UploadFormDataAsyncCallback, + weak_ptr_factory_.GetWeakPtr(), + base::Owned(submitted_form.release()), + forms_loaded_timestamp_, + initial_interaction_timestamp_, + timestamp)); + } + + return true; +} + +void AutofillManager::OnFormsSeen(const std::vector<FormData>& forms, + const TimeTicks& timestamp) { + autocheckout_manager_.OnFormsSeen(); + bool enabled = IsAutofillEnabled(); + if (!has_logged_autofill_enabled_) { + metric_logger_->LogIsAutofillEnabledAtPageLoad(enabled); + has_logged_autofill_enabled_ = true; + } + + if (!enabled) + return; + + forms_loaded_timestamp_ = timestamp; + ParseForms(forms); +} + +void AutofillManager::OnTextFieldDidChange(const FormData& form, + const FormFieldData& field, + const TimeTicks& timestamp) { + FormStructure* form_structure = NULL; + AutofillField* autofill_field = NULL; + if (!GetCachedFormAndField(form, field, &form_structure, &autofill_field)) + return; + + if (!user_did_type_) { + user_did_type_ = true; + metric_logger_->LogUserHappinessMetric(AutofillMetrics::USER_DID_TYPE); + } + + if (autofill_field->is_autofilled) { + autofill_field->is_autofilled = false; + metric_logger_->LogUserHappinessMetric( + AutofillMetrics::USER_DID_EDIT_AUTOFILLED_FIELD); + + if (!user_did_edit_autofilled_field_) { + user_did_edit_autofilled_field_ = true; + metric_logger_->LogUserHappinessMetric( + AutofillMetrics::USER_DID_EDIT_AUTOFILLED_FIELD_ONCE); + } + } + + UpdateInitialInteractionTimestamp(timestamp); +} + +void AutofillManager::OnQueryFormFieldAutofill(int query_id, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box, + bool display_warning) { + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + + if (external_delegate_) { + external_delegate_->OnQuery(query_id, + form, + field, + bounding_box, + display_warning); + } + + RenderViewHost* host = NULL; + FormStructure* form_structure = NULL; + AutofillField* autofill_field = NULL; + if (GetHost(&host) && + GetCachedFormAndField(form, field, &form_structure, &autofill_field) && + // Don't send suggestions for forms that aren't auto-fillable. + form_structure->IsAutofillable(false)) { + AutofillFieldType type = autofill_field->type(); + bool is_filling_credit_card = + (AutofillType(type).group() == AutofillType::CREDIT_CARD); + if (is_filling_credit_card) { + GetCreditCardSuggestions( + field, type, &values, &labels, &icons, &unique_ids); + } else { + GetProfileSuggestions( + form_structure, field, type, &values, &labels, &icons, &unique_ids); + } + + DCHECK_EQ(values.size(), labels.size()); + DCHECK_EQ(values.size(), icons.size()); + DCHECK_EQ(values.size(), unique_ids.size()); + + if (!values.empty()) { + // Don't provide Autofill suggestions when Autofill is disabled, and don't + // provide credit card suggestions for non-HTTPS pages. However, provide a + // warning to the user in these cases. + int warning = 0; + if (!form_structure->IsAutofillable(true)) + warning = IDS_AUTOFILL_WARNING_FORM_DISABLED; + else if (is_filling_credit_card && !FormIsHTTPS(*form_structure)) + warning = IDS_AUTOFILL_WARNING_INSECURE_CONNECTION; + if (warning) { + values.assign(1, l10n_util::GetStringUTF16(warning)); + labels.assign(1, string16()); + icons.assign(1, string16()); + unique_ids.assign(1, + WebKit::WebAutofillClient::MenuItemIDWarningMessage); + } else { + bool section_is_autofilled = + SectionIsAutofilled(*form_structure, form, + autofill_field->section()); + if (section_is_autofilled) { + // If the relevant section is auto-filled and the renderer is querying + // for suggestions, then the user is editing the value of a field. + // In this case, mimic autocomplete: don't display labels or icons, + // as that information is redundant. + labels.assign(labels.size(), string16()); + icons.assign(icons.size(), string16()); + } + + // When filling credit card suggestions, the values and labels are + // typically obfuscated, which makes detecting duplicates hard. Since + // duplicates only tend to be a problem when filling address forms + // anyway, only don't de-dup credit card suggestions. + if (!is_filling_credit_card) + RemoveDuplicateSuggestions(&values, &labels, &icons, &unique_ids); + + // The first time we show suggestions on this page, log the number of + // suggestions shown. + if (!has_logged_address_suggestions_count_ && !section_is_autofilled) { + metric_logger_->LogAddressSuggestionsCount(values.size()); + has_logged_address_suggestions_count_ = true; + } + } + } + + // If form is known to be at the start of the autofillable flow (i.e, when + // Autofill server said so), then trigger payments UI while also returning + // standard autofill suggestions to renderer process. + if (autocheckout_manager_.IsStartOfAutofillableFlow()) { + bool bubble_shown = + autocheckout_manager_.MaybeShowAutocheckoutBubble( + form.origin, + form.ssl_status, + web_contents()->GetView()->GetContentNativeView(), + bounding_box); + if (bubble_shown) + return; + } + } + + // Add the results from AutoComplete. They come back asynchronously, so we + // hand off what we generated and they will send the results back to the + // renderer. + autocomplete_history_manager_.OnGetAutocompleteSuggestions( + query_id, field.name, field.value, values, labels, icons, unique_ids); +} + +void AutofillManager::OnFillAutofillFormData(int query_id, + const FormData& form, + const FormFieldData& field, + int unique_id) { + RenderViewHost* host = NULL; + const FormGroup* form_group = NULL; + size_t variant = 0; + FormStructure* form_structure = NULL; + AutofillField* autofill_field = NULL; + // NOTE: GetHost may invalidate |form_group| because it causes the + // PersonalDataManager to reload Mac address book entries. Thus it must + // come before GetProfileOrCreditCard. + if (!GetHost(&host) || + !GetProfileOrCreditCard(unique_id, &form_group, &variant) || + !GetCachedFormAndField(form, field, &form_structure, &autofill_field)) + return; + + DCHECK(host); + DCHECK(form_structure); + DCHECK(autofill_field); + + FormData result = form; + + // If the relevant section is auto-filled, we should fill |field| but not the + // rest of the form. + if (SectionIsAutofilled(*form_structure, form, autofill_field->section())) { + for (std::vector<FormFieldData>::iterator iter = result.fields.begin(); + iter != result.fields.end(); ++iter) { + if ((*iter) == field) { + form_group->FillFormField(*autofill_field, variant, &(*iter)); + // Mark the cached field as autofilled, so that we can detect when a + // user edits an autofilled field (for metrics). + autofill_field->is_autofilled = true; + break; + } + } + + host->Send(new AutofillMsg_FormDataFilled(host->GetRoutingID(), query_id, + result)); + return; + } + + // Cache the field type for the field from which the user initiated autofill. + FieldTypeGroup initiating_group_type = + AutofillType(autofill_field->type()).group(); + DCHECK_EQ(form_structure->field_count(), form.fields.size()); + for (size_t i = 0; i < form_structure->field_count(); ++i) { + if (form_structure->field(i)->section() != autofill_field->section()) + continue; + + DCHECK_EQ(*form_structure->field(i), result.fields[i]); + + const AutofillField* cached_field = form_structure->field(i); + FieldTypeGroup field_group_type = + AutofillType(cached_field->type()).group(); + if (field_group_type != AutofillType::NO_GROUP) { + // If the field being filled is either + // (a) the field that the user initiated the fill from, or + // (b) part of the same logical unit, e.g. name or phone number, + // then take the multi-profile "variant" into account. + // Otherwise fill with the default (zeroth) variant. + size_t use_variant = 0; + if (result.fields[i] == field || + field_group_type == initiating_group_type) { + use_variant = variant; + } + form_group->FillFormField(*cached_field, + use_variant, + &result.fields[i]); + // Mark the cached field as autofilled, so that we can detect when a user + // edits an autofilled field (for metrics). + form_structure->field(i)->is_autofilled = true; + } + } + + autofilled_form_signatures_.push_front(form_structure->FormSignature()); + // Only remember the last few forms that we've seen, both to avoid false + // positives and to avoid wasting memory. + if (autofilled_form_signatures_.size() > kMaxRecentFormSignaturesToRemember) + autofilled_form_signatures_.pop_back(); + + host->Send(new AutofillMsg_FormDataFilled( + host->GetRoutingID(), query_id, result)); +} + +void AutofillManager::OnShowAutofillDialog() { + manager_delegate_->ShowAutofillSettings(); +} + +void AutofillManager::OnDidPreviewAutofillFormData() { + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_AUTOFILL_DID_FILL_FORM_DATA, + content::Source<RenderViewHost>(web_contents()->GetRenderViewHost()), + content::NotificationService::NoDetails()); +} + +void AutofillManager::OnDidFillAutofillFormData(const TimeTicks& timestamp) { + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_AUTOFILL_DID_FILL_FORM_DATA, + content::Source<RenderViewHost>(web_contents()->GetRenderViewHost()), + content::NotificationService::NoDetails()); + + metric_logger_->LogUserHappinessMetric(AutofillMetrics::USER_DID_AUTOFILL); + if (!user_did_autofill_) { + user_did_autofill_ = true; + metric_logger_->LogUserHappinessMetric( + AutofillMetrics::USER_DID_AUTOFILL_ONCE); + } + + UpdateInitialInteractionTimestamp(timestamp); +} + +void AutofillManager::OnDidShowAutofillSuggestions(bool is_new_popup) { + content::NotificationService::current()->Notify( + chrome::NOTIFICATION_AUTOFILL_DID_SHOW_SUGGESTIONS, + content::Source<RenderViewHost>(web_contents()->GetRenderViewHost()), + content::NotificationService::NoDetails()); + + if (is_new_popup) { + metric_logger_->LogUserHappinessMetric(AutofillMetrics::SUGGESTIONS_SHOWN); + + if (!did_show_suggestions_) { + did_show_suggestions_ = true; + metric_logger_->LogUserHappinessMetric( + AutofillMetrics::SUGGESTIONS_SHOWN_ONCE); + } + } +} + +void AutofillManager::OnHideAutofillPopup() { + if (IsNativeUiEnabled()) + manager_delegate_->HideAutofillPopup(); +} + +void AutofillManager::OnShowPasswordGenerationPopup( + const gfx::Rect& bounds, + int max_length, + const content::PasswordForm& form) { + password_generator_.reset(new autofill::PasswordGenerator(max_length)); + manager_delegate_->ShowPasswordGenerationBubble( + bounds, form, password_generator_.get()); +} + +void AutofillManager::RemoveAutofillProfileOrCreditCard(int unique_id) { + const FormGroup* form_group = NULL; + size_t variant = 0; + if (!GetProfileOrCreditCard(unique_id, &form_group, &variant)) { + NOTREACHED(); + return; + } + + // TODO(csharp): If we are dealing with a variant only the variant should + // be deleted, instead of doing nothing. + // http://crbug.com/124211 + if (variant != 0) + return; + + personal_data_->RemoveByGUID(form_group->GetGUID()); +} + +void AutofillManager::RemoveAutocompleteEntry(const string16& name, + const string16& value) { + autocomplete_history_manager_.OnRemoveAutocompleteEntry(name, value); +} + +content::WebContents* AutofillManager::GetWebContents() const { + return web_contents(); +} + +const std::vector<FormStructure*>& AutofillManager::GetFormStructures() { + return form_structures_.get(); +} + +void AutofillManager::ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + const content::SSLStatus& ssl_status, + autofill::DialogType dialog_type, + const base::Callback<void(const FormStructure*)>& callback) { + manager_delegate_->ShowRequestAutocompleteDialog( + form, source_url, ssl_status, *metric_logger_, dialog_type, callback); +} + +void AutofillManager::RequestAutocompleteDialogClosed() { + manager_delegate_->RequestAutocompleteDialogClosed(); +} + +void AutofillManager::OnAddPasswordFormMapping( + const FormFieldData& form, + const PasswordFormFillData& fill_data) { + if (external_delegate_) + external_delegate_->AddPasswordFormMapping(form, fill_data); +} + +void AutofillManager::OnShowPasswordSuggestions( + const FormFieldData& field, + const gfx::RectF& bounds, + const std::vector<string16>& suggestions) { + if (external_delegate_) + external_delegate_->OnShowPasswordSuggestions(suggestions, field, bounds); +} + +void AutofillManager::OnSetDataList(const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& unique_ids) { + if (labels.size() != values.size() || + icons.size() != values.size() || + unique_ids.size() != values.size()) { + return; + } + if (external_delegate_) { + external_delegate_->SetCurrentDataListValues(values, + labels, + icons, + unique_ids); + } +} + +void AutofillManager::OnRequestAutocomplete( + const FormData& form, + const GURL& frame_url, + const content::SSLStatus& ssl_status) { + if (!IsAutofillEnabled()) { + ReturnAutocompleteResult(WebFormElement::AutocompleteResultErrorDisabled, + FormData()); + return; + } + + base::Callback<void(const FormStructure*)> callback = + base::Bind(&AutofillManager::ReturnAutocompleteData, + weak_ptr_factory_.GetWeakPtr()); + ShowRequestAutocompleteDialog( + form, frame_url, ssl_status, + autofill::DIALOG_TYPE_REQUEST_AUTOCOMPLETE, callback); +} + +void AutofillManager::ReturnAutocompleteResult( + WebFormElement::AutocompleteResult result, const FormData& form_data) { + // web_contents() will be NULL when the interactive autocomplete is closed due + // to a tab or browser window closing. + if (!web_contents()) + return; + + RenderViewHost* host = web_contents()->GetRenderViewHost(); + if (!host) + return; + + host->Send(new AutofillMsg_RequestAutocompleteResult(host->GetRoutingID(), + result, + form_data)); +} + +void AutofillManager::ReturnAutocompleteData(const FormStructure* result) { + RequestAutocompleteDialogClosed(); + if (!result) { + ReturnAutocompleteResult(WebFormElement::AutocompleteResultErrorCancel, + FormData()); + } else { + ReturnAutocompleteResult(WebFormElement::AutocompleteResultSuccess, + result->ToFormData()); + } +} + +void AutofillManager::OnLoadedServerPredictions( + const std::string& response_xml) { + scoped_ptr<autofill::AutocheckoutPageMetaData> page_meta_data( + new autofill::AutocheckoutPageMetaData()); + + // Parse and store the server predictions. + FormStructure::ParseQueryResponse(response_xml, + form_structures_.get(), + page_meta_data.get(), + *metric_logger_); + + autocheckout_manager_.OnLoadedPageMetaData(page_meta_data.Pass()); + + // If the corresponding flag is set, annotate forms with the predicted types. + SendAutofillTypePredictions(form_structures_.get()); +} + +void AutofillManager::OnDidEndTextFieldEditing() { + if (external_delegate_) + external_delegate_->DidEndTextFieldEditing(); +} + +void AutofillManager::OnClickFailed(autofill::AutocheckoutStatus status) { + // TODO(ahutter): Plug into WalletClient. +} + +std::string AutofillManager::GetAutocheckoutURLPrefix() const { + if (!web_contents()) + return std::string(); + + autofill::autocheckout::WhitelistManager* whitelist_manager = + autofill::autocheckout::WhitelistManager::GetForBrowserContext( + web_contents()->GetBrowserContext()); + return whitelist_manager->GetMatchedURLPrefix(web_contents()->GetURL()); +} + +bool AutofillManager::IsAutofillEnabled() const { + return manager_delegate_->GetPrefs()->GetBoolean(prefs::kAutofillEnabled); +} + +void AutofillManager::SendAutofillTypePredictions( + const std::vector<FormStructure*>& forms) const { + if (!CommandLine::ForCurrentProcess()->HasSwitch( + switches::kShowAutofillTypePredictions)) + return; + + RenderViewHost* host = web_contents()->GetRenderViewHost(); + if (!host) + return; + + std::vector<FormDataPredictions> type_predictions; + FormStructure::GetFieldTypePredictions(forms, &type_predictions); + host->Send( + new AutofillMsg_FieldTypePredictionsAvailable(host->GetRoutingID(), + type_predictions)); +} + +void AutofillManager::ImportFormData(const FormStructure& submitted_form) { + const CreditCard* imported_credit_card; + if (!personal_data_->ImportFormData(submitted_form, &imported_credit_card)) + return; + + // If credit card information was submitted, we need to confirm whether to + // save it. + if (imported_credit_card) { + manager_delegate_->ConfirmSaveCreditCard( + *metric_logger_, + *imported_credit_card, + base::Bind(&PersonalDataManager::SaveImportedCreditCard, + base::Unretained(personal_data_), *imported_credit_card)); + } +} + +// Note that |submitted_form| is passed as a pointer rather than as a reference +// so that we can get memory management right across threads. Note also that we +// explicitly pass in all the time stamps of interest, as the cached ones might +// get reset before this method executes. +void AutofillManager::UploadFormDataAsyncCallback( + const FormStructure* submitted_form, + const TimeTicks& load_time, + const TimeTicks& interaction_time, + const TimeTicks& submission_time) { + submitted_form->LogQualityMetrics(*metric_logger_, + load_time, + interaction_time, + submission_time); + + if (submitted_form->ShouldBeCrowdsourced()) + UploadFormData(*submitted_form); +} + +void AutofillManager::UploadFormData(const FormStructure& submitted_form) { + if (disable_download_manager_requests_) + return; + + // Check if the form is among the forms that were recently auto-filled. + bool was_autofilled = false; + std::string form_signature = submitted_form.FormSignature(); + for (std::list<std::string>::const_iterator it = + autofilled_form_signatures_.begin(); + it != autofilled_form_signatures_.end() && !was_autofilled; + ++it) { + if (*it == form_signature) + was_autofilled = true; + } + + FieldTypeSet non_empty_types; + personal_data_->GetNonEmptyTypes(&non_empty_types); + + download_manager_.StartUploadRequest(submitted_form, was_autofilled, + non_empty_types); +} + +void AutofillManager::Reset() { + form_structures_.clear(); + has_logged_autofill_enabled_ = false; + has_logged_address_suggestions_count_ = false; + did_show_suggestions_ = false; + user_did_type_ = false; + user_did_autofill_ = false; + user_did_edit_autofilled_field_ = false; + forms_loaded_timestamp_ = TimeTicks(); + initial_interaction_timestamp_ = TimeTicks(); + + if (external_delegate_) + external_delegate_->Reset(); +} + +AutofillManager::AutofillManager(content::WebContents* web_contents, + autofill::AutofillManagerDelegate* delegate, + PersonalDataManager* personal_data) + : content::WebContentsObserver(web_contents), + manager_delegate_(delegate), + personal_data_(personal_data), + download_manager_(web_contents->GetBrowserContext(), this), + disable_download_manager_requests_(true), + autocomplete_history_manager_(web_contents), + autocheckout_manager_(this), + metric_logger_(new AutofillMetrics), + has_logged_autofill_enabled_(false), + has_logged_address_suggestions_count_(false), + did_show_suggestions_(false), + user_did_type_(false), + user_did_autofill_(false), + user_did_edit_autofilled_field_(false), + password_generation_enabled_(false), + external_delegate_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { + DCHECK(web_contents); + DCHECK(manager_delegate_); + RegisterWithSyncService(); + // Test code doesn't need registrar_. +} + +void AutofillManager::set_metric_logger(const AutofillMetrics* metric_logger) { + metric_logger_.reset(metric_logger); +} + +bool AutofillManager::GetHost(RenderViewHost** host) const { + if (!IsAutofillEnabled()) + return false; + + // No autofill data to return if the profiles are empty. + if (personal_data_->GetProfiles().empty() && + personal_data_->credit_cards().empty()) { + return false; + } + + *host = web_contents()->GetRenderViewHost(); + if (!*host) + return false; + + return true; +} + +bool AutofillManager::GetProfileOrCreditCard( + int unique_id, + const FormGroup** form_group, + size_t* variant) const { + // Unpack the |unique_id| into component parts. + GUIDPair credit_card_guid; + GUIDPair profile_guid; + UnpackGUIDs(unique_id, &credit_card_guid, &profile_guid); + DCHECK(!base::IsValidGUID(credit_card_guid.first) || + !base::IsValidGUID(profile_guid.first)); + + // Find the profile that matches the |profile_guid|, if one is specified. + // Otherwise find the credit card that matches the |credit_card_guid|, + // if specified. + if (base::IsValidGUID(profile_guid.first)) { + *form_group = personal_data_->GetProfileByGUID(profile_guid.first); + *variant = profile_guid.second; + } else if (base::IsValidGUID(credit_card_guid.first)) { + *form_group = + personal_data_->GetCreditCardByGUID(credit_card_guid.first); + *variant = credit_card_guid.second; + } + + return !!*form_group; +} + +bool AutofillManager::FindCachedForm(const FormData& form, + FormStructure** form_structure) const { + // Find the FormStructure that corresponds to |form|. + // Scan backward through the cached |form_structures_|, as updated versions of + // forms are added to the back of the list, whereas original versions of these + // forms might appear toward the beginning of the list. The communication + // protocol with the crowdsourcing server does not permit us to discard the + // original versions of the forms. + *form_structure = NULL; + for (std::vector<FormStructure*>::const_reverse_iterator iter = + form_structures_.rbegin(); + iter != form_structures_.rend(); ++iter) { + if (**iter == form) { + *form_structure = *iter; + + // The same form might be cached with multiple field counts: in some + // cases, non-autofillable fields are filtered out, whereas in other cases + // they are not. To avoid thrashing the cache, keep scanning until we + // find a cached version with the same number of fields, if there is one. + if ((*iter)->field_count() == form.fields.size()) + break; + } + } + + if (!(*form_structure)) + return false; + + return true; +} + +bool AutofillManager::GetCachedFormAndField(const FormData& form, + const FormFieldData& field, + FormStructure** form_structure, + AutofillField** autofill_field) { + // Find the FormStructure that corresponds to |form|. + // If we do not have this form in our cache but it is parseable, we'll add it + // in the call to |UpdateCachedForm()|. + if (!FindCachedForm(form, form_structure) && + !FormStructure(form, GetAutocheckoutURLPrefix()).ShouldBeParsed(false)) { + return false; + } + + // Update the cached form to reflect any dynamic changes to the form data, if + // necessary. + if (!UpdateCachedForm(form, *form_structure, form_structure)) + return false; + + // No data to return if there are no auto-fillable fields. + if (!(*form_structure)->autofill_count()) + return false; + + // Find the AutofillField that corresponds to |field|. + *autofill_field = NULL; + for (std::vector<AutofillField*>::const_iterator iter = + (*form_structure)->begin(); + iter != (*form_structure)->end(); ++iter) { + if ((**iter) == field) { + *autofill_field = *iter; + break; + } + } + + // Even though we always update the cache, the field might not exist if the + // website disables autocomplete while the user is interacting with the form. + // See http://crbug.com/160476 + return *autofill_field != NULL; +} + +bool AutofillManager::UpdateCachedForm(const FormData& live_form, + const FormStructure* cached_form, + FormStructure** updated_form) { + bool needs_update = + (!cached_form || + live_form.fields.size() != cached_form->field_count()); + for (size_t i = 0; !needs_update && i < cached_form->field_count(); ++i) { + needs_update = *cached_form->field(i) != live_form.fields[i]; + } + + if (!needs_update) + return true; + + if (form_structures_.size() >= kMaxFormCacheSize) + return false; + + // Add the new or updated form to our cache. + form_structures_.push_back( + new FormStructure(live_form, GetAutocheckoutURLPrefix())); + *updated_form = *form_structures_.rbegin(); + (*updated_form)->DetermineHeuristicTypes(*metric_logger_); + + // If we have cached data, propagate it to the updated form. + if (cached_form) { + std::map<string16, const AutofillField*> cached_fields; + for (size_t i = 0; i < cached_form->field_count(); ++i) { + const AutofillField* field = cached_form->field(i); + cached_fields[field->unique_name()] = field; + } + + for (size_t i = 0; i < (*updated_form)->field_count(); ++i) { + AutofillField* field = (*updated_form)->field(i); + std::map<string16, const AutofillField*>::iterator cached_field = + cached_fields.find(field->unique_name()); + if (cached_field != cached_fields.end()) { + field->set_server_type(cached_field->second->server_type()); + field->is_autofilled = cached_field->second->is_autofilled; + } + } + + // Note: We _must not_ remove the original version of the cached form from + // the list of |form_structures_|. Otherwise, we break parsing of the + // crowdsourcing server's response to our query. + } + + // Annotate the updated form with its predicted types. + std::vector<FormStructure*> forms(1, *updated_form); + SendAutofillTypePredictions(forms); + + return true; +} + +void AutofillManager::GetProfileSuggestions( + FormStructure* form, + const FormFieldData& field, + AutofillFieldType type, + std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<int>* unique_ids) const { + std::vector<AutofillFieldType> field_types(form->field_count()); + for (size_t i = 0; i < form->field_count(); ++i) { + field_types[i] = form->field(i)->type(); + } + std::vector<GUIDPair> guid_pairs; + + personal_data_->GetProfileSuggestions( + type, field.value, field.is_autofilled, field_types, + values, labels, icons, &guid_pairs); + + for (size_t i = 0; i < guid_pairs.size(); ++i) { + unique_ids->push_back(PackGUIDs(GUIDPair(std::string(), 0), + guid_pairs[i])); + } +} + +void AutofillManager::GetCreditCardSuggestions( + const FormFieldData& field, + AutofillFieldType type, + std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<int>* unique_ids) const { + std::vector<GUIDPair> guid_pairs; + personal_data_->GetCreditCardSuggestions( + type, field.value, values, labels, icons, &guid_pairs); + + for (size_t i = 0; i < guid_pairs.size(); ++i) { + unique_ids->push_back(PackGUIDs(guid_pairs[i], GUIDPair(std::string(), 0))); + } +} + +void AutofillManager::ParseForms(const std::vector<FormData>& forms) { + std::vector<FormStructure*> non_queryable_forms; + std::string autocheckout_url_prefix = GetAutocheckoutURLPrefix(); + for (std::vector<FormData>::const_iterator iter = forms.begin(); + iter != forms.end(); ++iter) { + scoped_ptr<FormStructure> form_structure( + new FormStructure(*iter, autocheckout_url_prefix)); + if (!form_structure->ShouldBeParsed(false)) + continue; + + form_structure->DetermineHeuristicTypes(*metric_logger_); + + // Set aside forms with method GET or author-specified types, so that they + // are not included in the query to the server. + if (form_structure->ShouldBeCrowdsourced()) + form_structures_.push_back(form_structure.release()); + else + non_queryable_forms.push_back(form_structure.release()); + } + + // If none of the forms were parsed, no use querying the server. + if (!form_structures_.empty() && !disable_download_manager_requests_) { + download_manager_.StartQueryRequest(form_structures_.get(), + *metric_logger_); + } + + for (std::vector<FormStructure*>::const_iterator iter = + non_queryable_forms.begin(); + iter != non_queryable_forms.end(); ++iter) { + form_structures_.push_back(*iter); + } + + if (!form_structures_.empty()) + metric_logger_->LogUserHappinessMetric(AutofillMetrics::FORMS_LOADED); + + // For the |non_queryable_forms|, we have all the field type info we're ever + // going to get about them. For the other forms, we'll wait until we get a + // response from the server. + SendAutofillTypePredictions(non_queryable_forms); +} + +int AutofillManager::GUIDToID(const GUIDPair& guid) const { + if (!base::IsValidGUID(guid.first)) + return 0; + + std::map<GUIDPair, int>::const_iterator iter = guid_id_map_.find(guid); + if (iter == guid_id_map_.end()) { + int id = guid_id_map_.size() + 1; + guid_id_map_[guid] = id; + id_guid_map_[id] = guid; + return id; + } else { + return iter->second; + } +} + +const GUIDPair AutofillManager::IDToGUID(int id) const { + if (id == 0) + return GUIDPair(std::string(), 0); + + std::map<int, GUIDPair>::const_iterator iter = id_guid_map_.find(id); + if (iter == id_guid_map_.end()) { + NOTREACHED(); + return GUIDPair(std::string(), 0); + } + + return iter->second; +} + +// When sending IDs (across processes) to the renderer we pack credit card and +// profile IDs into a single integer. Credit card IDs are sent in the high +// word and profile IDs are sent in the low word. +int AutofillManager::PackGUIDs(const GUIDPair& cc_guid, + const GUIDPair& profile_guid) const { + int cc_id = GUIDToID(cc_guid); + int profile_id = GUIDToID(profile_guid); + + DCHECK(cc_id <= std::numeric_limits<unsigned short>::max()); + DCHECK(profile_id <= std::numeric_limits<unsigned short>::max()); + + return cc_id << std::numeric_limits<unsigned short>::digits | profile_id; +} + +// When receiving IDs (across processes) from the renderer we unpack credit card +// and profile IDs from a single integer. Credit card IDs are stored in the +// high word and profile IDs are stored in the low word. +void AutofillManager::UnpackGUIDs(int id, + GUIDPair* cc_guid, + GUIDPair* profile_guid) const { + int cc_id = id >> std::numeric_limits<unsigned short>::digits & + std::numeric_limits<unsigned short>::max(); + int profile_id = id & std::numeric_limits<unsigned short>::max(); + + *cc_guid = IDToGUID(cc_id); + *profile_guid = IDToGUID(profile_id); +} + +void AutofillManager::UpdateInitialInteractionTimestamp( + const TimeTicks& interaction_timestamp) { + if (initial_interaction_timestamp_.is_null() || + interaction_timestamp < initial_interaction_timestamp_) { + initial_interaction_timestamp_ = interaction_timestamp; + } +} diff --git a/components/autofill/browser/autofill_manager.h b/components/autofill/browser/autofill_manager.h new file mode 100644 index 0000000..094043a --- /dev/null +++ b/components/autofill/browser/autofill_manager.h @@ -0,0 +1,442 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_MANAGER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_MANAGER_H_ + +#include <list> +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/weak_ptr.h" +#include "base/prefs/public/pref_change_registrar.h" +#include "base/string16.h" +#include "base/supports_user_data.h" +#include "base/time.h" +#include "components/autofill/browser/autocheckout_manager.h" +#include "components/autofill/browser/autocomplete_history_manager.h" +#include "components/autofill/browser/autofill_download.h" +#include "components/autofill/browser/autofill_manager_delegate.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/browser/personal_data_manager.h" +#include "components/autofill/common/autocheckout_status.h" +#include "components/autofill/common/form_data.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/common/ssl_status.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h" + +class AutofillExternalDelegate; +class AutofillField; +class AutofillProfile; +class AutofillMetrics; +class CreditCard; +class FormGroup; +class GURL; +class PrefRegistrySyncable; +class ProfileSyncService; + +struct FormData; +struct FormFieldData; +struct PasswordFormFillData; +struct ViewHostMsg_FrameNavigate_Params; + +namespace autofill { +class AutofillManagerDelegate; +class PasswordGenerator; +} + +namespace content { +class RenderViewHost; +class WebContents; + +struct PasswordForm; +} + +namespace gfx { +class Rect; +class RectF; +} + +namespace IPC { +class Message; +} + +// Manages saving and restoring the user's personal information entered into web +// forms. +class AutofillManager : public content::WebContentsObserver, + public AutofillDownloadManager::Observer, + public base::SupportsUserData::Data { + public: + static void CreateForWebContentsAndDelegate( + content::WebContents* contents, + autofill::AutofillManagerDelegate* delegate); + static AutofillManager* FromWebContents(content::WebContents* contents); + + // Registers our Enable/Disable Autofill pref. + static void RegisterUserPrefs(PrefRegistrySyncable* registry); + + // Set an external delegate. + void SetExternalDelegate(AutofillExternalDelegate* delegate); + + // Whether browser process will create and own the Autofill popup UI. + bool IsNativeUiEnabled(); + + // Called from our external delegate so they cannot be private. + virtual void OnFillAutofillFormData(int query_id, + const FormData& form, + const FormFieldData& field, + int unique_id); + void OnDidShowAutofillSuggestions(bool is_new_popup); + void OnDidFillAutofillFormData(const base::TimeTicks& timestamp); + void OnShowAutofillDialog(); + void OnDidPreviewAutofillFormData(); + void OnShowPasswordGenerationPopup(const gfx::Rect& bounds, + int max_length, + const content::PasswordForm& form); + + // Remove the credit card or Autofill profile that matches |unique_id| + // from the database. + void RemoveAutofillProfileOrCreditCard(int unique_id); + + // Remove the specified Autocomplete entry. + void RemoveAutocompleteEntry(const string16& name, const string16& value); + + // Returns the present web_contents state. + content::WebContents* GetWebContents() const; + + // Returns the present form structures seen by Autofill manager. + const std::vector<FormStructure*>& GetFormStructures(); + + // Causes the dialog for request autocomplete feature to be shown. + virtual void ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + const content::SSLStatus& ssl_status, + autofill::DialogType dialog_type, + const base::Callback<void(const FormStructure*)>& callback); + + // Happens when the autocomplete dialog runs its callback when being closed. + void RequestAutocompleteDialogClosed(); + + autofill::AutofillManagerDelegate* delegate() const { + return manager_delegate_; + } + + protected: + // Only test code should subclass AutofillManager. + AutofillManager(content::WebContents* web_contents, + autofill::AutofillManagerDelegate* delegate); + virtual ~AutofillManager(); + + // Test code should prefer to use this constructor. + AutofillManager(content::WebContents* web_contents, + autofill::AutofillManagerDelegate* delegate, + PersonalDataManager* personal_data); + + // Returns the value of the AutofillEnabled pref. + virtual bool IsAutofillEnabled() const; + + // Uploads the form data to the Autofill server. + virtual void UploadFormData(const FormStructure& submitted_form); + + // Reset cache. + void Reset(); + + // Informs the renderer of the current password generation state. This is a + // separate function to aid with testing. + virtual void SendPasswordGenerationStateToRenderer( + content::RenderViewHost* host, + bool enabled); + + // Logs quality metrics for the |submitted_form| and uploads the form data + // to the crowdsourcing server, if appropriate. + virtual void UploadFormDataAsyncCallback( + const FormStructure* submitted_form, + const base::TimeTicks& load_time, + const base::TimeTicks& interaction_time, + const base::TimeTicks& submission_time); + + // Maps GUIDs to and from IDs that are used to identify profiles and credit + // cards sent to and from the renderer process. + virtual int GUIDToID(const PersonalDataManager::GUIDPair& guid) const; + virtual const PersonalDataManager::GUIDPair IDToGUID(int id) const; + + // Methods for packing and unpacking credit card and profile IDs for sending + // and receiving to and from the renderer process. + int PackGUIDs(const PersonalDataManager::GUIDPair& cc_guid, + const PersonalDataManager::GUIDPair& profile_guid) const; + void UnpackGUIDs(int id, + PersonalDataManager::GUIDPair* cc_guid, + PersonalDataManager::GUIDPair* profile_guid) const; + + const AutofillMetrics* metric_logger() const { return metric_logger_.get(); } + void set_metric_logger(const AutofillMetrics* metric_logger); + + ScopedVector<FormStructure>* form_structures() { return &form_structures_; } + + // Exposed for testing. + AutofillExternalDelegate* external_delegate() { + return external_delegate_; + } + + // Exposed for testing. + autofill::AutocheckoutManager* autocheckout_manager() { + return &autocheckout_manager_; + } + + // Processes the submitted |form|, saving any new Autofill data and uploading + // the possible field types for the submitted fields to the crowdsouring + // server. Returns false if this form is not relevant for Autofill. + bool OnFormSubmitted(const FormData& form, + const base::TimeTicks& timestamp); + + // Tell the renderer the current interactive autocomplete finished. + virtual void ReturnAutocompleteResult( + WebKit::WebFormElement::AutocompleteResult result, + const FormData& form_data); + + private: + // content::WebContentsObserver: + virtual void RenderViewCreated(content::RenderViewHost* host) OVERRIDE; + virtual void DidNavigateMainFrame( + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void WebContentsDestroyed( + content::WebContents* web_contents) OVERRIDE; + + // AutofillDownloadManager::Observer: + virtual void OnLoadedServerPredictions( + const std::string& response_xml) OVERRIDE; + + void OnSyncStateChanged(); + + // Register as an observer with the sync service. + void RegisterWithSyncService(); + + // Called when password generation preference state changes. + void OnPasswordGenerationEnabledChanged(); + + // Determines what the current state of password generation is, and if it has + // changed from |password_generation_enabled_|. If it has changed or if + // |new_renderer| is true, it notifies the renderer of this change via + // SendPasswordGenerationStateToRenderer. + void UpdatePasswordGenerationState(content::RenderViewHost* host, + bool new_renderer); + + void OnFormsSeen(const std::vector<FormData>& forms, + const base::TimeTicks& timestamp); + void OnTextFieldDidChange(const FormData& form, + const FormFieldData& field, + const base::TimeTicks& timestamp); + + // The |bounding_box| is a window relative value. + void OnQueryFormFieldAutofill(int query_id, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounding_box, + bool display_warning); + void OnDidEndTextFieldEditing(); + void OnHideAutofillPopup(); + void OnAddPasswordFormMapping( + const FormFieldData& form, + const PasswordFormFillData& fill_data); + void OnShowPasswordSuggestions(const FormFieldData& field, + const gfx::RectF& bounds, + const std::vector<string16>& suggestions); + void OnSetDataList(const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& unique_ids); + + // Requests an interactive autocomplete UI be shown. + void OnRequestAutocomplete(const FormData& form, + const GURL& frame_url, + const content::SSLStatus& ssl_status); + + // Passes return data for an OnRequestAutocomplete call back to the page. + void ReturnAutocompleteData(const FormStructure* result); + + // Called to signal clicking an element failed in some way during an + // Autocheckout flow. + void OnClickFailed(autofill::AutocheckoutStatus status); + + // Returns the matched whitelist URL prefix for the current tab's url. + std::string GetAutocheckoutURLPrefix() const; + + // Fills |host| with the RenderViewHost for this tab. + // Returns false if Autofill is disabled or if the host is unavailable. + bool GetHost(content::RenderViewHost** host) const WARN_UNUSED_RESULT; + + // Unpacks |unique_id| and fills |form_group| and |variant| with the + // appropriate data source and variant index. Returns false if the unpacked + // id cannot be found. + bool GetProfileOrCreditCard(int unique_id, + const FormGroup** form_group, + size_t* variant) const WARN_UNUSED_RESULT; + + // Fills |form_structure| cached element corresponding to |form|. + // Returns false if the cached element was not found. + bool FindCachedForm(const FormData& form, + FormStructure** form_structure) const WARN_UNUSED_RESULT; + + // Fills |form_structure| and |autofill_field| with the cached elements + // corresponding to |form| and |field|. This might have the side-effect of + // updating the cache. Returns false if the |form| is not autofillable, or if + // it is not already present in the cache and the cache is full. + bool GetCachedFormAndField(const FormData& form, + const FormFieldData& field, + FormStructure** form_structure, + AutofillField** autofill_field) WARN_UNUSED_RESULT; + + // Re-parses |live_form| and adds the result to |form_structures_|. + // |cached_form| should be a pointer to the existing version of the form, or + // NULL if no cached version exists. The updated form is then written into + // |updated_form|. Returns false if the cache could not be updated. + bool UpdateCachedForm(const FormData& live_form, + const FormStructure* cached_form, + FormStructure** updated_form) WARN_UNUSED_RESULT; + + // Returns a list of values from the stored profiles that match |type| and the + // value of |field| and returns the labels of the matching profiles. |labels| + // is filled with the Profile label. + void GetProfileSuggestions(FormStructure* form, + const FormFieldData& field, + AutofillFieldType type, + std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<int>* unique_ids) const; + + // Returns a list of values from the stored credit cards that match |type| and + // the value of |field| and returns the labels of the matching credit cards. + void GetCreditCardSuggestions(const FormFieldData& field, + AutofillFieldType type, + std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<int>* unique_ids) const; + + // Parses the forms using heuristic matching and querying the Autofill server. + void ParseForms(const std::vector<FormData>& forms); + + // Imports the form data, submitted by the user, into |personal_data_|. + void ImportFormData(const FormStructure& submitted_form); + + // If |initial_interaction_timestamp_| is unset or is set to a later time than + // |interaction_timestamp|, updates the cached timestamp. The latter check is + // needed because IPC messages can arrive out of order. + void UpdateInitialInteractionTimestamp( + const base::TimeTicks& interaction_timestamp); + + // Send our current field type predictions to the renderer. This is a no-op if + // the appropriate command-line flag is not set. + void SendAutofillTypePredictions( + const std::vector<FormStructure*>& forms) const; + + autofill::AutofillManagerDelegate* const manager_delegate_; + + // The personal data manager, used to save and load personal data to/from the + // web database. This is overridden by the AutofillManagerTest. + // Weak reference. + // May be NULL. NULL indicates OTR. + PersonalDataManager* personal_data_; + + std::list<std::string> autofilled_form_signatures_; + + // Handles queries and uploads to Autofill servers. + AutofillDownloadManager download_manager_; + + // Should be set to true in AutofillManagerTest and other tests, false in + // AutofillDownloadManagerTest and in non-test environment. Is false by + // default for the public constructor, and true by default for the test-only + // constructors. + bool disable_download_manager_requests_; + + // Handles single-field autocomplete form data. + AutocompleteHistoryManager autocomplete_history_manager_; + + // Handles autocheckout flows. + autofill::AutocheckoutManager autocheckout_manager_; + + // For logging UMA metrics. Overridden by metrics tests. + scoped_ptr<const AutofillMetrics> metric_logger_; + // Have we logged whether Autofill is enabled for this page load? + bool has_logged_autofill_enabled_; + // Have we logged an address suggestions count metric for this page? + bool has_logged_address_suggestions_count_; + // Have we shown Autofill suggestions at least once? + bool did_show_suggestions_; + // Has the user manually edited at least one form field among the autofillable + // ones? + bool user_did_type_; + // Has the user autofilled a form on this page? + bool user_did_autofill_; + // Has the user edited a field that was previously autofilled? + bool user_did_edit_autofilled_field_; + // When the page finished loading. + base::TimeTicks forms_loaded_timestamp_; + // When the user first interacted with a potentially fillable form on this + // page. + base::TimeTicks initial_interaction_timestamp_; + // If password generation is enabled. We cache this value so that we don't + // spam the renderer with messages during startup when the sync state + // is changing rapidly. + bool password_generation_enabled_; + // Listens for changes to the 'enabled' state for password generation. + PrefChangeRegistrar registrar_; + + // To be passed to the password generation UI to generate the password. + scoped_ptr<autofill::PasswordGenerator> password_generator_; + + // Our copy of the form data. + ScopedVector<FormStructure> form_structures_; + + // GUID to ID mapping. We keep two maps to convert back and forth. + mutable std::map<PersonalDataManager::GUIDPair, int> guid_id_map_; + mutable std::map<int, PersonalDataManager::GUIDPair> id_guid_map_; + + // Delegate to perform external processing (display, selection) on + // our behalf. Weak. + AutofillExternalDelegate* external_delegate_; + + base::WeakPtrFactory<AutofillManager> weak_ptr_factory_; + + friend class AutofillManagerTest; + friend class FormStructureBrowserTest; + FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, + DeterminePossibleFieldTypesForUpload); + FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, + DeterminePossibleFieldTypesForUploadStressTest); + FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, + DisabledAutofillDispatchesError); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, AddressSuggestionsCount); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, AutofillIsEnabledAtPageLoad); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, DeveloperEngagement); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, FormFillDuration); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, + NoQualityMetricsForNonAutofillableForms); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, QualityMetrics); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, QualityMetricsForFailure); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, QualityMetricsWithExperimentId); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, SaneMetricsWithCacheMismatch); + FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, TestExternalDelegate); + FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, + TestTabContentsWithExternalDelegate); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, + UserHappinessFormLoadAndSubmission); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, UserHappinessFormInteraction); + + DISALLOW_COPY_AND_ASSIGN(AutofillManager); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_MANAGER_H_ diff --git a/components/autofill/browser/autofill_manager_delegate.h b/components/autofill/browser/autofill_manager_delegate.h new file mode 100644 index 0000000..1e228e1 --- /dev/null +++ b/components/autofill/browser/autofill_manager_delegate.h @@ -0,0 +1,145 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_MANAGER_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_MANAGER_DELEGATE_H_ + +#include <vector> + +#include "base/callback_forward.h" +#include "base/string16.h" +#include "ui/gfx/native_widget_types.h" + +namespace autofill { +class PasswordGenerator; +} + +namespace content { +struct PasswordForm; +struct SSLStatus; +} + +namespace gfx { +class Rect; +class RectF; +} + +class AutofillMetrics; +class AutofillPopupDelegate; +class CreditCard; +class FormStructure; +class GURL; +class InfoBarService; +class PersonalDataManager; +class PrefService; +class ProfileSyncServiceBase; + +struct FormData; + +namespace autofill { + +enum DialogType { + // Autofill dialog for the Autocheckout feature. + DIALOG_TYPE_AUTOCHECKOUT, + // Autofill dialog for the requestAutocomplete feature. + DIALOG_TYPE_REQUEST_AUTOCOMPLETE, +}; + +// A delegate interface that needs to be supplied to AutofillManager +// by the embedder. +// +// Each delegate instance is associated with a given context within +// which an AutofillManager is used (e.g. a single tab), so when we +// say "for the delegate" below, we mean "in the execution context the +// delegate is associated with" (e.g. for the tab the AutofillManager is +// attached to). +class AutofillManagerDelegate { + public: + virtual ~AutofillManagerDelegate() {} + + // Gets the PersonalDataManager instance associated with the delegate. + virtual PersonalDataManager* GetPersonalDataManager() = 0; + + // Gets the preferences associated with the delegate. + virtual PrefService* GetPrefs() = 0; + + // Hides the associated request autocomplete dialog (if it exists). + virtual void HideRequestAutocompleteDialog() = 0; + + // Returns true if saving passwords is currently enabled for the + // delegate. + virtual bool IsSavingPasswordsEnabled() const = 0; + + // Returns true if Sync is enabled for the passwords datatype. + virtual bool IsPasswordSyncEnabled() const = 0; + + // Sets a callback that will be called when sync state changes. + // + // Set the callback to an object for which |is_null()| evaluates to + // true to stop receiving notifications + // (e.g. SetSyncStateChangedCallback(base::Closure())). + virtual void SetSyncStateChangedCallback(const base::Closure& callback) = 0; + + // Causes an error explaining that Autocheckout has failed to be displayed to + // the user. + virtual void OnAutocheckoutError() = 0; + + // Causes the Autofill settings UI to be shown. + virtual void ShowAutofillSettings() = 0; + + // Run |save_card_callback| if the credit card should be imported as personal + // data. |metric_logger| can be used to log user actions. + virtual void ConfirmSaveCreditCard( + const AutofillMetrics& metric_logger, + const CreditCard& credit_card, + const base::Closure& save_card_callback) = 0; + + // Causes the password generation bubble UI to be shown using the + // specified form with the given bounds. + virtual void ShowPasswordGenerationBubble( + const gfx::Rect& bounds, + const content::PasswordForm& form, + autofill::PasswordGenerator* generator) = 0; + + // Causes the Autocheckout bubble UI to be displayed. |bounding_box| is the + // anchor for the bubble. |native_view| is the parent view of the bubble. + // |callback| is run if the bubble is accepted. + virtual void ShowAutocheckoutBubble( + const gfx::RectF& bounding_box, + const gfx::NativeView& native_view, + const base::Closure& callback) = 0; + + // Causes the dialog for request autocomplete feature to be shown. + virtual void ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + const content::SSLStatus& ssl_status, + const AutofillMetrics& metric_logger, + DialogType dialog_type, + const base::Callback<void(const FormStructure*)>& callback) = 0; + + // Called when the dialog for request autocomplete closes. (So UI code will + // free memory, etc.) + virtual void RequestAutocompleteDialogClosed() = 0; + + // Shows an Autofill popup with the given |values|, |labels|, |icons|, and + // |identifiers| for the element at |element_bounds|. |delegate| will be + // notified of popup events. + virtual void ShowAutofillPopup(const gfx::RectF& element_bounds, + const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& identifiers, + AutofillPopupDelegate* delegate) = 0; + + // Hide the Autofill popup if one is currently showing. + virtual void HideAutofillPopup() = 0; + + // Updates the Autocheckout progress bar. |value| must be in [0.0, 1.0]. + virtual void UpdateProgressBar(double value) = 0; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_MANAGER_DELEGATE_H_ diff --git a/components/autofill/browser/autofill_manager_unittest.cc b/components/autofill/browser/autofill_manager_unittest.cc new file mode 100644 index 0000000..6584def --- /dev/null +++ b/components/autofill/browser/autofill_manager_unittest.cc @@ -0,0 +1,3228 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <algorithm> +#include <vector> + +#include "base/command_line.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/prefs/pref_service.h" +#include "base/string16.h" +#include "base/stringprintf.h" +#include "base/strings/string_number_conversions.h" +#include "base/time.h" +#include "base/tuple.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/autofill/personal_data_manager_factory.h" +#include "chrome/browser/password_manager/password_manager.h" +#include "chrome/browser/password_manager/password_manager_delegate_impl.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/profile_sync_service_factory.h" +#include "chrome/browser/ui/autofill/tab_autofill_manager_delegate.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/common/pref_names.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/autocomplete_history_manager.h" +#include "components/autofill/browser/autofill_common_test.h" +#include "components/autofill/browser/autofill_manager.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/credit_card.h" +#include "components/autofill/browser/personal_data_manager.h" +#include "components/autofill/browser/test_autofill_external_delegate.h" +#include "components/autofill/common/autofill_messages.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_field_data.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/ssl_status.h" +#include "content/public/test/mock_render_process_host.h" +#include "content/public/test/test_browser_thread.h" +#include "googleurl/src/gurl.h" +#include "grit/generated_resources.h" +#include "ipc/ipc_test_sink.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/rect.h" + +typedef PersonalDataManager::GUIDPair GUIDPair; +using content::BrowserThread; +using content::WebContents; +using testing::_; +using WebKit::WebFormElement; + +namespace { + +// The page ID sent to the AutofillManager from the RenderView, used to send +// an IPC message back to the renderer. +const int kDefaultPageID = 137; + +typedef Tuple5<int, + std::vector<string16>, + std::vector<string16>, + std::vector<string16>, + std::vector<int> > AutofillParam; + +class TestPersonalDataManager : public PersonalDataManager { + public: + TestPersonalDataManager() { + CreateTestAutofillProfiles(&web_profiles_); + CreateTestCreditCards(&credit_cards_); + } + + void SetBrowserContext(content::BrowserContext* context) { + set_browser_context(context); + } + + // Factory method for keyed service. PersonalDataManager is NULL for testing. + static ProfileKeyedService* Build(Profile* profile) { + return NULL; + } + + MOCK_METHOD1(SaveImportedProfile, void(const AutofillProfile&)); + + AutofillProfile* GetProfileWithGUID(const char* guid) { + for (std::vector<AutofillProfile *>::iterator it = web_profiles_.begin(); + it != web_profiles_.end(); ++it) { + if (!(*it)->guid().compare(guid)) + return *it; + } + return NULL; + } + + CreditCard* GetCreditCardWithGUID(const char* guid) { + for (std::vector<CreditCard *>::iterator it = credit_cards_.begin(); + it != credit_cards_.end(); ++it){ + if (!(*it)->guid().compare(guid)) + return *it; + } + return NULL; + } + + void AddProfile(AutofillProfile* profile) { + web_profiles_.push_back(profile); + } + + void AddCreditCard(CreditCard* credit_card) { + credit_cards_.push_back(credit_card); + } + + virtual void RemoveByGUID(const std::string& guid) OVERRIDE { + CreditCard* credit_card = GetCreditCardWithGUID(guid.c_str()); + if (credit_card) { + credit_cards_.erase( + std::remove(credit_cards_.begin(), credit_cards_.end(), credit_card), + credit_cards_.end()); + } + + AutofillProfile* profile = GetProfileWithGUID(guid.c_str()); + if (profile) { + web_profiles_.erase( + std::remove(web_profiles_.begin(), web_profiles_.end(), profile), + web_profiles_.end()); + } + } + + // Do nothing (auxiliary profiles will be created in + // CreateTestAuxiliaryProfile). + virtual void LoadAuxiliaryProfiles() OVERRIDE {} + + void ClearAutofillProfiles() { + web_profiles_.clear(); + } + + void ClearCreditCards() { + credit_cards_.clear(); + } + + void CreateTestAuxiliaryProfiles() { + CreateTestAutofillProfiles(&auxiliary_profiles_); + } + + void CreateTestCreditCardsYearAndMonth(const char* year, const char* month) { + ClearCreditCards(); + CreditCard* credit_card = new CreditCard; + autofill_test::SetCreditCardInfo(credit_card, "Miku Hatsune", + "4234567890654321", // Visa + month, year); + credit_card->set_guid("00000000-0000-0000-0000-000000000007"); + credit_cards_.push_back(credit_card); + } + + private: + void CreateTestAutofillProfiles(ScopedVector<AutofillProfile>* profiles) { + AutofillProfile* profile = new AutofillProfile; + autofill_test::SetProfileInfo(profile, "Elvis", "Aaron", + "Presley", "theking@gmail.com", "RCA", + "3734 Elvis Presley Blvd.", "Apt. 10", + "Memphis", "Tennessee", "38116", "US", + "12345678901"); + profile->set_guid("00000000-0000-0000-0000-000000000001"); + profiles->push_back(profile); + profile = new AutofillProfile; + autofill_test::SetProfileInfo(profile, "Charles", "Hardin", + "Holley", "buddy@gmail.com", "Decca", + "123 Apple St.", "unit 6", "Lubbock", + "Texas", "79401", "US", "23456789012"); + profile->set_guid("00000000-0000-0000-0000-000000000002"); + profiles->push_back(profile); + profile = new AutofillProfile; + autofill_test::SetProfileInfo(profile, "", "", "", "", "", "", "", "", "", + "", "", ""); + profile->set_guid("00000000-0000-0000-0000-000000000003"); + profiles->push_back(profile); + } + + void CreateTestCreditCards(ScopedVector<CreditCard>* credit_cards) { + CreditCard* credit_card = new CreditCard; + autofill_test::SetCreditCardInfo(credit_card, "Elvis Presley", + "4234 5678 9012 3456", // Visa + "04", "2012"); + credit_card->set_guid("00000000-0000-0000-0000-000000000004"); + credit_cards->push_back(credit_card); + + credit_card = new CreditCard; + autofill_test::SetCreditCardInfo(credit_card, "Buddy Holly", + "5187654321098765", // Mastercard + "10", "2014"); + credit_card->set_guid("00000000-0000-0000-0000-000000000005"); + credit_cards->push_back(credit_card); + + credit_card = new CreditCard; + autofill_test::SetCreditCardInfo(credit_card, "", "", "", ""); + credit_card->set_guid("00000000-0000-0000-0000-000000000006"); + credit_cards->push_back(credit_card); + } + + DISALLOW_COPY_AND_ASSIGN(TestPersonalDataManager); +}; + +// Populates |form| with data corresponding to a simple address form. +// Note that this actually appends fields to the form data, which can be useful +// for building up more complex test forms. +void CreateTestAddressFormData(FormData* form) { + form->name = ASCIIToUTF16("MyForm"); + form->method = ASCIIToUTF16("POST"); + form->origin = GURL("http://myform.com/form.html"); + form->action = GURL("http://myform.com/submit.html"); + form->user_submitted = true; + + FormFieldData field; + autofill_test::CreateTestFormField( + "First Name", "firstname", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "Middle Name", "middlename", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "Last Name", "lastname", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "Address Line 1", "addr1", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "Address Line 2", "addr2", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "City", "city", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "State", "state", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "Postal Code", "zipcode", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "Country", "country", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "Phone Number", "phonenumber", "", "tel", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "Email", "email", "", "email", &field); + form->fields.push_back(field); +} + +// 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 = ASCIIToUTF16("MyForm"); + form->method = ASCIIToUTF16("POST"); + if (is_https) { + form->origin = GURL("https://myform.com/form.html"); + form->action = GURL("https://myform.com/submit.html"); + } else { + form->origin = GURL("http://myform.com/form.html"); + form->action = GURL("http://myform.com/submit.html"); + } + form->user_submitted = true; + + FormFieldData field; + autofill_test::CreateTestFormField( + "Name on Card", "nameoncard", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number", "cardnumber", "", "text", &field); + form->fields.push_back(field); + if (use_month_type) { + autofill_test::CreateTestFormField( + "Expiration Date", "ccmonth", "", "month", &field); + form->fields.push_back(field); + } else { + autofill_test::CreateTestFormField( + "Expiration Date", "ccmonth", "", "text", &field); + form->fields.push_back(field); + autofill_test::CreateTestFormField( + "", "ccyear", "", "text", &field); + form->fields.push_back(field); + } +} + +void ExpectSuggestions(int page_id, + const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& unique_ids, + int expected_page_id, + size_t expected_num_suggestions, + const string16 expected_values[], + const string16 expected_labels[], + const string16 expected_icons[], + const int expected_unique_ids[]) { + EXPECT_EQ(expected_page_id, page_id); + ASSERT_EQ(expected_num_suggestions, values.size()); + ASSERT_EQ(expected_num_suggestions, labels.size()); + ASSERT_EQ(expected_num_suggestions, icons.size()); + ASSERT_EQ(expected_num_suggestions, unique_ids.size()); + for (size_t i = 0; i < expected_num_suggestions; ++i) { + SCOPED_TRACE(StringPrintf("i: %" PRIuS, i)); + EXPECT_EQ(expected_values[i], values[i]); + EXPECT_EQ(expected_labels[i], labels[i]); + EXPECT_EQ(expected_icons[i], icons[i]); + EXPECT_EQ(expected_unique_ids[i], unique_ids[i]); + } +} + +void ExpectFilledField(const char* expected_label, + const char* expected_name, + const char* expected_value, + const char* 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(int page_id, + const FormData& filled_form, + int expected_page_id, + 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* phone, + const char* email, + const char* name_on_card, + const char* card_number, + const char* expiration_month, + const char* expiration_year, + bool has_address_fields, + bool has_credit_card_fields, + bool use_month_type) { + // The number of fields in the address and credit card forms created above. + const size_t kAddressFormSize = 11; + const size_t kCreditCardFormSize = use_month_type ? 3 : 4; + + EXPECT_EQ(expected_page_id, page_id); + EXPECT_EQ(ASCIIToUTF16("MyForm"), filled_form.name); + EXPECT_EQ(ASCIIToUTF16("POST"), filled_form.method); + if (has_credit_card_fields) { + EXPECT_EQ(GURL("https://myform.com/form.html"), filled_form.origin); + EXPECT_EQ(GURL("https://myform.com/submit.html"), filled_form.action); + } else { + EXPECT_EQ(GURL("http://myform.com/form.html"), filled_form.origin); + EXPECT_EQ(GURL("http://myform.com/submit.html"), filled_form.action); + } + EXPECT_TRUE(filled_form.user_submitted); + + size_t form_size = 0; + if (has_address_fields) + form_size += kAddressFormSize; + if (has_credit_card_fields) + form_size += kCreditCardFormSize; + ASSERT_EQ(form_size, filled_form.fields.size()); + + if (has_address_fields) { + ExpectFilledField("First Name", "firstname", first, "text", + filled_form.fields[0]); + ExpectFilledField("Middle Name", "middlename", middle, "text", + filled_form.fields[1]); + ExpectFilledField("Last Name", "lastname", last, "text", + filled_form.fields[2]); + ExpectFilledField("Address Line 1", "addr1", address1, "text", + filled_form.fields[3]); + ExpectFilledField("Address Line 2", "addr2", address2, "text", + filled_form.fields[4]); + ExpectFilledField("City", "city", city, "text", + filled_form.fields[5]); + ExpectFilledField("State", "state", state, "text", + filled_form.fields[6]); + ExpectFilledField("Postal Code", "zipcode", postal_code, "text", + filled_form.fields[7]); + ExpectFilledField("Country", "country", country, "text", + filled_form.fields[8]); + ExpectFilledField("Phone Number", "phonenumber", phone, "tel", + filled_form.fields[9]); + ExpectFilledField("Email", "email", email, "email", + filled_form.fields[10]); + } + + if (has_credit_card_fields) { + size_t offset = has_address_fields? kAddressFormSize : 0; + ExpectFilledField("Name on Card", "nameoncard", name_on_card, "text", + filled_form.fields[offset + 0]); + ExpectFilledField("Card Number", "cardnumber", card_number, "text", + filled_form.fields[offset + 1]); + if (use_month_type) { + std::string exp_year = expiration_year; + std::string exp_month = expiration_month; + std::string date; + if (!exp_year.empty() && !exp_month.empty()) + date = exp_year + "-" + exp_month; + + ExpectFilledField("Expiration Date", "ccmonth", date.c_str(), "month", + filled_form.fields[offset + 2]); + } else { + ExpectFilledField("Expiration Date", "ccmonth", expiration_month, "text", + filled_form.fields[offset + 2]); + ExpectFilledField("", "ccyear", expiration_year, "text", + filled_form.fields[offset + 3]); + } + } +} + +void ExpectFilledAddressFormElvis(int page_id, + const FormData& filled_form, + int expected_page_id, + bool has_credit_card_fields) { + ExpectFilledForm(page_id, filled_form, expected_page_id, "Elvis", "Aaron", + "Presley", "3734 Elvis Presley Blvd.", "Apt. 10", "Memphis", + "Tennessee", "38116", "United States", "12345678901", + "theking@gmail.com", "", "", "", "", true, + has_credit_card_fields, false); +} + +void ExpectFilledCreditCardFormElvis(int page_id, + const FormData& filled_form, + int expected_page_id, + bool has_address_fields) { + ExpectFilledForm(page_id, filled_form, expected_page_id, + "", "", "", "", "", "", "", "", "", "", "", + "Elvis Presley", "4234567890123456", "04", "2012", + has_address_fields, true, false); +} + +void ExpectFilledCreditCardYearMonthWithYearMonth(int page_id, + const FormData& filled_form, + int expected_page_id, + bool has_address_fields, + const char* year, + const char* month) { + ExpectFilledForm(page_id, filled_form, expected_page_id, + "", "", "", "", "", "", "", "", "", "", "", + "Miku Hatsune", "4234567890654321", month, year, + has_address_fields, true, true); +} + +class TestAutofillManager : public AutofillManager { + public: + TestAutofillManager(content::WebContents* web_contents, + autofill::AutofillManagerDelegate* delegate, + TestPersonalDataManager* personal_data) + : AutofillManager(web_contents, delegate, personal_data), + personal_data_(personal_data), + autofill_enabled_(true), + did_finish_async_form_submit_(false), + message_loop_is_running_(false) { + } + virtual ~TestAutofillManager() {} + + virtual bool IsAutofillEnabled() const OVERRIDE { return autofill_enabled_; } + + void set_autofill_enabled(bool autofill_enabled) { + autofill_enabled_ = autofill_enabled; + } + + const std::vector<std::pair<WebFormElement::AutocompleteResult, FormData> >& + request_autocomplete_results() const { + return request_autocomplete_results_; + } + + + void set_expected_submitted_field_types( + const std::vector<FieldTypeSet>& expected_types) { + expected_submitted_field_types_ = expected_types; + } + + virtual void UploadFormDataAsyncCallback( + const FormStructure* submitted_form, + const base::TimeTicks& load_time, + const base::TimeTicks& interaction_time, + const base::TimeTicks& submission_time) OVERRIDE { + if (message_loop_is_running_) { + MessageLoop::current()->Quit(); + message_loop_is_running_ = false; + } else { + did_finish_async_form_submit_ = true; + } + + // If we have expected field types set, make sure they match. + if (!expected_submitted_field_types_.empty()) { + ASSERT_EQ(expected_submitted_field_types_.size(), + submitted_form->field_count()); + for (size_t i = 0; i < expected_submitted_field_types_.size(); ++i) { + SCOPED_TRACE( + StringPrintf("Field %d with value %s", static_cast<int>(i), + UTF16ToUTF8(submitted_form->field(i)->value).c_str())); + const FieldTypeSet& possible_types = + submitted_form->field(i)->possible_types(); + EXPECT_EQ(expected_submitted_field_types_[i].size(), + possible_types.size()); + for (FieldTypeSet::const_iterator it = + expected_submitted_field_types_[i].begin(); + it != expected_submitted_field_types_[i].end(); ++it) { + EXPECT_TRUE(possible_types.count(*it)) + << "Expected type: " << AutofillType::FieldTypeToString(*it); + } + } + } + + AutofillManager::UploadFormDataAsyncCallback(submitted_form, + load_time, + interaction_time, + submission_time); + } + + // Wait for the asynchronous OnFormSubmitted() call to complete. + void WaitForAsyncFormSubmit() { + if (!did_finish_async_form_submit_) { + // TODO(isherman): It seems silly to need this variable. Is there some + // way I can just query the message loop's state? + message_loop_is_running_ = true; + MessageLoop::current()->Run(); + } else { + did_finish_async_form_submit_ = false; + } + } + + virtual void UploadFormData(const FormStructure& submitted_form) OVERRIDE { + submitted_form_signature_ = submitted_form.FormSignature(); + } + + virtual void SendPasswordGenerationStateToRenderer( + content::RenderViewHost* host, bool enabled) OVERRIDE { + sent_states_.push_back(enabled); + } + + const std::vector<bool>& GetSentStates() { + return sent_states_; + } + + void ClearSentStates() { + sent_states_.clear(); + } + + const std::string GetSubmittedFormSignature() { + return submitted_form_signature_; + } + + AutofillProfile* GetProfileWithGUID(const char* guid) { + return personal_data_->GetProfileWithGUID(guid); + } + + CreditCard* GetCreditCardWithGUID(const char* guid) { + return personal_data_->GetCreditCardWithGUID(guid); + } + + void AddProfile(AutofillProfile* profile) { + personal_data_->AddProfile(profile); + } + + void AddCreditCard(CreditCard* credit_card) { + personal_data_->AddCreditCard(credit_card); + } + + int GetPackedCreditCardID(int credit_card_id) { + std::string credit_card_guid = + base::StringPrintf("00000000-0000-0000-0000-%012d", credit_card_id); + + return PackGUIDs(GUIDPair(credit_card_guid, 0), GUIDPair(std::string(), 0)); + } + + void AddSeenForm(FormStructure* form) { + form_structures()->push_back(form); + } + + virtual void ReturnAutocompleteResult( + WebFormElement::AutocompleteResult result, + const FormData& form_data) OVERRIDE { + request_autocomplete_results_.push_back(std::make_pair(result, form_data)); + } + + private: + // Weak reference. + TestPersonalDataManager* personal_data_; + + bool autofill_enabled_; + std::vector<std::pair<WebFormElement::AutocompleteResult, FormData> > + request_autocomplete_results_; + + bool did_finish_async_form_submit_; + bool message_loop_is_running_; + + std::string submitted_form_signature_; + std::vector<FieldTypeSet> expected_submitted_field_types_; + std::vector<bool> sent_states_; + + DISALLOW_COPY_AND_ASSIGN(TestAutofillManager); +}; + +} // namespace + +class AutofillManagerTest : public ChromeRenderViewHostTestHarness { + public: + AutofillManagerTest() + : ChromeRenderViewHostTestHarness(), + ui_thread_(BrowserThread::UI, &message_loop_), + file_thread_(BrowserThread::FILE), + io_thread_(BrowserThread::IO) { + } + + virtual ~AutofillManagerTest() { + } + + virtual void SetUp() OVERRIDE { + TestingProfile* profile = CreateProfile(); + profile->CreateRequestContext(); + browser_context_.reset(profile); + PersonalDataManagerFactory::GetInstance()->SetTestingFactory( + profile, TestPersonalDataManager::Build); + + ChromeRenderViewHostTestHarness::SetUp(); + io_thread_.StartIOThread(); + autofill::TabAutofillManagerDelegate::CreateForWebContents(web_contents()); + personal_data_.SetBrowserContext(profile); + autofill_manager_.reset(new TestAutofillManager( + web_contents(), + autofill::TabAutofillManagerDelegate::FromWebContents(web_contents()), + &personal_data_)); + + file_thread_.Start(); + } + + virtual void TearDown() OVERRIDE { + // Order of destruction is important as AutofillManager relies on + // PersonalDataManager to be around when it gets destroyed. Also, a real + // AutofillManager is tied to the lifetime of the WebContents, so it must + // be destroyed at the destruction of the WebContents. + autofill_manager_.reset(); + file_thread_.Stop(); + ChromeRenderViewHostTestHarness::TearDown(); + io_thread_.Stop(); + } + + virtual TestingProfile* CreateProfile() { + return new TestingProfile(); + } + + void UpdatePasswordGenerationState(bool new_renderer) { + autofill_manager_->UpdatePasswordGenerationState(NULL, new_renderer); + } + + void GetAutofillSuggestions(int query_id, + const FormData& form, + const FormFieldData& field) { + autofill_manager_->OnQueryFormFieldAutofill(query_id, + form, + field, + gfx::Rect(), + false); + } + + void GetAutofillSuggestions(const FormData& form, + const FormFieldData& field) { + GetAutofillSuggestions(kDefaultPageID, form, field); + } + + void AutocompleteSuggestionsReturned(const std::vector<string16>& result) { + autofill_manager_->autocomplete_history_manager_.SendSuggestions(&result); + } + + void FormsSeen(const std::vector<FormData>& forms) { + autofill_manager_->OnFormsSeen(forms, base::TimeTicks()); + } + + void FormSubmitted(const FormData& form) { + if (autofill_manager_->OnFormSubmitted(form, base::TimeTicks::Now())) + autofill_manager_->WaitForAsyncFormSubmit(); + } + + void FillAutofillFormData(int query_id, + const FormData& form, + const FormFieldData& field, + int unique_id) { + autofill_manager_->OnFillAutofillFormData(query_id, form, field, unique_id); + } + + int PackGUIDs(const GUIDPair& cc_guid, const GUIDPair& profile_guid) const { + return autofill_manager_->PackGUIDs(cc_guid, profile_guid); + } + + bool GetAutofillSuggestionsMessage(int* page_id, + std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<int>* unique_ids) { + const uint32 kMsgID = AutofillMsg_SuggestionsReturned::ID; + const IPC::Message* message = + process()->sink().GetFirstMessageMatching(kMsgID); + if (!message) + return false; + + AutofillParam autofill_param; + AutofillMsg_SuggestionsReturned::Read(message, &autofill_param); + if (page_id) + *page_id = autofill_param.a; + if (values) + *values = autofill_param.b; + if (labels) + *labels = autofill_param.c; + if (icons) + *icons = autofill_param.d; + if (unique_ids) + *unique_ids = autofill_param.e; + + autofill_manager_->autocomplete_history_manager_.CancelPendingQuery(); + process()->sink().ClearMessages(); + return true; + } + + bool GetAutofillFormDataFilledMessage(int* page_id, FormData* results) { + const uint32 kMsgID = AutofillMsg_FormDataFilled::ID; + const IPC::Message* message = + process()->sink().GetFirstMessageMatching(kMsgID); + if (!message) + return false; + Tuple2<int, FormData> autofill_param; + AutofillMsg_FormDataFilled::Read(message, &autofill_param); + if (page_id) + *page_id = autofill_param.a; + if (results) + *results = autofill_param.b; + + process()->sink().ClearMessages(); + return true; + } + + protected: + content::TestBrowserThread ui_thread_; + content::TestBrowserThread file_thread_; + content::TestBrowserThread io_thread_; + + scoped_ptr<TestAutofillManager> autofill_manager_; + TestPersonalDataManager personal_data_; + + // Used when we want an off the record profile. This will store the original + // profile from which the off the record profile is derived. + scoped_ptr<Profile> other_browser_context_; + + private: + DISALLOW_COPY_AND_ASSIGN(AutofillManagerTest); +}; + +class IncognitoAutofillManagerTest : public AutofillManagerTest { + public: + IncognitoAutofillManagerTest() {} + virtual ~IncognitoAutofillManagerTest() {} + + virtual TestingProfile* CreateProfile() OVERRIDE { + // Create an incognito profile. + TestingProfile::Builder builder; + scoped_ptr<TestingProfile> profile = builder.Build(); + profile->set_incognito(true); + return profile.release(); + } +}; + +class TestFormStructure : public FormStructure { + public: + explicit TestFormStructure(const FormData& form) + : FormStructure(form, std::string()) {} + virtual ~TestFormStructure() {} + + void SetFieldTypes(const std::vector<AutofillFieldType>& heuristic_types, + const std::vector<AutofillFieldType>& server_types) { + ASSERT_EQ(field_count(), heuristic_types.size()); + ASSERT_EQ(field_count(), server_types.size()); + + for (size_t i = 0; i < field_count(); ++i) { + AutofillField* form_field = field(i); + ASSERT_TRUE(form_field); + form_field->set_heuristic_type(heuristic_types[i]); + form_field->set_server_type(server_types[i]); + } + + UpdateAutofillCount(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestFormStructure); +}; + +// Test that we return all address profile suggestions when all form fields are +// empty. +TEST_F(AutofillManagerTest, GetProfileSuggestionsEmptyValue) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + const FormFieldData& field = form.fields[0]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + GetAutofillSuggestionsMessage( + &page_id, &values, &labels, &icons, &unique_ids); + + string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles") + }; + // Inferred labels include full first relevant field, which in this case is + // the address line 1. + string16 expected_labels[] = { + ASCIIToUTF16("3734 Elvis Presley Blvd."), + ASCIIToUTF16("123 Apple St.") + }; + string16 expected_icons[] = {string16(), string16()}; + int expected_unique_ids[] = {1, 2}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that we return only matching address profile suggestions when the +// selected form field has been partially filled out. +TEST_F(AutofillManagerTest, GetProfileSuggestionsMatchCharacter) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + FormFieldData field; + autofill_test::CreateTestFormField("First Name", "firstname", "E", "text", + &field); + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = {ASCIIToUTF16("Elvis")}; + string16 expected_labels[] = {ASCIIToUTF16("3734 Elvis Presley Blvd.")}; + string16 expected_icons[] = {string16()}; + int expected_unique_ids[] = {1}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that we return no suggestions when the form has no relevant fields. +TEST_F(AutofillManagerTest, GetProfileSuggestionsUnknownFields) { + // Set up our form data. + FormData form; + form.name = ASCIIToUTF16("MyForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://myform.com/form.html"); + form.action = GURL("http://myform.com/submit.html"); + form.user_submitted = true; + + FormFieldData field; + autofill_test::CreateTestFormField("Username", "username", "", "text", + &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField("Password", "password", "", "password", + &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField("Quest", "quest", "", "quest", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField("Color", "color", "", "text", &field); + form.fields.push_back(field); + + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GetAutofillSuggestions(form, field); + EXPECT_FALSE(GetAutofillSuggestionsMessage(NULL, NULL, NULL, NULL, NULL)); +} + +// Test that we cull duplicate profile suggestions. +TEST_F(AutofillManagerTest, GetProfileSuggestionsWithDuplicates) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Add a duplicate profile. + AutofillProfile* duplicate_profile = + new AutofillProfile( + *(autofill_manager_->GetProfileWithGUID( + "00000000-0000-0000-0000-000000000001"))); + autofill_manager_->AddProfile(duplicate_profile); + + const FormFieldData& field = form.fields[0]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles") + }; + string16 expected_labels[] = { + ASCIIToUTF16("3734 Elvis Presley Blvd."), + ASCIIToUTF16("123 Apple St.") + }; + string16 expected_icons[] = {string16(), string16()}; + int expected_unique_ids[] = {1, 2}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that we return no suggestions when autofill is disabled. +TEST_F(AutofillManagerTest, GetProfileSuggestionsAutofillDisabledByUser) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Disable Autofill. + autofill_manager_->set_autofill_enabled(false); + + const FormFieldData& field = form.fields[0]; + GetAutofillSuggestions(form, field); + EXPECT_FALSE(GetAutofillSuggestionsMessage(NULL, NULL, NULL, NULL, NULL)); +} + +// Test that we return a warning explaining that autofill suggestions are +// unavailable when the form method is GET rather than POST. +TEST_F(AutofillManagerTest, GetProfileSuggestionsMethodGet) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + form.method = ASCIIToUTF16("GET"); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + const FormFieldData& field = form.fields[0]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_FORM_DISABLED) + }; + string16 expected_labels[] = {string16()}; + string16 expected_icons[] = {string16()}; + int expected_unique_ids[] = + {WebKit::WebAutofillClient::MenuItemIDWarningMessage}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); + + // Now add some Autocomplete suggestions. We should return the autocomplete + // suggestions and the warning; these will be culled by the renderer. + const int kPageID2 = 2; + GetAutofillSuggestions(kPageID2, form, field); + + std::vector<string16> suggestions; + suggestions.push_back(ASCIIToUTF16("Jay")); + suggestions.push_back(ASCIIToUTF16("Jason")); + AutocompleteSuggestionsReturned(suggestions); + + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values2[] = { + l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_FORM_DISABLED), + ASCIIToUTF16("Jay"), + ASCIIToUTF16("Jason") + }; + string16 expected_labels2[] = {string16(), string16(), string16()}; + string16 expected_icons2[] = {string16(), string16(), string16()}; + int expected_unique_ids2[] = {-1, 0, 0}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kPageID2, arraysize(expected_values2), expected_values2, + expected_labels2, expected_icons2, expected_unique_ids2); + + // Now clear the test profiles and try again -- we shouldn't return a warning. + personal_data_.ClearAutofillProfiles(); + GetAutofillSuggestions(form, field); + EXPECT_FALSE(GetAutofillSuggestionsMessage(NULL, NULL, NULL, NULL, NULL)); +} + +// Test that we return all credit card profile suggestions when all form fields +// are empty. +TEST_F(AutofillManagerTest, GetCreditCardSuggestionsEmptyValue) { + // Set up our form data. + FormData form; + CreateTestCreditCardFormData(&form, true, false); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + FormFieldData field = form.fields[1]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + ASCIIToUTF16("************3456"), + ASCIIToUTF16("************8765") + }; + string16 expected_labels[] = {ASCIIToUTF16("*3456"), ASCIIToUTF16("*8765")}; + string16 expected_icons[] = { + ASCIIToUTF16("visaCC"), + ASCIIToUTF16("genericCC") + }; + int expected_unique_ids[] = { + autofill_manager_->GetPackedCreditCardID(4), + autofill_manager_->GetPackedCreditCardID(5) + }; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that we return only matching credit card profile suggestions when the +// selected form field has been partially filled out. +TEST_F(AutofillManagerTest, GetCreditCardSuggestionsMatchCharacter) { + // Set up our form data. + FormData form; + CreateTestCreditCardFormData(&form, true, false); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + FormFieldData field; + autofill_test::CreateTestFormField( + "Card Number", "cardnumber", "4", "text", &field); + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = {ASCIIToUTF16("************3456")}; + string16 expected_labels[] = {ASCIIToUTF16("*3456")}; + string16 expected_icons[] = {ASCIIToUTF16("visaCC")}; + int expected_unique_ids[] = {autofill_manager_->GetPackedCreditCardID(4)}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that we return credit card profile suggestions when the selected form +// field is not the credit card number field. +TEST_F(AutofillManagerTest, GetCreditCardSuggestionsNonCCNumber) { + // Set up our form data. + FormData form; + CreateTestCreditCardFormData(&form, true, false); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + const FormFieldData& field = form.fields[0]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + ASCIIToUTF16("Elvis Presley"), + ASCIIToUTF16("Buddy Holly") + }; + string16 expected_labels[] = {ASCIIToUTF16("*3456"), ASCIIToUTF16("*8765")}; + string16 expected_icons[] = { + ASCIIToUTF16("visaCC"), + ASCIIToUTF16("genericCC") + }; + int expected_unique_ids[] = { + autofill_manager_->GetPackedCreditCardID(4), + autofill_manager_->GetPackedCreditCardID(5) + }; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that we return a warning explaining that credit card profile suggestions +// are unavailable when the form is not https. +TEST_F(AutofillManagerTest, GetCreditCardSuggestionsNonHTTPS) { + // Set up our form data. + FormData form; + CreateTestCreditCardFormData(&form, false, false); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + const FormFieldData& field = form.fields[0]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_INSECURE_CONNECTION) + }; + string16 expected_labels[] = {string16()}; + string16 expected_icons[] = {string16()}; + int expected_unique_ids[] = {-1}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); + + // Now add some Autocomplete suggestions. We should show the autocomplete + // suggestions and the warning. + const int kPageID2 = 2; + GetAutofillSuggestions(kPageID2, form, field); + + std::vector<string16> suggestions; + suggestions.push_back(ASCIIToUTF16("Jay")); + suggestions.push_back(ASCIIToUTF16("Jason")); + AutocompleteSuggestionsReturned(suggestions); + + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + string16 expected_values2[] = { + l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_INSECURE_CONNECTION), + ASCIIToUTF16("Jay"), + ASCIIToUTF16("Jason") + }; + string16 expected_labels2[] = {string16(), string16(), string16()}; + string16 expected_icons2[] = {string16(), string16(), string16()}; + int expected_unique_ids2[] = {-1, 0, 0}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kPageID2, arraysize(expected_values2), expected_values2, + expected_labels2, expected_icons2, expected_unique_ids2); + + // Clear the test credit cards and try again -- we shouldn't return a warning. + personal_data_.ClearCreditCards(); + GetAutofillSuggestions(form, field); + EXPECT_FALSE(GetAutofillSuggestionsMessage(NULL, NULL, NULL, NULL, NULL)); +} + +// Test that we return all credit card suggestions in the case that two cards +// have the same obfuscated number. +TEST_F(AutofillManagerTest, GetCreditCardSuggestionsRepeatedObfuscatedNumber) { + // Add a credit card with the same obfuscated number as Elvis's. + // |credit_card| will be owned by the mock PersonalDataManager. + CreditCard* credit_card = new CreditCard; + autofill_test::SetCreditCardInfo(credit_card, "Elvis Presley", + "5231567890123456", // Mastercard + "04", "2012"); + credit_card->set_guid("00000000-0000-0000-0000-000000000007"); + autofill_manager_->AddCreditCard(credit_card); + + // Set up our form data. + FormData form; + CreateTestCreditCardFormData(&form, true, false); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + FormFieldData field = form.fields[1]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + ASCIIToUTF16("************3456"), + ASCIIToUTF16("************8765"), + ASCIIToUTF16("************3456") + }; + string16 expected_labels[] = { + ASCIIToUTF16("*3456"), + ASCIIToUTF16("*8765"), + ASCIIToUTF16("*3456"), + }; + string16 expected_icons[] = { + ASCIIToUTF16("visaCC"), + ASCIIToUTF16("genericCC"), + ASCIIToUTF16("masterCardCC") + }; + int expected_unique_ids[] = { + autofill_manager_->GetPackedCreditCardID(4), + autofill_manager_->GetPackedCreditCardID(5), + autofill_manager_->GetPackedCreditCardID(7) + }; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that we return profile and credit card suggestions for combined forms. +TEST_F(AutofillManagerTest, GetAddressAndCreditCardSuggestions) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + CreateTestCreditCardFormData(&form, true, false); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + FormFieldData field = form.fields[0]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right address suggestions to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles") + }; + string16 expected_labels[] = { + ASCIIToUTF16("3734 Elvis Presley Blvd."), + ASCIIToUTF16("123 Apple St.") + }; + string16 expected_icons[] = {string16(), string16()}; + int expected_unique_ids[] = {1, 2}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); + + const int kPageID2 = 2; + autofill_test::CreateTestFormField( + "Card Number", "cardnumber", "", "text", &field); + GetAutofillSuggestions(kPageID2, form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the credit card suggestions to the renderer. + page_id = 0; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values2[] = { + ASCIIToUTF16("************3456"), + ASCIIToUTF16("************8765") + }; + string16 expected_labels2[] = {ASCIIToUTF16("*3456"), ASCIIToUTF16("*8765")}; + string16 expected_icons2[] = { + ASCIIToUTF16("visaCC"), + ASCIIToUTF16("genericCC") + }; + int expected_unique_ids2[] = { + autofill_manager_->GetPackedCreditCardID(4), + autofill_manager_->GetPackedCreditCardID(5) + }; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kPageID2, arraysize(expected_values2), expected_values2, + expected_labels2, expected_icons2, expected_unique_ids2); +} + +// 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(AutofillManagerTest, GetAddressAndCreditCardSuggestionsNonHttps) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + CreateTestCreditCardFormData(&form, false, false); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + FormFieldData field = form.fields[0]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right address suggestions to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles") + }; + string16 expected_labels[] = { + ASCIIToUTF16("3734 Elvis Presley Blvd."), + ASCIIToUTF16("123 Apple St.") + }; + string16 expected_icons[] = {string16(), string16()}; + int expected_unique_ids[] = {1, 2}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); + + autofill_test::CreateTestFormField( + "Card Number", "cardnumber", "", "text", &field); + const int kPageID2 = 2; + GetAutofillSuggestions(kPageID2, form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values2[] = { + l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_INSECURE_CONNECTION) + }; + string16 expected_labels2[] = {string16()}; + string16 expected_icons2[] = {string16()}; + int expected_unique_ids2[] = {-1}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kPageID2, arraysize(expected_values2), expected_values2, + expected_labels2, expected_icons2, expected_unique_ids2); + + // Clear the test credit cards and try again -- we shouldn't return a warning. + personal_data_.ClearCreditCards(); + GetAutofillSuggestions(form, field); + EXPECT_FALSE(GetAutofillSuggestionsMessage(NULL, NULL, NULL, NULL, NULL)); +} + +// Test that we correctly combine autofill and autocomplete suggestions. +TEST_F(AutofillManagerTest, GetCombinedAutofillAndAutocompleteSuggestions) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + const FormFieldData& field = form.fields[0]; + GetAutofillSuggestions(form, field); + + // Add some Autocomplete suggestions. + // This triggers the combined message send. + std::vector<string16> suggestions; + suggestions.push_back(ASCIIToUTF16("Jay")); + // This suggestion is a duplicate, and should be trimmed. + suggestions.push_back(ASCIIToUTF16("Elvis")); + suggestions.push_back(ASCIIToUTF16("Jason")); + AutocompleteSuggestionsReturned(suggestions); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles"), + ASCIIToUTF16("Jay"), + ASCIIToUTF16("Jason") + }; + string16 expected_labels[] = { + ASCIIToUTF16("3734 Elvis Presley Blvd."), + ASCIIToUTF16("123 Apple St."), + string16(), + string16() + }; + string16 expected_icons[] = {string16(), string16(), string16(), string16()}; + int expected_unique_ids[] = {1, 2, 0, 0}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that we return autocomplete-like suggestions when trying to autofill +// already filled forms. +TEST_F(AutofillManagerTest, GetFieldSuggestionsWhenFormIsAutofilled) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Mark one of the fields as filled. + form.fields[2].is_autofilled = true; + const FormFieldData& field = form.fields[0]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles") + }; + string16 expected_labels[] = {string16(), string16()}; + string16 expected_icons[] = {string16(), string16()}; + int expected_unique_ids[] = {1, 2}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that nothing breaks when there are autocomplete suggestions but no +// autofill suggestions. +TEST_F(AutofillManagerTest, GetFieldSuggestionsForAutocompleteOnly) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + FormFieldData field; + autofill_test::CreateTestFormField( + "Some Field", "somefield", "", "text", &field); + form.fields.push_back(field); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GetAutofillSuggestions(form, field); + + // Add some Autocomplete suggestions. + // This triggers the combined message send. + std::vector<string16> suggestions; + suggestions.push_back(ASCIIToUTF16("one")); + suggestions.push_back(ASCIIToUTF16("two")); + AutocompleteSuggestionsReturned(suggestions); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + ASCIIToUTF16("one"), + ASCIIToUTF16("two") + }; + string16 expected_labels[] = {string16(), string16()}; + string16 expected_icons[] = {string16(), string16()}; + int expected_unique_ids[] = {0, 0}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that we do not return duplicate values drawn from multiple profiles when +// filling an already filled field. +TEST_F(AutofillManagerTest, GetFieldSuggestionsWithDuplicateValues) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // |profile| will be owned by the mock PersonalDataManager. + AutofillProfile* profile = new AutofillProfile; + autofill_test::SetProfileInfo(profile, "Elvis", "", "", "", "", "", "", "", + "", "", "", ""); + profile->set_guid("00000000-0000-0000-0000-000000000101"); + autofill_manager_->AddProfile(profile); + + FormFieldData& field = form.fields[0]; + field.is_autofilled = true; + field.value = ASCIIToUTF16("Elvis"); + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { ASCIIToUTF16("Elvis") }; + string16 expected_labels[] = { string16() }; + string16 expected_icons[] = { string16() }; + int expected_unique_ids[] = { 1 }; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that a non-default value is suggested for multi-valued profile, on an +// unfilled form. +TEST_F(AutofillManagerTest, GetFieldSuggestionsForMultiValuedProfileUnfilled) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // |profile| will be owned by the mock PersonalDataManager. + AutofillProfile* profile = new AutofillProfile; + autofill_test::SetProfileInfo(profile, "Elvis", "", "Presley", "me@x.com", "", + "", "", "", "", "", "", ""); + profile->set_guid("00000000-0000-0000-0000-000000000101"); + std::vector<string16> multi_values(2); + multi_values[0] = ASCIIToUTF16("Elvis Presley"); + multi_values[1] = ASCIIToUTF16("Elena Love"); + profile->SetRawMultiInfo(NAME_FULL, multi_values); + personal_data_.ClearAutofillProfiles(); + autofill_manager_->AddProfile(profile); + + { + // Get the first name field. + // Start out with "E", hoping for either "Elvis" or "Elena. + FormFieldData& field = form.fields[0]; + field.value = ASCIIToUTF16("E"); + field.is_autofilled = false; + GetAutofillSuggestions(form, field); + + // Trigger the |Send|. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, + &icons, &unique_ids)); + string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Elena") + }; + string16 expected_labels[] = { + ASCIIToUTF16("me@x.com"), + ASCIIToUTF16("me@x.com") + }; + string16 expected_icons[] = { string16(), string16() }; + int expected_unique_ids[] = { 1, 2 }; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), + expected_values, expected_labels, expected_icons, + expected_unique_ids); + } + + { + // Get the first name field. + // This time, start out with "Ele", hoping for "Elena". + FormFieldData& field = form.fields[0]; + field.value = ASCIIToUTF16("Ele"); + field.is_autofilled = false; + GetAutofillSuggestions(form, field); + + // Trigger the |Send|. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, + &icons, &unique_ids)); + + string16 expected_values[] = { ASCIIToUTF16("Elena") }; + string16 expected_labels[] = { ASCIIToUTF16("me@x.com") }; + string16 expected_icons[] = { string16() }; + int expected_unique_ids[] = { 2 }; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), + expected_values, expected_labels, expected_icons, + expected_unique_ids); + } +} + +// Test that all values are suggested for multi-valued profile, on a filled +// form. This is the per-field "override" case. +TEST_F(AutofillManagerTest, GetFieldSuggestionsForMultiValuedProfileFilled) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // |profile| will be owned by the mock PersonalDataManager. + AutofillProfile* profile = new AutofillProfile; + profile->set_guid("00000000-0000-0000-0000-000000000102"); + std::vector<string16> multi_values(3); + multi_values[0] = ASCIIToUTF16("Travis Smith"); + multi_values[1] = ASCIIToUTF16("Cynthia Love"); + multi_values[2] = ASCIIToUTF16("Zac Mango"); + profile->SetRawMultiInfo(NAME_FULL, multi_values); + autofill_manager_->AddProfile(profile); + + // Get the first name field. And start out with "Travis", hoping for all the + // multi-valued variants as suggestions. + FormFieldData& field = form.fields[0]; + field.value = ASCIIToUTF16("Travis"); + field.is_autofilled = true; + GetAutofillSuggestions(form, field); + + // Trigger the |Send|. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + EXPECT_TRUE(GetAutofillSuggestionsMessage(&page_id, &values, &labels, &icons, + &unique_ids)); + + string16 expected_values[] = { + ASCIIToUTF16("Travis"), + ASCIIToUTF16("Cynthia"), + ASCIIToUTF16("Zac") + }; + string16 expected_labels[] = { string16(), string16(), string16() }; + string16 expected_icons[] = { string16(), string16(), string16() }; + int expected_unique_ids[] = { 1, 2, 3 }; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +TEST_F(AutofillManagerTest, GetProfileSuggestionsFancyPhone) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + AutofillProfile* profile = new AutofillProfile; + profile->set_guid("00000000-0000-0000-0000-000000000103"); + std::vector<string16> multi_values(1); + multi_values[0] = ASCIIToUTF16("Natty Bumppo"); + profile->SetRawMultiInfo(NAME_FULL, multi_values); + multi_values[0] = ASCIIToUTF16("1800PRAIRIE"); + profile->SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, multi_values); + autofill_manager_->AddProfile(profile); + + const FormFieldData& field = form.fields[9]; + GetAutofillSuggestions(form, field); + + // No suggestions provided, so send an empty vector as the results. + // This triggers the combined message send. + AutocompleteSuggestionsReturned(std::vector<string16>()); + + // Test that we sent the right message to the renderer. + int page_id = 0; + std::vector<string16> values; + std::vector<string16> labels; + std::vector<string16> icons; + std::vector<int> unique_ids; + GetAutofillSuggestionsMessage( + &page_id, &values, &labels, &icons, &unique_ids); + + string16 expected_values[] = { + ASCIIToUTF16("12345678901"), + ASCIIToUTF16("23456789012"), + ASCIIToUTF16("18007724743"), // 1800PRAIRIE + }; + // Inferred labels include full first relevant field, which in this case is + // the address line 1. + string16 expected_labels[] = { + ASCIIToUTF16("Elvis Aaron Presley"), + ASCIIToUTF16("Charles Hardin Holley"), + ASCIIToUTF16("Natty Bumppo"), + }; + string16 expected_icons[] = {string16(), string16(), string16()}; + int expected_unique_ids[] = {1, 2, 3}; + ExpectSuggestions(page_id, values, labels, icons, unique_ids, + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that we correctly fill an address form. +TEST_F(AutofillManagerTest, FillAddressForm) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledAddressFormElvis(page_id, results, kDefaultPageID, false); +} + +// Test that we correctly fill an address form from an auxiliary profile. +TEST_F(AutofillManagerTest, FillAddressFormFromAuxiliaryProfile) { + personal_data_.ClearAutofillProfiles(); + PrefService* prefs = components::UserPrefs::Get(profile()); + prefs->SetBoolean(prefs::kAutofillAuxiliaryProfilesEnabled, true); + personal_data_.CreateTestAuxiliaryProfiles(); + + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledAddressFormElvis(page_id, results, kDefaultPageID, false); +} + +// Test that we correctly fill a credit card form. +TEST_F(AutofillManagerTest, FillCreditCardForm) { + // Set up our form data. + FormData form; + CreateTestCreditCardFormData(&form, true, false); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GUIDPair guid("00000000-0000-0000-0000-000000000004", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(guid, empty)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledCreditCardFormElvis(page_id, results, kDefaultPageID, false); +} + +// Test that we correctly fill a credit card form with month input type. +// 1. year empty, month empty +TEST_F(AutofillManagerTest, FillCreditCardFormNoYearNoMonth) { + // Same as the SetUp(), but generate 4 credit cards with year month + // combination. + personal_data_.CreateTestCreditCardsYearAndMonth("", ""); + // Set up our form data. + FormData form; + CreateTestCreditCardFormData(&form, true, true); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GUIDPair guid("00000000-0000-0000-0000-000000000007", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(guid, empty)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledCreditCardYearMonthWithYearMonth(page_id, results, + kDefaultPageID, false, "", ""); +} + + +// Test that we correctly fill a credit card form with month input type. +// 2. year empty, month non-empty +TEST_F(AutofillManagerTest, FillCreditCardFormNoYearMonth) { + // Same as the SetUp(), but generate 4 credit cards with year month + // combination. + personal_data_.CreateTestCreditCardsYearAndMonth("", "04"); + // Set up our form data. + FormData form; + CreateTestCreditCardFormData(&form, true, true); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GUIDPair guid("00000000-0000-0000-0000-000000000007", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(guid, empty)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledCreditCardYearMonthWithYearMonth(page_id, results, + kDefaultPageID, false, "", "04"); +} + +// Test that we correctly fill a credit card form with month input type. +// 3. year non-empty, month empty +TEST_F(AutofillManagerTest, FillCreditCardFormYearNoMonth) { + // Same as the SetUp(), but generate 4 credit cards with year month + // combination. + personal_data_.CreateTestCreditCardsYearAndMonth("2012", ""); + // Set up our form data. + FormData form; + CreateTestCreditCardFormData(&form, true, true); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GUIDPair guid("00000000-0000-0000-0000-000000000007", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(guid, empty)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledCreditCardYearMonthWithYearMonth(page_id, results, + kDefaultPageID, false, "2012", ""); +} + +// Test that we correctly fill a credit card form with month input type. +// 4. year non-empty, month empty +TEST_F(AutofillManagerTest, FillCreditCardFormYearMonth) { + // Same as the SetUp(), but generate 4 credit cards with year month + // combination. + personal_data_.ClearCreditCards(); + personal_data_.CreateTestCreditCardsYearAndMonth("2012", "04"); + // Set up our form data. + FormData form; + CreateTestCreditCardFormData(&form, true, true); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GUIDPair guid("00000000-0000-0000-0000-000000000007", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(guid, empty)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledCreditCardYearMonthWithYearMonth(page_id, results, + kDefaultPageID, false, "2012", "04"); +} + +// Test that we correctly fill a combined address and credit card form. +TEST_F(AutofillManagerTest, FillAddressAndCreditCardForm) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + CreateTestCreditCardFormData(&form, true, false); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // First fill the address data. + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Address"); + ExpectFilledAddressFormElvis(page_id, results, kDefaultPageID, true); + } + + // Now fill the credit card data. + const int kPageID2 = 2; + GUIDPair guid2("00000000-0000-0000-0000-000000000004", 0); + FillAutofillFormData(kPageID2, form, form.fields.back(), + PackGUIDs(guid2, empty)); + + page_id = 0; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Credit card"); + ExpectFilledCreditCardFormElvis(page_id, results, kPageID2, true); + } +} + +// Test that we correctly fill a form that has multiple logical sections, e.g. +// both a billing and a shipping address. +TEST_F(AutofillManagerTest, FillFormWithMultipleSections) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + const size_t kAddressFormSize = form.fields.size(); + CreateTestAddressFormData(&form); + for (size_t i = kAddressFormSize; i < form.fields.size(); ++i) { + // Make sure the fields have distinct names. + form.fields[i].name = form.fields[i].name + ASCIIToUTF16("_"); + } + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Fill the first section. + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Address 1"); + + // The second address section should be empty. + ASSERT_EQ(results.fields.size(), 2*kAddressFormSize); + for (size_t i = kAddressFormSize; i < form.fields.size(); ++i) { + EXPECT_EQ(string16(), results.fields[i].value); + } + + // The first address section should be filled with Elvis's data. + results.fields.resize(kAddressFormSize); + ExpectFilledAddressFormElvis(page_id, results, kDefaultPageID, false); + } + + // Fill the second section, with the initiating field somewhere in the middle + // of the section. + const int kPageID2 = 2; + GUIDPair guid2("00000000-0000-0000-0000-000000000001", 0); + ASSERT_LT(9U, kAddressFormSize); + FillAutofillFormData(kPageID2, form, form.fields[kAddressFormSize + 9], + PackGUIDs(empty, guid2)); + + page_id = 0; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Address 2"); + ASSERT_EQ(results.fields.size(), form.fields.size()); + + // The first address section should be empty. + ASSERT_EQ(results.fields.size(), 2*kAddressFormSize); + for (size_t i = 0; i < kAddressFormSize; ++i) { + EXPECT_EQ(string16(), results.fields[i].value); + } + + // The second address section should be filled with Elvis's data. + FormData secondSection = results; + secondSection.fields.erase(secondSection.fields.begin(), + secondSection.fields.begin() + kAddressFormSize); + for (size_t i = 0; i < kAddressFormSize; ++i) { + // Restore the expected field names. + string16 name = secondSection.fields[i].name; + string16 original_name = name.substr(0, name.size() - 1); + secondSection.fields[i].name = original_name; + } + ExpectFilledAddressFormElvis(page_id, secondSection, kPageID2, false); + } +} + +// Test that we correctly fill a form that has author-specified sections, which +// might not match our expected section breakdown. +TEST_F(AutofillManagerTest, FillFormWithAuthorSpecifiedSections) { + // Create a form with a billing section and an unnamed section, interleaved. + // The billing section includes both address and credit card fields. + FormData form; + form.name = ASCIIToUTF16("MyForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("https://myform.com/form.html"); + form.action = GURL("https://myform.com/submit.html"); + form.user_submitted = true; + + FormFieldData field; + + autofill_test::CreateTestFormField("", "country", "", "text", &field); + field.autocomplete_attribute = "section-billing country"; + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "firstname", "", "text", &field); + field.autocomplete_attribute = "given-name"; + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "lastname", "", "text", &field); + field.autocomplete_attribute = "family-name"; + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "address", "", "text", &field); + field.autocomplete_attribute = "section-billing street-address"; + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "city", "", "text", &field); + field.autocomplete_attribute = "section-billing locality"; + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "state", "", "text", &field); + field.autocomplete_attribute = "section-billing region"; + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "zip", "", "text", &field); + field.autocomplete_attribute = "section-billing postal-code"; + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "ccname", "", "text", &field); + field.autocomplete_attribute = "section-billing cc-name"; + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "ccnumber", "", "text", &field); + field.autocomplete_attribute = "section-billing cc-number"; + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "ccexp", "", "text", &field); + field.autocomplete_attribute = "section-billing cc-exp"; + form.fields.push_back(field); + + autofill_test::CreateTestFormField("", "email", "", "text", &field); + field.autocomplete_attribute = "email"; + form.fields.push_back(field); + + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Fill the unnamed section. + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[1], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Unnamed section"); + EXPECT_EQ(kDefaultPageID, page_id); + EXPECT_EQ(ASCIIToUTF16("MyForm"), results.name); + EXPECT_EQ(ASCIIToUTF16("POST"), results.method); + EXPECT_EQ(GURL("https://myform.com/form.html"), results.origin); + EXPECT_EQ(GURL("https://myform.com/submit.html"), results.action); + EXPECT_TRUE(results.user_submitted); + ASSERT_EQ(11U, results.fields.size()); + + ExpectFilledField("", "country", "", "text", results.fields[0]); + ExpectFilledField("", "firstname", "Elvis", "text", results.fields[1]); + ExpectFilledField("", "lastname", "Presley", "text", results.fields[2]); + ExpectFilledField("", "address", "", "text", results.fields[3]); + ExpectFilledField("", "city", "", "text", results.fields[4]); + ExpectFilledField("", "state", "", "text", results.fields[5]); + ExpectFilledField("", "zip", "", "text", results.fields[6]); + ExpectFilledField("", "ccname", "", "text", results.fields[7]); + ExpectFilledField("", "ccnumber", "", "text", results.fields[8]); + ExpectFilledField("", "ccexp", "", "text", results.fields[9]); + ExpectFilledField("", "email", "theking@gmail.com", "text", + results.fields[10]); + } + + // Fill the address portion of the billing section. + const int kPageID2 = 2; + GUIDPair guid2("00000000-0000-0000-0000-000000000001", 0); + FillAutofillFormData(kPageID2, form, form.fields[0], + PackGUIDs(empty, guid2)); + + page_id = 0; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Billing address"); + EXPECT_EQ(kPageID2, page_id); + EXPECT_EQ(ASCIIToUTF16("MyForm"), results.name); + EXPECT_EQ(ASCIIToUTF16("POST"), results.method); + EXPECT_EQ(GURL("https://myform.com/form.html"), results.origin); + EXPECT_EQ(GURL("https://myform.com/submit.html"), results.action); + EXPECT_TRUE(results.user_submitted); + ASSERT_EQ(11U, results.fields.size()); + + ExpectFilledField("", "country", "United States", "text", + results.fields[0]); + ExpectFilledField("", "firstname", "", "text", results.fields[1]); + ExpectFilledField("", "lastname", "", "text", results.fields[2]); + ExpectFilledField("", "address", "3734 Elvis Presley Blvd.", "text", + results.fields[3]); + ExpectFilledField("", "city", "Memphis", "text", results.fields[4]); + ExpectFilledField("", "state", "Tennessee", "text", results.fields[5]); + ExpectFilledField("", "zip", "38116", "text", results.fields[6]); + ExpectFilledField("", "ccname", "", "text", results.fields[7]); + ExpectFilledField("", "ccnumber", "", "text", results.fields[8]); + ExpectFilledField("", "ccexp", "", "text", results.fields[9]); + ExpectFilledField("", "email", "", "text", results.fields[10]); + } + + // Fill the credit card portion of the billing section. + const int kPageID3 = 3; + GUIDPair guid3("00000000-0000-0000-0000-000000000004", 0); + FillAutofillFormData(kPageID3, form, form.fields[form.fields.size() - 2], + PackGUIDs(guid3, empty)); + + page_id = 0; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Credit card"); + EXPECT_EQ(kPageID3, page_id); + EXPECT_EQ(ASCIIToUTF16("MyForm"), results.name); + EXPECT_EQ(ASCIIToUTF16("POST"), results.method); + EXPECT_EQ(GURL("https://myform.com/form.html"), results.origin); + EXPECT_EQ(GURL("https://myform.com/submit.html"), results.action); + EXPECT_TRUE(results.user_submitted); + ASSERT_EQ(11U, results.fields.size()); + + ExpectFilledField("", "country", "", "text", results.fields[0]); + ExpectFilledField("", "firstname", "", "text", results.fields[1]); + ExpectFilledField("", "lastname", "", "text", results.fields[2]); + ExpectFilledField("", "address", "", "text", results.fields[3]); + ExpectFilledField("", "city", "", "text", results.fields[4]); + ExpectFilledField("", "state", "", "text", results.fields[5]); + ExpectFilledField("", "zip", "", "text", results.fields[6]); + ExpectFilledField("", "ccname", "Elvis Presley", "text", results.fields[7]); + ExpectFilledField("", "ccnumber", "4234567890123456", "text", + results.fields[8]); + ExpectFilledField("", "ccexp", "04/2012", "text", results.fields[9]); + ExpectFilledField("", "email", "", "text", results.fields[10]); + } +} + +// Test that we correctly fill a form that has a single logical section with +// multiple email address fields. +TEST_F(AutofillManagerTest, FillFormWithMultipleEmails) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + FormFieldData field; + autofill_test::CreateTestFormField( + "Confirm email", "email2", "", "text", &field); + form.fields.push_back(field); + + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Fill the form. + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + + // The second email address should be filled. + EXPECT_EQ(ASCIIToUTF16("theking@gmail.com"), results.fields.back().value); + + // The remainder of the form should be filled as usual. + results.fields.pop_back(); + ExpectFilledAddressFormElvis(page_id, results, kDefaultPageID, false); +} + +// Test that we correctly fill a previously auto-filled form. +TEST_F(AutofillManagerTest, FillAutofilledForm) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + // Mark one of the address fields as autofilled. + form.fields[4].is_autofilled = true; + CreateTestCreditCardFormData(&form, true, false); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // First fill the address data. + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Address"); + ExpectFilledForm(page_id, results, kDefaultPageID, + "Elvis", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", true, true, false); + } + + // Now fill the credit card data. + const int kPageID2 = 2; + GUIDPair guid2("00000000-0000-0000-0000-000000000004", 0); + FillAutofillFormData(kPageID2, form, form.fields.back(), + PackGUIDs(guid2, empty)); + + page_id = 0; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Credit card 1"); + ExpectFilledCreditCardFormElvis(page_id, results, kPageID2, true); + } + + // Now set the credit card fields to also be auto-filled, and try again to + // fill the credit card data + for (std::vector<FormFieldData>::iterator iter = form.fields.begin(); + iter != form.fields.end(); + ++iter) { + iter->is_autofilled = true; + } + + const int kPageID3 = 3; + FillAutofillFormData(kPageID3, form, *form.fields.rbegin(), + PackGUIDs(guid2, empty)); + + page_id = 0; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + { + SCOPED_TRACE("Credit card 2"); + ExpectFilledForm(page_id, results, kPageID3, + "", "", "", "", "", "", "", "", "", "", "", "", "", "", + "2012", true, true, false); + } +} + +// Test that we correctly fill an address form with a non-default variant for a +// multi-valued field. +TEST_F(AutofillManagerTest, FillAddressFormWithVariantType) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Add a name variant to the Elvis profile. + AutofillProfile* profile = autofill_manager_->GetProfileWithGUID( + "00000000-0000-0000-0000-000000000001"); + const string16 elvis_name = profile->GetRawInfo(NAME_FULL); + + std::vector<string16> name_variants; + name_variants.push_back(ASCIIToUTF16("Some Other Guy")); + name_variants.push_back(elvis_name); + profile->SetRawMultiInfo(NAME_FULL, name_variants); + + GUIDPair guid(profile->guid(), 1); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results1; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results1)); + { + SCOPED_TRACE("Valid variant"); + ExpectFilledAddressFormElvis(page_id, results1, kDefaultPageID, false); + } + + // Try filling with a variant that doesn't exist. The fields to which this + // variant would normally apply should not be filled. + const int kPageID2 = 2; + GUIDPair guid2(profile->guid(), 2); + FillAutofillFormData(kPageID2, form, form.fields[0], + PackGUIDs(empty, guid2)); + + page_id = 0; + FormData results2; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results2)); + { + SCOPED_TRACE("Invalid variant"); + ExpectFilledForm(page_id, results2, kPageID2, "", "", "", + "3734 Elvis Presley Blvd.", "Apt. 10", "Memphis", + "Tennessee", "38116", "United States", "12345678901", + "theking@gmail.com", "", "", "", "", true, false, false); + } +} + +// Test that we correctly fill a phone number split across multiple fields. +TEST_F(AutofillManagerTest, FillPhoneNumber) { + // In one form, rely on the maxlength attribute to imply phone number parts. + // In the other form, rely on the autocompletetype attribute. + FormData form_with_maxlength; + form_with_maxlength.name = ASCIIToUTF16("MyMaxlengthPhoneForm"); + form_with_maxlength.method = ASCIIToUTF16("POST"); + form_with_maxlength.origin = GURL("http://myform.com/phone_form.html"); + form_with_maxlength.action = GURL("http://myform.com/phone_submit.html"); + form_with_maxlength.user_submitted = true; + FormData form_with_autocompletetype = form_with_maxlength; + form_with_autocompletetype.name = ASCIIToUTF16("MyAutocompletetypePhoneForm"); + + struct { + const char* label; + const char* name; + size_t max_length; + const char* autocomplete_attribute; + } test_fields[] = { + { "country code", "country_code", 1, "tel-country-code" }, + { "area code", "area_code", 3, "tel-area-code" }, + { "phone", "phone_prefix", 3, "tel-local-prefix" }, + { "-", "phone_suffix", 4, "tel-local-suffix" }, + { "Phone Extension", "ext", 3, "tel-extension" } + }; + + FormFieldData field; + const size_t default_max_length = field.max_length; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_fields); ++i) { + autofill_test::CreateTestFormField( + test_fields[i].label, test_fields[i].name, "", "text", &field); + field.max_length = test_fields[i].max_length; + field.autocomplete_attribute = std::string(); + form_with_maxlength.fields.push_back(field); + + field.max_length = default_max_length; + field.autocomplete_attribute = test_fields[i].autocomplete_attribute; + form_with_autocompletetype.fields.push_back(field); + } + + std::vector<FormData> forms; + forms.push_back(form_with_maxlength); + forms.push_back(form_with_autocompletetype); + FormsSeen(forms); + + // We should be able to fill prefix and suffix fields for US numbers. + AutofillProfile* work_profile = autofill_manager_->GetProfileWithGUID( + "00000000-0000-0000-0000-000000000002"); + ASSERT_TRUE(work_profile != NULL); + work_profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, + ASCIIToUTF16("16505554567")); + + GUIDPair guid(work_profile->guid(), 0); + GUIDPair empty(std::string(), 0); + + int page_id = 1; + FillAutofillFormData(page_id, form_with_maxlength, + *form_with_maxlength.fields.begin(), + PackGUIDs(empty, guid)); + page_id = 0; + FormData results1; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results1)); + EXPECT_EQ(1, page_id); + + ASSERT_EQ(5U, results1.fields.size()); + EXPECT_EQ(ASCIIToUTF16("1"), results1.fields[0].value); + EXPECT_EQ(ASCIIToUTF16("650"), results1.fields[1].value); + EXPECT_EQ(ASCIIToUTF16("555"), results1.fields[2].value); + EXPECT_EQ(ASCIIToUTF16("4567"), results1.fields[3].value); + EXPECT_EQ(string16(), results1.fields[4].value); + + page_id = 2; + FillAutofillFormData(page_id, form_with_autocompletetype, + *form_with_autocompletetype.fields.begin(), + PackGUIDs(empty, guid)); + page_id = 0; + FormData results2; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results2)); + EXPECT_EQ(2, page_id); + + ASSERT_EQ(5U, results2.fields.size()); + EXPECT_EQ(ASCIIToUTF16("1"), results2.fields[0].value); + EXPECT_EQ(ASCIIToUTF16("650"), results2.fields[1].value); + EXPECT_EQ(ASCIIToUTF16("555"), results2.fields[2].value); + EXPECT_EQ(ASCIIToUTF16("4567"), results2.fields[3].value); + EXPECT_EQ(string16(), results2.fields[4].value); + + // We should not be able to fill prefix and suffix fields for international + // numbers. + work_profile->SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("GB")); + work_profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, + ASCIIToUTF16("447700954321")); + page_id = 3; + FillAutofillFormData(page_id, form_with_maxlength, + *form_with_maxlength.fields.begin(), + PackGUIDs(empty, guid)); + page_id = 0; + FormData results3; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results3)); + EXPECT_EQ(3, page_id); + + ASSERT_EQ(5U, results3.fields.size()); + EXPECT_EQ(ASCIIToUTF16("44"), results3.fields[0].value); + EXPECT_EQ(ASCIIToUTF16("7700"), results3.fields[1].value); + EXPECT_EQ(ASCIIToUTF16("954321"), results3.fields[2].value); + EXPECT_EQ(ASCIIToUTF16("954321"), results3.fields[3].value); + EXPECT_EQ(string16(), results3.fields[4].value); + + page_id = 4; + FillAutofillFormData(page_id, form_with_autocompletetype, + *form_with_autocompletetype.fields.begin(), + PackGUIDs(empty, guid)); + page_id = 0; + FormData results4; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results4)); + EXPECT_EQ(4, page_id); + + ASSERT_EQ(5U, results4.fields.size()); + EXPECT_EQ(ASCIIToUTF16("44"), results4.fields[0].value); + EXPECT_EQ(ASCIIToUTF16("7700"), results4.fields[1].value); + EXPECT_EQ(ASCIIToUTF16("954321"), results4.fields[2].value); + EXPECT_EQ(ASCIIToUTF16("954321"), results4.fields[3].value); + EXPECT_EQ(string16(), results4.fields[4].value); + + // We should fill all phone fields with the same phone number variant. + std::vector<string16> phone_variants; + phone_variants.push_back(ASCIIToUTF16("16505554567")); + phone_variants.push_back(ASCIIToUTF16("18887771234")); + work_profile->SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + work_profile->SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, phone_variants); + + page_id = 5; + GUIDPair variant_guid(work_profile->guid(), 1); + FillAutofillFormData(page_id, form_with_maxlength, + *form_with_maxlength.fields.begin(), + PackGUIDs(empty, variant_guid)); + page_id = 0; + FormData results5; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results5)); + EXPECT_EQ(5, page_id); + + ASSERT_EQ(5U, results5.fields.size()); + EXPECT_EQ(ASCIIToUTF16("1"), results5.fields[0].value); + EXPECT_EQ(ASCIIToUTF16("888"), results5.fields[1].value); + EXPECT_EQ(ASCIIToUTF16("777"), results5.fields[2].value); + EXPECT_EQ(ASCIIToUTF16("1234"), results5.fields[3].value); + EXPECT_EQ(string16(), results5.fields[4].value); +} + +// Test that we can still fill a form when a field has been removed from it. +TEST_F(AutofillManagerTest, FormChangesRemoveField) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + + // Add a field -- we'll remove it again later. + FormFieldData field; + autofill_test::CreateTestFormField("Some", "field", "", "text", &field); + form.fields.insert(form.fields.begin() + 3, field); + + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Now, after the call to |FormsSeen|, we remove the field before filling. + form.fields.erase(form.fields.begin() + 3); + + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledAddressFormElvis(page_id, results, kDefaultPageID, false); +} + +// Test that we can still fill a form when a field has been added to it. +TEST_F(AutofillManagerTest, FormChangesAddField) { + // The offset of the phone field in the address form. + const int kPhoneFieldOffset = 9; + + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + + // Remove the phone field -- we'll add it back later. + std::vector<FormFieldData>::iterator pos = + form.fields.begin() + kPhoneFieldOffset; + FormFieldData field = *pos; + pos = form.fields.erase(pos); + + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Now, after the call to |FormsSeen|, we restore the field before filling. + form.fields.insert(pos, field); + + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledAddressFormElvis(page_id, results, kDefaultPageID, false); +} + +// Test that we are able to save form data when forms are submitted. +TEST_F(AutofillManagerTest, FormSubmitted) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Fill the form. + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledAddressFormElvis(page_id, results, kDefaultPageID, false); + + // Simulate form submission. We should call into the PDM to try to save the + // filled data. + EXPECT_CALL(personal_data_, SaveImportedProfile(::testing::_)).Times(1); + FormSubmitted(results); +} + +// Test that we are able to save form data when forms are submitted and we only +// have server data for the field types. +TEST_F(AutofillManagerTest, FormSubmittedServerTypes) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + + // Simulate having seen this form on page load. + // |form_structure| will be owned by |autofill_manager_|. + TestFormStructure* form_structure = new TestFormStructure(form); + AutofillMetrics metrics_logger; // ignored + form_structure->DetermineHeuristicTypes(metrics_logger); + + // Clear the heuristic types, and instead set the appropriate server types. + std::vector<AutofillFieldType> heuristic_types, server_types; + for (size_t i = 0; i < form.fields.size(); ++i) { + heuristic_types.push_back(UNKNOWN_TYPE); + server_types.push_back(form_structure->field(i)->type()); + } + form_structure->SetFieldTypes(heuristic_types, server_types); + autofill_manager_->AddSeenForm(form_structure); + + // Fill the form. + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + ExpectFilledAddressFormElvis(page_id, results, kDefaultPageID, false); + + // Simulate form submission. We should call into the PDM to try to save the + // filled data. + EXPECT_CALL(personal_data_, SaveImportedProfile(::testing::_)).Times(1); + FormSubmitted(results); +} + +// Test that the form signature for an uploaded form always matches the form +// signature from the query. +TEST_F(AutofillManagerTest, FormSubmittedWithDifferentFields) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Cache the expected form signature. + std::string signature = FormStructure(form, std::string()).FormSignature(); + + // 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, autofill_manager_->GetSubmittedFormSignature()); +} + +// Test that we do not save form data when submitted fields contain default +// values. +TEST_F(AutofillManagerTest, FormSubmittedWithDefaultValues) { + // Set up our form data. + FormData form; + CreateTestAddressFormData(&form); + form.fields[3].value = ASCIIToUTF16("Enter your address"); + + // Convert the state field to a <select> popup, to make sure that we only + // reject default values for text fields. + ASSERT_TRUE(form.fields[6].name == ASCIIToUTF16("state")); + form.fields[6].form_control_type = "select-one"; + form.fields[6].value = ASCIIToUTF16("Tennessee"); + + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // Fill the form. + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + FillAutofillFormData(kDefaultPageID, form, form.fields[3], + PackGUIDs(empty, guid)); + + int page_id = 0; + FormData results; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&page_id, &results)); + + // Simulate form submission. We should call into the PDM to try to save the + // filled data. + EXPECT_CALL(personal_data_, SaveImportedProfile(::testing::_)).Times(1); + FormSubmitted(results); + + // Set the address field's value back to the default value. + results.fields[3].value = ASCIIToUTF16("Enter your address"); + + // Simulate form submission. We should not call into the PDM to try to save + // the filled data, since the filled form is effectively missing an address. + EXPECT_CALL(personal_data_, SaveImportedProfile(::testing::_)).Times(0); + FormSubmitted(results); +} + +// Checks that resetting the auxiliary profile enabled preference does the right +// thing on all platforms. +TEST_F(AutofillManagerTest, AuxiliaryProfilesReset) { + PrefService* prefs = components::UserPrefs::Get(profile()); +#if defined(OS_MACOSX) + // Auxiliary profiles is implemented on Mac only. It enables Mac Address + // Book integration. + ASSERT_TRUE(prefs->GetBoolean(prefs::kAutofillAuxiliaryProfilesEnabled)); + prefs->SetBoolean(prefs::kAutofillAuxiliaryProfilesEnabled, false); + prefs->ClearPref(prefs::kAutofillAuxiliaryProfilesEnabled); + ASSERT_TRUE(prefs->GetBoolean(prefs::kAutofillAuxiliaryProfilesEnabled)); +#else + ASSERT_FALSE(prefs->GetBoolean(prefs::kAutofillAuxiliaryProfilesEnabled)); + prefs->SetBoolean(prefs::kAutofillAuxiliaryProfilesEnabled, true); + prefs->ClearPref(prefs::kAutofillAuxiliaryProfilesEnabled); + ASSERT_FALSE(prefs->GetBoolean(prefs::kAutofillAuxiliaryProfilesEnabled)); +#endif +} + +TEST_F(AutofillManagerTest, DeterminePossibleFieldTypesForUpload) { + FormData form; + form.name = ASCIIToUTF16("MyForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://myform.com/form.html"); + form.action = GURL("http://myform.com/submit.html"); + form.user_submitted = true; + + std::vector<FieldTypeSet> expected_types; + + // These fields should all match. + FormFieldData field; + FieldTypeSet types; + autofill_test::CreateTestFormField("", "1", "Elvis", "text", &field); + types.clear(); + types.insert(NAME_FIRST); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "2", "Aaron", "text", &field); + types.clear(); + types.insert(NAME_MIDDLE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "3", "A", "text", &field); + types.clear(); + types.insert(NAME_MIDDLE_INITIAL); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "4", "Presley", "text", &field); + types.clear(); + types.insert(NAME_LAST); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "5", "Elvis Presley", "text", &field); + types.clear(); + types.insert(CREDIT_CARD_NAME); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "6", "Elvis Aaron Presley", "text", + &field); + types.clear(); + types.insert(NAME_FULL); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "7", "theking@gmail.com", "email", + &field); + types.clear(); + types.insert(EMAIL_ADDRESS); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "8", "RCA", "text", &field); + types.clear(); + types.insert(COMPANY_NAME); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "9", "3734 Elvis Presley Blvd.", + "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_LINE1); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "10", "Apt. 10", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_LINE2); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "11", "Memphis", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_CITY); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "12", "Tennessee", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_STATE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "13", "38116", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_ZIP); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "14", "USA", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_COUNTRY); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "15", "United States", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_COUNTRY); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "16", "+1 (234) 567-8901", "text", + &field); + types.clear(); + types.insert(PHONE_HOME_WHOLE_NUMBER); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "17", "2345678901", "text", &field); + types.clear(); + types.insert(PHONE_HOME_CITY_AND_NUMBER); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "18", "1", "text", &field); + types.clear(); + types.insert(PHONE_HOME_COUNTRY_CODE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "19", "234", "text", &field); + types.clear(); + types.insert(PHONE_HOME_CITY_CODE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "20", "5678901", "text", &field); + types.clear(); + types.insert(PHONE_HOME_NUMBER); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "21", "567", "text", &field); + types.clear(); + types.insert(PHONE_HOME_NUMBER); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "22", "8901", "text", &field); + types.clear(); + types.insert(PHONE_HOME_NUMBER); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "23", "4234-5678-9012-3456", "text", + &field); + types.clear(); + types.insert(CREDIT_CARD_NUMBER); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "24", "04", "text", &field); + types.clear(); + types.insert(CREDIT_CARD_EXP_MONTH); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "25", "April", "text", &field); + types.clear(); + types.insert(CREDIT_CARD_EXP_MONTH); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "26", "2012", "text", &field); + types.clear(); + types.insert(CREDIT_CARD_EXP_4_DIGIT_YEAR); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "27", "12", "text", &field); + types.clear(); + types.insert(CREDIT_CARD_EXP_2_DIGIT_YEAR); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "28", "04/2012", "text", &field); + types.clear(); + types.insert(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR); + form.fields.push_back(field); + expected_types.push_back(types); + + // Make sure that we trim whitespace properly. + autofill_test::CreateTestFormField("", "29", "", "text", &field); + types.clear(); + types.insert(EMPTY_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "30", " ", "text", &field); + types.clear(); + types.insert(EMPTY_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "31", " Elvis", "text", &field); + types.clear(); + types.insert(NAME_FIRST); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "32", "Elvis ", "text", &field); + types.clear(); + types.insert(NAME_FIRST); + form.fields.push_back(field); + expected_types.push_back(types); + + // These fields should not match, as they differ by case. + autofill_test::CreateTestFormField("", "33", "elvis", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "34", "3734 Elvis Presley BLVD", + "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + // These fields should not match, as they are unsupported variants. + autofill_test::CreateTestFormField("", "35", "Elvis Aaron", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "36", "Mr. Presley", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "37", "3734 Elvis Presley", "text", + &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "38", "TN", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "39", "38116-1023", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "20", "5", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "20", "56", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_test::CreateTestFormField("", "20", "901", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + autofill_manager_->set_expected_submitted_field_types(expected_types); + FormSubmitted(form); +} + +TEST_F(AutofillManagerTest, UpdatePasswordSyncState) { + PasswordManagerDelegateImpl::CreateForWebContents(web_contents()); + PasswordManager::CreateForWebContentsAndDelegate( + web_contents(), + PasswordManagerDelegateImpl::FromWebContents(web_contents())); + + PrefService* prefs = components::UserPrefs::Get(profile()); + + // Allow this test to control what should get synced. + prefs->SetBoolean(prefs::kSyncKeepEverythingSynced, false); + // Always set password generation enabled check box so we can test the + // behavior of password sync. + prefs->SetBoolean(prefs::kPasswordGenerationEnabled, true); + + // Sync some things, but not passwords. Shouldn't send anything since + // password generation is disabled by default. + ProfileSyncService* sync_service = ProfileSyncServiceFactory::GetForProfile( + profile()); + sync_service->SetSyncSetupCompleted(); + syncer::ModelTypeSet preferred_set; + preferred_set.Put(syncer::EXTENSIONS); + preferred_set.Put(syncer::PREFERENCES); + sync_service->ChangePreferredDataTypes(preferred_set); + syncer::ModelTypeSet new_set = sync_service->GetPreferredDataTypes(); + UpdatePasswordGenerationState(false); + EXPECT_EQ(0u, autofill_manager_->GetSentStates().size()); + + // Now sync passwords. + preferred_set.Put(syncer::PASSWORDS); + sync_service->ChangePreferredDataTypes(preferred_set); + UpdatePasswordGenerationState(false); + EXPECT_EQ(1u, autofill_manager_->GetSentStates().size()); + EXPECT_TRUE(autofill_manager_->GetSentStates()[0]); + autofill_manager_->ClearSentStates(); + + // Add some additional synced state. Nothing should be sent. + preferred_set.Put(syncer::THEMES); + sync_service->ChangePreferredDataTypes(preferred_set); + UpdatePasswordGenerationState(false); + EXPECT_EQ(0u, autofill_manager_->GetSentStates().size()); + + // Disable syncing. This should disable the feature. + sync_service->DisableForUser(); + UpdatePasswordGenerationState(false); + EXPECT_EQ(1u, autofill_manager_->GetSentStates().size()); + EXPECT_FALSE(autofill_manager_->GetSentStates()[0]); + autofill_manager_->ClearSentStates(); + + // When a new render_view is created, we send the state even if it's the + // same. + UpdatePasswordGenerationState(true); + EXPECT_EQ(1u, autofill_manager_->GetSentStates().size()); + EXPECT_FALSE(autofill_manager_->GetSentStates()[0]); + autofill_manager_->ClearSentStates(); +} + +TEST_F(IncognitoAutofillManagerTest, UpdatePasswordSyncStateIncognito) { + // Disable password manager by going incognito, and enable syncing. The + // feature should still be disabled, and nothing will be sent. + PasswordManagerDelegateImpl::CreateForWebContents(web_contents()); + PasswordManager::CreateForWebContentsAndDelegate( + web_contents(), + PasswordManagerDelegateImpl::FromWebContents(web_contents())); + + PrefService* prefs = components::UserPrefs::Get(profile()); + + // Allow this test to control what should get synced. + prefs->SetBoolean(prefs::kSyncKeepEverythingSynced, false); + // Always set password generation enabled check box so we can test the + // behavior of password sync. + prefs->SetBoolean(prefs::kPasswordGenerationEnabled, true); + + browser_sync::SyncPrefs sync_prefs(profile()->GetPrefs()); + sync_prefs.SetSyncSetupCompleted(); + UpdatePasswordGenerationState(false); + EXPECT_EQ(0u, autofill_manager_->GetSentStates().size()); +} + +TEST_F(AutofillManagerTest, UpdatePasswordGenerationState) { + PasswordManagerDelegateImpl::CreateForWebContents(web_contents()); + PasswordManager::CreateForWebContentsAndDelegate( + web_contents(), + PasswordManagerDelegateImpl::FromWebContents(web_contents())); + + PrefService* prefs = components::UserPrefs::Get(profile()); + + // Always set password sync enabled so we can test the behavior of password + // generation. + prefs->SetBoolean(prefs::kSyncKeepEverythingSynced, false); + ProfileSyncService* sync_service = ProfileSyncServiceFactory::GetForProfile( + profile()); + sync_service->SetSyncSetupCompleted(); + syncer::ModelTypeSet preferred_set; + preferred_set.Put(syncer::PASSWORDS); + sync_service->ChangePreferredDataTypes(preferred_set); + + // Enabled state remains false, should not sent. + prefs->SetBoolean(prefs::kPasswordGenerationEnabled, false); + UpdatePasswordGenerationState(false); + EXPECT_EQ(0u, autofill_manager_->GetSentStates().size()); + + // Enabled state from false to true, should sent true. + prefs->SetBoolean(prefs::kPasswordGenerationEnabled, true); + UpdatePasswordGenerationState(false); + EXPECT_EQ(1u, autofill_manager_->GetSentStates().size()); + EXPECT_TRUE(autofill_manager_->GetSentStates()[0]); + autofill_manager_->ClearSentStates(); + + // Enabled states remains true, should not sent. + prefs->SetBoolean(prefs::kPasswordGenerationEnabled, true); + UpdatePasswordGenerationState(false); + EXPECT_EQ(0u, autofill_manager_->GetSentStates().size()); + + // Enabled states from true to false, should sent false. + prefs->SetBoolean(prefs::kPasswordGenerationEnabled, false); + UpdatePasswordGenerationState(false); + EXPECT_EQ(1u, autofill_manager_->GetSentStates().size()); + EXPECT_FALSE(autofill_manager_->GetSentStates()[0]); + autofill_manager_->ClearSentStates(); + + // When a new render_view is created, we send the state even if it's the + // same. + UpdatePasswordGenerationState(true); + EXPECT_EQ(1u, autofill_manager_->GetSentStates().size()); + EXPECT_FALSE(autofill_manager_->GetSentStates()[0]); + autofill_manager_->ClearSentStates(); +} + +TEST_F(AutofillManagerTest, RemoveProfile) { + // Add and remove an Autofill profile. + AutofillProfile* profile = new AutofillProfile; + std::string guid = "00000000-0000-0000-0000-000000000102"; + profile->set_guid(guid.c_str()); + autofill_manager_->AddProfile(profile); + + GUIDPair guid_pair(guid, 0); + GUIDPair empty(std::string(), 0); + int id = PackGUIDs(empty, guid_pair); + + autofill_manager_->RemoveAutofillProfileOrCreditCard(id); + + EXPECT_FALSE(autofill_manager_->GetProfileWithGUID(guid.c_str())); +} + +TEST_F(AutofillManagerTest, RemoveCreditCard){ + // Add and remove an Autofill credit card. + CreditCard* credit_card = new CreditCard; + std::string guid = "00000000-0000-0000-0000-000000100007"; + credit_card->set_guid(guid.c_str()); + autofill_manager_->AddCreditCard(credit_card); + + GUIDPair guid_pair(guid, 0); + GUIDPair empty(std::string(), 0); + int id = PackGUIDs(guid_pair, empty); + + autofill_manager_->RemoveAutofillProfileOrCreditCard(id); + + EXPECT_FALSE(autofill_manager_->GetCreditCardWithGUID(guid.c_str())); +} + +TEST_F(AutofillManagerTest, RemoveProfileVariant) { + // Add and remove an Autofill profile. + AutofillProfile* profile = new AutofillProfile; + std::string guid = "00000000-0000-0000-0000-000000000102"; + profile->set_guid(guid.c_str()); + autofill_manager_->AddProfile(profile); + + GUIDPair guid_pair(guid, 1); + GUIDPair empty(std::string(), 0); + int id = PackGUIDs(empty, guid_pair); + + autofill_manager_->RemoveAutofillProfileOrCreditCard(id); + + // TODO(csharp): Currently variants should not be deleted, but once they are + // update these expectations. + // http://crbug.com/124211 + EXPECT_TRUE(autofill_manager_->GetProfileWithGUID(guid.c_str())); +} + +TEST_F(AutofillManagerTest, DisabledAutofillDispatchesError) { + EXPECT_TRUE(autofill_manager_->request_autocomplete_results().empty()); + + autofill_manager_->set_autofill_enabled(false); + autofill_manager_->OnRequestAutocomplete(FormData(), + GURL(), + content::SSLStatus()); + + EXPECT_EQ(1U, autofill_manager_->request_autocomplete_results().size()); + EXPECT_EQ(WebFormElement::AutocompleteResultErrorDisabled, + autofill_manager_->request_autocomplete_results()[0].first); +} + +namespace { + +class MockAutofillExternalDelegate : public AutofillExternalDelegate { + public: + explicit MockAutofillExternalDelegate(content::WebContents* web_contents, + AutofillManager* autofill_manager) + : AutofillExternalDelegate(web_contents, autofill_manager) {} + virtual ~MockAutofillExternalDelegate() {} + + MOCK_METHOD5(OnQuery, void(int query_id, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounds, + bool display_warning)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillExternalDelegate); +}; + +} // namespace + +// Test our external delegate is called at the right time. +TEST_F(AutofillManagerTest, TestExternalDelegate) { + MockAutofillExternalDelegate external_delegate(web_contents(), + autofill_manager_.get()); + EXPECT_CALL(external_delegate, OnQuery(_, _, _, _, _)); + autofill_manager_->SetExternalDelegate(&external_delegate); + + FormData form; + CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + const FormFieldData& field = form.fields[0]; + GetAutofillSuggestions(form, field); // should call the delegate's OnQuery() + + autofill_manager_->SetExternalDelegate(NULL); +} diff --git a/components/autofill/browser/autofill_merge_unittest.cc b/components/autofill/browser/autofill_merge_unittest.cc new file mode 100644 index 0000000..dad4fdb --- /dev/null +++ b/components/autofill/browser/autofill_merge_unittest.cc @@ -0,0 +1,217 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <vector> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_common_test.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/data_driven_test.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/browser/personal_data_manager.h" +#include "components/autofill/common/form_data.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" + +namespace { + +const base::FilePath::CharType kTestName[] = FILE_PATH_LITERAL("merge"); +const base::FilePath::CharType kFileNamePattern[] = FILE_PATH_LITERAL("*.in"); + +const char kFieldSeparator[] = ": "; +const char kProfileSeparator[] = "---"; +const size_t kFieldOffset = arraysize(kFieldSeparator) - 1; + +const AutofillFieldType kProfileFieldTypes[] = { + NAME_FIRST, + NAME_MIDDLE, + NAME_LAST, + EMAIL_ADDRESS, + COMPANY_NAME, + ADDRESS_HOME_LINE1, + ADDRESS_HOME_LINE2, + ADDRESS_HOME_CITY, + ADDRESS_HOME_STATE, + ADDRESS_HOME_ZIP, + ADDRESS_HOME_COUNTRY, + PHONE_HOME_WHOLE_NUMBER +}; + +// Serializes the |profiles| into a string. +std::string SerializeProfiles(const std::vector<AutofillProfile*>& profiles) { + std::string result; + for (size_t i = 0; i < profiles.size(); ++i) { + result += kProfileSeparator; + result += "\n"; + for (size_t j = 0; j < arraysize(kProfileFieldTypes); ++j) { + AutofillFieldType type = kProfileFieldTypes[j]; + std::vector<string16> values; + profiles[i]->GetRawMultiInfo(type, &values); + for (size_t k = 0; k < values.size(); ++k) { + result += AutofillType::FieldTypeToString(type); + result += kFieldSeparator; + result += UTF16ToUTF8(values[k]); + result += "\n"; + } + } + } + + return result; +} + +class PersonalDataManagerMock : public PersonalDataManager { + public: + PersonalDataManagerMock(); + virtual ~PersonalDataManagerMock(); + + // Reset the saved profiles. + void Reset(); + + // PersonalDataManager: + virtual void SaveImportedProfile(const AutofillProfile& profile) OVERRIDE; + virtual const std::vector<AutofillProfile*>& web_profiles() const OVERRIDE; + + private: + ScopedVector<AutofillProfile> profiles_; + + DISALLOW_COPY_AND_ASSIGN(PersonalDataManagerMock); +}; + +PersonalDataManagerMock::PersonalDataManagerMock() : PersonalDataManager() { +} + +PersonalDataManagerMock::~PersonalDataManagerMock() { +} + +void PersonalDataManagerMock::Reset() { + profiles_.clear(); +} + +void PersonalDataManagerMock::SaveImportedProfile( + const AutofillProfile& profile) { + std::vector<AutofillProfile> profiles; + if (!MergeProfile(profile, profiles_.get(), &profiles)) + profiles_.push_back(new AutofillProfile(profile)); +} + +const std::vector<AutofillProfile*>& PersonalDataManagerMock::web_profiles() + const { + return profiles_.get(); +} + +} // namespace + +// A data-driven test for verifying merging of Autofill profiles. Each input is +// a structured dump of a set of implicitly detected autofill profiles. The +// corresponding output file is a dump of the saved profiles that result from +// importing the input profiles. The output file format is identical to the +// input format. +class AutofillMergeTest : public testing::Test, + public DataDrivenTest { + protected: + AutofillMergeTest(); + virtual ~AutofillMergeTest(); + + // testing::Test: + virtual void SetUp(); + + // DataDrivenTest: + virtual void GenerateResults(const std::string& input, + std::string* output) OVERRIDE; + + // Deserializes a set of Autofill profiles from |profiles|, imports each + // sequentially, and fills |merged_profiles| with the serialized result. + void MergeProfiles(const std::string& profiles, std::string* merged_profiles); + + PersonalDataManagerMock personal_data_; + + private: + DISALLOW_COPY_AND_ASSIGN(AutofillMergeTest); +}; + +AutofillMergeTest::AutofillMergeTest() : DataDrivenTest() { +} + +AutofillMergeTest::~AutofillMergeTest() { +} + +void AutofillMergeTest::SetUp() { + autofill_test::DisableSystemServices(NULL); +} + +void AutofillMergeTest::GenerateResults(const std::string& input, + std::string* output) { + MergeProfiles(input, output); +} + +void AutofillMergeTest::MergeProfiles(const std::string& profiles, + std::string* merged_profiles) { + // Start with no saved profiles. + personal_data_.Reset(); + + // Create a test form. + FormData form; + form.name = ASCIIToUTF16("MyTestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("https://www.example.com/origin.html"); + form.action = GURL("https://www.example.com/action.html"); + form.user_submitted = true; + + // Parse the input line by line. + std::vector<std::string> lines; + Tokenize(profiles, "\n", &lines); + for (size_t i = 0; i < lines.size(); ++i) { + std::string line = lines[i]; + + if (line != kProfileSeparator) { + // Add a field to the current profile. + size_t separator_pos = line.find(kFieldSeparator); + ASSERT_NE(std::string::npos, separator_pos); + string16 field_type = UTF8ToUTF16(line.substr(0, separator_pos)); + string16 value = UTF8ToUTF16(line.substr(separator_pos + kFieldOffset)); + + FormFieldData field; + field.label = field_type; + field.name = field_type; + field.value = value; + field.form_control_type = "text"; + form.fields.push_back(field); + } + + // The first line is always a profile separator, and the last profile is not + // followed by an explicit separator. + if ((i > 0 && line == kProfileSeparator) || i == lines.size() - 1) { + // Reached the end of a profile. Try to import it. + FormStructure form_structure(form, std::string()); + for (size_t i = 0; i < form_structure.field_count(); ++i) { + // Set the heuristic type for each field, which is currently serialized + // into the field's name. + AutofillField* field = + const_cast<AutofillField*>(form_structure.field(i)); + AutofillFieldType type = + AutofillType::StringToFieldType(UTF16ToUTF8(field->name)); + field->set_heuristic_type(type); + } + + // Import the profile. + const CreditCard* imported_credit_card; + personal_data_.ImportFormData(form_structure, &imported_credit_card); + EXPECT_EQ(static_cast<const CreditCard*>(NULL), imported_credit_card); + + // Clear the |form| to start a new profile. + form.fields.clear(); + } + } + + *merged_profiles = SerializeProfiles(personal_data_.web_profiles()); +} + +TEST_F(AutofillMergeTest, DataDrivenMergeProfiles) { + RunDataDrivenTest(GetInputDirectory(kTestName), GetOutputDirectory(kTestName), + kFileNamePattern); +} diff --git a/components/autofill/browser/autofill_metrics.cc b/components/autofill/browser/autofill_metrics.cc new file mode 100644 index 0000000..b59ba54 --- /dev/null +++ b/components/autofill/browser/autofill_metrics.cc @@ -0,0 +1,453 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_metrics.h" + +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/time.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/common/form_data.h" + +namespace { + +// Server experiments we support. +enum ServerExperiment { + NO_EXPERIMENT = 0, + UNKNOWN_EXPERIMENT, + ACCEPTANCE_RATIO_06, + ACCEPTANCE_RATIO_1, + ACCEPTANCE_RATIO_2, + ACCEPTANCE_RATIO_4, + ACCEPTANCE_RATIO_05_WINNER_LEAD_RATIO_15, + ACCEPTANCE_RATIO_05_WINNER_LEAD_RATIO_25, + ACCEPTANCE_RATIO_05_WINNER_LEAD_RATIO_15_MIN_FORM_SCORE_5, + TOOLBAR_DATA_ONLY, + ACCEPTANCE_RATIO_04_WINNER_LEAD_RATIO_3_MIN_FORM_SCORE_4, + NO_SERVER_RESPONSE, + PROBABILITY_PICKER_05, + PROBABILITY_PICKER_025, + PROBABILITY_PICKER_025_CC_THRESHOLD_03, + PROBABILITY_PICKER_025_CONTEXTUAL_CC_THRESHOLD_03, + PROBABILITY_PICKER_025_CONTEXTUAL_CC_THRESHOLD_03_WITH_FALLBACK, + PROBABILITY_PICKER_05_CC_NAME_THRESHOLD_03_EXPERIMENT_1, + NUM_SERVER_EXPERIMENTS +}; + +enum FieldTypeGroupForMetrics { + AMBIGUOUS = 0, + NAME, + COMPANY, + ADDRESS_LINE_1, + ADDRESS_LINE_2, + ADDRESS_CITY, + ADDRESS_STATE, + ADDRESS_ZIP, + ADDRESS_COUNTRY, + PHONE, + FAX, // Deprecated. + EMAIL, + CREDIT_CARD_NAME, + CREDIT_CARD_NUMBER, + CREDIT_CARD_DATE, + CREDIT_CARD_TYPE, + NUM_FIELD_TYPE_GROUPS_FOR_METRICS +}; + +// First, translates |field_type| to the corresponding logical |group| from +// |FieldTypeGroupForMetrics|. Then, interpolates this with the given |metric|, +// which should be in the range [0, |num_possible_metrics|). +// Returns the interpolated index. +// +// The interpolation maps the pair (|group|, |metric|) to a single index, so +// that all the indicies for a given group are adjacent. In particular, with +// the groups {AMBIGUOUS, NAME, ...} combining with the metrics {UNKNOWN, MATCH, +// MISMATCH}, we create this set of mapped indices: +// { +// AMBIGUOUS+UNKNOWN, +// AMBIGUOUS+MATCH, +// AMBIGUOUS+MISMATCH, +// NAME+UNKNOWN, +// NAME+MATCH, +// NAME+MISMATCH, +// ... +// }. +// +// Clients must ensure that |field_type| is one of the types Chrome supports +// natively, e.g. |field_type| must not be a billng address. +int GetFieldTypeGroupMetric(const AutofillFieldType field_type, + const int metric, + const int num_possible_metrics) { + DCHECK(metric < num_possible_metrics); + + FieldTypeGroupForMetrics group; + switch (AutofillType(field_type).group()) { + case AutofillType::NO_GROUP: + group = AMBIGUOUS; + break; + + case AutofillType::NAME: + group = NAME; + break; + + case AutofillType::COMPANY: + group = COMPANY; + break; + + case AutofillType::ADDRESS_HOME: + switch (field_type) { + case ADDRESS_HOME_LINE1: + group = ADDRESS_LINE_1; + break; + case ADDRESS_HOME_LINE2: + group = ADDRESS_LINE_2; + break; + case ADDRESS_HOME_CITY: + group = ADDRESS_CITY; + break; + case ADDRESS_HOME_STATE: + group = ADDRESS_STATE; + break; + case ADDRESS_HOME_ZIP: + group = ADDRESS_ZIP; + break; + case ADDRESS_HOME_COUNTRY: + group = ADDRESS_COUNTRY; + break; + default: + NOTREACHED(); + group = AMBIGUOUS; + } + break; + + case AutofillType::EMAIL: + group = EMAIL; + break; + + case AutofillType::PHONE: + group = PHONE; + break; + + case AutofillType::CREDIT_CARD: + switch (field_type) { + case ::CREDIT_CARD_NAME: + group = CREDIT_CARD_NAME; + break; + case ::CREDIT_CARD_NUMBER: + group = CREDIT_CARD_NUMBER; + break; + case ::CREDIT_CARD_TYPE: + group = CREDIT_CARD_TYPE; + default: + group = CREDIT_CARD_DATE; + } + break; + + default: + NOTREACHED(); + group = AMBIGUOUS; + } + + // Interpolate the |metric| with the |group|, so that all metrics for a given + // |group| are adjacent. + return (group * num_possible_metrics) + metric; +} + +// A version of the UMA_HISTOGRAM_ENUMERATION macro that allows the |name| +// to vary over the program's runtime. +void LogUMAHistogramEnumeration(const std::string& name, + int sample, + int boundary_value) { + // Note: This leaks memory, which is expected behavior. + base::HistogramBase* histogram = + base::LinearHistogram::FactoryGet( + name, + 1, + boundary_value, + boundary_value + 1, + base::HistogramBase::kUmaTargetedHistogramFlag); + histogram->Add(sample); +} + +// A version of the UMA_HISTOGRAM_LONG_TIMES macro that allows the |name| +// to vary over the program's runtime. +void LogUMAHistogramLongTimes(const std::string& name, + const base::TimeDelta& duration) { + // Note: This leaks memory, which is expected behavior. + base::HistogramBase* histogram = + base::Histogram::FactoryTimeGet( + name, + base::TimeDelta::FromMilliseconds(1), + base::TimeDelta::FromHours(1), + 50, + base::HistogramBase::kUmaTargetedHistogramFlag); + histogram->AddTime(duration); +} + +// Logs a type quality metric. The primary histogram name is constructed based +// on |base_name| and |experiment_id|. The field-specific histogram name also +// factors in the |field_type|. Logs a sample of |metric|, which should be in +// the range [0, |num_possible_metrics|). +void LogTypeQualityMetric(const std::string& base_name, + const int metric, + const int num_possible_metrics, + const AutofillFieldType field_type, + const std::string& experiment_id) { + DCHECK(metric < num_possible_metrics); + + std::string histogram_name = base_name; + if (!experiment_id.empty()) + histogram_name += "_" + experiment_id; + LogUMAHistogramEnumeration(histogram_name, metric, num_possible_metrics); + + std::string sub_histogram_name = base_name + ".ByFieldType"; + if (!experiment_id.empty()) + sub_histogram_name += "_" + experiment_id; + const int field_type_group_metric = + GetFieldTypeGroupMetric(field_type, metric, num_possible_metrics); + const int num_field_type_group_metrics = + num_possible_metrics * NUM_FIELD_TYPE_GROUPS_FOR_METRICS; + LogUMAHistogramEnumeration(sub_histogram_name, + field_type_group_metric, + num_field_type_group_metrics); +} + +void LogServerExperimentId(const std::string& histogram_name, + const std::string& experiment_id) { + ServerExperiment metric = UNKNOWN_EXPERIMENT; + + const std::string default_experiment_name = + FormStructure(FormData(), std::string()).server_experiment_id(); + if (experiment_id.empty()) + metric = NO_EXPERIMENT; + else if (experiment_id == "ar06") + metric = ACCEPTANCE_RATIO_06; + else if (experiment_id == "ar1") + metric = ACCEPTANCE_RATIO_1; + else if (experiment_id == "ar2") + metric = ACCEPTANCE_RATIO_2; + else if (experiment_id == "ar4") + metric = ACCEPTANCE_RATIO_4; + else if (experiment_id == "ar05wlr15") + metric = ACCEPTANCE_RATIO_05_WINNER_LEAD_RATIO_15; + else if (experiment_id == "ar05wlr25") + metric = ACCEPTANCE_RATIO_05_WINNER_LEAD_RATIO_25; + else if (experiment_id == "ar05wr15fs5") + metric = ACCEPTANCE_RATIO_05_WINNER_LEAD_RATIO_15_MIN_FORM_SCORE_5; + else if (experiment_id == "tbar1") + metric = TOOLBAR_DATA_ONLY; + else if (experiment_id == "ar04wr3fs4") + metric = ACCEPTANCE_RATIO_04_WINNER_LEAD_RATIO_3_MIN_FORM_SCORE_4; + else if (experiment_id == default_experiment_name) + metric = NO_SERVER_RESPONSE; + else if (experiment_id == "fp05") + metric = PROBABILITY_PICKER_05; + else if (experiment_id == "fp025") + metric = PROBABILITY_PICKER_025; + else if (experiment_id == "fp05cc03") + metric = PROBABILITY_PICKER_025_CC_THRESHOLD_03; + else if (experiment_id == "fp05cco03") + metric = PROBABILITY_PICKER_025_CONTEXTUAL_CC_THRESHOLD_03; + else if (experiment_id == "fp05cco03cstd") + metric = PROBABILITY_PICKER_025_CONTEXTUAL_CC_THRESHOLD_03_WITH_FALLBACK; + else if (experiment_id == "fp05cc03e1") + metric = PROBABILITY_PICKER_05_CC_NAME_THRESHOLD_03_EXPERIMENT_1; + + DCHECK(metric < NUM_SERVER_EXPERIMENTS); + LogUMAHistogramEnumeration(histogram_name, metric, NUM_SERVER_EXPERIMENTS); +} + +} // namespace + +AutofillMetrics::AutofillMetrics() { +} + +AutofillMetrics::~AutofillMetrics() { +} + +void AutofillMetrics::LogAutocheckoutBubbleMetric(BubbleMetric metric) const { + DCHECK(metric < NUM_BUBBLE_METRICS); + + UMA_HISTOGRAM_ENUMERATION("Autocheckout.Bubble", metric, NUM_BUBBLE_METRICS); +} + +void AutofillMetrics::LogCreditCardInfoBarMetric(InfoBarMetric metric) const { + DCHECK(metric < NUM_INFO_BAR_METRICS); + + UMA_HISTOGRAM_ENUMERATION("Autofill.CreditCardInfoBar", metric, + NUM_INFO_BAR_METRICS); +} + +void AutofillMetrics::LogRequestAutocompleteUiDuration( + const base::TimeDelta& duration, + autofill::DialogType dialog_type, + DialogDismissalAction dismissal_action) const { + std::string prefix; + switch (dialog_type) { + case autofill::DIALOG_TYPE_AUTOCHECKOUT: + prefix = "Autocheckout"; + break; + + case autofill::DIALOG_TYPE_REQUEST_AUTOCOMPLETE: + prefix = "RequestAutocomplete"; + break; + } + + std::string suffix; + switch (dismissal_action) { + case DIALOG_ACCEPTED: + suffix = "Submit"; + break; + + case DIALOG_CANCELED: + suffix = "Cancel"; + break; + } + + LogUMAHistogramLongTimes(prefix + ".UiDuration", duration); + LogUMAHistogramLongTimes(prefix + ".UiDuration." + suffix, duration); +} + +void AutofillMetrics::LogAutocheckoutDuration( + const base::TimeDelta& duration, + AutocheckoutCompletionStatus status) const { + std::string suffix; + switch (status) { + case AUTOCHECKOUT_FAILED: + suffix = "Failed"; + break; + + case AUTOCHECKOUT_SUCCEEDED: + suffix = "Succeeded"; + break; + } + + LogUMAHistogramLongTimes("Autocheckout.FlowDuration", duration); + LogUMAHistogramLongTimes("Autocheckout.FlowDuration." + suffix, duration); +} + +void AutofillMetrics::LogDeveloperEngagementMetric( + DeveloperEngagementMetric metric) const { + DCHECK(metric < NUM_DEVELOPER_ENGAGEMENT_METRICS); + + UMA_HISTOGRAM_ENUMERATION("Autofill.DeveloperEngagement", metric, + NUM_DEVELOPER_ENGAGEMENT_METRICS); +} + +void AutofillMetrics::LogHeuristicTypePrediction( + FieldTypeQualityMetric metric, + AutofillFieldType field_type, + const std::string& experiment_id) const { + LogTypeQualityMetric("Autofill.Quality.HeuristicType", + metric, NUM_FIELD_TYPE_QUALITY_METRICS, + field_type, experiment_id); +} + +void AutofillMetrics::LogOverallTypePrediction( + FieldTypeQualityMetric metric, + AutofillFieldType field_type, + const std::string& experiment_id) const { + LogTypeQualityMetric("Autofill.Quality.PredictedType", + metric, NUM_FIELD_TYPE_QUALITY_METRICS, + field_type, experiment_id); +} + +void AutofillMetrics::LogServerTypePrediction( + FieldTypeQualityMetric metric, + AutofillFieldType field_type, + const std::string& experiment_id) const { + LogTypeQualityMetric("Autofill.Quality.ServerType", + metric, NUM_FIELD_TYPE_QUALITY_METRICS, + field_type, experiment_id); +} + +void AutofillMetrics::LogQualityMetric(QualityMetric metric, + const std::string& experiment_id) const { + DCHECK(metric < NUM_QUALITY_METRICS); + + std::string histogram_name = "Autofill.Quality"; + if (!experiment_id.empty()) + histogram_name += "_" + experiment_id; + + LogUMAHistogramEnumeration(histogram_name, metric, NUM_QUALITY_METRICS); +} + +void AutofillMetrics::LogServerQueryMetric(ServerQueryMetric metric) const { + DCHECK(metric < NUM_SERVER_QUERY_METRICS); + + UMA_HISTOGRAM_ENUMERATION("Autofill.ServerQueryResponse", metric, + NUM_SERVER_QUERY_METRICS); +} + +void AutofillMetrics::LogUserHappinessMetric(UserHappinessMetric metric) const { + DCHECK(metric < NUM_USER_HAPPINESS_METRICS); + + UMA_HISTOGRAM_ENUMERATION("Autofill.UserHappiness", metric, + NUM_USER_HAPPINESS_METRICS); +} + +void AutofillMetrics::LogFormFillDurationFromLoadWithAutofill( + const base::TimeDelta& duration) const { + UMA_HISTOGRAM_CUSTOM_TIMES("Autofill.FillDuration.FromLoad.WithAutofill", + duration, + base::TimeDelta::FromMilliseconds(100), + base::TimeDelta::FromMinutes(10), + 50); +} + +void AutofillMetrics::LogFormFillDurationFromLoadWithoutAutofill( + const base::TimeDelta& duration) const { + UMA_HISTOGRAM_CUSTOM_TIMES("Autofill.FillDuration.FromLoad.WithoutAutofill", + duration, + base::TimeDelta::FromMilliseconds(100), + base::TimeDelta::FromMinutes(10), + 50); +} + +void AutofillMetrics::LogFormFillDurationFromInteractionWithAutofill( + const base::TimeDelta& duration) const { + UMA_HISTOGRAM_CUSTOM_TIMES( + "Autofill.FillDuration.FromInteraction.WithAutofill", + duration, + base::TimeDelta::FromMilliseconds(100), + base::TimeDelta::FromMinutes(10), + 50); +} + +void AutofillMetrics::LogFormFillDurationFromInteractionWithoutAutofill( + const base::TimeDelta& duration) const { + UMA_HISTOGRAM_CUSTOM_TIMES( + "Autofill.FillDuration.FromInteraction.WithoutAutofill", + duration, + base::TimeDelta::FromMilliseconds(100), + base::TimeDelta::FromMinutes(10), + 50); +} + +void AutofillMetrics::LogIsAutofillEnabledAtStartup(bool enabled) const { + UMA_HISTOGRAM_BOOLEAN("Autofill.IsEnabled.Startup", enabled); +} + +void AutofillMetrics::LogIsAutofillEnabledAtPageLoad(bool enabled) const { + UMA_HISTOGRAM_BOOLEAN("Autofill.IsEnabled.PageLoad", enabled); +} + +void AutofillMetrics::LogStoredProfileCount(size_t num_profiles) const { + UMA_HISTOGRAM_COUNTS("Autofill.StoredProfileCount", num_profiles); +} + +void AutofillMetrics::LogAddressSuggestionsCount(size_t num_suggestions) const { + UMA_HISTOGRAM_COUNTS("Autofill.AddressSuggestionsCount", num_suggestions); +} + +void AutofillMetrics::LogServerExperimentIdForQuery( + const std::string& experiment_id) const { + LogServerExperimentId("Autofill.ServerExperimentId.Query", experiment_id); +} + +void AutofillMetrics::LogServerExperimentIdForUpload( + const std::string& experiment_id) const { + LogServerExperimentId("Autofill.ServerExperimentId.Upload", experiment_id); +} diff --git a/components/autofill/browser/autofill_metrics.h b/components/autofill/browser/autofill_metrics.h new file mode 100644 index 0000000..36453b9 --- /dev/null +++ b/components/autofill/browser/autofill_metrics.h @@ -0,0 +1,248 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_METRICS_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_METRICS_H_ + +#include <stddef.h> +#include <string> + +#include "base/basictypes.h" +#include "components/autofill/browser/autofill_manager_delegate.h" +#include "components/autofill/browser/field_types.h" + +namespace base { +class TimeDelta; +} + +class AutofillMetrics { + public: + // The success or failure of Autocheckout. + enum AutocheckoutCompletionStatus { + AUTOCHECKOUT_FAILED, // The user canceled out of the dialog after + // an Autocheckout failure. + AUTOCHECKOUT_SUCCEEDED, // The dialog was closed after Autocheckout + // succeeded. + }; + + // The action a user took to dismiss a bubble. + enum BubbleMetric { + BUBBLE_CREATED = 0, // The bubble was created. + BUBBLE_ACCEPTED, // The user accepted, i.e. confirmed, the bubble. + BUBBLE_DISMISSED, // The user dismissed the bubble. + BUBBLE_IGNORED, // The user did not interact with the bubble. + NUM_BUBBLE_METRICS, + }; + + enum DeveloperEngagementMetric { + // Parsed a form that is potentially autofillable. + FILLABLE_FORM_PARSED = 0, + // Parsed a form that is potentially autofillable and contains at least one + // web developer-specified field type hint, a la + // http://is.gd/whatwg_autocomplete + FILLABLE_FORM_CONTAINS_TYPE_HINTS, + NUM_DEVELOPER_ENGAGEMENT_METRICS, + }; + + // The action the user took to dismiss a dialog. + enum DialogDismissalAction { + DIALOG_ACCEPTED = 0, // The user accepted, i.e. confirmed, the dialog. + DIALOG_CANCELED, // The user canceled out of the dialog. + }; + + enum InfoBarMetric { + INFOBAR_SHOWN = 0, // We showed an infobar, e.g. prompting to save credit + // card info. + INFOBAR_ACCEPTED, // The user explicitly accepted the infobar. + INFOBAR_DENIED, // The user explicitly denied the infobar. + INFOBAR_IGNORED, // The user completely ignored the infobar (logged on + // tab close). + NUM_INFO_BAR_METRICS, + }; + + // Metrics measuring how well we predict field types. Exactly three such + // metrics are logged for each fillable field in a submitted form: for + // the heuristic prediction, for the crowd-sourced prediction, and for the + // overall prediction. + enum FieldTypeQualityMetric { + TYPE_UNKNOWN = 0, // Offered no prediction. + TYPE_MATCH, // Predicted correctly. + TYPE_MISMATCH, // Predicted incorrectly. + NUM_FIELD_TYPE_QUALITY_METRICS, + }; + + enum QualityMetric { + // Logged for each potentially fillable field in a submitted form. + FIELD_SUBMITTED = 0, + + // A simple successs metric, logged for each field that returns true for + // |is_autofilled()|. + FIELD_AUTOFILLED, + + // A simple failure metric, logged for each field that returns false for + // |is_autofilled()| but has a value that is present in the personal data + // manager. + FIELD_NOT_AUTOFILLED, + + // The below are only logged when |FIELD_AUTOFILL_FAILED| is also logged. + NOT_AUTOFILLED_HEURISTIC_TYPE_UNKNOWN, + NOT_AUTOFILLED_HEURISTIC_TYPE_MATCH, + NOT_AUTOFILLED_HEURISTIC_TYPE_MISMATCH, + NOT_AUTOFILLED_SERVER_TYPE_UNKNOWN, + NOT_AUTOFILLED_SERVER_TYPE_MATCH, + NOT_AUTOFILLED_SERVER_TYPE_MISMATCH, + NUM_QUALITY_METRICS, + }; + + // Each of these is logged at most once per query to the server, which in turn + // occurs at most once per page load. + enum ServerQueryMetric { + QUERY_SENT = 0, // Sent a query to the server. + QUERY_RESPONSE_RECEIVED, // Received a response. + QUERY_RESPONSE_PARSED, // Successfully parsed the server response. + + // The response was parseable, but provided no improvements relative to our + // heuristics. + QUERY_RESPONSE_MATCHED_LOCAL_HEURISTICS, + + // Our heuristics detected at least one auto-fillable field, and the server + // response overrode the type of at least one field. + QUERY_RESPONSE_OVERRODE_LOCAL_HEURISTICS, + + // Our heuristics did not detect any auto-fillable fields, but the server + // response did detect at least one. + QUERY_RESPONSE_WITH_NO_LOCAL_HEURISTICS, + NUM_SERVER_QUERY_METRICS, + }; + + // Each of these metrics is logged only for potentially autofillable forms, + // i.e. forms with at least three fields, etc. + // These are used to derive certain "user happiness" metrics. For example, we + // can compute the ratio (USER_DID_EDIT_AUTOFILLED_FIELD / USER_DID_AUTOFILL) + // to see how often users have to correct autofilled data. + enum UserHappinessMetric { + // Loaded a page containing forms. + FORMS_LOADED, + // Submitted a fillable form -- i.e. one with at least three field values + // that match the user's stored Autofill data -- and all matching fields + // were autofilled. + SUBMITTED_FILLABLE_FORM_AUTOFILLED_ALL, + // Submitted a fillable form and some (but not all) matching fields were + // autofilled. + SUBMITTED_FILLABLE_FORM_AUTOFILLED_SOME, + // Submitted a fillable form and no fields were autofilled. + SUBMITTED_FILLABLE_FORM_AUTOFILLED_NONE, + // Submitted a non-fillable form. + SUBMITTED_NON_FILLABLE_FORM, + + // User manually filled one of the form fields. + USER_DID_TYPE, + // We showed a popup containing Autofill suggestions. + SUGGESTIONS_SHOWN, + // Same as above, but only logged once per page load. + SUGGESTIONS_SHOWN_ONCE, + // User autofilled at least part of the form. + USER_DID_AUTOFILL, + // Same as above, but only logged once per page load. + USER_DID_AUTOFILL_ONCE, + // User edited a previously autofilled field. + USER_DID_EDIT_AUTOFILLED_FIELD, + // Same as above, but only logged once per page load. + USER_DID_EDIT_AUTOFILLED_FIELD_ONCE, + NUM_USER_HAPPINESS_METRICS, + }; + + AutofillMetrics(); + virtual ~AutofillMetrics(); + + // Logs how the user interacted with the Autocheckout bubble. + virtual void LogAutocheckoutBubbleMetric(BubbleMetric metric) const; + + virtual void LogCreditCardInfoBarMetric(InfoBarMetric metric) const; + + virtual void LogDeveloperEngagementMetric( + DeveloperEngagementMetric metric) const; + + virtual void LogHeuristicTypePrediction( + FieldTypeQualityMetric metric, + AutofillFieldType field_type, + const std::string& experiment_id) const; + virtual void LogOverallTypePrediction( + FieldTypeQualityMetric metric, + AutofillFieldType field_type, + const std::string& experiment_id) const; + virtual void LogServerTypePrediction(FieldTypeQualityMetric metric, + AutofillFieldType field_type, + const std::string& experiment_id) const; + + virtual void LogQualityMetric(QualityMetric metric, + const std::string& experiment_id) const; + + virtual void LogServerQueryMetric(ServerQueryMetric metric) const; + + virtual void LogUserHappinessMetric(UserHappinessMetric metric) const; + + // This should be called when the requestAutocomplete dialog, invoked by the + // |requester|, is closed. |duration| should be the time elapsed between the + // dialog being shown and it being closed. |dismissal_action| should indicate + // whether the user dismissed the dialog by submitting the form data or by + // cancelling. + virtual void LogRequestAutocompleteUiDuration( + const base::TimeDelta& duration, + autofill::DialogType dialog_type, + DialogDismissalAction dismissal_action) const; + + virtual void LogAutocheckoutDuration( + const base::TimeDelta& duration, + AutocheckoutCompletionStatus status) const; + + // This should be called when a form that has been Autofilled is submitted. + // |duration| should be the time elapsed between form load and submission. + virtual void LogFormFillDurationFromLoadWithAutofill( + const base::TimeDelta& duration) const; + + // This should be called when a fillable form that has not been Autofilled is + // submitted. |duration| should be the time elapsed between form load and + // submission. + virtual void LogFormFillDurationFromLoadWithoutAutofill( + const base::TimeDelta& duration) const; + + // This should be called when a form that has been Autofilled is submitted. + // |duration| should be the time elapsed between the initial form interaction + // and submission. + virtual void LogFormFillDurationFromInteractionWithAutofill( + const base::TimeDelta& duration) const; + + // This should be called when a fillable form that has not been Autofilled is + // submitted. |duration| should be the time elapsed between the initial form + // interaction and submission. + virtual void LogFormFillDurationFromInteractionWithoutAutofill( + const base::TimeDelta& duration) const; + + // This should be called each time a page containing forms is loaded. + virtual void LogIsAutofillEnabledAtPageLoad(bool enabled) const; + + // This should be called each time a new profile is launched. + virtual void LogIsAutofillEnabledAtStartup(bool enabled) const; + + // This should be called each time a new profile is launched. + virtual void LogStoredProfileCount(size_t num_profiles) const; + + // Log the number of Autofill suggestions presented to the user when filling a + // form. + virtual void LogAddressSuggestionsCount(size_t num_suggestions) const; + + // Logs the experiment id corresponding to a server query response. + virtual void LogServerExperimentIdForQuery( + const std::string& experiment_id) const; + + // Logs the experiment id corresponding to an upload to the server. + virtual void LogServerExperimentIdForUpload( + const std::string& experiment_id) const; + + private: + DISALLOW_COPY_AND_ASSIGN(AutofillMetrics); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_METRICS_H_ diff --git a/components/autofill/browser/autofill_metrics_unittest.cc b/components/autofill/browser/autofill_metrics_unittest.cc new file mode 100644 index 0000000..4a9efac --- /dev/null +++ b/components/autofill/browser/autofill_metrics_unittest.cc @@ -0,0 +1,1578 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/autofill/autofill_cc_infobar_delegate.h" +#include "chrome/browser/autofill/personal_data_manager_factory.h" +#include "chrome/browser/ui/autofill/tab_autofill_manager_delegate.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/autocheckout_page_meta_data.h" +#include "components/autofill/browser/autofill_common_test.h" +#include "components/autofill/browser/autofill_manager.h" +#include "components/autofill/browser/autofill_manager_delegate.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/browser/personal_data_manager.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_field_data.h" +#include "content/public/test/test_browser_thread.h" +#include "googleurl/src/gurl.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" + +using content::BrowserThread; +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Mock; +using base::TimeTicks; +using base::TimeDelta; + +namespace { + +class MockAutofillMetrics : public AutofillMetrics { + public: + MockAutofillMetrics() {} + MOCK_CONST_METHOD1(LogCreditCardInfoBarMetric, void(InfoBarMetric metric)); + MOCK_CONST_METHOD1(LogDeveloperEngagementMetric, + void(DeveloperEngagementMetric metric)); + MOCK_CONST_METHOD3(LogHeuristicTypePrediction, + void(FieldTypeQualityMetric metric, + AutofillFieldType field_type, + const std::string& experiment_id)); + MOCK_CONST_METHOD3(LogOverallTypePrediction, + void(FieldTypeQualityMetric metric, + AutofillFieldType field_type, + const std::string& experiment_id)); + MOCK_CONST_METHOD3(LogServerTypePrediction, + void(FieldTypeQualityMetric metric, + AutofillFieldType field_type, + const std::string& experiment_id)); + MOCK_CONST_METHOD2(LogQualityMetric, void(QualityMetric metric, + const std::string& experiment_id)); + MOCK_CONST_METHOD1(LogServerQueryMetric, void(ServerQueryMetric metric)); + MOCK_CONST_METHOD1(LogUserHappinessMetric, void(UserHappinessMetric metric)); + MOCK_CONST_METHOD1(LogFormFillDurationFromLoadWithAutofill, + void(const TimeDelta& duration)); + MOCK_CONST_METHOD1(LogFormFillDurationFromLoadWithoutAutofill, + void(const TimeDelta& duration)); + MOCK_CONST_METHOD1(LogFormFillDurationFromInteractionWithAutofill, + void(const TimeDelta& duration)); + MOCK_CONST_METHOD1(LogFormFillDurationFromInteractionWithoutAutofill, + void(const TimeDelta& duration)); + MOCK_CONST_METHOD1(LogIsAutofillEnabledAtPageLoad, void(bool enabled)); + MOCK_CONST_METHOD1(LogIsAutofillEnabledAtStartup, void(bool enabled)); + MOCK_CONST_METHOD1(LogStoredProfileCount, void(size_t num_profiles)); + MOCK_CONST_METHOD1(LogAddressSuggestionsCount, void(size_t num_suggestions)); + MOCK_CONST_METHOD1(LogServerExperimentIdForQuery, + void(const std::string& experiment_id)); + MOCK_CONST_METHOD1(LogServerExperimentIdForUpload, + void(const std::string& experiment_id)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillMetrics); +}; + +class TestPersonalDataManager : public PersonalDataManager { + public: + TestPersonalDataManager() : autofill_enabled_(true) { + set_metric_logger(new MockAutofillMetrics); + CreateTestAutofillProfiles(&web_profiles_); + } + + void SetBrowserContext(content::BrowserContext* context) { + set_browser_context(context); + } + + // Overridden to avoid a trip to the database. This should be a no-op except + // for the side-effect of logging the profile count. + virtual void LoadProfiles() OVERRIDE { + std::vector<AutofillProfile*> profiles; + web_profiles_.release(&profiles); + WDResult<std::vector<AutofillProfile*> > result(AUTOFILL_PROFILES_RESULT, + profiles); + ReceiveLoadedProfiles(0, &result); + } + + // Overridden to avoid a trip to the database. + virtual void LoadCreditCards() OVERRIDE {} + + const MockAutofillMetrics* metric_logger() const { + return static_cast<const MockAutofillMetrics*>( + PersonalDataManager::metric_logger()); + } + + void set_autofill_enabled(bool autofill_enabled) { + autofill_enabled_ = autofill_enabled; + } + + virtual bool IsAutofillEnabled() const OVERRIDE { + return autofill_enabled_; + } + + MOCK_METHOD1(SaveImportedCreditCard, + void(const CreditCard& imported_credit_card)); + + private: + void CreateTestAutofillProfiles(ScopedVector<AutofillProfile>* profiles) { + AutofillProfile* profile = new AutofillProfile; + autofill_test::SetProfileInfo(profile, "Elvis", "Aaron", + "Presley", "theking@gmail.com", "RCA", + "3734 Elvis Presley Blvd.", "Apt. 10", + "Memphis", "Tennessee", "38116", "US", + "12345678901"); + profile->set_guid("00000000-0000-0000-0000-000000000001"); + profiles->push_back(profile); + profile = new AutofillProfile; + autofill_test::SetProfileInfo(profile, "Charles", "Hardin", + "Holley", "buddy@gmail.com", "Decca", + "123 Apple St.", "unit 6", "Lubbock", + "Texas", "79401", "US", "2345678901"); + profile->set_guid("00000000-0000-0000-0000-000000000002"); + profiles->push_back(profile); + } + + bool autofill_enabled_; + + DISALLOW_COPY_AND_ASSIGN(TestPersonalDataManager); +}; + +class TestFormStructure : public FormStructure { + public: + explicit TestFormStructure(const FormData& form) + : FormStructure(form, std::string()) {} + virtual ~TestFormStructure() {} + + void SetFieldTypes(const std::vector<AutofillFieldType>& heuristic_types, + const std::vector<AutofillFieldType>& server_types) { + ASSERT_EQ(field_count(), heuristic_types.size()); + ASSERT_EQ(field_count(), server_types.size()); + + for (size_t i = 0; i < field_count(); ++i) { + AutofillField* form_field = field(i); + ASSERT_TRUE(form_field); + form_field->set_heuristic_type(heuristic_types[i]); + form_field->set_server_type(server_types[i]); + } + + UpdateAutofillCount(); + } + + virtual std::string server_experiment_id() const OVERRIDE { + return server_experiment_id_; + } + void set_server_experiment_id(const std::string& server_experiment_id) { + server_experiment_id_ = server_experiment_id; + } + + private: + std::string server_experiment_id_; + DISALLOW_COPY_AND_ASSIGN(TestFormStructure); +}; + +class TestAutofillManager : public AutofillManager { + public: + TestAutofillManager(content::WebContents* web_contents, + autofill::AutofillManagerDelegate* manager_delegate, + TestPersonalDataManager* personal_manager) + : AutofillManager(web_contents, manager_delegate, personal_manager), + autofill_enabled_(true), + did_finish_async_form_submit_(false), + message_loop_is_running_(false) { + set_metric_logger(new MockAutofillMetrics); + } + virtual ~TestAutofillManager() {} + + virtual bool IsAutofillEnabled() const OVERRIDE { return autofill_enabled_; } + + void set_autofill_enabled(bool autofill_enabled) { + autofill_enabled_ = autofill_enabled; + } + + MockAutofillMetrics* metric_logger() { + return static_cast<MockAutofillMetrics*>(const_cast<AutofillMetrics*>( + AutofillManager::metric_logger())); + } + + void AddSeenForm(const FormData& form, + const std::vector<AutofillFieldType>& heuristic_types, + const std::vector<AutofillFieldType>& server_types, + const std::string& experiment_id) { + FormData empty_form = form; + for (size_t i = 0; i < empty_form.fields.size(); ++i) { + empty_form.fields[i].value = string16(); + } + + // |form_structure| will be owned by |form_structures()|. + TestFormStructure* form_structure = new TestFormStructure(empty_form); + form_structure->SetFieldTypes(heuristic_types, server_types); + form_structure->set_server_experiment_id(experiment_id); + form_structures()->push_back(form_structure); + } + + void FormSubmitted(const FormData& form, const TimeTicks& timestamp) { + if (!OnFormSubmitted(form, timestamp)) + return; + + // Wait for the asynchronous FormSubmitted() call to complete. + if (!did_finish_async_form_submit_) { + // TODO(isherman): It seems silly to need this variable. Is there some + // way I can just query the message loop's state? + message_loop_is_running_ = true; + MessageLoop::current()->Run(); + } else { + did_finish_async_form_submit_ = false; + } + } + + virtual void UploadFormDataAsyncCallback( + const FormStructure* submitted_form, + const base::TimeTicks& load_time, + const base::TimeTicks& interaction_time, + const base::TimeTicks& submission_time) OVERRIDE { + if (message_loop_is_running_) { + MessageLoop::current()->Quit(); + message_loop_is_running_ = false; + } else { + did_finish_async_form_submit_ = true; + } + + AutofillManager::UploadFormDataAsyncCallback(submitted_form, + load_time, + interaction_time, + submission_time); + } + + private: + bool autofill_enabled_; + bool did_finish_async_form_submit_; + bool message_loop_is_running_; + + DISALLOW_COPY_AND_ASSIGN(TestAutofillManager); +}; + +} // namespace + +class AutofillMetricsTest : public ChromeRenderViewHostTestHarness { + public: + AutofillMetricsTest(); + virtual ~AutofillMetricsTest(); + + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + protected: + scoped_ptr<ConfirmInfoBarDelegate> CreateDelegate( + MockAutofillMetrics* metric_logger); + + content::TestBrowserThread ui_thread_; + content::TestBrowserThread file_thread_; + content::TestBrowserThread io_thread_; + + scoped_ptr<TestAutofillManager> autofill_manager_; + TestPersonalDataManager personal_data_; + + private: + std::string default_gmock_verbosity_level_; + + DISALLOW_COPY_AND_ASSIGN(AutofillMetricsTest); +}; + +AutofillMetricsTest::AutofillMetricsTest() + : ChromeRenderViewHostTestHarness(), + ui_thread_(BrowserThread::UI, &message_loop_), + file_thread_(BrowserThread::FILE), + io_thread_(BrowserThread::IO) { +} + +AutofillMetricsTest::~AutofillMetricsTest() { + // Order of destruction is important as AutofillManager relies on + // PersonalDataManager to be around when it gets destroyed. + autofill_manager_.reset(); +} + +void AutofillMetricsTest::SetUp() { + TestingProfile* profile = new TestingProfile(); + profile->CreateRequestContext(); + browser_context_.reset(profile); + PersonalDataManagerFactory::GetInstance()->SetTestingFactory(profile, NULL); + + ChromeRenderViewHostTestHarness::SetUp(); + io_thread_.StartIOThread(); + autofill::TabAutofillManagerDelegate::CreateForWebContents(web_contents()); + personal_data_.SetBrowserContext(profile); + autofill_manager_.reset(new TestAutofillManager( + web_contents(), + autofill::TabAutofillManagerDelegate::FromWebContents(web_contents()), + &personal_data_)); + + file_thread_.Start(); + + // Ignore any metrics that we haven't explicitly set expectations for. + // If we don't override the verbosity level, we'll get lots of log spew from + // mocked functions that aren't relevant to a test but happen to be called + // during the test's execution. + // CAUTION: This is a global variable. So as to not affect other tests, this + // _must_ be restored to its original value at the end of the test. + default_gmock_verbosity_level_ = ::testing::FLAGS_gmock_verbose; + ::testing::FLAGS_gmock_verbose = "error"; +} + +void AutofillMetricsTest::TearDown() { + // Restore the global Gmock verbosity level to its default value. + ::testing::FLAGS_gmock_verbose = default_gmock_verbosity_level_; + + // Order of destruction is important as AutofillManager relies on + // PersonalDataManager to be around when it gets destroyed. Also, a real + // AutofillManager is tied to the lifetime of the WebContents, so it must + // be destroyed at the destruction of the WebContents. + autofill_manager_.reset(); + profile()->ResetRequestContext(); + file_thread_.Stop(); + ChromeRenderViewHostTestHarness::TearDown(); + io_thread_.Stop(); +} + +scoped_ptr<ConfirmInfoBarDelegate> AutofillMetricsTest::CreateDelegate( + MockAutofillMetrics* metric_logger) { + EXPECT_CALL(*metric_logger, + LogCreditCardInfoBarMetric(AutofillMetrics::INFOBAR_SHOWN)); + + CreditCard credit_card; + return AutofillCCInfoBarDelegate::CreateForTesting( + metric_logger, + base::Bind(&TestPersonalDataManager::SaveImportedCreditCard, + base::Unretained(&personal_data_), credit_card)); +} + +// Test that we log quality metrics appropriately. +TEST_F(AutofillMetricsTest, QualityMetrics) { + // Set up our form data. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + form.user_submitted = true; + + std::vector<AutofillFieldType> heuristic_types, server_types; + FormFieldData field; + + autofill_test::CreateTestFormField( + "Autofilled", "autofilled", "Elvis Aaron Presley", "text", &field); + field.is_autofilled = true; + form.fields.push_back(field); + heuristic_types.push_back(NAME_FULL); + server_types.push_back(NAME_FIRST); + + autofill_test::CreateTestFormField( + "Autofill Failed", "autofillfailed", "buddy@gmail.com", "text", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_NUMBER); + server_types.push_back(EMAIL_ADDRESS); + + autofill_test::CreateTestFormField( + "Empty", "empty", "", "text", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(NAME_FULL); + server_types.push_back(NAME_FIRST); + + autofill_test::CreateTestFormField( + "Unknown", "unknown", "garbage", "text", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_NUMBER); + server_types.push_back(EMAIL_ADDRESS); + + autofill_test::CreateTestFormField( + "Select", "select", "USA", "select-one", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(UNKNOWN_TYPE); + server_types.push_back(NO_SERVER_DATA); + + autofill_test::CreateTestFormField( + "Phone", "phone", "2345678901", "tel", &field); + field.is_autofilled = true; + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_CITY_AND_NUMBER); + server_types.push_back(PHONE_HOME_WHOLE_NUMBER); + + // Simulate having seen this form on page load. + autofill_manager_->AddSeenForm(form, heuristic_types, server_types, + std::string()); + + // Establish our expectations. + ::testing::InSequence dummy; + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerExperimentIdForUpload(std::string())); + // Autofilled field + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_MATCH, + NAME_FULL, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_MISMATCH, + NAME_FULL, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_MISMATCH, + NAME_FULL, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_AUTOFILLED, + std::string())); + // Non-autofilled field for which we had data + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_MISMATCH, + EMAIL_ADDRESS, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_MATCH, + EMAIL_ADDRESS, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_MATCH, + EMAIL_ADDRESS, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_NOT_AUTOFILLED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MISMATCH, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MATCH, + std::string())); + // Empty field + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + // Unknown field + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + // <select> field + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + ADDRESS_HOME_COUNTRY, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + ADDRESS_HOME_COUNTRY, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + ADDRESS_HOME_COUNTRY, std::string())); + // Phone field + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_MATCH, + PHONE_HOME_WHOLE_NUMBER, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_MATCH, + PHONE_HOME_WHOLE_NUMBER, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_MATCH, + PHONE_HOME_WHOLE_NUMBER, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_AUTOFILLED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_SOME)); + + // Simulate form submission. + EXPECT_NO_FATAL_FAILURE(autofill_manager_->FormSubmitted(form, + TimeTicks::Now())); +} + +// Test that we log the appropriate additional metrics when Autofill failed. +TEST_F(AutofillMetricsTest, QualityMetricsForFailure) { + // Set up our form data. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + form.user_submitted = true; + + struct { + const char* label; + const char* name; + const char* value; + AutofillFieldType heuristic_type; + AutofillFieldType server_type; + AutofillMetrics::QualityMetric heuristic_metric; + AutofillMetrics::QualityMetric server_metric; + } failure_cases[] = { + { + "Heuristics unknown, server unknown", "0,0", "Elvis", + UNKNOWN_TYPE, NO_SERVER_DATA, + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_UNKNOWN, + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_UNKNOWN + }, + { + "Heuristics match, server unknown", "1,0", "Aaron", + NAME_MIDDLE, NO_SERVER_DATA, + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MATCH, + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_UNKNOWN + }, + { + "Heuristics mismatch, server unknown", "2,0", "Presley", + PHONE_HOME_NUMBER, NO_SERVER_DATA, + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MISMATCH, + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_UNKNOWN + }, + { + "Heuristics unknown, server match", "0,1", "theking@gmail.com", + UNKNOWN_TYPE, EMAIL_ADDRESS, + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_UNKNOWN, + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MATCH + }, + { + "Heuristics match, server match", "1,1", "3734 Elvis Presley Blvd.", + ADDRESS_HOME_LINE1, ADDRESS_HOME_LINE1, + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MATCH, + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MATCH + }, + { + "Heuristics mismatch, server match", "2,1", "Apt. 10", + PHONE_HOME_NUMBER, ADDRESS_HOME_LINE2, + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MISMATCH, + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MATCH + }, + { + "Heuristics unknown, server mismatch", "0,2", "Memphis", + UNKNOWN_TYPE, PHONE_HOME_NUMBER, + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_UNKNOWN, + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MISMATCH + }, + { + "Heuristics match, server mismatch", "1,2", "Tennessee", + ADDRESS_HOME_STATE, PHONE_HOME_NUMBER, + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MATCH, + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MISMATCH + }, + { + "Heuristics mismatch, server mismatch", "2,2", "38116", + PHONE_HOME_NUMBER, PHONE_HOME_NUMBER, + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MISMATCH, + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MISMATCH + } + }; + + std::vector<AutofillFieldType> heuristic_types, server_types; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(failure_cases); ++i) { + FormFieldData field; + autofill_test::CreateTestFormField(failure_cases[i].label, + failure_cases[i].name, + failure_cases[i].value, "text", &field); + form.fields.push_back(field); + heuristic_types.push_back(failure_cases[i].heuristic_type); + server_types.push_back(failure_cases[i].server_type); + + } + + // Simulate having seen this form with the desired heuristic and server types. + // |form_structure| will be owned by |autofill_manager_|. + autofill_manager_->AddSeenForm(form, heuristic_types, server_types, + std::string()); + + + // Establish our expectations. + ::testing::InSequence dummy; + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerExperimentIdForUpload(std::string())); + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(failure_cases); ++i) { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_NOT_AUTOFILLED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(failure_cases[i].heuristic_metric, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(failure_cases[i].server_metric, + std::string())); + } + + // Simulate form submission. + EXPECT_NO_FATAL_FAILURE(autofill_manager_->FormSubmitted(form, + TimeTicks::Now())); +} + +// Test that we behave sanely when the cached form differs from the submitted +// one. +TEST_F(AutofillMetricsTest, SaneMetricsWithCacheMismatch) { + // Set up our form data. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + form.user_submitted = true; + + std::vector<AutofillFieldType> heuristic_types, server_types; + + FormFieldData field; + autofill_test::CreateTestFormField( + "Both match", "match", "Elvis Aaron Presley", "text", &field); + field.is_autofilled = true; + form.fields.push_back(field); + heuristic_types.push_back(NAME_FULL); + server_types.push_back(NAME_FULL); + autofill_test::CreateTestFormField( + "Both mismatch", "mismatch", "buddy@gmail.com", "text", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_NUMBER); + server_types.push_back(PHONE_HOME_NUMBER); + autofill_test::CreateTestFormField( + "Only heuristics match", "mixed", "Memphis", "text", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(ADDRESS_HOME_CITY); + server_types.push_back(PHONE_HOME_NUMBER); + autofill_test::CreateTestFormField( + "Unknown", "unknown", "garbage", "text", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(UNKNOWN_TYPE); + server_types.push_back(UNKNOWN_TYPE); + + // Simulate having seen this form with the desired heuristic and server types. + // |form_structure| will be owned by |autofill_manager_|. + autofill_manager_->AddSeenForm(form, heuristic_types, server_types, + std::string()); + + + // Add a field and re-arrange the remaining form fields before submitting. + std::vector<FormFieldData> cached_fields = form.fields; + form.fields.clear(); + autofill_test::CreateTestFormField( + "New field", "new field", "Tennessee", "text", &field); + form.fields.push_back(field); + form.fields.push_back(cached_fields[2]); + form.fields.push_back(cached_fields[1]); + form.fields.push_back(cached_fields[3]); + form.fields.push_back(cached_fields[0]); + + // Establish our expectations. + ::testing::InSequence dummy; + // New field + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerExperimentIdForUpload(std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + ADDRESS_HOME_STATE, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + ADDRESS_HOME_STATE, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + ADDRESS_HOME_STATE, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_NOT_AUTOFILLED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_UNKNOWN, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_UNKNOWN, + std::string())); + // Only heuristics match + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_MATCH, + ADDRESS_HOME_CITY, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_MISMATCH, + ADDRESS_HOME_CITY, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_MISMATCH, + ADDRESS_HOME_CITY, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_NOT_AUTOFILLED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MATCH, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MISMATCH, + std::string())); + // Both mismatch + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_MISMATCH, + EMAIL_ADDRESS, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_MISMATCH, + EMAIL_ADDRESS, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_MISMATCH, + EMAIL_ADDRESS, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_NOT_AUTOFILLED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MISMATCH, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MISMATCH, + std::string())); + // Unknown + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + // Both match + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_MATCH, + NAME_FULL, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_MATCH, + NAME_FULL, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_MATCH, + NAME_FULL, std::string())); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_AUTOFILLED, + std::string())); + + // Simulate form submission. + EXPECT_NO_FATAL_FAILURE(autofill_manager_->FormSubmitted(form, + TimeTicks::Now())); +} + +// Verify that we correctly log metrics regarding developer engagement. +TEST_F(AutofillMetricsTest, DeveloperEngagement) { + // Start with a non-fillable form. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + + FormFieldData field; + autofill_test::CreateTestFormField("Name", "name", "", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField("Email", "email", "", "text", &field); + form.fields.push_back(field); + + std::vector<FormData> forms(1, form); + + // Ensure no metrics are logged when loading a non-fillable form. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogDeveloperEngagementMetric(_)).Times(0); + autofill_manager_->OnFormsSeen(forms, TimeTicks()); + autofill_manager_->Reset(); + Mock::VerifyAndClearExpectations(autofill_manager_->metric_logger()); + } + + // Add another field to the form, so that it becomes fillable. + autofill_test::CreateTestFormField("Phone", "phone", "", "text", &field); + forms.back().fields.push_back(field); + + // Expect only the "form parsed" metric to be logged; no metrics about + // author-specified field type hints. + { + EXPECT_CALL( + *autofill_manager_->metric_logger(), + LogDeveloperEngagementMetric( + AutofillMetrics::FILLABLE_FORM_PARSED)).Times(1); + EXPECT_CALL( + *autofill_manager_->metric_logger(), + LogDeveloperEngagementMetric( + AutofillMetrics::FILLABLE_FORM_CONTAINS_TYPE_HINTS)).Times(0); + autofill_manager_->OnFormsSeen(forms, TimeTicks()); + autofill_manager_->Reset(); + Mock::VerifyAndClearExpectations(autofill_manager_->metric_logger()); + } + + // Add some fields with an author-specified field type to the form. + // We need to add at least three fields, because a form must have at least + // three fillable fields to be considered to be autofillable; and if at least + // one field specifies an explicit type hint, we don't apply any of our usual + // local heuristics to detect field types in the rest of the form. + autofill_test::CreateTestFormField("", "", "", "text", &field); + field.autocomplete_attribute = "given-name"; + forms.back().fields.push_back(field); + autofill_test::CreateTestFormField("", "", "", "text", &field); + field.autocomplete_attribute = "email"; + forms.back().fields.push_back(field); + autofill_test::CreateTestFormField("", "", "", "text", &field); + field.autocomplete_attribute = "street-address"; + forms.back().fields.push_back(field); + + // Expect both the "form parsed" metric and the author-specified field type + // hints metric to be logged. + { + EXPECT_CALL( + *autofill_manager_->metric_logger(), + LogDeveloperEngagementMetric( + AutofillMetrics::FILLABLE_FORM_PARSED)).Times(1); + EXPECT_CALL( + *autofill_manager_->metric_logger(), + LogDeveloperEngagementMetric( + AutofillMetrics::FILLABLE_FORM_CONTAINS_TYPE_HINTS)).Times(1); + autofill_manager_->OnFormsSeen(forms, TimeTicks()); + autofill_manager_->Reset(); + Mock::VerifyAndClearExpectations(autofill_manager_->metric_logger()); + } +} + +// Test that we don't log quality metrics for non-autofillable forms. +TEST_F(AutofillMetricsTest, NoQualityMetricsForNonAutofillableForms) { + // Forms must include at least three fields to be auto-fillable. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + form.user_submitted = true; + + FormFieldData field; + autofill_test::CreateTestFormField( + "Autofilled", "autofilled", "Elvis Presley", "text", &field); + field.is_autofilled = true; + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Autofill Failed", "autofillfailed", "buddy@gmail.com", "text", &field); + form.fields.push_back(field); + + // Simulate form submission. + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())).Times(0); + EXPECT_NO_FATAL_FAILURE(autofill_manager_->FormSubmitted(form, + TimeTicks::Now())); + + // Search forms are not auto-fillable. + form.action = GURL("http://example.com/search?q=Elvis%20Presley"); + autofill_test::CreateTestFormField( + "Empty", "empty", "", "text", &field); + form.fields.push_back(field); + + // Simulate form submission. + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + std::string())).Times(0); + EXPECT_NO_FATAL_FAILURE(autofill_manager_->FormSubmitted(form, + TimeTicks::Now())); +} + +// Test that we recored the experiment id appropriately. +TEST_F(AutofillMetricsTest, QualityMetricsWithExperimentId) { + // Set up our form data. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + form.user_submitted = true; + + std::vector<AutofillFieldType> heuristic_types, server_types; + FormFieldData field; + + autofill_test::CreateTestFormField( + "Autofilled", "autofilled", "Elvis Aaron Presley", "text", &field); + field.is_autofilled = true; + form.fields.push_back(field); + heuristic_types.push_back(NAME_FULL); + server_types.push_back(NAME_FIRST); + + autofill_test::CreateTestFormField( + "Autofill Failed", "autofillfailed", "buddy@gmail.com", "text", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_NUMBER); + server_types.push_back(EMAIL_ADDRESS); + + autofill_test::CreateTestFormField( + "Empty", "empty", "", "text", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(NAME_FULL); + server_types.push_back(NAME_FIRST); + + autofill_test::CreateTestFormField( + "Unknown", "unknown", "garbage", "text", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(PHONE_HOME_NUMBER); + server_types.push_back(EMAIL_ADDRESS); + + autofill_test::CreateTestFormField( + "Select", "select", "USA", "select-one", &field); + field.is_autofilled = false; + form.fields.push_back(field); + heuristic_types.push_back(UNKNOWN_TYPE); + server_types.push_back(NO_SERVER_DATA); + + const std::string experiment_id = "ThatOughtaDoIt"; + + // Simulate having seen this form on page load. + // |form_structure| will be owned by |autofill_manager_|. + autofill_manager_->AddSeenForm(form, heuristic_types, server_types, + experiment_id); + + // Establish our expectations. + ::testing::InSequence dummy; + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerExperimentIdForUpload(experiment_id)); + // Autofilled field + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_MATCH, + NAME_FULL, experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_MISMATCH, + NAME_FULL, experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_MISMATCH, + NAME_FULL, experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_AUTOFILLED, + experiment_id)); + // Non-autofilled field for which we had data + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_MISMATCH, + EMAIL_ADDRESS, experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_MATCH, + EMAIL_ADDRESS, experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_MATCH, + EMAIL_ADDRESS, experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_NOT_AUTOFILLED, + experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MISMATCH, + experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MATCH, + experiment_id)); + // Empty field + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + experiment_id)); + // Unknown field + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + experiment_id)); + // <select> field + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogHeuristicTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + ADDRESS_HOME_COUNTRY, experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogServerTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + ADDRESS_HOME_COUNTRY, experiment_id)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogOverallTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + ADDRESS_HOME_COUNTRY, experiment_id)); + + // Simulate form submission. + EXPECT_NO_FATAL_FAILURE(autofill_manager_->FormSubmitted(form, + TimeTicks::Now())); +} + +// Test that the profile count is logged correctly. +TEST_F(AutofillMetricsTest, StoredProfileCount) { + // The metric should be logged when the profiles are first loaded. + EXPECT_CALL(*personal_data_.metric_logger(), + LogStoredProfileCount(2)).Times(1); + personal_data_.LoadProfiles(); + + // The metric should only be logged once. + EXPECT_CALL(*personal_data_.metric_logger(), + LogStoredProfileCount(::testing::_)).Times(0); + personal_data_.LoadProfiles(); +} + +// Test that we correctly log when Autofill is enabled. +TEST_F(AutofillMetricsTest, AutofillIsEnabledAtStartup) { + personal_data_.set_autofill_enabled(true); + EXPECT_CALL(*personal_data_.metric_logger(), + LogIsAutofillEnabledAtStartup(true)).Times(1); + personal_data_.Init(profile()); +} + +// Test that we correctly log when Autofill is disabled. +TEST_F(AutofillMetricsTest, AutofillIsDisabledAtStartup) { + personal_data_.set_autofill_enabled(false); + EXPECT_CALL(*personal_data_.metric_logger(), + LogIsAutofillEnabledAtStartup(false)).Times(1); + personal_data_.Init(profile()); +} + +// Test that we log the number of Autofill suggestions when filling a form. +TEST_F(AutofillMetricsTest, AddressSuggestionsCount) { + // Set up our form data. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + form.user_submitted = true; + + FormFieldData field; + std::vector<AutofillFieldType> field_types; + autofill_test::CreateTestFormField("Name", "name", "", "text", &field); + form.fields.push_back(field); + field_types.push_back(NAME_FULL); + autofill_test::CreateTestFormField("Email", "email", "", "email", &field); + form.fields.push_back(field); + field_types.push_back(EMAIL_ADDRESS); + autofill_test::CreateTestFormField("Phone", "phone", "", "tel", &field); + form.fields.push_back(field); + field_types.push_back(PHONE_HOME_NUMBER); + + // Simulate having seen this form on page load. + // |form_structure| will be owned by |autofill_manager_|. + autofill_manager_->AddSeenForm(form, field_types, field_types, + std::string()); + + // Establish our expectations. + ::testing::InSequence dummy; + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogAddressSuggestionsCount(2)).Times(1); + + // Simulate activating the autofill popup for the phone field. + autofill_manager_->OnQueryFormFieldAutofill( + 0, form, field, gfx::Rect(), false); + + // Simulate activating the autofill popup for the email field after typing. + // No new metric should be logged, since we're still on the same page. + autofill_test::CreateTestFormField("Email", "email", "b", "email", &field); + autofill_manager_->OnQueryFormFieldAutofill( + 0, form, field, gfx::Rect(), false); + + // Reset the autofill manager state. + autofill_manager_->Reset(); + autofill_manager_->AddSeenForm(form, field_types, field_types, + std::string()); + + // Establish our expectations. + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogAddressSuggestionsCount(1)).Times(1); + + // Simulate activating the autofill popup for the email field after typing. + autofill_manager_->OnQueryFormFieldAutofill( + 0, form, field, gfx::Rect(), false); + + // Reset the autofill manager state again. + autofill_manager_->Reset(); + autofill_manager_->AddSeenForm(form, field_types, field_types, + std::string()); + + // Establish our expectations. + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogAddressSuggestionsCount(::testing::_)).Times(0); + + // Simulate activating the autofill popup for the email field after typing. + form.fields[0].is_autofilled = true; + autofill_manager_->OnQueryFormFieldAutofill( + 0, form, field, gfx::Rect(), false); +} + +// Test that we log whether Autofill is enabled when filling a form. +TEST_F(AutofillMetricsTest, AutofillIsEnabledAtPageLoad) { + // Establish our expectations. + ::testing::InSequence dummy; + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogIsAutofillEnabledAtPageLoad(true)).Times(1); + + autofill_manager_->set_autofill_enabled(true); + autofill_manager_->OnFormsSeen(std::vector<FormData>(), TimeTicks()); + + // Reset the autofill manager state. + autofill_manager_->Reset(); + + // Establish our expectations. + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogIsAutofillEnabledAtPageLoad(false)).Times(1); + + autofill_manager_->set_autofill_enabled(false); + autofill_manager_->OnFormsSeen(std::vector<FormData>(), TimeTicks()); +} + +// Test that credit card infobar metrics are logged correctly. +TEST_F(AutofillMetricsTest, CreditCardInfoBar) { + MockAutofillMetrics metric_logger; + ::testing::InSequence dummy; + + // Accept the infobar. + { + scoped_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(&metric_logger)); + ASSERT_TRUE(infobar); + EXPECT_CALL(personal_data_, SaveImportedCreditCard(_)); + EXPECT_CALL(metric_logger, + LogCreditCardInfoBarMetric(AutofillMetrics::INFOBAR_ACCEPTED)).Times(1); + EXPECT_CALL(metric_logger, + LogCreditCardInfoBarMetric(AutofillMetrics::INFOBAR_IGNORED)).Times(0); + EXPECT_TRUE(infobar->Accept()); + } + + // Cancel the infobar. + { + scoped_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(&metric_logger)); + ASSERT_TRUE(infobar); + EXPECT_CALL(metric_logger, + LogCreditCardInfoBarMetric(AutofillMetrics::INFOBAR_DENIED)).Times(1); + EXPECT_CALL(metric_logger, + LogCreditCardInfoBarMetric(AutofillMetrics::INFOBAR_IGNORED)).Times(0); + EXPECT_TRUE(infobar->Cancel()); + } + + // Dismiss the infobar. + { + scoped_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(&metric_logger)); + ASSERT_TRUE(infobar); + EXPECT_CALL(metric_logger, + LogCreditCardInfoBarMetric(AutofillMetrics::INFOBAR_DENIED)).Times(1); + EXPECT_CALL(metric_logger, + LogCreditCardInfoBarMetric(AutofillMetrics::INFOBAR_IGNORED)).Times(0); + infobar->InfoBarDismissed(); + } + + // Ignore the infobar. + { + scoped_ptr<ConfirmInfoBarDelegate> infobar(CreateDelegate(&metric_logger)); + ASSERT_TRUE(infobar); + EXPECT_CALL(metric_logger, + LogCreditCardInfoBarMetric(AutofillMetrics::INFOBAR_IGNORED)).Times(1); + } +} + +// Test that server query response experiment id metrics are logged correctly. +TEST_F(AutofillMetricsTest, ServerQueryExperimentIdForQuery) { + MockAutofillMetrics metric_logger; + ::testing::InSequence dummy; + + // No experiment specified. + EXPECT_CALL(metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_RESPONSE_RECEIVED)); + EXPECT_CALL(metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_RESPONSE_PARSED)); + EXPECT_CALL(metric_logger, + LogServerExperimentIdForQuery(std::string())); + EXPECT_CALL(metric_logger, + LogServerQueryMetric( + AutofillMetrics::QUERY_RESPONSE_MATCHED_LOCAL_HEURISTICS)); + autofill::AutocheckoutPageMetaData page_meta_data; + FormStructure::ParseQueryResponse( + "<autofillqueryresponse></autofillqueryresponse>", + std::vector<FormStructure*>(), + &page_meta_data, + metric_logger); + + // Experiment "ar1" specified. + EXPECT_CALL(metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_RESPONSE_RECEIVED)); + EXPECT_CALL(metric_logger, + LogServerQueryMetric(AutofillMetrics::QUERY_RESPONSE_PARSED)); + EXPECT_CALL(metric_logger, + LogServerExperimentIdForQuery("ar1")); + EXPECT_CALL(metric_logger, + LogServerQueryMetric( + AutofillMetrics::QUERY_RESPONSE_MATCHED_LOCAL_HEURISTICS)); + FormStructure::ParseQueryResponse( + "<autofillqueryresponse experimentid=\"ar1\"></autofillqueryresponse>", + std::vector<FormStructure*>(), + &page_meta_data, + metric_logger); +} + +// Verify that we correctly log user happiness metrics dealing with form loading +// and form submission. +TEST_F(AutofillMetricsTest, UserHappinessFormLoadAndSubmission) { + // Start with a form with insufficiently many fields. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + form.user_submitted = true; + + FormFieldData field; + autofill_test::CreateTestFormField("Name", "name", "", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField("Email", "email", "", "text", &field); + form.fields.push_back(field); + + std::vector<FormData> forms(1, form); + + // Expect no notifications when the form is first seen. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric(AutofillMetrics::FORMS_LOADED)).Times(0); + autofill_manager_->OnFormsSeen(forms, TimeTicks()); + } + + + // Expect no notifications when the form is submitted. + { + EXPECT_CALL( + *autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_ALL)).Times(0); + EXPECT_CALL( + *autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_SOME)).Times(0); + EXPECT_CALL( + *autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_NONE)).Times(0); + EXPECT_CALL( + *autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_NON_FILLABLE_FORM)).Times(0); + autofill_manager_->FormSubmitted(form, TimeTicks::Now()); + } + + // Add more fields to the form. + autofill_test::CreateTestFormField("Phone", "phone", "", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField("Unknown", "unknown", "", "text", &field); + form.fields.push_back(field); + forms.front() = form; + + // Expect a notification when the form is first seen. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric(AutofillMetrics::FORMS_LOADED)); + autofill_manager_->OnFormsSeen(forms, TimeTicks()); + } + + // Expect a notification when the form is submitted. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_NON_FILLABLE_FORM)); + autofill_manager_->FormSubmitted(form, TimeTicks::Now()); + } + + // Fill in two of the fields. + form.fields[0].value = ASCIIToUTF16("Elvis Aaron Presley"); + form.fields[1].value = ASCIIToUTF16("theking@gmail.com"); + forms.front() = form; + + // Expect a notification when the form is submitted. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_NON_FILLABLE_FORM)); + autofill_manager_->FormSubmitted(form, TimeTicks::Now()); + } + + // Fill in the third field. + form.fields[2].value = ASCIIToUTF16("12345678901"); + forms.front() = form; + + // Expect notifications when the form is submitted. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_NONE)); + autofill_manager_->FormSubmitted(form, TimeTicks::Now()); + } + + + // Mark one of the fields as autofilled. + form.fields[1].is_autofilled = true; + forms.front() = form; + + // Expect notifications when the form is submitted. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_SOME)); + autofill_manager_->FormSubmitted(form, TimeTicks::Now()); + } + + // Mark all of the fillable fields as autofilled. + form.fields[0].is_autofilled = true; + form.fields[2].is_autofilled = true; + forms.front() = form; + + // Expect notifications when the form is submitted. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_ALL)); + autofill_manager_->FormSubmitted(form, TimeTicks::Now()); + } + + // Clear out the third field's value. + form.fields[2].value = string16(); + forms.front() = form; + + // Expect notifications when the form is submitted. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_NON_FILLABLE_FORM)); + autofill_manager_->FormSubmitted(form, TimeTicks::Now()); + } +} + +// Verify that we correctly log user happiness metrics dealing with form +// interaction. +TEST_F(AutofillMetricsTest, UserHappinessFormInteraction) { + // Load a fillable form. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + form.user_submitted = true; + + FormFieldData field; + autofill_test::CreateTestFormField("Name", "name", "", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField("Email", "email", "", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField("Phone", "phone", "", "text", &field); + form.fields.push_back(field); + + std::vector<FormData> forms(1, form); + + // Expect a notification when the form is first seen. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric(AutofillMetrics::FORMS_LOADED)); + autofill_manager_->OnFormsSeen(forms, TimeTicks()); + } + + // Simulate typing. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric(AutofillMetrics::USER_DID_TYPE)); + autofill_manager_->OnTextFieldDidChange(form, form.fields.front(), + TimeTicks()); + } + + // Simulate suggestions shown twice for a single edit (i.e. multiple + // keystrokes in a single field). + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUGGESTIONS_SHOWN)).Times(1); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUGGESTIONS_SHOWN_ONCE)).Times(1); + autofill_manager_->OnDidShowAutofillSuggestions(true); + autofill_manager_->OnDidShowAutofillSuggestions(false); + } + + // Simulate suggestions shown for a different field. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric(AutofillMetrics::SUGGESTIONS_SHOWN)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::SUGGESTIONS_SHOWN_ONCE)).Times(0); + autofill_manager_->OnDidShowAutofillSuggestions(true); + } + + // Simulate invoking autofill. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric(AutofillMetrics::USER_DID_AUTOFILL)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::USER_DID_AUTOFILL_ONCE)); + autofill_manager_->OnDidFillAutofillFormData(TimeTicks()); + } + + // Simulate editing an autofilled field. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::USER_DID_EDIT_AUTOFILLED_FIELD)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::USER_DID_EDIT_AUTOFILLED_FIELD_ONCE)); + PersonalDataManager::GUIDPair guid( + "00000000-0000-0000-0000-000000000001", 0); + PersonalDataManager::GUIDPair empty(std::string(), 0); + autofill_manager_->OnFillAutofillFormData( + 0, form, form.fields.front(), + autofill_manager_->PackGUIDs(empty, guid)); + autofill_manager_->OnTextFieldDidChange(form, form.fields.front(), + TimeTicks()); + // Simulate a second keystroke; make sure we don't log the metric twice. + autofill_manager_->OnTextFieldDidChange(form, form.fields.front(), + TimeTicks()); + } + + // Simulate invoking autofill again. + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric(AutofillMetrics::USER_DID_AUTOFILL)); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::USER_DID_AUTOFILL_ONCE)).Times(0); + autofill_manager_->OnDidFillAutofillFormData(TimeTicks()); + + // Simulate editing another autofilled field. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogUserHappinessMetric( + AutofillMetrics::USER_DID_EDIT_AUTOFILLED_FIELD)); + autofill_manager_->OnTextFieldDidChange(form, form.fields[1], TimeTicks()); + } +} + +// Verify that we correctly log metrics tracking the duration of form fill. +TEST_F(AutofillMetricsTest, FormFillDuration) { + // Load a fillable form. + FormData form; + form.name = ASCIIToUTF16("TestForm"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://example.com/form.html"); + form.action = GURL("http://example.com/submit.html"); + form.user_submitted = true; + + FormFieldData field; + autofill_test::CreateTestFormField("Name", "name", "", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField("Email", "email", "", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField("Phone", "phone", "", "text", &field); + form.fields.push_back(field); + + std::vector<FormData> forms(1, form); + + // Fill the field values for form submission. + form.fields[0].value = ASCIIToUTF16("Elvis Aaron Presley"); + form.fields[1].value = ASCIIToUTF16("theking@gmail.com"); + form.fields[2].value = ASCIIToUTF16("12345678901"); + + // Expect only form load metrics to be logged if the form is submitted without + // user interaction. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromLoadWithAutofill(_)).Times(0); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromLoadWithoutAutofill( + TimeDelta::FromInternalValue(16))); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromInteractionWithAutofill(_)).Times(0); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromInteractionWithoutAutofill(_)).Times(0); + autofill_manager_->OnFormsSeen(forms, TimeTicks::FromInternalValue(1)); + autofill_manager_->FormSubmitted(form, TimeTicks::FromInternalValue(17)); + autofill_manager_->Reset(); + Mock::VerifyAndClearExpectations(autofill_manager_->metric_logger()); + } + + // Expect metric to be logged if the user manually edited a form field. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromLoadWithAutofill(_)).Times(0); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromLoadWithoutAutofill( + TimeDelta::FromInternalValue(16))); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromInteractionWithAutofill(_)).Times(0); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromInteractionWithoutAutofill( + TimeDelta::FromInternalValue(14))); + autofill_manager_->OnFormsSeen(forms, TimeTicks::FromInternalValue(1)); + autofill_manager_->OnTextFieldDidChange(form, form.fields.front(), + TimeTicks::FromInternalValue(3)); + autofill_manager_->FormSubmitted(form, TimeTicks::FromInternalValue(17)); + autofill_manager_->Reset(); + Mock::VerifyAndClearExpectations(autofill_manager_->metric_logger()); + } + + // Expect metric to be logged if the user autofilled the form. + form.fields[0].is_autofilled = true; + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromLoadWithAutofill( + TimeDelta::FromInternalValue(16))); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromLoadWithoutAutofill(_)).Times(0); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromInteractionWithAutofill( + TimeDelta::FromInternalValue(12))); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromInteractionWithoutAutofill(_)).Times(0); + autofill_manager_->OnFormsSeen(forms, TimeTicks::FromInternalValue(1)); + autofill_manager_->OnDidFillAutofillFormData( + TimeTicks::FromInternalValue(5)); + autofill_manager_->FormSubmitted(form, TimeTicks::FromInternalValue(17)); + autofill_manager_->Reset(); + Mock::VerifyAndClearExpectations(autofill_manager_->metric_logger()); + } + + // Expect metric to be logged if the user both manually filled some fields + // and autofilled others. Messages can arrive out of order, so make sure they + // take precedence appropriately. + { + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromLoadWithAutofill( + TimeDelta::FromInternalValue(16))); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromLoadWithoutAutofill(_)).Times(0); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromInteractionWithAutofill( + TimeDelta::FromInternalValue(14))); + EXPECT_CALL(*autofill_manager_->metric_logger(), + LogFormFillDurationFromInteractionWithoutAutofill(_)).Times(0); + autofill_manager_->OnFormsSeen(forms, TimeTicks::FromInternalValue(1)); + autofill_manager_->OnDidFillAutofillFormData( + TimeTicks::FromInternalValue(5)); + autofill_manager_->OnTextFieldDidChange(form, form.fields.front(), + TimeTicks::FromInternalValue(3)); + autofill_manager_->FormSubmitted(form, TimeTicks::FromInternalValue(17)); + autofill_manager_->Reset(); + Mock::VerifyAndClearExpectations(autofill_manager_->metric_logger()); + } +} diff --git a/components/autofill/browser/autofill_popup_delegate.h b/components/autofill/browser/autofill_popup_delegate.h new file mode 100644 index 0000000..ad1c990 --- /dev/null +++ b/components/autofill/browser/autofill_popup_delegate.h @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_POPUP_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_POPUP_DELEGATE_H_ + +#include "base/string16.h" + +namespace content { +class KeyboardListener; +} + +// An interface for interaction with AutofillPopupController. Will be notified +// of events by the controller. +class AutofillPopupDelegate { + public: + // Called when the Autofill popup is shown. |listener| may be used to pass + // keyboard events to the popup. + virtual void OnPopupShown(content::KeyboardListener* listener) = 0; + + // Called when the Autofill popup is hidden. |listener| must be unregistered + // if it was registered in OnPopupShown. + virtual void OnPopupHidden(content::KeyboardListener* listener) = 0; + + // Called when the autofill suggestion indicated by |identifier| has been + // temporarily selected (e.g., hovered). + virtual void DidSelectSuggestion(int identifier) = 0; + + // Inform the delegate that a row in the popup has been chosen. + virtual void DidAcceptSuggestion(const string16& value, int identifier) = 0; + + // Delete the described suggestion. + virtual void RemoveSuggestion(const string16& value, int identifier) = 0; + + // Informs the delegate that the Autofill previewed form should be cleared. + virtual void ClearPreviewedForm() = 0; +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_POPUP_DELEGATE_H_ diff --git a/components/autofill/browser/autofill_profile.cc b/components/autofill/browser/autofill_profile.cc new file mode 100644 index 0000000..f793fa4 --- /dev/null +++ b/components/autofill/browser/autofill_profile.cc @@ -0,0 +1,848 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_profile.h" + +#include <algorithm> +#include <functional> +#include <map> +#include <ostream> +#include <set> + +#include "base/basictypes.h" +#include "base/guid.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/address.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/contact_info.h" +#include "components/autofill/browser/phone_number.h" +#include "components/autofill/browser/phone_number_i18n.h" +#include "components/autofill/common/form_field_data.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +// Like |AutofillType::GetEquivalentFieldType()|, but also returns |NAME_FULL| +// for first, middle, and last name field types. +AutofillFieldType GetEquivalentFieldTypeCollapsingNames( + AutofillFieldType field_type) { + if (field_type == NAME_FIRST || field_type == NAME_MIDDLE || + field_type == NAME_LAST || field_type == NAME_MIDDLE_INITIAL) + return NAME_FULL; + + return AutofillType::GetEquivalentFieldType(field_type); +} + +// Fills |distinguishing_fields| with a list of fields to use when creating +// labels that can help to distinguish between two profiles. Draws fields from +// |suggested_fields| if it is non-NULL; otherwise returns a default list. +// If |suggested_fields| is non-NULL, does not include |excluded_field| in the +// list. Otherwise, |excluded_field| is ignored, and should be set to +// |UNKNOWN_TYPE| by convention. The resulting list of fields is sorted in +// decreasing order of importance. +void GetFieldsForDistinguishingProfiles( + const std::vector<AutofillFieldType>* suggested_fields, + AutofillFieldType excluded_field, + std::vector<AutofillFieldType>* distinguishing_fields) { + static const AutofillFieldType kDefaultDistinguishingFields[] = { + NAME_FULL, + ADDRESS_HOME_LINE1, + ADDRESS_HOME_LINE2, + ADDRESS_HOME_CITY, + ADDRESS_HOME_STATE, + ADDRESS_HOME_ZIP, + ADDRESS_HOME_COUNTRY, + EMAIL_ADDRESS, + PHONE_HOME_WHOLE_NUMBER, + COMPANY_NAME, + }; + + if (!suggested_fields) { + DCHECK_EQ(excluded_field, UNKNOWN_TYPE); + distinguishing_fields->assign( + kDefaultDistinguishingFields, + kDefaultDistinguishingFields + arraysize(kDefaultDistinguishingFields)); + return; + } + + // Keep track of which fields we've seen so that we avoid duplicate entries. + // Always ignore fields of unknown type and the excluded field. + std::set<AutofillFieldType> seen_fields; + seen_fields.insert(UNKNOWN_TYPE); + seen_fields.insert(GetEquivalentFieldTypeCollapsingNames(excluded_field)); + + distinguishing_fields->clear(); + for (std::vector<AutofillFieldType>::const_iterator it = + suggested_fields->begin(); + it != suggested_fields->end(); ++it) { + AutofillFieldType suggested_type = + GetEquivalentFieldTypeCollapsingNames(*it); + if (seen_fields.insert(suggested_type).second) + distinguishing_fields->push_back(suggested_type); + } + + // Special case: If the excluded field is a partial name (e.g. first name) and + // the suggested fields include other name fields, include |NAME_FULL| in the + // list of distinguishing fields as a last-ditch fallback. This allows us to + // distinguish between profiles that are identical except for the name. + if (excluded_field != NAME_FULL && + GetEquivalentFieldTypeCollapsingNames(excluded_field) == NAME_FULL) { + for (std::vector<AutofillFieldType>::const_iterator it = + suggested_fields->begin(); + it != suggested_fields->end(); ++it) { + if (*it != excluded_field && + GetEquivalentFieldTypeCollapsingNames(*it) == NAME_FULL) { + distinguishing_fields->push_back(NAME_FULL); + break; + } + } + } +} + +// A helper function for string streaming. Concatenates multi-valued entries +// stored for a given |type| into a single string. This string is returned. +const string16 MultiString(const AutofillProfile& p, AutofillFieldType type) { + std::vector<string16> values; + p.GetRawMultiInfo(type, &values); + string16 accumulate; + for (size_t i = 0; i < values.size(); ++i) { + if (i > 0) + accumulate += ASCIIToUTF16(" "); + accumulate += values[i]; + } + return accumulate; +} + +string16 GetFormGroupInfo(const FormGroup& form_group, + AutofillFieldType type, + const std::string& app_locale) { + return app_locale.empty() ? + form_group.GetRawInfo(type) : + form_group.GetInfo(type, app_locale); +} + +template <class T> +void CopyValuesToItems(AutofillFieldType type, + const std::vector<string16>& values, + std::vector<T>* form_group_items, + const T& prototype) { + form_group_items->resize(values.size(), prototype); + for (size_t i = 0; i < form_group_items->size(); ++i) { + (*form_group_items)[i].SetRawInfo(type, + CollapseWhitespace(values[i], false)); + } + // Must have at least one (possibly empty) element. + if (form_group_items->empty()) + form_group_items->resize(1, prototype); +} + +template <class T> +void CopyItemsToValues(AutofillFieldType type, + const std::vector<T>& form_group_items, + const std::string& app_locale, + std::vector<string16>* values) { + values->resize(form_group_items.size()); + for (size_t i = 0; i < values->size(); ++i) { + (*values)[i] = GetFormGroupInfo(form_group_items[i], type, app_locale); + } +} + +// Collapse compound field types to their "full" type. I.e. First name +// collapses to full name, area code collapses to full phone, etc. +void CollapseCompoundFieldTypes(FieldTypeSet* type_set) { + FieldTypeSet collapsed_set; + for (FieldTypeSet::iterator iter = type_set->begin(); iter != type_set->end(); + ++iter) { + switch (*iter) { + case NAME_FIRST: + case NAME_MIDDLE: + case NAME_LAST: + case NAME_MIDDLE_INITIAL: + case NAME_FULL: + case NAME_SUFFIX: + collapsed_set.insert(NAME_FULL); + break; + + case PHONE_HOME_NUMBER: + case PHONE_HOME_CITY_CODE: + case PHONE_HOME_COUNTRY_CODE: + case PHONE_HOME_CITY_AND_NUMBER: + case PHONE_HOME_WHOLE_NUMBER: + collapsed_set.insert(PHONE_HOME_WHOLE_NUMBER); + break; + + default: + collapsed_set.insert(*iter); + } + } + std::swap(*type_set, collapsed_set); +} + +class FindByPhone { + public: + FindByPhone(const string16& phone, const std::string& country_code) + : phone_(phone), + country_code_(country_code) { + } + + bool operator()(const string16& phone) { + return autofill_i18n::PhoneNumbersMatch(phone, phone_, country_code_); + } + + bool operator()(const string16* phone) { + return autofill_i18n::PhoneNumbersMatch(*phone, phone_, country_code_); + } + + private: + string16 phone_; + std::string country_code_; +}; + +// Functor used to check for case-insensitive equality of two strings. +struct CaseInsensitiveStringEquals + : public std::binary_function<string16, string16, bool> +{ + bool operator()(const string16& x, const string16& y) const { + return + x.size() == y.size() && StringToLowerASCII(x) == StringToLowerASCII(y); + } +}; + +} // namespace + +AutofillProfile::AutofillProfile(const std::string& guid) + : guid_(guid), + name_(1), + email_(1), + home_number_(1, PhoneNumber(this)) { +} + +AutofillProfile::AutofillProfile() + : guid_(base::GenerateGUID()), + name_(1), + email_(1), + home_number_(1, PhoneNumber(this)) { +} + +AutofillProfile::AutofillProfile(const AutofillProfile& profile) + : FormGroup() { + operator=(profile); +} + +AutofillProfile::~AutofillProfile() { +} + +AutofillProfile& AutofillProfile::operator=(const AutofillProfile& profile) { + if (this == &profile) + return *this; + + label_ = profile.label_; + guid_ = profile.guid_; + + name_ = profile.name_; + email_ = profile.email_; + company_ = profile.company_; + home_number_ = profile.home_number_; + + for (size_t i = 0; i < home_number_.size(); ++i) + home_number_[i].set_profile(this); + + address_ = profile.address_; + + return *this; +} + +std::string AutofillProfile::GetGUID() const { + return guid(); +} + +void AutofillProfile::GetMatchingTypes(const string16& text, + const std::string& app_locale, + FieldTypeSet* matching_types) const { + FormGroupList info = FormGroups(); + for (FormGroupList::const_iterator it = info.begin(); it != info.end(); ++it) + (*it)->GetMatchingTypes(text, app_locale, matching_types); +} + +string16 AutofillProfile::GetRawInfo(AutofillFieldType type) const { + AutofillFieldType return_type = AutofillType::GetEquivalentFieldType(type); + const FormGroup* form_group = FormGroupForType(return_type); + if (!form_group) + return string16(); + + return form_group->GetRawInfo(return_type); +} + +void AutofillProfile::SetRawInfo(AutofillFieldType type, + const string16& value) { + FormGroup* form_group = MutableFormGroupForType(type); + if (form_group) + form_group->SetRawInfo(type, CollapseWhitespace(value, false)); +} + +string16 AutofillProfile::GetInfo(AutofillFieldType type, + const std::string& app_locale) const { + AutofillFieldType return_type = AutofillType::GetEquivalentFieldType(type); + const FormGroup* form_group = FormGroupForType(return_type); + if (!form_group) + return string16(); + + return form_group->GetInfo(return_type, app_locale); +} + +bool AutofillProfile::SetInfo(AutofillFieldType type, + const string16& value, + const std::string& app_locale) { + FormGroup* form_group = MutableFormGroupForType(type); + if (!form_group) + return false; + + return + form_group->SetInfo(type, CollapseWhitespace(value, false), app_locale); +} + +void AutofillProfile::SetRawMultiInfo(AutofillFieldType type, + const std::vector<string16>& values) { + switch (AutofillType(type).group()) { + case AutofillType::NAME: + CopyValuesToItems(type, values, &name_, NameInfo()); + break; + case AutofillType::EMAIL: + CopyValuesToItems(type, values, &email_, EmailInfo()); + break; + case AutofillType::PHONE: + CopyValuesToItems(type, + values, + &home_number_, + PhoneNumber(this)); + break; + default: + if (values.size() == 1) { + SetRawInfo(type, values[0]); + } else if (values.size() == 0) { + SetRawInfo(type, string16()); + } else { + // Shouldn't attempt to set multiple values on single-valued field. + NOTREACHED(); + } + break; + } +} + +void AutofillProfile::GetRawMultiInfo(AutofillFieldType type, + std::vector<string16>* values) const { + GetMultiInfoImpl(type, std::string(), values); +} + +void AutofillProfile::GetMultiInfo(AutofillFieldType type, + const std::string& app_locale, + std::vector<string16>* values) const { + GetMultiInfoImpl(type, app_locale, values); +} + +void AutofillProfile::FillFormField(const AutofillField& field, + size_t variant, + FormFieldData* field_data) const { + AutofillFieldType type = field.type(); + DCHECK_NE(AutofillType::CREDIT_CARD, AutofillType(type).group()); + DCHECK(field_data); + + if (type == PHONE_HOME_NUMBER) { + FillPhoneNumberField(field, variant, field_data); + } else if (field_data->form_control_type == "select-one") { + FillSelectControl(type, field_data); + } else { + std::vector<string16> values; + GetMultiInfo(type, AutofillCountry::ApplicationLocale(), &values); + if (variant >= values.size()) { + // If the variant is unavailable, bail. This case is reachable, for + // example if Sync updates a profile during the filling process. + return; + } + + field_data->value = values[variant]; + } +} + +void AutofillProfile::FillPhoneNumberField(const AutofillField& field, + size_t variant, + FormFieldData* field_data) const { + std::vector<string16> values; + GetMultiInfo(field.type(), AutofillCountry::ApplicationLocale(), &values); + DCHECK(variant < values.size()); + + // If we are filling a phone number, check to see if the size field + // matches the "prefix" or "suffix" sizes and fill accordingly. + string16 number = values[variant]; + if (number.length() == + PhoneNumber::kPrefixLength + PhoneNumber::kSuffixLength) { + if (field.phone_part() == AutofillField::PHONE_PREFIX || + field_data->max_length == PhoneNumber::kPrefixLength) { + number = number.substr(PhoneNumber::kPrefixOffset, + PhoneNumber::kPrefixLength); + } else if (field.phone_part() == AutofillField::PHONE_SUFFIX || + field_data->max_length == PhoneNumber::kSuffixLength) { + number = number.substr(PhoneNumber::kSuffixOffset, + PhoneNumber::kSuffixLength); + } + } + + field_data->value = number; +} + +const string16 AutofillProfile::Label() const { + return label_; +} + +const std::string AutofillProfile::CountryCode() const { + return address_.country_code(); +} + +void AutofillProfile::SetCountryCode(const std::string& country_code) { + address_.set_country_code(country_code); +} + +bool AutofillProfile::IsEmpty() const { + FieldTypeSet types; + GetNonEmptyTypes(AutofillCountry::ApplicationLocale(), &types); + return types.empty(); +} + +int AutofillProfile::Compare(const AutofillProfile& profile) const { + const AutofillFieldType single_value_types[] = { COMPANY_NAME, + ADDRESS_HOME_LINE1, + ADDRESS_HOME_LINE2, + ADDRESS_HOME_CITY, + ADDRESS_HOME_STATE, + ADDRESS_HOME_ZIP, + ADDRESS_HOME_COUNTRY }; + + for (size_t i = 0; i < arraysize(single_value_types); ++i) { + int comparison = GetRawInfo(single_value_types[i]).compare( + profile.GetRawInfo(single_value_types[i])); + if (comparison != 0) + return comparison; + } + + const AutofillFieldType multi_value_types[] = { NAME_FIRST, + NAME_MIDDLE, + NAME_LAST, + EMAIL_ADDRESS, + PHONE_HOME_WHOLE_NUMBER }; + + for (size_t i = 0; i < arraysize(multi_value_types); ++i) { + std::vector<string16> values_a; + std::vector<string16> values_b; + GetRawMultiInfo(multi_value_types[i], &values_a); + profile.GetRawMultiInfo(multi_value_types[i], &values_b); + if (values_a.size() < values_b.size()) + return -1; + if (values_a.size() > values_b.size()) + return 1; + for (size_t j = 0; j < values_a.size(); ++j) { + int comparison = values_a[j].compare(values_b[j]); + if (comparison != 0) + return comparison; + } + } + + return 0; +} + +bool AutofillProfile::operator==(const AutofillProfile& profile) const { + return guid_ == profile.guid_ && Compare(profile) == 0; +} + +bool AutofillProfile::operator!=(const AutofillProfile& profile) const { + return !operator==(profile); +} + +const string16 AutofillProfile::PrimaryValue() const { + return GetRawInfo(ADDRESS_HOME_LINE1) + GetRawInfo(ADDRESS_HOME_CITY); +} + +bool AutofillProfile::IsSubsetOf(const AutofillProfile& profile) const { + FieldTypeSet types; + GetNonEmptyTypes(AutofillCountry::ApplicationLocale(), &types); + + for (FieldTypeSet::const_iterator iter = types.begin(); iter != types.end(); + ++iter) { + if (*iter == NAME_FULL) { + // Ignore the compound "full name" field type. We are only interested in + // comparing the constituent parts. For example, if |this| has a middle + // name saved, but |profile| lacks one, |profile| could still be a subset + // of |this|. + continue; + } else if (AutofillType(*iter).group() == AutofillType::PHONE) { + // Phone numbers should be canonicalized prior to being compared. + if (*iter != PHONE_HOME_WHOLE_NUMBER) { + continue; + } else if (!autofill_i18n::PhoneNumbersMatch(GetRawInfo(*iter), + profile.GetRawInfo(*iter), + CountryCode())) { + return false; + } + } else if (StringToLowerASCII(GetRawInfo(*iter)) != + StringToLowerASCII(profile.GetRawInfo(*iter))) { + return false; + } + } + + return true; +} + +void AutofillProfile::OverwriteWithOrAddTo(const AutofillProfile& profile) { + FieldTypeSet field_types; + profile.GetNonEmptyTypes(AutofillCountry::ApplicationLocale(), &field_types); + + // Only transfer "full" types (e.g. full name) and not fragments (e.g. + // first name, last name). + CollapseCompoundFieldTypes(&field_types); + + for (FieldTypeSet::const_iterator iter = field_types.begin(); + iter != field_types.end(); ++iter) { + if (AutofillProfile::SupportsMultiValue(*iter)) { + std::vector<string16> new_values; + profile.GetRawMultiInfo(*iter, &new_values); + std::vector<string16> existing_values; + GetRawMultiInfo(*iter, &existing_values); + + // GetMultiInfo always returns at least one element, even if the profile + // has no data stored for this field type. + if (existing_values.size() == 1 && existing_values.front().empty()) + existing_values.clear(); + + FieldTypeGroup group = AutofillType(*iter).group(); + for (std::vector<string16>::iterator value_iter = new_values.begin(); + value_iter != new_values.end(); ++value_iter) { + // Don't add duplicates. + if (group == AutofillType::PHONE) { + AddPhoneIfUnique(*value_iter, &existing_values); + } else { + std::vector<string16>::const_iterator existing_iter = std::find_if( + existing_values.begin(), existing_values.end(), + std::bind1st(CaseInsensitiveStringEquals(), *value_iter)); + if (existing_iter == existing_values.end()) + existing_values.insert(existing_values.end(), *value_iter); + } + } + SetRawMultiInfo(*iter, existing_values); + } else { + string16 new_value = profile.GetRawInfo(*iter); + if (StringToLowerASCII(GetRawInfo(*iter)) != + StringToLowerASCII(new_value)) { + SetRawInfo(*iter, new_value); + } + } + } +} + +// static +bool AutofillProfile::SupportsMultiValue(AutofillFieldType type) { + AutofillType::FieldTypeGroup group = AutofillType(type).group(); + return group == AutofillType::NAME || + group == AutofillType::EMAIL || + group == AutofillType::PHONE; +} + +// static +bool AutofillProfile::AdjustInferredLabels( + std::vector<AutofillProfile*>* profiles) { + const size_t kMinimalFieldsShown = 2; + + std::vector<string16> created_labels; + CreateInferredLabels(profiles, NULL, UNKNOWN_TYPE, kMinimalFieldsShown, + &created_labels); + DCHECK_EQ(profiles->size(), created_labels.size()); + + bool updated_labels = false; + for (size_t i = 0; i < profiles->size(); ++i) { + if ((*profiles)[i]->Label() != created_labels[i]) { + updated_labels = true; + (*profiles)[i]->label_ = created_labels[i]; + } + } + return updated_labels; +} + +// static +void AutofillProfile::CreateInferredLabels( + const std::vector<AutofillProfile*>* profiles, + const std::vector<AutofillFieldType>* suggested_fields, + AutofillFieldType excluded_field, + size_t minimal_fields_shown, + std::vector<string16>* created_labels) { + DCHECK(profiles); + DCHECK(created_labels); + + std::vector<AutofillFieldType> fields_to_use; + GetFieldsForDistinguishingProfiles(suggested_fields, excluded_field, + &fields_to_use); + + // Construct the default label for each profile. Also construct a map that + // associates each label with the profiles that have this label. This map is + // then used to detect which labels need further differentiating fields. + std::map<string16, std::list<size_t> > labels; + for (size_t i = 0; i < profiles->size(); ++i) { + string16 label = + (*profiles)[i]->ConstructInferredLabel(fields_to_use, + minimal_fields_shown); + labels[label].push_back(i); + } + + created_labels->resize(profiles->size()); + for (std::map<string16, std::list<size_t> >::const_iterator it = + labels.begin(); + it != labels.end(); ++it) { + if (it->second.size() == 1) { + // This label is unique, so use it without any further ado. + string16 label = it->first; + size_t profile_index = it->second.front(); + (*created_labels)[profile_index] = label; + } else { + // We have more than one profile with the same label, so add + // differentiating fields. + CreateDifferentiatingLabels(*profiles, it->second, fields_to_use, + minimal_fields_shown, created_labels); + } + } +} + +void AutofillProfile::GetSupportedTypes(FieldTypeSet* supported_types) const { + FormGroupList info = FormGroups(); + for (FormGroupList::const_iterator it = info.begin(); it != info.end(); ++it) + (*it)->GetSupportedTypes(supported_types); +} + +bool AutofillProfile::FillCountrySelectControl(FormFieldData* field_data) + const { + std::string country_code = CountryCode(); + std::string app_locale = AutofillCountry::ApplicationLocale(); + + DCHECK_EQ(field_data->option_values.size(), + field_data->option_contents.size()); + for (size_t i = 0; i < field_data->option_values.size(); ++i) { + // Canonicalize each <option> value to a country code, and compare to the + // target country code. + string16 value = field_data->option_values[i]; + string16 contents = field_data->option_contents[i]; + if (country_code == AutofillCountry::GetCountryCode(value, app_locale) || + country_code == AutofillCountry::GetCountryCode(contents, app_locale)) { + field_data->value = value; + return true; + } + } + + return false; +} + +void AutofillProfile::GetMultiInfoImpl(AutofillFieldType type, + const std::string& app_locale, + std::vector<string16>* values) const { + switch (AutofillType(type).group()) { + case AutofillType::NAME: + CopyItemsToValues(type, name_, app_locale, values); + break; + case AutofillType::EMAIL: + CopyItemsToValues(type, email_, app_locale, values); + break; + case AutofillType::PHONE: + CopyItemsToValues(type, home_number_, app_locale, values); + break; + default: + values->resize(1); + (*values)[0] = GetFormGroupInfo(*this, type, app_locale); + } +} + +void AutofillProfile::AddPhoneIfUnique(const string16& phone, + std::vector<string16>* existing_phones) { + DCHECK(existing_phones); + // Phones allow "fuzzy" matching, so "1-800-FLOWERS", "18003569377", + // "(800)356-9377" and "356-9377" are considered the same. + if (std::find_if(existing_phones->begin(), existing_phones->end(), + FindByPhone(phone, CountryCode())) == + existing_phones->end()) { + existing_phones->push_back(phone); + } +} + +string16 AutofillProfile::ConstructInferredLabel( + const std::vector<AutofillFieldType>& included_fields, + size_t num_fields_to_use) const { + const string16 separator = + l10n_util::GetStringUTF16(IDS_AUTOFILL_ADDRESS_SUMMARY_SEPARATOR); + + string16 label; + size_t num_fields_used = 0; + for (std::vector<AutofillFieldType>::const_iterator it = + included_fields.begin(); + it != included_fields.end() && num_fields_used < num_fields_to_use; + ++it) { + string16 field = GetRawInfo(*it); + if (field.empty()) + continue; + + if (!label.empty()) + label.append(separator); + + label.append(field); + ++num_fields_used; + } + return label; +} + +// static +void AutofillProfile::CreateDifferentiatingLabels( + const std::vector<AutofillProfile*>& profiles, + const std::list<size_t>& indices, + const std::vector<AutofillFieldType>& fields, + size_t num_fields_to_include, + std::vector<string16>* created_labels) { + // For efficiency, we first construct a map of fields to their text values and + // each value's frequency. + std::map<AutofillFieldType, + std::map<string16, size_t> > field_text_frequencies_by_field; + for (std::vector<AutofillFieldType>::const_iterator field = fields.begin(); + field != fields.end(); ++field) { + std::map<string16, size_t>& field_text_frequencies = + field_text_frequencies_by_field[*field]; + + for (std::list<size_t>::const_iterator it = indices.begin(); + it != indices.end(); ++it) { + const AutofillProfile* profile = profiles[*it]; + string16 field_text = profile->GetRawInfo(*field); + + // If this label is not already in the map, add it with frequency 0. + if (!field_text_frequencies.count(field_text)) + field_text_frequencies[field_text] = 0; + + // Now, increment the frequency for this label. + ++field_text_frequencies[field_text]; + } + } + + // Now comes the meat of the algorithm. For each profile, we scan the list of + // fields to use, looking for two things: + // 1. A (non-empty) field that differentiates the profile from all others + // 2. At least |num_fields_to_include| non-empty fields + // Before we've satisfied condition (2), we include all fields, even ones that + // are identical across all the profiles. Once we've satisfied condition (2), + // we only include fields that that have at last two distinct values. + for (std::list<size_t>::const_iterator it = indices.begin(); + it != indices.end(); ++it) { + const AutofillProfile* profile = profiles[*it]; + + std::vector<AutofillFieldType> label_fields; + bool found_differentiating_field = false; + for (std::vector<AutofillFieldType>::const_iterator field = fields.begin(); + field != fields.end(); ++field) { + // Skip over empty fields. + string16 field_text = profile->GetRawInfo(*field); + if (field_text.empty()) + continue; + + std::map<string16, size_t>& field_text_frequencies = + field_text_frequencies_by_field[*field]; + found_differentiating_field |= + !field_text_frequencies.count(string16()) && + (field_text_frequencies[field_text] == 1); + + // Once we've found enough non-empty fields, skip over any remaining + // fields that are identical across all the profiles. + if (label_fields.size() >= num_fields_to_include && + (field_text_frequencies.size() == 1)) + continue; + + label_fields.push_back(*field); + + // If we've (1) found a differentiating field and (2) found at least + // |num_fields_to_include| non-empty fields, we're done! + if (found_differentiating_field && + label_fields.size() >= num_fields_to_include) + break; + } + + (*created_labels)[*it] = + profile->ConstructInferredLabel(label_fields, + label_fields.size()); + } +} + +AutofillProfile::FormGroupList AutofillProfile::FormGroups() const { + FormGroupList v(5); + v[0] = &name_[0]; + v[1] = &email_[0]; + v[2] = &company_; + v[3] = &home_number_[0]; + v[4] = &address_; + return v; +} + +const FormGroup* AutofillProfile::FormGroupForType( + AutofillFieldType type) const { + return const_cast<AutofillProfile*>(this)->MutableFormGroupForType(type); +} + +FormGroup* AutofillProfile::MutableFormGroupForType(AutofillFieldType type) { + FormGroup* form_group = NULL; + switch (AutofillType(type).group()) { + case AutofillType::NAME: + form_group = &name_[0]; + break; + case AutofillType::EMAIL: + form_group = &email_[0]; + break; + case AutofillType::COMPANY: + form_group = &company_; + break; + case AutofillType::PHONE: + form_group = &home_number_[0]; + break; + case AutofillType::ADDRESS_HOME: + form_group = &address_; + break; + default: + break; + } + + return form_group; +} + +// So we can compare AutofillProfiles with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const AutofillProfile& profile) { + return os + << UTF16ToUTF8(profile.Label()) + << " " + << profile.guid() + << " " + << UTF16ToUTF8(MultiString(profile, NAME_FIRST)) + << " " + << UTF16ToUTF8(MultiString(profile, NAME_MIDDLE)) + << " " + << UTF16ToUTF8(MultiString(profile, NAME_LAST)) + << " " + << UTF16ToUTF8(MultiString(profile, EMAIL_ADDRESS)) + << " " + << UTF16ToUTF8(profile.GetRawInfo(COMPANY_NAME)) + << " " + << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_LINE1)) + << " " + << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_LINE2)) + << " " + << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_CITY)) + << " " + << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_STATE)) + << " " + << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_ZIP)) + << " " + << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_COUNTRY)) + << " " + << UTF16ToUTF8(MultiString(profile, PHONE_HOME_WHOLE_NUMBER)); +} diff --git a/components/autofill/browser/autofill_profile.h b/components/autofill/browser/autofill_profile.h new file mode 100644 index 0000000..8dbf63a --- /dev/null +++ b/components/autofill/browser/autofill_profile.h @@ -0,0 +1,211 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_PROFILE_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_PROFILE_H_ + +#include <stddef.h> + +#include <iosfwd> +#include <list> +#include <string> +#include <vector> + +#include "base/string16.h" +#include "components/autofill/browser/address.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/contact_info.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/form_group.h" +#include "components/autofill/browser/phone_number.h" + +struct FormFieldData; + +// A collection of FormGroups stored in a profile. AutofillProfile also +// implements the FormGroup interface so that owners of this object can request +// form information from the profile, and the profile will delegate the request +// to the requested form group type. +class AutofillProfile : public FormGroup { + public: + explicit AutofillProfile(const std::string& guid); + + // For use in STL containers. + AutofillProfile(); + AutofillProfile(const AutofillProfile& profile); + virtual ~AutofillProfile(); + + AutofillProfile& operator=(const AutofillProfile& profile); + + // FormGroup: + virtual std::string GetGUID() const OVERRIDE; + virtual void GetMatchingTypes(const string16& text, + const std::string& app_locale, + FieldTypeSet* matching_types) const OVERRIDE; + virtual string16 GetRawInfo(AutofillFieldType type) const OVERRIDE; + virtual void SetRawInfo(AutofillFieldType type, + const string16& value) OVERRIDE; + virtual string16 GetInfo(AutofillFieldType type, + const std::string& app_locale) const OVERRIDE; + virtual bool SetInfo(AutofillFieldType type, + const string16& value, + const std::string& app_locale) OVERRIDE; + virtual void FillFormField(const AutofillField& field, + size_t variant, + FormFieldData* field_data) const OVERRIDE; + + // Multi-value equivalents to |GetInfo| and |SetInfo|. + void SetRawMultiInfo(AutofillFieldType type, + const std::vector<string16>& values); + void GetRawMultiInfo(AutofillFieldType type, + std::vector<string16>* values) const; + void GetMultiInfo(AutofillFieldType type, + const std::string& app_locale, + std::vector<string16>* values) const; + + // Set |field_data|'s value for phone number based on contents of |this|. + // The |field| specifies the type of the phone and whether this is a + // phone prefix or suffix. The |variant| parameter specifies which value in a + // multi-valued profile. + void FillPhoneNumberField(const AutofillField& field, + size_t variant, + FormFieldData* field_data) const; + + // The user-visible label of the profile, generated in relation to other + // profiles. Shows at least 2 fields that differentiate profile from other + // profiles. See AdjustInferredLabels() further down for more description. + const string16 Label() const; + + // This guid is the primary identifier for |AutofillProfile| objects. + // TODO(estade): remove this and just use GetGUID(). |guid_| can probably + // be moved to FormGroup. + const std::string guid() const { return guid_; } + void set_guid(const std::string& guid) { guid_ = guid; } + + // Accessors for the stored address's country code. + const std::string CountryCode() const; + void SetCountryCode(const std::string& country_code); + + // Returns true if there are no values (field types) set. + bool IsEmpty() const; + + // Comparison for Sync. Returns 0 if the profile is the same as |this|, + // or < 0, or > 0 if it is different. The implied ordering can be used for + // culling duplicates. The ordering is based on collation order of the + // textual contents of the fields. + // GUIDs are not compared, only the values of the contents themselves. + // Full profile comparision, comparison includes multi-valued fields. + int Compare(const AutofillProfile& profile) const; + + // Equality operators compare GUIDs and the contents in the comparison. + bool operator==(const AutofillProfile& profile) const; + virtual bool operator!=(const AutofillProfile& profile) const; + + // Returns concatenation of full name and address line 1. This acts as the + // basis of comparison for new values that are submitted through forms to + // aid with correct aggregation of new data. + const string16 PrimaryValue() const; + + // Returns true if the data in this AutofillProfile is a subset of the data in + // |profile|. + bool IsSubsetOf(const AutofillProfile& profile) const; + + // Overwrites the single-valued field data in |profile| with this + // Profile. Or, for multi-valued fields append the new values. + void OverwriteWithOrAddTo(const AutofillProfile& profile); + + // Returns |true| if |type| accepts multi-values. + static bool SupportsMultiValue(AutofillFieldType type); + + // Adjusts the labels according to profile data. + // Labels contain minimal different combination of: + // 1. Full name. + // 2. Address. + // 3. E-mail. + // 4. Phone. + // 5. Company name. + // Profile labels are changed accordingly to these rules. + // Returns true if any of the profiles were updated. + // This function is useful if you want to adjust unique labels for all + // profiles. For non permanent situations (selection of profile, when user + // started typing in the field, for example) use CreateInferredLabels(). + static bool AdjustInferredLabels(std::vector<AutofillProfile*>* profiles); + + // Creates inferred labels for |profiles|, according to the rules above and + // stores them in |created_labels|. If |suggested_fields| is not NULL, the + // resulting label fields are drawn from |suggested_fields|, except excluding + // |excluded_field|. Otherwise, the label fields are drawn from a default set, + // and |excluded_field| is ignored; by convention, it should be of + // |UNKNOWN_TYPE| when |suggested_fields| is NULL. Each label includes at + // least |minimal_fields_shown| fields, if possible. + static void CreateInferredLabels( + const std::vector<AutofillProfile*>* profiles, + const std::vector<AutofillFieldType>* suggested_fields, + AutofillFieldType excluded_field, + size_t minimal_fields_shown, + std::vector<string16>* created_labels); + + private: + typedef std::vector<const FormGroup*> FormGroupList; + + // FormGroup: + virtual bool FillCountrySelectControl(FormFieldData* field) const OVERRIDE; + virtual void GetSupportedTypes(FieldTypeSet* supported_types) const OVERRIDE; + + // Shared implementation for GetRawMultiInfo() and GetMultiInfo(). Pass an + // empty |app_locale| to get the raw info; otherwise, the returned info is + // canonicalized according to the given |app_locale|, if appropriate. + void GetMultiInfoImpl(AutofillFieldType type, + const std::string& app_locale, + std::vector<string16>* values) const; + + // Checks if the |phone| is in the |existing_phones| using fuzzy matching: + // for example, "1-800-FLOWERS", "18003569377", "(800)356-9377" and "356-9377" + // are considered the same. + // Adds the |phone| to the |existing_phones| if not already there. + void AddPhoneIfUnique(const string16& phone, + std::vector<string16>* existing_phones); + + // Builds inferred label from the first |num_fields_to_include| non-empty + // fields in |label_fields|. Uses as many fields as possible if there are not + // enough non-empty fields. + string16 ConstructInferredLabel( + const std::vector<AutofillFieldType>& label_fields, + size_t num_fields_to_include) const; + + // Creates inferred labels for |profiles| at indices corresponding to + // |indices|, and stores the results to the corresponding elements of + // |created_labels|. These labels include enough fields to differentiate among + // the profiles, if possible; and also at least |num_fields_to_include| + // fields, if possible. The label fields are drawn from |fields|. + static void CreateDifferentiatingLabels( + const std::vector<AutofillProfile*>& profiles, + const std::list<size_t>& indices, + const std::vector<AutofillFieldType>& fields, + size_t num_fields_to_include, + std::vector<string16>* created_labels); + + // Utilities for listing and lookup of the data members that constitute + // user-visible profile information. + FormGroupList FormGroups() const; + const FormGroup* FormGroupForType(AutofillFieldType type) const; + FormGroup* MutableFormGroupForType(AutofillFieldType type); + + // The label presented to the user when selecting a profile. + string16 label_; + + // The guid of this profile. + std::string guid_; + + // Personal information for this profile. + std::vector<NameInfo> name_; + std::vector<EmailInfo> email_; + CompanyInfo company_; + std::vector<PhoneNumber> home_number_; + Address address_; +}; + +// So we can compare AutofillProfiles with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const AutofillProfile& profile); + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_PROFILE_H_ diff --git a/components/autofill/browser/autofill_profile_unittest.cc b/components/autofill/browser/autofill_profile_unittest.cc new file mode 100644 index 0000000..bccb6e6 --- /dev/null +++ b/components/autofill/browser/autofill_profile_unittest.cc @@ -0,0 +1,848 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/guid.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/stl_util.h" +#include "base/string16.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_common_test.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/common/form_field_data.h" +#include "grit/generated_resources.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +bool UpdateProfileLabel(AutofillProfile *profile) { + std::vector<AutofillProfile*> profiles; + profiles.push_back(profile); + return AutofillProfile::AdjustInferredLabels(&profiles); +} + +} // namespace + +// Tests different possibilities for summary string generation. +// Based on existence of first name, last name, and address line 1. +TEST(AutofillProfileTest, PreviewSummaryString) { + // Case 0/null: "" + AutofillProfile profile0; + // Empty profile - nothing to update. + EXPECT_FALSE(UpdateProfileLabel(&profile0)); + string16 summary0 = profile0.Label(); + EXPECT_EQ(string16(), summary0); + + // Case 0a/empty name and address, so the first two fields of the rest of the + // data is used: "Hollywood, CA" + AutofillProfile profile00; + autofill_test::SetProfileInfo(&profile00, "", "", "", + "johnwayne@me.xyz", "Fox", "", "", "Hollywood", "CA", "91601", "US", + "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile00)); + string16 summary00 = profile00.Label(); + EXPECT_EQ(ASCIIToUTF16("Hollywood, CA"), summary00); + + // Case 1: "<address>" without line 2. + AutofillProfile profile1; + autofill_test::SetProfileInfo(&profile1, "", "", "", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "", "Hollywood", "CA", + "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile1)); + string16 summary1 = profile1.Label(); + EXPECT_EQ(ASCIIToUTF16("123 Zoo St., Hollywood"), summary1); + + // Case 1a: "<address>" with line 2. + AutofillProfile profile1a; + autofill_test::SetProfileInfo(&profile1a, "", "", "", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile1a)); + string16 summary1a = profile1a.Label(); + EXPECT_EQ(ASCIIToUTF16("123 Zoo St., unit 5"), summary1a); + + // Case 2: "<lastname>" + AutofillProfile profile2; + autofill_test::SetProfileInfo(&profile2, "", "Mitchell", + "Morrison", "johnwayne@me.xyz", "Fox", "", "", "Hollywood", "CA", + "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile2)); + string16 summary2 = profile2.Label(); + // Summary includes full name, to the maximal extent available. + EXPECT_EQ(ASCIIToUTF16("Mitchell Morrison, Hollywood"), summary2); + + // Case 3: "<lastname>, <address>" + AutofillProfile profile3; + autofill_test::SetProfileInfo(&profile3, "", "Mitchell", + "Morrison", "johnwayne@me.xyz", "Fox", "123 Zoo St.", "", + "Hollywood", "CA", "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile3)); + string16 summary3 = profile3.Label(); + EXPECT_EQ(ASCIIToUTF16("Mitchell Morrison, 123 Zoo St."), summary3); + + // Case 4: "<firstname>" + AutofillProfile profile4; + autofill_test::SetProfileInfo(&profile4, "Marion", "Mitchell", "", + "johnwayne@me.xyz", "Fox", "", "", "Hollywood", "CA", "91601", "US", + "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile4)); + string16 summary4 = profile4.Label(); + EXPECT_EQ(ASCIIToUTF16("Marion Mitchell, Hollywood"), summary4); + + // Case 5: "<firstname>, <address>" + AutofillProfile profile5; + autofill_test::SetProfileInfo(&profile5, "Marion", "Mitchell", "", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile5)); + string16 summary5 = profile5.Label(); + EXPECT_EQ(ASCIIToUTF16("Marion Mitchell, 123 Zoo St."), summary5); + + // Case 6: "<firstname> <lastname>" + AutofillProfile profile6; + autofill_test::SetProfileInfo(&profile6, "Marion", "Mitchell", + "Morrison", "johnwayne@me.xyz", "Fox", "", "", "Hollywood", "CA", + "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile6)); + string16 summary6 = profile6.Label(); + EXPECT_EQ(ASCIIToUTF16("Marion Mitchell Morrison, Hollywood"), + summary6); + + // Case 7: "<firstname> <lastname>, <address>" + AutofillProfile profile7; + autofill_test::SetProfileInfo(&profile7, "Marion", "Mitchell", + "Morrison", "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", + "Hollywood", "CA", "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile7)); + string16 summary7 = profile7.Label(); + EXPECT_EQ(ASCIIToUTF16("Marion Mitchell Morrison, 123 Zoo St."), + summary7); + + // Case 7a: "<firstname> <lastname>, <address>" - same as #7, except for + // e-mail. + AutofillProfile profile7a; + autofill_test::SetProfileInfo(&profile7a, "Marion", "Mitchell", + "Morrison", "marion@me.xyz", "Fox", "123 Zoo St.", "unit 5", + "Hollywood", "CA", "91601", "US", "16505678910"); + std::vector<AutofillProfile*> profiles; + profiles.push_back(&profile7); + profiles.push_back(&profile7a); + EXPECT_TRUE(AutofillProfile::AdjustInferredLabels(&profiles)); + summary7 = profile7.Label(); + string16 summary7a = profile7a.Label(); + EXPECT_EQ(ASCIIToUTF16( + "Marion Mitchell Morrison, 123 Zoo St., johnwayne@me.xyz"), summary7); + EXPECT_EQ(ASCIIToUTF16( + "Marion Mitchell Morrison, 123 Zoo St., marion@me.xyz"), summary7a); +} + +TEST(AutofillProfileTest, AdjustInferredLabels) { + std::vector<AutofillProfile*> profiles; + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo( + profiles[0], + "John", + "", + "Doe", + "johndoe@hades.com", + "Underworld", + "666 Erebus St.", + "", + "Elysium", "CA", + "91111", + "US", + "16502111111"); + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo( + profiles[1], + "Jane", + "", + "Doe", + "janedoe@tertium.com", + "Pluto Inc.", + "123 Letha Shore.", + "", + "Dis", "CA", + "91222", + "US", + "12345678910"); + // As labels are empty they are adjusted the first time. + EXPECT_TRUE(AutofillProfile::AdjustInferredLabels(&profiles)); + // No need to adjust them anymore. + EXPECT_FALSE(AutofillProfile::AdjustInferredLabels(&profiles)); + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St."), + profiles[0]->Label()); + EXPECT_EQ(ASCIIToUTF16("Jane Doe, 123 Letha Shore."), + profiles[1]->Label()); + + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo( + profiles[2], + "John", + "", + "Doe", + "johndoe@tertium.com", + "Underworld", + "666 Erebus St.", + "", + "Elysium", "CA", + "91111", + "US", + "16502111111"); + EXPECT_TRUE(AutofillProfile::AdjustInferredLabels(&profiles)); + + // Profile 0 and 2 inferred label now includes an e-mail. + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., johndoe@hades.com"), + profiles[0]->Label()); + EXPECT_EQ(ASCIIToUTF16("Jane Doe, 123 Letha Shore."), + profiles[1]->Label()); + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., johndoe@tertium.com"), + profiles[2]->Label()); + + delete profiles[2]; + profiles.pop_back(); + + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo( + profiles[2], + "John", + "", + "Doe", + "johndoe@hades.com", + "Underworld", + "666 Erebus St.", + "", + "Elysium", "CO", // State is different + "91111", + "US", + "16502111111"); + + EXPECT_TRUE(AutofillProfile::AdjustInferredLabels(&profiles)); + + // Profile 0 and 2 inferred label now includes a state. + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., CA"), + profiles[0]->Label()); + EXPECT_EQ(ASCIIToUTF16("Jane Doe, 123 Letha Shore."), + profiles[1]->Label()); + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., CO"), + profiles[2]->Label()); + + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo( + profiles[3], + "John", + "", + "Doe", + "johndoe@hades.com", + "Underworld", + "666 Erebus St.", + "", + "Elysium", "CO", // State is different for some. + "91111", + "US", + "16504444444"); // Phone is different for some. + + EXPECT_TRUE(AutofillProfile::AdjustInferredLabels(&profiles)); + + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., CA"), + profiles[0]->Label()); + EXPECT_EQ(ASCIIToUTF16("Jane Doe, 123 Letha Shore."), + profiles[1]->Label()); + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., CO, 16502111111"), + profiles[2]->Label()); + // This one differs from other ones by unique phone, so no need for extra + // information. + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., CO, 16504444444"), + profiles[3]->Label()); + + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo( + profiles[4], + "John", + "", + "Doe", + "johndoe@styx.com", // E-Mail is different for some. + "Underworld", + "666 Erebus St.", + "", + "Elysium", "CO", // State is different for some. + "91111", + "US", + "16504444444"); // Phone is different for some. + + EXPECT_TRUE(AutofillProfile::AdjustInferredLabels(&profiles)); + + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., CA"), + profiles[0]->Label()); + EXPECT_EQ(ASCIIToUTF16("Jane Doe, 123 Letha Shore."), + profiles[1]->Label()); + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., CO, johndoe@hades.com," + " 16502111111"), + profiles[2]->Label()); + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., CO, johndoe@hades.com," + " 16504444444"), + profiles[3]->Label()); + // This one differs from other ones by unique e-mail, so no need for extra + // information. + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., CO, johndoe@styx.com"), + profiles[4]->Label()); + + EXPECT_FALSE(AutofillProfile::AdjustInferredLabels(&profiles)); + + // Clean up. + STLDeleteContainerPointers(profiles.begin(), profiles.end()); +} + +TEST(AutofillProfileTest, CreateInferredLabels) { + std::vector<AutofillProfile*> profiles; + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo(profiles[0], + "John", + "", + "Doe", + "johndoe@hades.com", + "Underworld", + "666 Erebus St.", + "", + "Elysium", "CA", + "91111", + "US", + "16502111111"); + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo(profiles[1], + "Jane", + "", + "Doe", + "janedoe@tertium.com", + "Pluto Inc.", + "123 Letha Shore.", + "", + "Dis", "CA", + "91222", + "US", + "12345678910"); + std::vector<string16> labels; + // Two fields at least - no filter. + AutofillProfile::CreateInferredLabels(&profiles, NULL, UNKNOWN_TYPE, 2, + &labels); + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St."), labels[0]); + EXPECT_EQ(ASCIIToUTF16("Jane Doe, 123 Letha Shore."), labels[1]); + + // Three fields at least - no filter. + AutofillProfile::CreateInferredLabels(&profiles, NULL, UNKNOWN_TYPE, 3, + &labels); + EXPECT_EQ(ASCIIToUTF16("John Doe, 666 Erebus St., Elysium"), + labels[0]); + EXPECT_EQ(ASCIIToUTF16("Jane Doe, 123 Letha Shore., Dis"), + labels[1]); + + std::vector<AutofillFieldType> suggested_fields; + suggested_fields.push_back(ADDRESS_HOME_CITY); + suggested_fields.push_back(ADDRESS_HOME_STATE); + suggested_fields.push_back(ADDRESS_HOME_ZIP); + + // Two fields at least, from suggested fields - no filter. + AutofillProfile::CreateInferredLabels(&profiles, &suggested_fields, + UNKNOWN_TYPE, 2, &labels); + EXPECT_EQ(ASCIIToUTF16("Elysium, CA"), labels[0]); + EXPECT_EQ(ASCIIToUTF16("Dis, CA"), labels[1]); + + // Three fields at least, from suggested fields - no filter. + AutofillProfile::CreateInferredLabels(&profiles, &suggested_fields, + UNKNOWN_TYPE, 3, &labels); + EXPECT_EQ(ASCIIToUTF16("Elysium, CA, 91111"), labels[0]); + EXPECT_EQ(ASCIIToUTF16("Dis, CA, 91222"), labels[1]); + + // Three fields at least, from suggested fields - but filter reduces available + // fields to two. + AutofillProfile::CreateInferredLabels(&profiles, &suggested_fields, + ADDRESS_HOME_STATE, 3, &labels); + EXPECT_EQ(ASCIIToUTF16("Elysium, 91111"), labels[0]); + EXPECT_EQ(ASCIIToUTF16("Dis, 91222"), labels[1]); + + suggested_fields.clear(); + // In our implementation we always display NAME_FULL for all NAME* fields... + suggested_fields.push_back(NAME_MIDDLE); + // One field at least, from suggested fields - no filter. + AutofillProfile::CreateInferredLabels(&profiles, &suggested_fields, + UNKNOWN_TYPE, 1, &labels); + EXPECT_EQ(ASCIIToUTF16("John Doe"), labels[0]); + EXPECT_EQ(ASCIIToUTF16("Jane Doe"), labels[1]); + + // One field at least, from suggested fields - filter the same as suggested + // field. + AutofillProfile::CreateInferredLabels(&profiles, &suggested_fields, + NAME_MIDDLE, 1, &labels); + EXPECT_EQ(string16(), labels[0]); + EXPECT_EQ(string16(), labels[1]); + + suggested_fields.clear(); + // In our implementation we always display NAME_FULL for NAME_MIDDLE_INITIAL + suggested_fields.push_back(NAME_MIDDLE_INITIAL); + // One field at least, from suggested fields - no filter. + AutofillProfile::CreateInferredLabels(&profiles, &suggested_fields, + UNKNOWN_TYPE, 1, &labels); + EXPECT_EQ(ASCIIToUTF16("John Doe"), labels[0]); + EXPECT_EQ(ASCIIToUTF16("Jane Doe"), labels[1]); + + // One field at least, from suggested fields - filter same as the first non- + // unknown suggested field. + suggested_fields.clear(); + suggested_fields.push_back(UNKNOWN_TYPE); + suggested_fields.push_back(NAME_FULL); + suggested_fields.push_back(ADDRESS_HOME_LINE1); + AutofillProfile::CreateInferredLabels(&profiles, &suggested_fields, NAME_FULL, + 1, &labels); + EXPECT_EQ(string16(ASCIIToUTF16("666 Erebus St.")), labels[0]); + EXPECT_EQ(string16(ASCIIToUTF16("123 Letha Shore.")), labels[1]); + + // Clean up. + STLDeleteContainerPointers(profiles.begin(), profiles.end()); +} + +// Test that we fall back to using the full name if there are no other +// distinguishing fields, but only if it makes sense given the suggested fields. +TEST(AutofillProfileTest, CreateInferredLabelsFallsBackToFullName) { + ScopedVector<AutofillProfile> profiles; + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo(profiles[0], + "John", "", "Doe", "doe@example.com", "", + "88 Nowhere Ave.", "", "", "", "", "", ""); + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo(profiles[1], + "Johnny", "K", "Doe", "doe@example.com", "", + "88 Nowhere Ave.", "", "", "", "", "", ""); + + // If the only name field in the suggested fields is the excluded field, we + // should not fall back to the full name as a distinguishing field. + std::vector<AutofillFieldType> suggested_fields; + suggested_fields.push_back(NAME_LAST); + suggested_fields.push_back(ADDRESS_HOME_LINE1); + suggested_fields.push_back(EMAIL_ADDRESS); + std::vector<string16> labels; + AutofillProfile::CreateInferredLabels(&profiles.get(), &suggested_fields, + NAME_LAST, 1, &labels); + ASSERT_EQ(2U, labels.size()); + EXPECT_EQ(ASCIIToUTF16("88 Nowhere Ave."), labels[0]); + EXPECT_EQ(ASCIIToUTF16("88 Nowhere Ave."), labels[1]); + + // Otherwise, we should. + suggested_fields.push_back(NAME_FIRST); + AutofillProfile::CreateInferredLabels(&profiles.get(), &suggested_fields, + NAME_LAST, 1, &labels); + ASSERT_EQ(2U, labels.size()); + EXPECT_EQ(ASCIIToUTF16("88 Nowhere Ave., John Doe"), labels[0]); + EXPECT_EQ(ASCIIToUTF16("88 Nowhere Ave., Johnny K Doe"), labels[1]); +} + +// Test that we do not show duplicate fields in the labels. +TEST(AutofillProfileTest, CreateInferredLabelsNoDuplicatedFields) { + ScopedVector<AutofillProfile> profiles; + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo(profiles[0], + "John", "", "Doe", "doe@example.com", "", + "88 Nowhere Ave.", "", "", "", "", "", ""); + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo(profiles[1], + "John", "", "Doe", "dojo@example.com", "", + "88 Nowhere Ave.", "", "", "", "", "", ""); + + // If the only name field in the suggested fields is the excluded field, we + // should not fall back to the full name as a distinguishing field. + std::vector<AutofillFieldType> suggested_fields; + suggested_fields.push_back(ADDRESS_HOME_LINE1); + suggested_fields.push_back(ADDRESS_BILLING_LINE1); + suggested_fields.push_back(EMAIL_ADDRESS); + std::vector<string16> labels; + AutofillProfile::CreateInferredLabels(&profiles.get(), &suggested_fields, + UNKNOWN_TYPE, 2, &labels); + ASSERT_EQ(2U, labels.size()); + EXPECT_EQ(ASCIIToUTF16("88 Nowhere Ave., doe@example.com"), labels[0]); + EXPECT_EQ(ASCIIToUTF16("88 Nowhere Ave., dojo@example.com"), labels[1]); +} + +// Make sure that empty fields are not treated as distinguishing fields. +TEST(AutofillProfileTest, CreateInferredLabelsSkipsEmptyFields) { + ScopedVector<AutofillProfile> profiles; + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo(profiles[0], + "John", "", "Doe", "doe@example.com", + "Gogole", "", "", "", "", "", "", ""); + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo(profiles[1], + "John", "", "Doe", "doe@example.com", + "Ggoole", "", "", "", "", "", "", ""); + profiles.push_back(new AutofillProfile); + autofill_test::SetProfileInfo(profiles[2], + "John", "", "Doe", "john.doe@example.com", + "Goolge", "", "", "", "", "", "", ""); + + std::vector<string16> labels; + AutofillProfile::CreateInferredLabels(&profiles.get(), NULL, UNKNOWN_TYPE, 3, + &labels); + ASSERT_EQ(3U, labels.size()); + EXPECT_EQ(ASCIIToUTF16("John Doe, doe@example.com, Gogole"), labels[0]); + EXPECT_EQ(ASCIIToUTF16("John Doe, doe@example.com, Ggoole"), labels[1]); + EXPECT_EQ(ASCIIToUTF16("John Doe, john.doe@example.com, Goolge"), labels[2]); + + // A field must have a non-empty value for each profile to be considered a + // distinguishing field. + profiles[1]->SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16("88 Nowhere Ave.")); + AutofillProfile::CreateInferredLabels(&profiles.get(), NULL, UNKNOWN_TYPE, 1, + &labels); + ASSERT_EQ(3U, labels.size()); + EXPECT_EQ(ASCIIToUTF16("John Doe, doe@example.com, Gogole"), labels[0]); + EXPECT_EQ(ASCIIToUTF16("John Doe, 88 Nowhere Ave., doe@example.com, Ggoole"), + labels[1]) << labels[1]; + EXPECT_EQ(ASCIIToUTF16("John Doe, john.doe@example.com"), labels[2]); +} + +TEST(AutofillProfileTest, IsSubsetOf) { + scoped_ptr<AutofillProfile> a, b; + + // |a| is a subset of |b|. + a.reset(new AutofillProfile); + b.reset(new AutofillProfile); + autofill_test::SetProfileInfo(a.get(), "Thomas", NULL, "Jefferson", + "declaration_guy@gmail.com", NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL); + autofill_test::SetProfileInfo(b.get(), "Thomas", NULL, "Jefferson", + "declaration_guy@gmail.com", "United States Government", "Monticello", + NULL, "Charlottesville", "Virginia", "22902", NULL, NULL); + EXPECT_TRUE(a->IsSubsetOf(*b)); + + // |b| is not a subset of |a|. + EXPECT_FALSE(b->IsSubsetOf(*a)); + + // |a| is a subset of |a|. + EXPECT_TRUE(a->IsSubsetOf(*a)); + + // One field in |b| is different. + a.reset(new AutofillProfile); + b.reset(new AutofillProfile); + autofill_test::SetProfileInfo(a.get(), "Thomas", NULL, "Jefferson", + "declaration_guy@gmail.com", NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL); + autofill_test::SetProfileInfo(a.get(), "Thomas", NULL, "Adams", + "declaration_guy@gmail.com", NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL); + EXPECT_FALSE(a->IsSubsetOf(*b)); +} + +TEST(AutofillProfileTest, AssignmentOperator) { + AutofillProfile a, b; + + // Result of assignment should be logically equal to the original profile. + autofill_test::SetProfileInfo(&a, "Marion", "Mitchell", "Morrison", + "marion@me.xyz", "Fox", "123 Zoo St.", "unit 5", + "Hollywood", "CA", "91601", "US", + "12345678910"); + b = a; + EXPECT_TRUE(a == b); + + // Assignment to self should not change the profile value. + a = a; + EXPECT_TRUE(a == b); +} + +TEST(AutofillProfileTest, Copy) { + AutofillProfile a; + + // Clone should be logically equal to the original. + autofill_test::SetProfileInfo(&a, "Marion", "Mitchell", "Morrison", + "marion@me.xyz", "Fox", "123 Zoo St.", "unit 5", + "Hollywood", "CA", "91601", "US", + "12345678910"); + AutofillProfile b(a); + EXPECT_TRUE(a == b); +} + +TEST(AutofillProfileTest, Compare) { + AutofillProfile a, b; + + // Empty profiles are the same. + EXPECT_EQ(0, a.Compare(b)); + + // GUIDs don't count. + a.set_guid(base::GenerateGUID()); + b.set_guid(base::GenerateGUID()); + EXPECT_EQ(0, a.Compare(b)); + + // Different values produce non-zero results. + autofill_test::SetProfileInfo(&a, "Jimmy", NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + autofill_test::SetProfileInfo(&b, "Ringo", NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + EXPECT_GT(0, a.Compare(b)); + EXPECT_LT(0, b.Compare(a)); + + // Phone numbers are compared by the full number, including the area code. + // This is a regression test for http://crbug.com/163024 + autofill_test::SetProfileInfo(&a, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, "650.555.4321"); + autofill_test::SetProfileInfo(&b, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, "408.555.4321"); + EXPECT_GT(0, a.Compare(b)); + EXPECT_LT(0, b.Compare(a)); +} + +TEST(AutofillProfileTest, CountryCode) { + AutofillProfile profile; + EXPECT_EQ(std::string(), profile.CountryCode()); + + profile.SetCountryCode("US"); + EXPECT_EQ("US", profile.CountryCode()); +} + +TEST(AutofillProfileTest, MultiValueNames) { + AutofillProfile p; + const string16 kJohnDoe(ASCIIToUTF16("John Doe")); + const string16 kJohnPDoe(ASCIIToUTF16("John P. Doe")); + std::vector<string16> set_values; + set_values.push_back(kJohnDoe); + set_values.push_back(kJohnPDoe); + p.SetRawMultiInfo(NAME_FULL, set_values); + + // Expect regular |GetInfo| returns the first element. + EXPECT_EQ(kJohnDoe, p.GetRawInfo(NAME_FULL)); + + // Ensure that we get out what we put in. + std::vector<string16> get_values; + p.GetRawMultiInfo(NAME_FULL, &get_values); + ASSERT_EQ(2UL, get_values.size()); + EXPECT_EQ(kJohnDoe, get_values[0]); + EXPECT_EQ(kJohnPDoe, get_values[1]); + + // Update the values. + AutofillProfile p2 = p; + EXPECT_EQ(0, p.Compare(p2)); + const string16 kNoOne(ASCIIToUTF16("No One")); + set_values[1] = kNoOne; + p.SetRawMultiInfo(NAME_FULL, set_values); + p.GetRawMultiInfo(NAME_FULL, &get_values); + ASSERT_EQ(2UL, get_values.size()); + EXPECT_EQ(kJohnDoe, get_values[0]); + EXPECT_EQ(kNoOne, get_values[1]); + EXPECT_NE(0, p.Compare(p2)); + + // Delete values. + set_values.clear(); + p.SetRawMultiInfo(NAME_FULL, set_values); + p.GetRawMultiInfo(NAME_FULL, &get_values); + ASSERT_EQ(1UL, get_values.size()); + EXPECT_EQ(string16(), get_values[0]); + + // Expect regular |GetInfo| returns empty value. + EXPECT_EQ(string16(), p.GetRawInfo(NAME_FULL)); +} + +TEST(AutofillProfileTest, MultiValueEmails) { + AutofillProfile p; + const string16 kJohnDoe(ASCIIToUTF16("john@doe.com")); + const string16 kJohnPDoe(ASCIIToUTF16("john_p@doe.com")); + std::vector<string16> set_values; + set_values.push_back(kJohnDoe); + set_values.push_back(kJohnPDoe); + p.SetRawMultiInfo(EMAIL_ADDRESS, set_values); + + // Expect regular |GetInfo| returns the first element. + EXPECT_EQ(kJohnDoe, p.GetRawInfo(EMAIL_ADDRESS)); + + // Ensure that we get out what we put in. + std::vector<string16> get_values; + p.GetRawMultiInfo(EMAIL_ADDRESS, &get_values); + ASSERT_EQ(2UL, get_values.size()); + EXPECT_EQ(kJohnDoe, get_values[0]); + EXPECT_EQ(kJohnPDoe, get_values[1]); + + // Update the values. + AutofillProfile p2 = p; + EXPECT_EQ(0, p.Compare(p2)); + const string16 kNoOne(ASCIIToUTF16("no@one.com")); + set_values[1] = kNoOne; + p.SetRawMultiInfo(EMAIL_ADDRESS, set_values); + p.GetRawMultiInfo(EMAIL_ADDRESS, &get_values); + ASSERT_EQ(2UL, get_values.size()); + EXPECT_EQ(kJohnDoe, get_values[0]); + EXPECT_EQ(kNoOne, get_values[1]); + EXPECT_NE(0, p.Compare(p2)); + + // Delete values. + set_values.clear(); + p.SetRawMultiInfo(EMAIL_ADDRESS, set_values); + p.GetRawMultiInfo(EMAIL_ADDRESS, &get_values); + ASSERT_EQ(1UL, get_values.size()); + EXPECT_EQ(string16(), get_values[0]); + + // Expect regular |GetInfo| returns empty value. + EXPECT_EQ(string16(), p.GetRawInfo(EMAIL_ADDRESS)); +} + +TEST(AutofillProfileTest, MultiValuePhone) { + AutofillProfile p; + const string16 kJohnDoe(ASCIIToUTF16("4151112222")); + const string16 kJohnPDoe(ASCIIToUTF16("4151113333")); + std::vector<string16> set_values; + set_values.push_back(kJohnDoe); + set_values.push_back(kJohnPDoe); + p.SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, set_values); + + // Expect regular |GetInfo| returns the first element. + EXPECT_EQ(kJohnDoe, p.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); + + // Ensure that we get out what we put in. + std::vector<string16> get_values; + p.GetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, &get_values); + ASSERT_EQ(2UL, get_values.size()); + EXPECT_EQ(kJohnDoe, get_values[0]); + EXPECT_EQ(kJohnPDoe, get_values[1]); + + // Update the values. + AutofillProfile p2 = p; + EXPECT_EQ(0, p.Compare(p2)); + const string16 kNoOne(ASCIIToUTF16("4152110000")); + set_values[1] = kNoOne; + p.SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, set_values); + p.GetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, &get_values); + ASSERT_EQ(2UL, get_values.size()); + EXPECT_EQ(kJohnDoe, get_values[0]); + EXPECT_EQ(kNoOne, get_values[1]); + EXPECT_NE(0, p.Compare(p2)); + + // Delete values. + set_values.clear(); + p.SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, set_values); + p.GetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, &get_values); + ASSERT_EQ(1UL, get_values.size()); + EXPECT_EQ(string16(), get_values[0]); + + // Expect regular |GetInfo| returns empty value. + EXPECT_EQ(string16(), p.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); +} + +TEST(AutofillProfileTest, AddressCountryFull) { + const char* const kCountries[] = { + "Albania", "Canada" + }; + std::vector<string16> options(arraysize(kCountries)); + for (size_t i = 0; i < arraysize(kCountries); ++i) { + options[i] = ASCIIToUTF16(kCountries[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = options; + field.option_contents = options; + + AutofillProfile profile; + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("CA")); + profile.FillSelectControl(ADDRESS_HOME_COUNTRY, &field); + EXPECT_EQ(ASCIIToUTF16("Canada"), field.value); +} + +TEST(AutofillProfileTest, AddressCountryAbbrev) { + const char* const kCountries[] = { + "AL", "CA" + }; + std::vector<string16> options(arraysize(kCountries)); + for (size_t i = 0; i < arraysize(kCountries); ++i) { + options[i] = ASCIIToUTF16(kCountries[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = options; + field.option_contents = options; + + AutofillProfile profile; + profile.SetInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("Canada"), "en-US"); + profile.FillSelectControl(ADDRESS_HOME_COUNTRY, &field); + EXPECT_EQ(ASCIIToUTF16("CA"), field.value); +} + +TEST(AutofillProfileTest, AddressStateFull) { + const char* const kStates[] = { + "Alabama", "California" + }; + std::vector<string16> options(arraysize(kStates)); + for (size_t i = 0; i < arraysize(kStates); ++i) { + options[i] = ASCIIToUTF16(kStates[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = options; + field.option_contents = options; + + AutofillProfile profile; + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("CA")); + profile.FillSelectControl(ADDRESS_HOME_STATE, &field); + EXPECT_EQ(ASCIIToUTF16("California"), field.value); +} + +TEST(AutofillProfileTest, AddressStateAbbrev) { + const char* const kStates[] = { + "AL", "CA" + }; + std::vector<string16> options(arraysize(kStates)); + for (size_t i = 0; i < arraysize(kStates); ++i) { + options[i] = ASCIIToUTF16(kStates[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = options; + field.option_contents = options; + + AutofillProfile profile; + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("California")); + profile.FillSelectControl(ADDRESS_HOME_STATE, &field); + EXPECT_EQ(ASCIIToUTF16("CA"), field.value); +} + +TEST(AutofillProfileTest, FillByValue) { + const char* const kStates[] = { + "Alabama", "California" + }; + std::vector<string16> values(arraysize(kStates)); + std::vector<string16> contents(arraysize(kStates)); + for (unsigned int i = 0; i < arraysize(kStates); ++i) { + values[i] = ASCIIToUTF16(kStates[i]); + contents[i] = ASCIIToUTF16(base::StringPrintf("%u", i)); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = values; + field.option_contents = contents; + + AutofillProfile profile; + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("California")); + profile.FillSelectControl(ADDRESS_HOME_STATE, &field); + EXPECT_EQ(ASCIIToUTF16("California"), field.value); +} + +TEST(AutofillProfileTest, FillByContents) { + const char* const kStates[] = { + "Alabama", "California" + }; + std::vector<string16> values(arraysize(kStates)); + std::vector<string16> contents(arraysize(kStates)); + for (unsigned int i = 0; i < arraysize(kStates); ++i) { + values[i] = ASCIIToUTF16(base::StringPrintf("%u", i + 1)); + contents[i] = ASCIIToUTF16(kStates[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = values; + field.option_contents = contents; + + AutofillProfile profile; + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("California")); + profile.FillSelectControl(ADDRESS_HOME_STATE, &field); + EXPECT_EQ(ASCIIToUTF16("2"), field.value); +} diff --git a/components/autofill/browser/autofill_regex_constants.cc.utf8 b/components/autofill/browser/autofill_regex_constants.cc.utf8 new file mode 100644 index 0000000..53aa9d5 --- /dev/null +++ b/components/autofill/browser/autofill_regex_constants.cc.utf8 @@ -0,0 +1,293 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file contains UTF8 strings that we want as char arrays. To avoid +// different compilers, we use a script to convert the UTF8 strings into +// numeric literals (\x##). + +#include "components/autofill/browser/autofill_regex_constants.h" + +namespace autofill { + +///////////////////////////////////////////////////////////////////////////// +// address_field.cc +///////////////////////////////////////////////////////////////////////////// +const char kAttentionIgnoredRe[] = "attention|attn"; +const char kRegionIgnoredRe[] = + "province|region|other" + "|provincia" // es + "|bairro|suburb"; // pt-BR, pt-PT +const char kCompanyRe[] = + "company|business|organization|organisation" + "|firma|firmenname" // de-DE + "|empresa" // es + "|societe|société" // fr-FR + "|ragione.?sociale" // it-IT + "|会社" // ja-JP + "|название.?компании" // ru + "|单位|公司" // zh-CN + "|회사|직장"; // ko-KR +const char kAddressLine1Re[] = + "address.*line|address1|addr1|street" + "|strasse|straße|hausnummer|housenumber" // de-DE + "|house.?name" // en-GB + "|direccion|dirección" // es + "|adresse" // fr-FR + "|indirizzo" // it-IT + "|住所1" // ja-JP + "|morada|endereço" // pt-BR, pt-PT + "|Адрес" // ru + "|地址" // zh-CN + "|주소.?1"; // ko-KR +const char kAddressLine1LabelRe[] = + "address" + "|adresse" // fr-FR + "|indirizzo" // it-IT + "|住所" // ja-JP + "|地址" // zh-CN + "|주소"; // ko-KR +const char kAddressLine2Re[] = + "address.*line2|address2|addr2|street|suite|unit" + "|adresszusatz|ergänzende.?angaben" // de-DE + "|direccion2|colonia|adicional" // es + "|addresssuppl|complementnom|appartement" // fr-FR + "|indirizzo2" // it-IT + "|住所2" // ja-JP + "|complemento|addrcomplement" // pt-BR, pt-PT + "|Улица" // ru + "|地址2" // zh-CN + "|주소.?2"; // ko-KR +const char kAddressLine2LabelRe[] = + "address" + "|adresse" // fr-FR + "|indirizzo" // it-IT + "|地址" // zh-CN + "|주소"; // ko-KR +const char kAddressLine3Re[] = + "address.*line3|address3|addr3|street|line3" + "|municipio" // es + "|batiment|residence" // fr-FR + "|indirizzo3"; // it-IT +const char kCountryRe[] = + "country|countries|location" + "|país|pais" // es + "|国" // ja-JP + "|国家" // zh-CN + "|국가|나라"; // ko-KR +const char kZipCodeRe[] = + "zip|postal|post.*code|pcode|^1z$" + "|postleitzahl" // de-DE + "|\\bcp\\b" // es + "|\\bcdp\\b" // fr-FR + "|\\bcap\\b" // it-IT + "|郵便番号" // ja-JP + "|codigo|codpos|\\bcep\\b" // pt-BR, pt-PT + "|Почтовый.?Индекс" // ru + "|邮政编码|邮编" // zh-CN + "|郵遞區號" // zh-TW + "|우편.?번호"; // ko-KR +const char kZip4Re[] = + "zip|^-$|post2" + "|codpos2"; // pt-BR, pt-PT +const char kCityRe[] = + "city|town" + "|\\bort\\b|stadt" // de-DE + "|suburb" // en-AU + "|ciudad|provincia|localidad|poblacion" // es + "|ville|commune" // fr-FR + "|localita" // it-IT + "|市区町村" // ja-JP + "|cidade" // pt-BR, pt-PT + "|Город" // ru + "|市" // zh-CN + "|分區" // zh-TW + "|^시[^도·・]|시[·・]?군[·・]?구"; // ko-KR +const char kStateRe[] = + "(?<!united )state|county|region|province" + "|land" // de-DE + "|county|principality" // en-UK + "|都道府県" // ja-JP + "|estado|provincia" // pt-BR, pt-PT + "|область" // ru + "|省" // zh-CN + "|地區" // zh-TW + "|^시[·・]?도"; // ko-KR +const char kAddressTypeSameAsRe[] = "same as"; +const char kAddressTypeUseMyRe[] = "use my"; +const char kBillingDesignatorRe[] = "bill"; +const char kShippingDesignatorRe[] = "ship"; + +///////////////////////////////////////////////////////////////////////////// +// credit_card_field.cc +///////////////////////////////////////////////////////////////////////////// +const char kNameOnCardRe[] = + "card.?holder|name.*\\bon\\b.*card|cc.?name|cc.?full.?name|owner" + "|karteninhaber" // de-DE + "|nombre.*tarjeta" // es + "|nom.*carte" // fr-FR + "|nome.*cart" // it-IT + "|名前" // ja-JP + "|Имя.*карты" // ru + "|信用卡开户名|开户名|持卡人姓名" // zh-CN + "|持卡人姓名"; // zh-TW +const char kNameOnCardContextualRe[] = + "name"; +const char kCardNumberRe[] = + "card.?number|card.?#|card.?no|cc.?num|acct.?num" + "|nummer" // de-DE + "|credito|numero|número" // es + "|numéro" // fr-FR + "|カード番号" // ja-JP + "|Номер.*карты" // ru + "|信用卡号|信用卡号码" // zh-CN + "|信用卡卡號" // zh-TW + "|카드"; // ko-KR +const char kCardCvcRe[] = + "verification|card identification|security code|cvn|cvv|cvc|csc|\\bcid\\b"; +const char kCardTypeRe[] = + "card.?type|cc.?type|payment.?method"; + +// "Expiration date" is the most common label here, but some pages have +// "Expires", "exp. date" or "exp. month" and "exp. year". We also look +// for the field names ccmonth and ccyear, which appear on at least 4 of +// our test pages. + +// On at least one page (The China Shop2.html) we find only the labels +// "month" and "year". So for now we match these words directly; we'll +// see if this turns out to be too general. + +// Toolbar Bug 51451: indeed, simply matching "month" is too general for +// https://rps.fidelity.com/ftgw/rps/RtlCust/CreatePIN/Init. +// Instead, we match only words beginning with "month". +const char kExpirationMonthRe[] = + "expir|exp.*mo|exp.*date|ccmonth|cardmonth" + "|gueltig|gültig|monat" // de-DE + "|fecha" // es + "|date.*exp" // fr-FR + "|scadenza" // it-IT + "|有効期限" // ja-JP + "|validade" // pt-BR, pt-PT + "|Срок действия карты" // ru + "|月"; // zh-CN +const char kExpirationYearRe[] = + "exp|^/|year" + "|ablaufdatum|gueltig|gültig|yahr" // de-DE + "|fecha" // es + "|scadenza" // it-IT + "|有効期限" // ja-JP + "|validade" // pt-BR, pt-PT + "|Срок действия карты" // ru + "|年|有效期"; // zh-CN + +// This regex is a little bit nasty, but it is simply requiring exactly two +// adjacent y's. +const char kExpirationDate2DigitYearRe[] = + "exp.*date.*[^y]yy([^y]|$)"; +const char kExpirationDateRe[] = + "expir|exp.*date" + "|gueltig|gültig" // de-DE + "|fecha" // es + "|date.*exp" // fr-FR + "|scadenza" // it-IT + "|有効期限" // ja-JP + "|validade" // pt-BR, pt-PT + "|Срок действия карты"; // ru +const char kCardIgnoredRe[] = + "^card"; +const char kGiftCardRe[] = + "gift.?card"; + + +///////////////////////////////////////////////////////////////////////////// +// email_field.cc +///////////////////////////////////////////////////////////////////////////// +const char kEmailRe[] = + "e.?mail" + "|メールアドレス" // ja-JP + "|Электронной.?Почты" // ru + "|邮件|邮箱" // zh-CN + "|電郵地址" // zh-TW + "|(이메일|전자.?우편|[Ee]-?mail)(.?주소)?"; // ko-KR + + +///////////////////////////////////////////////////////////////////////////// +// name_field.cc +///////////////////////////////////////////////////////////////////////////// +const char kNameIgnoredRe[] = + "user.?name|user.?id|nickname|maiden name|title|prefix|suffix" + "|vollständiger.?name" // de-DE + "|用户名" // zh-CN + "|(사용자.?)?아이디|사용자.?ID"; // ko-KR +const char kNameRe[] = + "^name|full.?name|your.?name|customer.?name|firstandlastname|bill.?name" + "|ship.?name" + "|nombre.*y.*apellidos" // es + "|^nom" // fr-FR + "|お名前|氏名" // ja-JP + "|^nome" // pt-BR, pt-PT + "|姓名" // zh-CN + "|성명"; // ko-KR +const char kNameSpecificRe[] = + "^name" + "|^nom" // fr-FR + "|^nome"; // pt-BR, pt-PT +const char kFirstNameRe[] = + "first.*name|initials|fname|first$" + "|vorname" // de-DE + "|nombre" // es + "|forename|prénom|prenom" // fr-FR + "|名" // ja-JP + "|nome" // pt-BR, pt-PT + "|Имя" // ru + "|이름"; // ko-KR +const char kMiddleInitialRe[] = "middle.*initial|m\\.i\\.|mi$|\\bmi\\b"; +const char kMiddleNameRe[] = + "middle.*name|mname|middle$" + "|apellido.?materno|lastlastname"; // es +const char kLastNameRe[] = + "last.*name|lname|surname|last$|secondname" + "|nachname" // de-DE + "|apellido" // es + "|famille|^nom" // fr-FR + "|cognome" // it-IT + "|姓" // ja-JP + "|morada|apelidos|surename|sobrenome" // pt-BR, pt-PT + "|Фамилия" // ru + "|성[^명]?"; // ko-KR + +///////////////////////////////////////////////////////////////////////////// +// phone_field.cc +///////////////////////////////////////////////////////////////////////////// +const char kPhoneRe[] = + "phone|mobile" + "|telefonnummer" // de-DE + "|telefono|teléfono" // es + "|telfixe" // fr-FR + "|電話" // ja-JP + "|telefone|telemovel" // pt-BR, pt-PT + "|телефон" // ru + "|电话" // zh-CN + "|(전화|핸드폰|휴대폰|휴대전화)(.?번호)?"; // ko-KR +const char kCountryCodeRe[] = + "country.*code|ccode|_cc"; +const char kAreaCodeNotextRe[] = + "^\\($"; +const char kAreaCodeRe[] = + "area.*code|acode|area" + "|지역.?번호"; // ko-KR +const char kPhonePrefixSeparatorRe[] = + "^-$|^\\)$"; +const char kPhoneSuffixSeparatorRe[] = + "^-$"; +const char kPhonePrefixRe[] = + "prefix|exchange" + "|preselection" // fr-FR + "|ddd"; // pt-BR, pt-PT +const char kPhoneSuffixRe[] = + "suffix"; +const char kPhoneExtensionRe[] = + "\\bext|ext\\b|extension" + "|ramal"; // pt-BR, pt-PT + +} // namespace autofill diff --git a/components/autofill/browser/autofill_regex_constants.h b/components/autofill/browser/autofill_regex_constants.h new file mode 100644 index 0000000..c2258c0 --- /dev/null +++ b/components/autofill/browser/autofill_regex_constants.h @@ -0,0 +1,59 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_REGEX_CONSTANTS_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_REGEX_CONSTANTS_H_ + +namespace autofill { + +extern const char kAttentionIgnoredRe[]; +extern const char kRegionIgnoredRe[]; +extern const char kCompanyRe[]; +extern const char kAddressLine1Re[]; +extern const char kAddressLine1LabelRe[]; +extern const char kAddressLine2Re[]; +extern const char kAddressLine2LabelRe[]; +extern const char kAddressLine3Re[]; +extern const char kCountryRe[]; +extern const char kZipCodeRe[]; +extern const char kZip4Re[]; +extern const char kCityRe[]; +extern const char kStateRe[]; +extern const char kAddressTypeSameAsRe[]; +extern const char kAddressTypeUseMyRe[]; +extern const char kBillingDesignatorRe[]; +extern const char kShippingDesignatorRe[]; +extern const char kNameOnCardRe[]; +extern const char kNameOnCardContextualRe[]; +extern const char kCardNumberRe[]; +extern const char kCardCvcRe[]; +extern const char kCardTypeRe[]; +extern const char kExpirationMonthRe[]; +extern const char kExpirationYearRe[]; +extern const char kExpirationDate2DigitYearRe[]; +extern const char kExpirationDateRe[]; +extern const char kCardIgnoredRe[]; +extern const char kGiftCardRe[]; +extern const char kEmailRe[]; +extern const char kNameIgnoredRe[]; +extern const char kNameRe[]; +extern const char kNameSpecificRe[]; +extern const char kFirstNameRe[]; +extern const char kMiddleInitialRe[]; +extern const char kMiddleNameRe[]; +extern const char kLastNameRe[]; +extern const char kPhoneRe[]; +extern const char kCountryCodeRe[]; +extern const char kAreaCodeNotextRe[]; +extern const char kAreaCodeRe[]; +extern const char kFaxRe[]; +extern const char kPhonePrefixSeparatorRe[]; +extern const char kPhoneSuffixSeparatorRe[]; +extern const char kPhonePrefixRe[]; +extern const char kPhoneSuffixRe[]; +extern const char kPhoneExtensionRe[]; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_REGEX_CONSTANTS_H_ diff --git a/components/autofill/browser/autofill_regexes.cc b/components/autofill/browser/autofill_regexes.cc new file mode 100644 index 0000000..64df56a --- /dev/null +++ b/components/autofill/browser/autofill_regexes.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_regexes.h" + +#include <map> +#include <utility> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/singleton.h" +#include "base/stl_util.h" +#include "base/string16.h" +#include "third_party/icu/public/i18n/unicode/regex.h" + +namespace { + +// A singleton class that serves as a cache of compiled regex patterns. +class AutofillRegexes { + public: + static AutofillRegexes* GetInstance(); + + // Returns the compiled regex matcher corresponding to |pattern|. + icu::RegexMatcher* GetMatcher(const string16& pattern); + + private: + AutofillRegexes(); + ~AutofillRegexes(); + friend struct DefaultSingletonTraits<AutofillRegexes>; + + // Maps patterns to their corresponding regex matchers. + std::map<string16, icu::RegexMatcher*> matchers_; + + DISALLOW_COPY_AND_ASSIGN(AutofillRegexes); +}; + +// static +AutofillRegexes* AutofillRegexes::GetInstance() { + return Singleton<AutofillRegexes>::get(); +} + +AutofillRegexes::AutofillRegexes() { +} + +AutofillRegexes::~AutofillRegexes() { + STLDeleteContainerPairSecondPointers(matchers_.begin(), + matchers_.end()); +} + +icu::RegexMatcher* AutofillRegexes::GetMatcher(const string16& pattern) { + if (!matchers_.count(pattern)) { + const icu::UnicodeString icu_pattern(pattern.data(), pattern.length()); + + UErrorCode status = U_ZERO_ERROR; + icu::RegexMatcher* matcher = new icu::RegexMatcher(icu_pattern, + UREGEX_CASE_INSENSITIVE, + status); + DCHECK(U_SUCCESS(status)); + + matchers_.insert(std::make_pair(pattern, matcher)); + } + + return matchers_[pattern]; +} + +} // namespace + +namespace autofill { + +bool MatchesPattern(const string16& input, const string16& pattern) { + icu::RegexMatcher* matcher = + AutofillRegexes::GetInstance()->GetMatcher(pattern); + icu::UnicodeString icu_input(input.data(), input.length()); + matcher->reset(icu_input); + + UErrorCode status = U_ZERO_ERROR; + UBool match = matcher->find(0, status); + DCHECK(U_SUCCESS(status)); + return !!match; +} + +} // namespace autofill diff --git a/components/autofill/browser/autofill_regexes.h b/components/autofill/browser/autofill_regexes.h new file mode 100644 index 0000000..8d5a8f97 --- /dev/null +++ b/components/autofill/browser/autofill_regexes.h @@ -0,0 +1,19 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_REGEXES_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_REGEXES_H_ + +#include "base/string16.h" + +// Parsing utilities. +namespace autofill { + +// Case-insensitive regular expression matching. +// Returns true if |pattern| is found in |input|. +bool MatchesPattern(const string16& input, const string16& pattern); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_REGEXES_H_ diff --git a/components/autofill/browser/autofill_regexes_unittest.cc b/components/autofill/browser/autofill_regexes_unittest.cc new file mode 100644 index 0000000..1c2cbab --- /dev/null +++ b/components/autofill/browser/autofill_regexes_unittest.cc @@ -0,0 +1,60 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_regexes.h" + +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_regex_constants.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(AutofillRegexesTest, AutofillRegexes) { + struct TestCase { + const char* const input; + const char* const pattern; + }; + + const TestCase kPositiveCases[] = { + // Empty pattern + {"", ""}, + {"Look, ma' -- a non-empty string!", ""}, + // Substring + {"string", "tri"}, + // Substring at beginning + {"string", "str"}, + {"string", "^str"}, + // Substring at end + {"string", "ring"}, + {"string", "ring$"}, + // Case-insensitive + {"StRiNg", "string"}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kPositiveCases); ++i) { + const TestCase& test_case = kPositiveCases[i]; + SCOPED_TRACE(test_case.input); + SCOPED_TRACE(test_case.pattern); + EXPECT_TRUE(autofill::MatchesPattern(ASCIIToUTF16(test_case.input), + ASCIIToUTF16(test_case.pattern))); + } + + const TestCase kNegativeCases[] = { + // Empty string + {"", "Look, ma' -- a non-empty pattern!"}, + // Substring + {"string", "trn"}, + // Substring at beginning + {"string", " str"}, + {"string", "^tri"}, + // Substring at end + {"string", "ring "}, + {"string", "rin$"}, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kNegativeCases); ++i) { + const TestCase& test_case = kNegativeCases[i]; + SCOPED_TRACE(test_case.input); + SCOPED_TRACE(test_case.pattern); + EXPECT_FALSE(autofill::MatchesPattern(ASCIIToUTF16(test_case.input), + ASCIIToUTF16(test_case.pattern))); + } +} diff --git a/components/autofill/browser/autofill_scanner.cc b/components/autofill/browser/autofill_scanner.cc new file mode 100644 index 0000000..fafdbab --- /dev/null +++ b/components/autofill/browser/autofill_scanner.cc @@ -0,0 +1,54 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_scanner.h" + +#include "base/logging.h" +#include "components/autofill/browser/autofill_field.h" + +AutofillScanner::AutofillScanner( + const std::vector<const AutofillField*>& fields) + : cursor_(fields.begin()), + saved_cursor_(fields.begin()), + begin_(fields.begin()), + end_(fields.end()) { +} + +AutofillScanner::~AutofillScanner() { +} + +void AutofillScanner::Advance() { + DCHECK(!IsEnd()); + ++cursor_; +} + +const AutofillField* AutofillScanner::Cursor() const { + if (IsEnd()) { + NOTREACHED(); + return NULL; + } + + return *cursor_; +} + +bool AutofillScanner::IsEnd() const { + return cursor_ == end_; +} + +void AutofillScanner::Rewind() { + DCHECK(saved_cursor_ != end_); + cursor_ = saved_cursor_; + saved_cursor_ = end_; +} + +void AutofillScanner::RewindTo(size_t index) { + DCHECK(index < static_cast<size_t>(end_ - begin_)); + cursor_ = begin_ + index; + saved_cursor_ = end_; +} + +size_t AutofillScanner::SaveCursor() { + saved_cursor_ = cursor_; + return static_cast<size_t>(cursor_ - begin_); +} diff --git a/components/autofill/browser/autofill_scanner.h b/components/autofill/browser/autofill_scanner.h new file mode 100644 index 0000000..c099910 --- /dev/null +++ b/components/autofill/browser/autofill_scanner.h @@ -0,0 +1,57 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_SCANNER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_SCANNER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/string16.h" + +class AutofillField; + +// A helper class for parsing a stream of |AutofillField|'s with lookahead. +class AutofillScanner { + public: + explicit AutofillScanner(const std::vector<const AutofillField*>& fields); + ~AutofillScanner(); + + // Advances the cursor by one step, if possible. + void Advance(); + + // Returns the current field in the stream, or |NULL| if there are no more + // fields in the stream. + const AutofillField* Cursor() const; + + // Returns |true| if the cursor has reached the end of the stream. + bool IsEnd() const; + + // Restores the most recently saved cursor. See also |SaveCursor()|. + void Rewind(); + + // Repositions the cursor to the specified |index|. See also |SaveCursor()|. + void RewindTo(size_t index); + + // Saves and returns the current cursor position. See also |Rewind()| and + // |RewindTo()|. + size_t SaveCursor(); + + private: + // Indicates the current position in the stream, represented as a vector. + std::vector<const AutofillField*>::const_iterator cursor_; + + // The most recently saved cursor. + std::vector<const AutofillField*>::const_iterator saved_cursor_; + + // The beginning pointer for the stream. + const std::vector<const AutofillField*>::const_iterator begin_; + + // The past-the-end pointer for the stream. + const std::vector<const AutofillField*>::const_iterator end_; + + DISALLOW_COPY_AND_ASSIGN(AutofillScanner); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_SCANNER_H_ diff --git a/components/autofill/browser/autofill_server_field_info.h b/components/autofill/browser/autofill_server_field_info.h new file mode 100644 index 0000000..34f7ea6 --- /dev/null +++ b/components/autofill/browser/autofill_server_field_info.h @@ -0,0 +1,20 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_SERVER_FIELD_INFO_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_SERVER_FIELD_INFO_H_ + +#include <string> + +#include "components/autofill/browser/field_types.h" + +struct AutofillServerFieldInfo { + // The predicted type returned by the Autofill server for this field. + AutofillFieldType field_type; + // Default value to be used for the field (only applies to + // FIELD_WITH_DEFAULT_TYPE field type) + std::string default_value; +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_SERVER_FIELD_INFO_H_ diff --git a/components/autofill/browser/autofill_type.cc b/components/autofill/browser/autofill_type.cc new file mode 100644 index 0000000..a715cbc --- /dev/null +++ b/components/autofill/browser/autofill_type.cc @@ -0,0 +1,294 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_type.h" + +#include <ostream> + +#include "base/logging.h" + +AutofillType::AutofillType(AutofillFieldType field_type) { + if ((field_type < NO_SERVER_DATA || field_type >= MAX_VALID_FIELD_TYPE) || + (field_type >= 15 && field_type <= 19) || + (field_type >= 25 && field_type <= 29) || + (field_type >= 44 && field_type <= 50)) + field_type_ = UNKNOWN_TYPE; + else + field_type_ = field_type; +} + +AutofillType::AutofillType(const AutofillType& autofill_type) { + *this = autofill_type; +} + +AutofillType& AutofillType::operator=(const AutofillType& autofill_type) { + if (this != &autofill_type) + this->field_type_ = autofill_type.field_type_; + return *this; +} + +AutofillFieldType AutofillType::field_type() const { + return field_type_; +} + +FieldTypeGroup AutofillType::group() const { + switch (field_type_) { + case NAME_FIRST: + case NAME_MIDDLE: + case NAME_LAST: + case NAME_MIDDLE_INITIAL: + case NAME_FULL: + case NAME_SUFFIX: + return NAME; + + case EMAIL_ADDRESS: + return EMAIL; + + case PHONE_HOME_NUMBER: + case PHONE_HOME_CITY_CODE: + case PHONE_HOME_COUNTRY_CODE: + case PHONE_HOME_CITY_AND_NUMBER: + case PHONE_HOME_WHOLE_NUMBER: + return PHONE; + + case ADDRESS_HOME_LINE1: + case ADDRESS_HOME_LINE2: + case ADDRESS_HOME_APT_NUM: + case ADDRESS_HOME_CITY: + case ADDRESS_HOME_STATE: + case ADDRESS_HOME_ZIP: + case ADDRESS_HOME_COUNTRY: + return ADDRESS_HOME; + + case ADDRESS_BILLING_LINE1: + case ADDRESS_BILLING_LINE2: + case ADDRESS_BILLING_APT_NUM: + case ADDRESS_BILLING_CITY: + case ADDRESS_BILLING_STATE: + case ADDRESS_BILLING_ZIP: + case ADDRESS_BILLING_COUNTRY: + return ADDRESS_BILLING; + + case CREDIT_CARD_NAME: + case CREDIT_CARD_NUMBER: + case CREDIT_CARD_EXP_MONTH: + case CREDIT_CARD_EXP_2_DIGIT_YEAR: + case CREDIT_CARD_EXP_4_DIGIT_YEAR: + case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: + case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: + case CREDIT_CARD_TYPE: + case CREDIT_CARD_VERIFICATION_CODE: + return CREDIT_CARD; + + case COMPANY_NAME: + return COMPANY; + + default: + return NO_GROUP; + } +} + +// static +AutofillFieldType AutofillType::GetEquivalentFieldType( + AutofillFieldType field_type) { + // When billing information is requested from the profile we map to the + // home address equivalents. + switch (field_type) { + case ADDRESS_BILLING_LINE1: + return ADDRESS_HOME_LINE1; + + case ADDRESS_BILLING_LINE2: + return ADDRESS_HOME_LINE2; + + case ADDRESS_BILLING_APT_NUM: + return ADDRESS_HOME_APT_NUM; + + case ADDRESS_BILLING_CITY: + return ADDRESS_HOME_CITY; + + case ADDRESS_BILLING_STATE: + return ADDRESS_HOME_STATE; + + case ADDRESS_BILLING_ZIP: + return ADDRESS_HOME_ZIP; + + case ADDRESS_BILLING_COUNTRY: + return ADDRESS_HOME_COUNTRY; + + default: + return field_type; + } +} + +// static +std::string AutofillType::FieldTypeToString(AutofillFieldType type) { + switch (type) { + case NO_SERVER_DATA: + return "NO_SERVER_DATA"; + case UNKNOWN_TYPE: + return "UNKNOWN_TYPE"; + case EMPTY_TYPE: + return "EMPTY_TYPE"; + case NAME_FIRST: + return "NAME_FIRST"; + case NAME_MIDDLE: + return "NAME_MIDDLE"; + case NAME_LAST: + return "NAME_LAST"; + case NAME_MIDDLE_INITIAL: + return "NAME_MIDDLE_INITIAL"; + case NAME_FULL: + return "NAME_FULL"; + case NAME_SUFFIX: + return "NAME_SUFFIX"; + case EMAIL_ADDRESS: + return "EMAIL_ADDRESS"; + case PHONE_HOME_NUMBER: + return "PHONE_HOME_NUMBER"; + case PHONE_HOME_CITY_CODE: + return "PHONE_HOME_CITY_CODE"; + case PHONE_HOME_COUNTRY_CODE: + return "PHONE_HOME_COUNTRY_CODE"; + case PHONE_HOME_CITY_AND_NUMBER: + return "PHONE_HOME_CITY_AND_NUMBER"; + case PHONE_HOME_WHOLE_NUMBER: + return "PHONE_HOME_WHOLE_NUMBER"; + case ADDRESS_HOME_LINE1: + return "ADDRESS_HOME_LINE1"; + case ADDRESS_HOME_LINE2: + return "ADDRESS_HOME_LINE2"; + case ADDRESS_HOME_APT_NUM: + return "ADDRESS_HOME_APT_NUM"; + case ADDRESS_HOME_CITY: + return "ADDRESS_HOME_CITY"; + case ADDRESS_HOME_STATE: + return "ADDRESS_HOME_STATE"; + case ADDRESS_HOME_ZIP: + return "ADDRESS_HOME_ZIP"; + case ADDRESS_HOME_COUNTRY: + return "ADDRESS_HOME_COUNTRY"; + case ADDRESS_BILLING_LINE1: + return "ADDRESS_BILLING_LINE1"; + case ADDRESS_BILLING_LINE2: + return "ADDRESS_BILLING_LINE2"; + case ADDRESS_BILLING_APT_NUM: + return "ADDRESS_BILLING_APT_NUM"; + case ADDRESS_BILLING_CITY: + return "ADDRESS_BILLING_CITY"; + case ADDRESS_BILLING_STATE: + return "ADDRESS_BILLING_STATE"; + case ADDRESS_BILLING_ZIP: + return "ADDRESS_BILLING_ZIP"; + case ADDRESS_BILLING_COUNTRY: + return "ADDRESS_BILLING_COUNTRY"; + case CREDIT_CARD_NAME: + return "CREDIT_CARD_NAME"; + case CREDIT_CARD_NUMBER: + return "CREDIT_CARD_NUMBER"; + case CREDIT_CARD_EXP_MONTH: + return "CREDIT_CARD_EXP_MONTH"; + case CREDIT_CARD_EXP_2_DIGIT_YEAR: + return "CREDIT_CARD_EXP_2_DIGIT_YEAR"; + case CREDIT_CARD_EXP_4_DIGIT_YEAR: + return "CREDIT_CARD_EXP_4_DIGIT_YEAR"; + case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: + return "CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR"; + case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: + return "CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR"; + case CREDIT_CARD_TYPE: + return "CREDIT_CARD_TYPE"; + case CREDIT_CARD_VERIFICATION_CODE: + return "CREDIT_CARD_VERIFICATION_CODE"; + case COMPANY_NAME: + return "COMPANY_NAME"; + default: + NOTREACHED() << "Invalid AutofillFieldType value."; + } + return std::string(); +} + +// static +AutofillFieldType AutofillType::StringToFieldType(const std::string& str) { + if (str == "NO_SERVER_DATA") + return NO_SERVER_DATA; + if (str == "UNKNOWN_TYPE") + return UNKNOWN_TYPE; + if (str == "EMPTY_TYPE") + return EMPTY_TYPE; + if (str == "NAME_FIRST") + return NAME_FIRST; + if (str == "NAME_MIDDLE") + return NAME_MIDDLE; + if (str == "NAME_LAST") + return NAME_LAST; + if (str == "NAME_MIDDLE_INITIAL") + return NAME_MIDDLE_INITIAL; + if (str == "NAME_FULL") + return NAME_FULL; + if (str == "NAME_SUFFIX") + return NAME_SUFFIX; + if (str == "EMAIL_ADDRESS") + return EMAIL_ADDRESS; + if (str == "PHONE_HOME_NUMBER") + return PHONE_HOME_NUMBER; + if (str == "PHONE_HOME_CITY_CODE") + return PHONE_HOME_CITY_CODE; + if (str == "PHONE_HOME_COUNTRY_CODE") + return PHONE_HOME_COUNTRY_CODE; + if (str == "PHONE_HOME_CITY_AND_NUMBER") + return PHONE_HOME_CITY_AND_NUMBER; + if (str == "PHONE_HOME_WHOLE_NUMBER") + return PHONE_HOME_WHOLE_NUMBER; + if (str == "ADDRESS_HOME_LINE1") + return ADDRESS_HOME_LINE1; + if (str == "ADDRESS_HOME_LINE2") + return ADDRESS_HOME_LINE2; + if (str == "ADDRESS_HOME_APT_NUM") + return ADDRESS_HOME_APT_NUM; + if (str == "ADDRESS_HOME_CITY") + return ADDRESS_HOME_CITY; + if (str == "ADDRESS_HOME_STATE") + return ADDRESS_HOME_STATE; + if (str == "ADDRESS_HOME_ZIP") + return ADDRESS_HOME_ZIP; + if (str == "ADDRESS_HOME_COUNTRY") + return ADDRESS_HOME_COUNTRY; + if (str == "ADDRESS_BILLING_LINE1") + return ADDRESS_BILLING_LINE1; + if (str == "ADDRESS_BILLING_LINE2") + return ADDRESS_BILLING_LINE2; + if (str == "ADDRESS_BILLING_APT_NUM") + return ADDRESS_BILLING_APT_NUM; + if (str == "ADDRESS_BILLING_CITY") + return ADDRESS_BILLING_CITY; + if (str == "ADDRESS_BILLING_STATE") + return ADDRESS_BILLING_STATE; + if (str == "ADDRESS_BILLING_ZIP") + return ADDRESS_BILLING_ZIP; + if (str == "ADDRESS_BILLING_COUNTRY") + return ADDRESS_BILLING_COUNTRY; + if (str == "CREDIT_CARD_NAME") + return CREDIT_CARD_NAME; + if (str == "CREDIT_CARD_NUMBER") + return CREDIT_CARD_NUMBER; + if (str == "CREDIT_CARD_EXP_MONTH") + return CREDIT_CARD_EXP_MONTH; + if (str == "CREDIT_CARD_EXP_2_DIGIT_YEAR") + return CREDIT_CARD_EXP_2_DIGIT_YEAR; + if (str == "CREDIT_CARD_EXP_4_DIGIT_YEAR") + return CREDIT_CARD_EXP_4_DIGIT_YEAR; + if (str == "CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR") + return CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR; + if (str == "CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR") + return CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR; + if (str == "CREDIT_CARD_TYPE") + return CREDIT_CARD_TYPE; + if (str == "CREDIT_CARD_VERIFICATION_CODE") + return CREDIT_CARD_VERIFICATION_CODE; + if (str == "COMPANY_NAME") + return COMPANY_NAME; + + NOTREACHED() << "Unknown AutofillFieldType " << str; + return UNKNOWN_TYPE; +} diff --git a/components/autofill/browser/autofill_type.h b/components/autofill/browser/autofill_type.h new file mode 100644 index 0000000..a199c24 --- /dev/null +++ b/components/autofill/browser/autofill_type.h @@ -0,0 +1,54 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_TYPE_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_TYPE_H_ + +#include <map> +#include <set> +#include <string> + +#include "base/string16.h" +#include "components/autofill/browser/field_types.h" + +// The high-level description of Autofill types, used to categorize form fields +// and for associating form fields with form values in the Web Database. +class AutofillType { + public: + enum FieldTypeGroup { + NO_GROUP, + NAME, + EMAIL, + COMPANY, + ADDRESS_HOME, + ADDRESS_BILLING, + PHONE, + CREDIT_CARD, + }; + + explicit AutofillType(AutofillFieldType field_type); + AutofillType(const AutofillType& autofill_type); + AutofillType& operator=(const AutofillType& autofill_type); + + AutofillFieldType field_type() const; + FieldTypeGroup group() const; + + // Maps |field_type| to a field type that can be directly stored in a profile + // (in the sense that it makes sense to call |AutofillProfile::SetInfo()| with + // the returned field type as the first parameter). + static AutofillFieldType GetEquivalentFieldType(AutofillFieldType field_type); + + // Utilities for serializing and deserializing an |AutofillFieldType|. + static std::string FieldTypeToString(AutofillFieldType field_type); + static AutofillFieldType StringToFieldType(const std::string& str); + + private: + AutofillFieldType field_type_; +}; + +typedef AutofillType::FieldTypeGroup FieldTypeGroup; +typedef std::set<AutofillFieldType> FieldTypeSet; +typedef std::map<string16, AutofillFieldType> FieldTypeMap; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_TYPE_H_ diff --git a/components/autofill/browser/autofill_type_unittest.cc b/components/autofill/browser/autofill_type_unittest.cc new file mode 100644 index 0000000..12654aa --- /dev/null +++ b/components/autofill/browser/autofill_type_unittest.cc @@ -0,0 +1,52 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_type.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +TEST(AutofillTypeTest, AutofillTypes) { + // No server data. + AutofillType none(NO_SERVER_DATA); + EXPECT_EQ(NO_SERVER_DATA, none.field_type()); + EXPECT_EQ(AutofillType::NO_GROUP, none.group()); + + // Unknown type. + AutofillType unknown(UNKNOWN_TYPE); + EXPECT_EQ(UNKNOWN_TYPE, unknown.field_type()); + EXPECT_EQ(AutofillType::NO_GROUP, unknown.group()); + + // Type with group but no subgroup. + AutofillType first(NAME_FIRST); + EXPECT_EQ(NAME_FIRST, first.field_type()); + EXPECT_EQ(AutofillType::NAME, first.group()); + + // Type with group and subgroup. + AutofillType phone(PHONE_HOME_NUMBER); + EXPECT_EQ(PHONE_HOME_NUMBER, phone.field_type()); + EXPECT_EQ(AutofillType::PHONE, phone.group()); + + // Last value, to check any offset errors. + AutofillType last(COMPANY_NAME); + EXPECT_EQ(COMPANY_NAME, last.field_type()); + EXPECT_EQ(AutofillType::COMPANY, last.group()); + + // Boundary (error) condition. + AutofillType boundary(MAX_VALID_FIELD_TYPE); + EXPECT_EQ(UNKNOWN_TYPE, boundary.field_type()); + EXPECT_EQ(AutofillType::NO_GROUP, boundary.group()); + + // Beyond the boundary (error) condition. + AutofillType beyond(static_cast<AutofillFieldType>(MAX_VALID_FIELD_TYPE+10)); + EXPECT_EQ(UNKNOWN_TYPE, beyond.field_type()); + EXPECT_EQ(AutofillType::NO_GROUP, beyond.group()); + + // In-between value. Missing from enum but within range. Error condition. + AutofillType between(static_cast<AutofillFieldType>(16)); + EXPECT_EQ(UNKNOWN_TYPE, between.field_type()); + EXPECT_EQ(AutofillType::NO_GROUP, between.group()); +} + +} // namespace diff --git a/components/autofill/browser/autofill_xml_parser.cc b/components/autofill/browser/autofill_xml_parser.cc new file mode 100644 index 0000000..a4e5a3d --- /dev/null +++ b/components/autofill/browser/autofill_xml_parser.cc @@ -0,0 +1,197 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/autofill_xml_parser.h" + +#include <stdlib.h> +#include <string.h> + +#include "base/logging.h" +#include "components/autofill/browser/autofill_server_field_info.h" +#include "third_party/libjingle/source/talk/xmllite/qname.h" + +AutofillXmlParser::AutofillXmlParser() + : succeeded_(true) { +} + +AutofillXmlParser::~AutofillXmlParser() {} + +void AutofillXmlParser::CharacterData( + buzz::XmlParseContext* context, const char* text, int len) { +} + +void AutofillXmlParser::EndElement(buzz::XmlParseContext* context, + const char* name) { +} + +void AutofillXmlParser::Error(buzz::XmlParseContext* context, + XML_Error error_code) { + succeeded_ = false; +} + +AutofillQueryXmlParser::AutofillQueryXmlParser( + std::vector<AutofillServerFieldInfo>* field_infos, + UploadRequired* upload_required, + std::string* experiment_id) + : field_infos_(field_infos), + upload_required_(upload_required), + current_page_number_(-1), + total_pages_(-1), + experiment_id_(experiment_id) { + DCHECK(upload_required_); + DCHECK(experiment_id_); +} + +AutofillQueryXmlParser::~AutofillQueryXmlParser() {} + +void AutofillQueryXmlParser::StartElement(buzz::XmlParseContext* context, + const char* name, + const char** attrs) { + buzz::QName qname = context->ResolveQName(name, false); + const std::string& element = qname.LocalPart(); + if (element.compare("autofillqueryresponse") == 0) { + // We check for the upload required attribute below, but if it's not + // present, we use the default upload rates. Likewise, by default we assume + // an empty experiment id. + *upload_required_ = USE_UPLOAD_RATES; + *experiment_id_ = std::string(); + + // |attrs| is a NULL-terminated list of (attribute, value) pairs. + while (*attrs) { + buzz::QName attribute_qname = context->ResolveQName(*attrs, true); + ++attrs; + const std::string& attribute_name = attribute_qname.LocalPart(); + if (attribute_name.compare("uploadrequired") == 0) { + if (strcmp(*attrs, "true") == 0) + *upload_required_ = UPLOAD_REQUIRED; + else if (strcmp(*attrs, "false") == 0) + *upload_required_ = UPLOAD_NOT_REQUIRED; + } else if (attribute_name.compare("experimentid") == 0) { + *experiment_id_ = *attrs; + } + ++attrs; + } + } else if (element.compare("field") == 0) { + if (!*attrs) { + // Missing the "autofilltype" attribute, abort. + context->RaiseError(XML_ERROR_ABORTED); + return; + } + + // Determine the field type from the attribute value. There should be one + // attribute (autofilltype) with an integer value. + AutofillServerFieldInfo field_info; + field_info.field_type = UNKNOWN_TYPE; + + // |attrs| is a NULL-terminated list of (attribute, value) pairs. + while (*attrs) { + buzz::QName attribute_qname = context->ResolveQName(*attrs, true); + ++attrs; + const std::string& attribute_name = attribute_qname.LocalPart(); + if (attribute_name.compare("autofilltype") == 0) { + int value = GetIntValue(context, *attrs); + if (value >= 0 && value < MAX_VALID_FIELD_TYPE) + field_info.field_type = static_cast<AutofillFieldType>(value); + else + field_info.field_type = NO_SERVER_DATA; + } else if (field_info.field_type == FIELD_WITH_DEFAULT_VALUE && + attribute_name.compare("defaultvalue") == 0) { + field_info.default_value = *attrs; + } + ++attrs; + } + + // Record this field type, default value pair. + field_infos_->push_back(field_info); + } else if (element.compare("autofill_flow") == 0) { + // |attrs| is a NULL-terminated list of (attribute, value) pairs. + while (*attrs) { + buzz::QName attribute_qname = context->ResolveQName(*attrs, true); + ++attrs; + const std::string& attribute_name = attribute_qname.LocalPart(); + if (attribute_name.compare("page_no") == 0) + current_page_number_ = GetIntValue(context, *attrs); + else if (attribute_name.compare("total_pages") == 0) + total_pages_ = GetIntValue(context, *attrs); + ++attrs; + } + } else if (element.compare("page_advance_button") == 0) { + // |attrs| is a NULL-terminated list of (attribute, value) pairs. + // If both id and css_selector are set, the first one to appear will take + // precedence. + while (*attrs) { + buzz::QName attribute_qname = context->ResolveQName(*attrs, true); + ++attrs; + const std::string& attribute_name = attribute_qname.LocalPart(); + buzz::QName value_qname = context->ResolveQName(*attrs, true); + ++attrs; + const std::string& attribute_value = value_qname.LocalPart(); + if (attribute_name.compare("id") == 0 && !attribute_value.empty()) { + proceed_element_descriptor_.reset(new autofill::WebElementDescriptor()); + proceed_element_descriptor_->retrieval_method = + autofill::WebElementDescriptor::ID; + proceed_element_descriptor_->descriptor = attribute_value; + break; + } else if (attribute_name.compare("css_selector") == 0 && + !attribute_value.empty()) { + proceed_element_descriptor_.reset(new autofill::WebElementDescriptor()); + proceed_element_descriptor_->retrieval_method = + autofill::WebElementDescriptor::CSS_SELECTOR; + proceed_element_descriptor_->descriptor = attribute_value; + break; + } + } + } +} + +int AutofillQueryXmlParser::GetIntValue(buzz::XmlParseContext* context, + const char* attribute) { + char* attr_end = NULL; + int value = strtol(attribute, &attr_end, 10); + if (attr_end != NULL && attr_end == attribute) { + context->RaiseError(XML_ERROR_SYNTAX); + return 0; + } + return value; +} + +AutofillUploadXmlParser::AutofillUploadXmlParser(double* positive_upload_rate, + double* negative_upload_rate) + : succeeded_(false), + positive_upload_rate_(positive_upload_rate), + negative_upload_rate_(negative_upload_rate) { + DCHECK(positive_upload_rate_); + DCHECK(negative_upload_rate_); +} + +void AutofillUploadXmlParser::StartElement(buzz::XmlParseContext* context, + const char* name, + const char** attrs) { + buzz::QName qname = context->ResolveQName(name, false); + const std::string &element = qname.LocalPart(); + if (element.compare("autofilluploadresponse") == 0) { + // Loop over all attributes to get the upload rates. + while (*attrs) { + buzz::QName attribute_qname = context->ResolveQName(attrs[0], true); + const std::string &attribute_name = attribute_qname.LocalPart(); + if (attribute_name.compare("positiveuploadrate") == 0) { + *positive_upload_rate_ = GetDoubleValue(context, attrs[1]); + } else if (attribute_name.compare("negativeuploadrate") == 0) { + *negative_upload_rate_ = GetDoubleValue(context, attrs[1]); + } + attrs += 2; // We peeked at attrs[0] and attrs[1], skip past both. + } + } +} + +double AutofillUploadXmlParser::GetDoubleValue(buzz::XmlParseContext* context, + const char* attribute) { + char* attr_end = NULL; + double value = strtod(attribute, &attr_end); + if (attr_end != NULL && attr_end == attribute) { + context->RaiseError(XML_ERROR_SYNTAX); + return 0.0; + } + return value; +} diff --git a/components/autofill/browser/autofill_xml_parser.h b/components/autofill/browser/autofill_xml_parser.h new file mode 100644 index 0000000..fc914f3 --- /dev/null +++ b/components/autofill/browser/autofill_xml_parser.h @@ -0,0 +1,167 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_XML_PARSER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_XML_PARSER_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "components/autofill/browser/autofill_server_field_info.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/common/web_element_descriptor.h" +#include "third_party/libjingle/source/talk/xmllite/xmlparser.h" + +// The base class that contains common functionality between +// AutofillQueryXmlParser and AutofillUploadXmlParser. +class AutofillXmlParser : public buzz::XmlParseHandler { + public: + AutofillXmlParser(); + virtual ~AutofillXmlParser(); + + // Returns true if no parsing errors were encountered. + bool succeeded() const { return succeeded_; } + + private: + // A callback for the end of an </element>, called by Expat. + // |context| is a parsing context used to resolve element/attribute names. + // |name| is the name of the element. + virtual void EndElement(buzz::XmlParseContext* context, + const char* name) OVERRIDE; + + // The callback for character data between tags (<element>text...</element>). + // |context| is a parsing context used to resolve element/attribute names. + // |text| is a pointer to the beginning of character data (not null + // terminated). + // |len| is the length of the string pointed to by text. + virtual void CharacterData(buzz::XmlParseContext* context, + const char* text, + int len) OVERRIDE; + + // The callback for parsing errors. + // |context| is a parsing context used to resolve names. + // |error_code| is a code representing the parsing error. + virtual void Error(buzz::XmlParseContext* context, + XML_Error error_code) OVERRIDE; + + // True if parsing succeeded. + bool succeeded_; + + DISALLOW_COPY_AND_ASSIGN(AutofillXmlParser); +}; + +// The XML parse handler for parsing Autofill query responses. A typical +// response looks like: +// +// <autofillqueryresponse experimentid="1"> +// <field autofilltype="0" /> +// <field autofilltype="1" /> +// <field autofilltype="3" /> +// <field autofilltype="2" /> +// </autofillqueryresponse> +// +// Fields are returned in the same order they were sent to the server. +// autofilltype: The server's guess at what type of field this is. 0 +// is unknown, other types are documented in +// components/autofill/browser/field_types.h. +class AutofillQueryXmlParser : public AutofillXmlParser { + public: + AutofillQueryXmlParser(std::vector<AutofillServerFieldInfo>* field_infos, + UploadRequired* upload_required, + std::string* experiment_id); + virtual ~AutofillQueryXmlParser(); + + int current_page_number() const { return current_page_number_; } + + int total_pages() const { return total_pages_; } + + // Returns the proceed element for multipage Autofill flows if the current + // page is part of such a flow or NULL otherwise. + const autofill::WebElementDescriptor* proceed_element_descriptor() const { + return proceed_element_descriptor_.get(); + } + + private: + // A callback for the beginning of a new <element>, called by Expat. + // |context| is a parsing context used to resolve element/attribute names. + // |name| is the name of the element. + // |attrs| is the list of attributes (names and values) for the element. + virtual void StartElement(buzz::XmlParseContext* context, + const char* name, + const char** attrs) OVERRIDE; + + // A helper function to retrieve integer values from strings. Raises an + // XML parse error if it fails. + // |context| is the current parsing context. + // |value| is the string to convert. + int GetIntValue(buzz::XmlParseContext* context, const char* attribute); + + // The parsed <field type, default value> pairs. + std::vector<AutofillServerFieldInfo>* field_infos_; + + // A flag indicating whether the client should upload Autofill data when this + // form is submitted. + UploadRequired* upload_required_; + + // Page number of present page in multipage autofill flow. + int current_page_number_; + + // Total number of pages in multipage autofill flow. + int total_pages_; + + // Proceed element for multipage Autofill flow. + scoped_ptr<autofill::WebElementDescriptor> proceed_element_descriptor_; + + // The server experiment to which this query response belongs. + // For the default server implementation, this is empty. + std::string* experiment_id_; + + DISALLOW_COPY_AND_ASSIGN(AutofillQueryXmlParser); +}; + +// The XML parser for handling Autofill upload responses. Typical upload +// responses look like: +// +// <autofilluploadresponse negativeuploadrate="0.00125" positiveuploadrate="1"/> +// +// The positive upload rate is the percentage of uploads to send to the server +// when something in the users profile matches what they have entered in a form. +// The negative upload rate is the percentage of uploads to send when nothing in +// the form matches what's in the users profile. +// The negative upload rate is typically much lower than the positive upload +// rate. +class AutofillUploadXmlParser : public AutofillXmlParser { + public: + AutofillUploadXmlParser(double* positive_upload_rate, + double* negative_upload_rate); + + private: + // A callback for the beginning of a new <element>, called by Expat. + // |context| is a parsing context used to resolve element/attribute names. + // |name| is the name of the element. + // |attrs| is the list of attributes (names and values) for the element. + virtual void StartElement(buzz::XmlParseContext* context, + const char* name, + const char** attrs) OVERRIDE; + + // A helper function to retrieve double values from strings. Raises an XML + // parse error if it fails. + // |context| is the current parsing context. + // |value| is the string to convert. + double GetDoubleValue(buzz::XmlParseContext* context, const char* attribute); + + // True if parsing succeeded. + bool succeeded_; + + double* positive_upload_rate_; + double* negative_upload_rate_; + + DISALLOW_COPY_AND_ASSIGN(AutofillUploadXmlParser); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOFILL_XML_PARSER_H_ diff --git a/components/autofill/browser/autofill_xml_parser_unittest.cc b/components/autofill/browser/autofill_xml_parser_unittest.cc new file mode 100644 index 0000000..3649e54 --- /dev/null +++ b/components/autofill/browser/autofill_xml_parser_unittest.cc @@ -0,0 +1,367 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "components/autofill/browser/autofill_xml_parser.h" +#include "components/autofill/browser/field_types.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libjingle/source/talk/xmllite/xmlparser.h" + +namespace { + +TEST(AutofillQueryXmlParserTest, BasicQuery) { + // An XML string representing a basic query response. + std::string xml = "<autofillqueryresponse>" + "<field autofilltype=\"0\" />" + "<field autofilltype=\"1\" />" + "<field autofilltype=\"3\" />" + "<field autofilltype=\"2\" />" + "<field autofilltype=\"61\" defaultvalue=\"default\"/>" + "</autofillqueryresponse>"; + + // Create a vector of AutofillServerFieldInfos, to assign the parsed field + // types to. + std::vector<AutofillServerFieldInfo> field_infos; + UploadRequired upload_required = USE_UPLOAD_RATES; + std::string experiment_id; + + // Create a parser. + AutofillQueryXmlParser parse_handler(&field_infos, &upload_required, + &experiment_id); + buzz::XmlParser parser(&parse_handler); + parser.Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler.succeeded()); + EXPECT_EQ(USE_UPLOAD_RATES, upload_required); + ASSERT_EQ(5U, field_infos.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos[0].field_type); + EXPECT_EQ(UNKNOWN_TYPE, field_infos[1].field_type); + EXPECT_EQ(NAME_FIRST, field_infos[2].field_type); + EXPECT_EQ(EMPTY_TYPE, field_infos[3].field_type); + EXPECT_EQ("", field_infos[3].default_value); + EXPECT_EQ(FIELD_WITH_DEFAULT_VALUE, field_infos[4].field_type); + EXPECT_EQ("default", field_infos[4].default_value); + EXPECT_EQ(std::string(), experiment_id); +} + +// Test parsing the upload required attribute. +TEST(AutofillQueryXmlParserTest, TestUploadRequired) { + std::vector<AutofillServerFieldInfo> field_infos; + UploadRequired upload_required = USE_UPLOAD_RATES; + std::string experiment_id; + + std::string xml = "<autofillqueryresponse uploadrequired=\"true\">" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + scoped_ptr<AutofillQueryXmlParser> parse_handler( + new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + scoped_ptr<buzz::XmlParser> parser(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(UPLOAD_REQUIRED, upload_required); + ASSERT_EQ(1U, field_infos.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos[0].field_type); + EXPECT_EQ(std::string(), experiment_id); + + field_infos.clear(); + xml = "<autofillqueryresponse uploadrequired=\"false\">" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + parse_handler.reset(new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(UPLOAD_NOT_REQUIRED, upload_required); + ASSERT_EQ(1U, field_infos.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos[0].field_type); + EXPECT_EQ(std::string(), experiment_id); + + field_infos.clear(); + xml = "<autofillqueryresponse uploadrequired=\"bad_value\">" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + parse_handler.reset(new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(USE_UPLOAD_RATES, upload_required); + ASSERT_EQ(1U, field_infos.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos[0].field_type); + EXPECT_EQ(std::string(), experiment_id); +} + +// Test parsing the experiment id attribute +TEST(AutofillQueryXmlParserTest, ParseExperimentId) { + std::vector<AutofillServerFieldInfo> field_infos; + UploadRequired upload_required = USE_UPLOAD_RATES; + std::string experiment_id; + + // When the attribute is missing, we should get back the default value -- the + // empty string. + std::string xml = "<autofillqueryresponse>" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + scoped_ptr<AutofillQueryXmlParser> parse_handler( + new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + scoped_ptr<buzz::XmlParser> parser(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(USE_UPLOAD_RATES, upload_required); + ASSERT_EQ(1U, field_infos.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos[0].field_type); + EXPECT_EQ(std::string(), experiment_id); + + field_infos.clear(); + + // When the attribute is present, make sure we parse it. + xml = "<autofillqueryresponse experimentid=\"FancyNewAlgorithm\">" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + parse_handler.reset(new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(USE_UPLOAD_RATES, upload_required); + ASSERT_EQ(1U, field_infos.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos[0].field_type); + EXPECT_EQ(std::string("FancyNewAlgorithm"), experiment_id); + + field_infos.clear(); + + // Make sure that we can handle parsing both the upload required and the + // experiment id attribute together. + xml = "<autofillqueryresponse uploadrequired=\"false\"" + " experimentid=\"ServerSmartyPants\">" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + parse_handler.reset(new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(UPLOAD_NOT_REQUIRED, upload_required); + ASSERT_EQ(1U, field_infos.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos[0].field_type); + EXPECT_EQ(std::string("ServerSmartyPants"), experiment_id); +} + +// Test XML response with autofill_flow information. +TEST(AutofillQueryXmlParserTest, ParseAutofillFlow) { + std::vector<AutofillServerFieldInfo> field_infos; + UploadRequired upload_required = USE_UPLOAD_RATES; + std::string experiment_id; + + std::string xml = "<autofillqueryresponse>" + "<field autofilltype=\"55\"/>" + "<autofill_flow page_no=\"1\" total_pages=\"10\">" + "<page_advance_button id=\"foo\"/>" + "</autofill_flow>" + "</autofillqueryresponse>"; + + scoped_ptr<AutofillQueryXmlParser> parse_handler( + new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + scoped_ptr<buzz::XmlParser> parser(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(1U, field_infos.size()); + EXPECT_EQ(1, parse_handler->current_page_number()); + EXPECT_EQ(10, parse_handler->total_pages()); + EXPECT_EQ("foo", parse_handler->proceed_element_descriptor()->descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::ID, + parse_handler->proceed_element_descriptor()->retrieval_method); + + // Clear |field_infos| for the next test; + field_infos.clear(); + + // Test css_selector as page_advance_button. + xml = "<autofillqueryresponse>" + "<field autofilltype=\"55\"/>" + "<autofill_flow page_no=\"1\" total_pages=\"10\">" + "<page_advance_button css_selector=\"[name="foo"]\"/>" + "</autofill_flow>" + "</autofillqueryresponse>"; + + parse_handler.reset(new AutofillQueryXmlParser(&field_infos, + &upload_required, + &experiment_id)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(1U, field_infos.size()); + EXPECT_EQ(1, parse_handler->current_page_number()); + EXPECT_EQ(10, parse_handler->total_pages()); + EXPECT_EQ("[name=\"foo\"]", + parse_handler->proceed_element_descriptor()->descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, + parse_handler->proceed_element_descriptor()->retrieval_method); + + // Clear |field_infos| for the next test; + field_infos.clear(); + + // Test first attribute is always the one set. + xml = "<autofillqueryresponse>" + "<field autofilltype=\"55\"/>" + "<autofill_flow page_no=\"1\" total_pages=\"10\">" + "<page_advance_button css_selector=\"[name="foo"]\"" + " id=\"foo\"/>" + "</autofill_flow>" + "</autofillqueryresponse>"; + + parse_handler.reset(new AutofillQueryXmlParser(&field_infos, + &upload_required, + &experiment_id)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(1U, field_infos.size()); + EXPECT_EQ(1, parse_handler->current_page_number()); + EXPECT_EQ(10, parse_handler->total_pages()); + EXPECT_EQ("[name=\"foo\"]", + parse_handler->proceed_element_descriptor()->descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, + parse_handler->proceed_element_descriptor()->retrieval_method); +} + +// Test badly formed XML queries. +TEST(AutofillQueryXmlParserTest, ParseErrors) { + std::vector<AutofillServerFieldInfo> field_infos; + UploadRequired upload_required = USE_UPLOAD_RATES; + std::string experiment_id; + + // Test no Autofill type. + std::string xml = "<autofillqueryresponse>" + "<field/>" + "</autofillqueryresponse>"; + + scoped_ptr<AutofillQueryXmlParser> parse_handler( + new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + scoped_ptr<buzz::XmlParser> parser(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_FALSE(parse_handler->succeeded()); + EXPECT_EQ(USE_UPLOAD_RATES, upload_required); + EXPECT_EQ(0U, field_infos.size()); + EXPECT_EQ(std::string(), experiment_id); + + // Test an incorrect Autofill type. + xml = "<autofillqueryresponse>" + "<field autofilltype=\"-1\"/>" + "</autofillqueryresponse>"; + + parse_handler.reset(new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(USE_UPLOAD_RATES, upload_required); + ASSERT_EQ(1U, field_infos.size()); + // AutofillType was out of range and should be set to NO_SERVER_DATA. + EXPECT_EQ(NO_SERVER_DATA, field_infos[0].field_type); + EXPECT_EQ(std::string(), experiment_id); + + // Test upper bound for the field type, MAX_VALID_FIELD_TYPE. + field_infos.clear(); + xml = "<autofillqueryresponse><field autofilltype=\"" + + base::IntToString(MAX_VALID_FIELD_TYPE) + "\"/></autofillqueryresponse>"; + + parse_handler.reset(new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler->succeeded()); + EXPECT_EQ(USE_UPLOAD_RATES, upload_required); + ASSERT_EQ(1U, field_infos.size()); + // AutofillType was out of range and should be set to NO_SERVER_DATA. + EXPECT_EQ(NO_SERVER_DATA, field_infos[0].field_type); + EXPECT_EQ(std::string(), experiment_id); + + // Test an incorrect Autofill type. + field_infos.clear(); + xml = "<autofillqueryresponse>" + "<field autofilltype=\"No Type\"/>" + "</autofillqueryresponse>"; + + // Parse fails but an entry is still added to field_infos. + parse_handler.reset(new AutofillQueryXmlParser(&field_infos, &upload_required, + &experiment_id)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_FALSE(parse_handler->succeeded()); + EXPECT_EQ(USE_UPLOAD_RATES, upload_required); + ASSERT_EQ(1U, field_infos.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos[0].field_type); + EXPECT_EQ(std::string(), experiment_id); +} + +// Test successfull upload response. +TEST(AutofillUploadXmlParser, TestSuccessfulResponse) { + std::string xml = "<autofilluploadresponse positiveuploadrate=\"0.5\" " + "negativeuploadrate=\"0.3\"/>"; + double positive = 0; + double negative = 0; + AutofillUploadXmlParser parse_handler(&positive, &negative); + buzz::XmlParser parser(&parse_handler); + parser.Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(parse_handler.succeeded()); + EXPECT_DOUBLE_EQ(0.5, positive); + EXPECT_DOUBLE_EQ(0.3, negative); +} + +// Test failed upload response. +TEST(AutofillUploadXmlParser, TestFailedResponse) { + std::string xml = "<autofilluploadresponse positiveuploadrate=\"\" " + "negativeuploadrate=\"0.3\"/>"; + double positive = 0; + double negative = 0; + scoped_ptr<AutofillUploadXmlParser> parse_handler( + new AutofillUploadXmlParser(&positive, &negative)); + scoped_ptr<buzz::XmlParser> parser(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(!parse_handler->succeeded()); + EXPECT_DOUBLE_EQ(0, positive); + EXPECT_DOUBLE_EQ(0.3, negative); // Partially parsed. + negative = 0; + + xml = "<autofilluploadresponse positiveuploadrate=\"0.5\" " + "negativeuploadrate=\"0.3\""; + parse_handler.reset(new AutofillUploadXmlParser(&positive, &negative)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(!parse_handler->succeeded()); + EXPECT_DOUBLE_EQ(0, positive); + EXPECT_DOUBLE_EQ(0, negative); + + xml = "bad data"; + parse_handler.reset(new AutofillUploadXmlParser(&positive, &negative)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(!parse_handler->succeeded()); + EXPECT_DOUBLE_EQ(0, positive); + EXPECT_DOUBLE_EQ(0, negative); + + xml = ""; + parse_handler.reset(new AutofillUploadXmlParser(&positive, &negative)); + parser.reset(new buzz::XmlParser(parse_handler.get())); + parser->Parse(xml.c_str(), xml.length(), true); + EXPECT_TRUE(!parse_handler->succeeded()); + EXPECT_DOUBLE_EQ(0, positive); + EXPECT_DOUBLE_EQ(0, negative); +} + +} // namespace diff --git a/components/autofill/browser/contact_info.cc b/components/autofill/browser/contact_info.cc new file mode 100644 index 0000000..7911ea8 --- /dev/null +++ b/components/autofill/browser/contact_info.cc @@ -0,0 +1,196 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/contact_info.h" + +#include <stddef.h> +#include <ostream> +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/field_types.h" + +static const AutofillFieldType kAutofillNameInfoTypes[] = { + NAME_FIRST, + NAME_MIDDLE, + NAME_LAST +}; + +static const size_t kAutofillNameInfoLength = + arraysize(kAutofillNameInfoTypes); + +NameInfo::NameInfo() {} + +NameInfo::NameInfo(const NameInfo& info) : FormGroup() { + *this = info; +} + +NameInfo::~NameInfo() {} + +NameInfo& NameInfo::operator=(const NameInfo& info) { + if (this == &info) + return *this; + + first_ = info.first_; + middle_ = info.middle_; + last_ = info.last_; + return *this; +} + +void NameInfo::GetSupportedTypes(FieldTypeSet* supported_types) const { + supported_types->insert(NAME_FIRST); + supported_types->insert(NAME_MIDDLE); + supported_types->insert(NAME_LAST); + supported_types->insert(NAME_MIDDLE_INITIAL); + supported_types->insert(NAME_FULL); +} + +string16 NameInfo::GetRawInfo(AutofillFieldType type) const { + if (type == NAME_FIRST) + return first(); + + if (type == NAME_MIDDLE) + return middle(); + + if (type == NAME_LAST) + return last(); + + if (type == NAME_MIDDLE_INITIAL) + return MiddleInitial(); + + if (type == NAME_FULL) + return FullName(); + + return string16(); +} + +void NameInfo::SetRawInfo(AutofillFieldType type, const string16& value) { + DCHECK_EQ(AutofillType::NAME, AutofillType(type).group()); + if (type == NAME_FIRST) + first_ = value; + else if (type == NAME_MIDDLE || type == NAME_MIDDLE_INITIAL) + middle_ = value; + else if (type == NAME_LAST) + last_ = value; + else if (type == NAME_FULL) + SetFullName(value); + else + NOTREACHED(); +} + +string16 NameInfo::FullName() const { + std::vector<string16> full_name; + if (!first_.empty()) + full_name.push_back(first_); + + if (!middle_.empty()) + full_name.push_back(middle_); + + if (!last_.empty()) + full_name.push_back(last_); + + return JoinString(full_name, ' '); +} + +string16 NameInfo::MiddleInitial() const { + if (middle_.empty()) + return string16(); + + string16 middle_name(middle()); + string16 initial; + initial.push_back(middle_name[0]); + return initial; +} + +void NameInfo::SetFullName(const string16& full) { + // Clear the names. + first_ = string16(); + middle_ = string16(); + last_ = string16(); + + std::vector<string16> full_name_tokens; + Tokenize(full, ASCIIToUTF16(" "), &full_name_tokens); + + // There are four possibilities: empty; first name; first and last names; + // first, middle (possibly multiple strings) and then the last name. + if (full_name_tokens.size() > 0) { + first_ = full_name_tokens[0]; + if (full_name_tokens.size() > 1) { + last_ = full_name_tokens.back(); + if (full_name_tokens.size() > 2) { + full_name_tokens.erase(full_name_tokens.begin()); + full_name_tokens.pop_back(); + middle_ = JoinString(full_name_tokens, ' '); + } + } + } +} + +EmailInfo::EmailInfo() {} + +EmailInfo::EmailInfo(const EmailInfo& info) : FormGroup() { + *this = info; +} + +EmailInfo::~EmailInfo() {} + +EmailInfo& EmailInfo::operator=(const EmailInfo& info) { + if (this == &info) + return *this; + + email_ = info.email_; + return *this; +} + +void EmailInfo::GetSupportedTypes(FieldTypeSet* supported_types) const { + supported_types->insert(EMAIL_ADDRESS); +} + +string16 EmailInfo::GetRawInfo(AutofillFieldType type) const { + if (type == EMAIL_ADDRESS) + return email_; + + return string16(); +} + +void EmailInfo::SetRawInfo(AutofillFieldType type, const string16& value) { + DCHECK_EQ(EMAIL_ADDRESS, type); + email_ = value; +} + +CompanyInfo::CompanyInfo() {} + +CompanyInfo::CompanyInfo(const CompanyInfo& info) : FormGroup() { + *this = info; +} + +CompanyInfo::~CompanyInfo() {} + +CompanyInfo& CompanyInfo::operator=(const CompanyInfo& info) { + if (this == &info) + return *this; + + company_name_ = info.company_name_; + return *this; +} + +void CompanyInfo::GetSupportedTypes(FieldTypeSet* supported_types) const { + supported_types->insert(COMPANY_NAME); +} + +string16 CompanyInfo::GetRawInfo(AutofillFieldType type) const { + if (type == COMPANY_NAME) + return company_name_; + + return string16(); +} + +void CompanyInfo::SetRawInfo(AutofillFieldType type, const string16& value) { + DCHECK_EQ(COMPANY_NAME, type); + company_name_ = value; +} diff --git a/components/autofill/browser/contact_info.h b/components/autofill/browser/contact_info.h new file mode 100644 index 0000000..7426627 --- /dev/null +++ b/components/autofill/browser/contact_info.h @@ -0,0 +1,94 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_CONTACT_INFO_H_ +#define COMPONENTS_AUTOFILL_BROWSER_CONTACT_INFO_H_ + +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/string16.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/form_group.h" + +// A form group that stores name information. +class NameInfo : public FormGroup { + public: + NameInfo(); + NameInfo(const NameInfo& info); + virtual ~NameInfo(); + + NameInfo& operator=(const NameInfo& info); + + // FormGroup: + virtual string16 GetRawInfo(AutofillFieldType type) const OVERRIDE; + virtual void SetRawInfo(AutofillFieldType type, + const string16& value) OVERRIDE; + + private: + // FormGroup: + virtual void GetSupportedTypes(FieldTypeSet* supported_types) const OVERRIDE; + + // Returns the full name, which can include up to the first, middle, and last + // name. + string16 FullName() const; + + // Returns the middle initial if |middle_| is non-empty. Returns an empty + // string otherwise. + string16 MiddleInitial() const; + + const string16& first() const { return first_; } + const string16& middle() const { return middle_; } + const string16& last() const { return last_; } + + // Sets |first_|, |middle_|, and |last_| to the tokenized |full|. + // It is tokenized on a space only. + void SetFullName(const string16& full); + + string16 first_; + string16 middle_; + string16 last_; +}; + +class EmailInfo : public FormGroup { + public: + EmailInfo(); + EmailInfo(const EmailInfo& info); + virtual ~EmailInfo(); + + EmailInfo& operator=(const EmailInfo& info); + + // FormGroup: + virtual string16 GetRawInfo(AutofillFieldType type) const OVERRIDE; + virtual void SetRawInfo(AutofillFieldType type, + const string16& value) OVERRIDE; + + private: + // FormGroup: + virtual void GetSupportedTypes(FieldTypeSet* supported_types) const OVERRIDE; + + string16 email_; +}; + +class CompanyInfo : public FormGroup { + public: + CompanyInfo(); + CompanyInfo(const CompanyInfo& info); + virtual ~CompanyInfo(); + + CompanyInfo& operator=(const CompanyInfo& info); + + // FormGroup: + virtual string16 GetRawInfo(AutofillFieldType type) const OVERRIDE; + virtual void SetRawInfo(AutofillFieldType type, + const string16& value) OVERRIDE; + + private: + // FormGroup: + virtual void GetSupportedTypes(FieldTypeSet* supported_types) const OVERRIDE; + + string16 company_name_; +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_CONTACT_INFO_H_ diff --git a/components/autofill/browser/contact_info_unittest.cc b/components/autofill/browser/contact_info_unittest.cc new file mode 100644 index 0000000..377a219 --- /dev/null +++ b/components/autofill/browser/contact_info_unittest.cc @@ -0,0 +1,101 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/contact_info.h" + +#include "base/basictypes.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/field_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(NameInfoTest, SetFullName) { + NameInfo name; + name.SetRawInfo(NAME_FULL, ASCIIToUTF16("Virgil")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("Virgil")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("Virgil")); + + name.SetRawInfo(NAME_FULL, ASCIIToUTF16("Murray Gell-Mann")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("Murray")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), ASCIIToUTF16("Gell-Mann")); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("Murray Gell-Mann")); + + name.SetRawInfo(NAME_FULL, + ASCIIToUTF16("Mikhail Yevgrafovich Saltykov-Shchedrin")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("Mikhail")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), ASCIIToUTF16("Yevgrafovich")); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), ASCIIToUTF16("Saltykov-Shchedrin")); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), + ASCIIToUTF16("Mikhail Yevgrafovich Saltykov-Shchedrin")); + + name.SetRawInfo(NAME_FULL, ASCIIToUTF16("Arthur Ignatius Conan Doyle")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("Arthur")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), ASCIIToUTF16("Ignatius Conan")); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), ASCIIToUTF16("Doyle")); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), + ASCIIToUTF16("Arthur Ignatius Conan Doyle")); +} + +TEST(NameInfoTest, GetFullName) { + NameInfo name; + name.SetRawInfo(NAME_FIRST, ASCIIToUTF16("First")); + name.SetRawInfo(NAME_MIDDLE, string16()); + name.SetRawInfo(NAME_LAST, string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("First")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("First")); + + name.SetRawInfo(NAME_FIRST, string16()); + name.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Middle")); + name.SetRawInfo(NAME_LAST, string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), ASCIIToUTF16("Middle")); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("Middle")); + + name.SetRawInfo(NAME_FIRST, string16()); + name.SetRawInfo(NAME_MIDDLE, string16()); + name.SetRawInfo(NAME_LAST, ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("Last")); + + name.SetRawInfo(NAME_FIRST, ASCIIToUTF16("First")); + name.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Middle")); + name.SetRawInfo(NAME_LAST, string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("First")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), ASCIIToUTF16("Middle")); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("First Middle")); + + name.SetRawInfo(NAME_FIRST, ASCIIToUTF16("First")); + name.SetRawInfo(NAME_MIDDLE, string16()); + name.SetRawInfo(NAME_LAST, ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("First")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("First Last")); + + name.SetRawInfo(NAME_FIRST, string16()); + name.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Middle")); + name.SetRawInfo(NAME_LAST, ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), string16()); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), ASCIIToUTF16("Middle")); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("Middle Last")); + + name.SetRawInfo(NAME_FIRST, ASCIIToUTF16("First")); + name.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Middle")); + name.SetRawInfo(NAME_LAST, ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("First")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), ASCIIToUTF16("Middle")); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("First Middle Last")); +} diff --git a/components/autofill/browser/credit_card.cc b/components/autofill/browser/credit_card.cc new file mode 100644 index 0000000..c93f9a5 --- /dev/null +++ b/components/autofill/browser/credit_card.cc @@ -0,0 +1,678 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/credit_card.h" + +#include <stddef.h> + +#include <ostream> +#include <string> + +#include "base/basictypes.h" +#include "base/guid.h" +#include "base/logging.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_regexes.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/validation.h" +#include "components/autofill/common/form_field_data.h" +#include "grit/generated_resources.h" +#include "grit/webkit_resources.h" +#include "third_party/icu/public/common/unicode/uloc.h" +#include "third_party/icu/public/i18n/unicode/dtfmtsym.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +const char16 kCreditCardObfuscationSymbol = '*'; + +// This is the maximum obfuscated symbols displayed. +// It is introduced to avoid rare cases where the credit card number is +// too large and fills the screen. +const size_t kMaxObfuscationSize = 20; + +std::string GetCreditCardType(const string16& number) { + // Don't check for a specific type if this is not a credit card number. + if (!autofill::IsValidCreditCardNumber(number)) + return kGenericCard; + + // Credit card number specifications taken from: + // http://en.wikipedia.org/wiki/Credit_card_numbers and + // http://www.beachnet.com/~hstiles/cardtype.html + // Card Type Prefix(es) Length + // --------------------------------------------------------------- + // Visa 4 13,16 + // American Express 34,37 15 + // Diners Club 300-305,2014,2149,36, 14,15 + // Discover Card 6011,65 16 + // JCB 3 16 + // JCB 2131,1800 15 + // MasterCard 51-55 16 + // Solo (debit card) 6334,6767 16,18,19 + + // We need at least 4 digits to work with. + if (number.length() < 4) + return kGenericCard; + + int first_four_digits = 0; + if (!base::StringToInt(number.substr(0, 4), &first_four_digits)) + return kGenericCard; + + int first_three_digits = first_four_digits / 10; + int first_two_digits = first_three_digits / 10; + int first_digit = first_two_digits / 10; + + switch (number.length()) { + case 13: + if (first_digit == 4) + return kVisaCard; + + break; + case 14: + if (first_three_digits >= 300 && first_three_digits <= 305) + return kDinersCard; + + if (first_digit == 36) + return kDinersCard; + + break; + case 15: + if (first_two_digits == 34 || first_two_digits == 37) + return kAmericanExpressCard; + + if (first_four_digits == 2131 || first_four_digits == 1800) + return kJCBCard; + + if (first_four_digits == 2014 || first_four_digits == 2149) + return kDinersCard; + + break; + case 16: + if (first_four_digits == 6011 || first_two_digits == 65) + return kDiscoverCard; + + if (first_four_digits == 6334 || first_four_digits == 6767) + return kSoloCard; + + if (first_two_digits >= 51 && first_two_digits <= 55) + return kMasterCard; + + if (first_digit == 3) + return kJCBCard; + + if (first_digit == 4) + return kVisaCard; + + break; + case 18: + case 19: + if (first_four_digits == 6334 || first_four_digits == 6767) + return kSoloCard; + + break; + } + + return kGenericCard; +} + +bool ConvertYear(const string16& year, int* num) { + // If the |year| is empty, clear the stored value. + if (year.empty()) { + *num = 0; + return true; + } + + // Try parsing the |year| as a number. + if (base::StringToInt(year, num)) + return true; + + *num = 0; + return false; +} + +bool ConvertMonth(const string16& month, + const std::string& app_locale, + int* num) { + // If the |month| is empty, clear the stored value. + if (month.empty()) { + *num = 0; + return true; + } + + // Try parsing the |month| as a number. + if (base::StringToInt(month, num)) + return true; + + // If the locale is unknown, give up. + if (app_locale.empty()) + return false; + + // Otherwise, try parsing the |month| as a named month, e.g. "January" or + // "Jan". + string16 lowercased_month = StringToLowerASCII(month); + + UErrorCode status = U_ZERO_ERROR; + icu::Locale locale(app_locale.c_str()); + icu::DateFormatSymbols date_format_symbols(locale, status); + DCHECK(status == U_ZERO_ERROR || status == U_USING_FALLBACK_WARNING || + status == U_USING_DEFAULT_WARNING); + + int32_t num_months; + const icu::UnicodeString* months = date_format_symbols.getMonths(num_months); + for (int32_t i = 0; i < num_months; ++i) { + const string16 icu_month = string16(months[i].getBuffer(), + months[i].length()); + if (lowercased_month == StringToLowerASCII(icu_month)) { + *num = i + 1; // Adjust from 0-indexed to 1-indexed. + return true; + } + } + + months = date_format_symbols.getShortMonths(num_months); + for (int32_t i = 0; i < num_months; ++i) { + const string16 icu_month = string16(months[i].getBuffer(), + months[i].length()); + if (lowercased_month == StringToLowerASCII(icu_month)) { + *num = i + 1; // Adjust from 0-indexed to 1-indexed. + return true; + } + } + + *num = 0; + return false; +} + +} // namespace + +CreditCard::CreditCard(const std::string& guid) + : type_(kGenericCard), + expiration_month_(0), + expiration_year_(0), + guid_(guid) { +} + +CreditCard::CreditCard() + : type_(kGenericCard), + expiration_month_(0), + expiration_year_(0), + guid_(base::GenerateGUID()) { +} + +CreditCard::CreditCard(const CreditCard& credit_card) : FormGroup() { + operator=(credit_card); +} + +CreditCard::~CreditCard() {} + +// static +const string16 CreditCard::StripSeparators(const string16& number) { + const char16 kSeparators[] = {'-', ' ', '\0'}; + string16 stripped; + RemoveChars(number, kSeparators, &stripped); + return stripped; +} + +std::string CreditCard::GetGUID() const { + return guid(); +} + +string16 CreditCard::GetRawInfo(AutofillFieldType type) const { + switch (type) { + case CREDIT_CARD_NAME: + return name_on_card_; + + case CREDIT_CARD_EXP_MONTH: + return ExpirationMonthAsString(); + + case CREDIT_CARD_EXP_2_DIGIT_YEAR: + return Expiration2DigitYearAsString(); + + case CREDIT_CARD_EXP_4_DIGIT_YEAR: + return Expiration4DigitYearAsString(); + + case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: { + string16 month = ExpirationMonthAsString(); + string16 year = Expiration2DigitYearAsString(); + if (!month.empty() && !year.empty()) + return month + ASCIIToUTF16("/") + year; + return string16(); + } + + case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: { + string16 month = ExpirationMonthAsString(); + string16 year = Expiration4DigitYearAsString(); + if (!month.empty() && !year.empty()) + return month + ASCIIToUTF16("/") + year; + return string16(); + } + + case CREDIT_CARD_TYPE: + return TypeForDisplay(); + + case CREDIT_CARD_NUMBER: + return number_; + + case CREDIT_CARD_VERIFICATION_CODE: + // Chrome doesn't store credit card verification codes. + return string16(); + + default: + // ComputeDataPresentForArray will hit this repeatedly. + return string16(); + } +} + +void CreditCard::SetRawInfo(AutofillFieldType type, const string16& value) { + switch (type) { + case CREDIT_CARD_NAME: + name_on_card_ = value; + break; + + case CREDIT_CARD_EXP_MONTH: + SetExpirationMonthFromString(value, std::string()); + break; + + case CREDIT_CARD_EXP_2_DIGIT_YEAR: + // This is a read-only attribute. + break; + + case CREDIT_CARD_EXP_4_DIGIT_YEAR: + SetExpirationYearFromString(value); + break; + + case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: + // This is a read-only attribute. + break; + + case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: + // This is a read-only attribute. + break; + + case CREDIT_CARD_TYPE: + // This is a read-only attribute, determined by the credit card number. + break; + + case CREDIT_CARD_NUMBER: { + // Don't change the real value if the input is an obfuscated string. + if (value.size() > 0 && value[0] != kCreditCardObfuscationSymbol) + SetNumber(value); + break; + } + + case CREDIT_CARD_VERIFICATION_CODE: + // Chrome doesn't store the credit card verification code. + break; + + default: + NOTREACHED() << "Attempting to set unknown info-type " << type; + break; + } +} + +string16 CreditCard::GetInfo(AutofillFieldType type, + const std::string& app_locale) const { + if (type == CREDIT_CARD_NUMBER) + return StripSeparators(number_); + + return GetRawInfo(type); +} + +bool CreditCard::SetInfo(AutofillFieldType type, + const string16& value, + const std::string& app_locale) { + if (type == CREDIT_CARD_NUMBER) + SetRawInfo(type, StripSeparators(value)); + else if (type == CREDIT_CARD_EXP_MONTH) + SetExpirationMonthFromString(value, app_locale); + else + SetRawInfo(type, value); + + return true; +} + +void CreditCard::GetMatchingTypes(const string16& text, + const std::string& app_locale, + FieldTypeSet* matching_types) const { + FormGroup::GetMatchingTypes(text, app_locale, matching_types); + + string16 card_number = GetInfo(CREDIT_CARD_NUMBER, app_locale); + if (!card_number.empty() && StripSeparators(text) == card_number) + matching_types->insert(CREDIT_CARD_NUMBER); + + int month; + if (ConvertMonth(text, app_locale, &month) && month != 0 && + month == expiration_month_) { + matching_types->insert(CREDIT_CARD_EXP_MONTH); + } +} + +const string16 CreditCard::Label() const { + string16 label; + if (number().empty()) + return name_on_card_; // No CC number, return name only. + + string16 obfuscated_cc_number = ObfuscatedNumber(); + if (!expiration_month_ || !expiration_year_) + return obfuscated_cc_number; // No expiration date set. + + // TODO(georgey): Internationalize date. + string16 formatted_date(ExpirationMonthAsString()); + formatted_date.append(ASCIIToUTF16("/")); + formatted_date.append(Expiration4DigitYearAsString()); + + label = l10n_util::GetStringFUTF16(IDS_CREDIT_CARD_NUMBER_PREVIEW_FORMAT, + obfuscated_cc_number, + formatted_date); + return label; +} + +void CreditCard::SetInfoForMonthInputType(const string16& value) { + // Check if |text| is "yyyy-mm" format first, and check normal month format. + if (!autofill::MatchesPattern(value, UTF8ToUTF16("^[0-9]{4}-[0-9]{1,2}$"))) + return; + + std::vector<string16> year_month; + base::SplitString(value, L'-', &year_month); + DCHECK_EQ((int)year_month.size(), 2); + int num = 0; + bool converted = false; + converted = base::StringToInt(year_month[0], &num); + DCHECK(converted); + SetExpirationYear(num); + converted = base::StringToInt(year_month[1], &num); + DCHECK(converted); + SetExpirationMonth(num); +} + +string16 CreditCard::ObfuscatedNumber() const { + // If the number is shorter than four digits, there's no need to obfuscate it. + if (number_.size() < 4) + return number_; + + string16 number = StripSeparators(number_); + + // Avoid making very long obfuscated numbers. + size_t obfuscated_digits = std::min(kMaxObfuscationSize, number.size() - 4); + string16 result(obfuscated_digits, kCreditCardObfuscationSymbol); + return result.append(LastFourDigits()); +} + +string16 CreditCard::LastFourDigits() const { + static const size_t kNumLastDigits = 4; + + string16 number = StripSeparators(number_); + if (number.size() < kNumLastDigits) + return string16(); + + return number.substr(number.size() - kNumLastDigits, kNumLastDigits); +} + +string16 CreditCard::TypeForDisplay() const { + if (type_ == kAmericanExpressCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_AMEX); + if (type_ == kDinersCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DINERS); + if (type_ == kDiscoverCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_DISCOVER); + if (type_ == kJCBCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_JCB); + if (type_ == kMasterCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_MASTERCARD); + if (type_ == kSoloCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_SOLO); + if (type_ == kVisaCard) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_VISA); + + // If you hit this DCHECK, the above list of cases needs to be updated to + // include a new card. + DCHECK_EQ(kGenericCard, type_); + return string16(); +} + +string16 CreditCard::TypeAndLastFourDigits() const { + string16 type = TypeForDisplay(); + // TODO(estade): type may be empty, we probably want to return + // "Card - 1234" or something in that case. + + string16 digits = LastFourDigits(); + if (digits.empty()) + return type; + + // TODO(estade): i18n. + return type + ASCIIToUTF16(" - ") + digits; +} + +int CreditCard::IconResourceId() const { + if (type_ == kAmericanExpressCard) + return IDR_AUTOFILL_CC_AMEX; + if (type_ == kDinersCard) + return IDR_AUTOFILL_CC_DINERS; + if (type_ == kDiscoverCard) + return IDR_AUTOFILL_CC_DISCOVER; + if (type_ == kJCBCard) + return IDR_AUTOFILL_CC_JCB; + if (type_ == kMasterCard) + return IDR_AUTOFILL_CC_MASTERCARD; + if (type_ == kSoloCard) + return IDR_AUTOFILL_CC_SOLO; + if (type_ == kVisaCard) + return IDR_AUTOFILL_CC_VISA; + + // If you hit this DCHECK, the above list of cases needs to be updated to + // include a new card. + DCHECK_EQ(kGenericCard, type_); + return IDR_AUTOFILL_CC_GENERIC; +} + +void CreditCard::operator=(const CreditCard& credit_card) { + if (this == &credit_card) + return; + + number_ = credit_card.number_; + name_on_card_ = credit_card.name_on_card_; + type_ = credit_card.type_; + expiration_month_ = credit_card.expiration_month_; + expiration_year_ = credit_card.expiration_year_; + guid_ = credit_card.guid_; +} + +bool CreditCard::UpdateFromImportedCard(const CreditCard& imported_card, + const std::string& app_locale) { + if (this->GetInfo(CREDIT_CARD_NUMBER, app_locale) != + imported_card.GetInfo(CREDIT_CARD_NUMBER, app_locale)) { + return false; + } + + // Note that the card number is intentionally not updated, so as to preserve + // any formatting (i.e. separator characters). Since the card number is not + // updated, there is no reason to update the card type, either. + if (!imported_card.name_on_card_.empty()) + name_on_card_ = imported_card.name_on_card_; + + // The expiration date for |imported_card| should always be set. + DCHECK(imported_card.expiration_month_ && imported_card.expiration_year_); + expiration_month_ = imported_card.expiration_month_; + expiration_year_ = imported_card.expiration_year_; + + return true; +} + +void CreditCard::FillFormField(const AutofillField& field, + size_t /*variant*/, + FormFieldData* field_data) const { + DCHECK_EQ(AutofillType::CREDIT_CARD, AutofillType(field.type()).group()); + DCHECK(field_data); + + const std::string app_locale = AutofillCountry::ApplicationLocale(); + + if (field_data->form_control_type == "select-one") { + FillSelectControl(field.type(), field_data); + } else if (field_data->form_control_type == "month") { + // HTML5 input="month" consists of year-month. + string16 year = GetInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, app_locale); + string16 month = GetInfo(CREDIT_CARD_EXP_MONTH, app_locale); + if (!year.empty() && !month.empty()) { + // Fill the value only if |this| includes both year and month + // information. + field_data->value = year + ASCIIToUTF16("-") + month; + } + } else { + field_data->value = GetInfo(field.type(), app_locale); + } +} + +int CreditCard::Compare(const CreditCard& credit_card) const { + // The following CreditCard field types are the only types we store in the + // WebDB so far, so we're only concerned with matching these types in the + // credit card. + const AutofillFieldType types[] = { CREDIT_CARD_NAME, + CREDIT_CARD_NUMBER, + CREDIT_CARD_EXP_MONTH, + CREDIT_CARD_EXP_4_DIGIT_YEAR }; + for (size_t index = 0; index < arraysize(types); ++index) { + int comparison = GetRawInfo(types[index]).compare( + credit_card.GetRawInfo(types[index])); + if (comparison != 0) + return comparison; + } + + return 0; +} + +bool CreditCard::operator==(const CreditCard& credit_card) const { + if (guid_ != credit_card.guid_) + return false; + + return Compare(credit_card) == 0; +} + +bool CreditCard::operator!=(const CreditCard& credit_card) const { + return !operator==(credit_card); +} + +bool CreditCard::IsEmpty() const { + FieldTypeSet types; + GetNonEmptyTypes(AutofillCountry::ApplicationLocale(), &types); + return types.empty(); +} + +bool CreditCard::IsComplete() const { + return + autofill::IsValidCreditCardNumber(number_) && + expiration_month_ != 0 && + expiration_year_ != 0; +} + +void CreditCard::GetSupportedTypes(FieldTypeSet* supported_types) const { + supported_types->insert(CREDIT_CARD_NAME); + supported_types->insert(CREDIT_CARD_NUMBER); + supported_types->insert(CREDIT_CARD_TYPE); + supported_types->insert(CREDIT_CARD_EXP_MONTH); + supported_types->insert(CREDIT_CARD_EXP_2_DIGIT_YEAR); + supported_types->insert(CREDIT_CARD_EXP_4_DIGIT_YEAR); + supported_types->insert(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR); + supported_types->insert(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR); +} + +string16 CreditCard::ExpirationMonthAsString() const { + if (expiration_month_ == 0) + return string16(); + + string16 month = base::IntToString16(expiration_month_); + if (expiration_month_ >= 10) + return month; + + string16 zero = ASCIIToUTF16("0"); + zero.append(month); + return zero; +} + +string16 CreditCard::Expiration4DigitYearAsString() const { + if (expiration_year_ == 0) + return string16(); + + return base::IntToString16(Expiration4DigitYear()); +} + +string16 CreditCard::Expiration2DigitYearAsString() const { + if (expiration_year_ == 0) + return string16(); + + return base::IntToString16(Expiration2DigitYear()); +} + +void CreditCard::SetExpirationMonthFromString(const string16& text, + const std::string& app_locale) { + int month; + if (!ConvertMonth(text, app_locale, &month)) + return; + + SetExpirationMonth(month); +} + +void CreditCard::SetExpirationYearFromString(const string16& text) { + int year; + if (!ConvertYear(text, &year)) + return; + + SetExpirationYear(year); +} + +void CreditCard::SetNumber(const string16& number) { + number_ = number; + type_ = GetCreditCardType(StripSeparators(number_)); +} + +void CreditCard::SetExpirationMonth(int expiration_month) { + if (expiration_month < 0 || expiration_month > 12) + return; + + expiration_month_ = expiration_month; +} + +void CreditCard::SetExpirationYear(int expiration_year) { + if (expiration_year != 0 && + (expiration_year < 2006 || expiration_year > 10000)) { + return; + } + + expiration_year_ = expiration_year; +} + +// So we can compare CreditCards with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const CreditCard& credit_card) { + return os + << UTF16ToUTF8(credit_card.Label()) + << " " + << credit_card.guid() + << " " + << UTF16ToUTF8(credit_card.GetRawInfo(CREDIT_CARD_NAME)) + << " " + << UTF16ToUTF8(credit_card.GetRawInfo(CREDIT_CARD_TYPE)) + << " " + << UTF16ToUTF8(credit_card.GetRawInfo(CREDIT_CARD_NUMBER)) + << " " + << UTF16ToUTF8(credit_card.GetRawInfo(CREDIT_CARD_EXP_MONTH)) + << " " + << UTF16ToUTF8(credit_card.GetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR)); +} + +// These values must match the values in WebKitPlatformSupportImpl in +// webkit/glue. We send these strings to WebKit, which then asks +// WebKitPlatformSupportImpl to load the image data. +const char* const kAmericanExpressCard = "americanExpressCC"; +const char* const kDinersCard = "dinersCC"; +const char* const kDiscoverCard = "discoverCC"; +const char* const kGenericCard = "genericCC"; +const char* const kJCBCard = "jcbCC"; +const char* const kMasterCard = "masterCardCC"; +const char* const kSoloCard = "soloCC"; +const char* const kVisaCard = "visaCC"; diff --git a/components/autofill/browser/credit_card.h b/components/autofill/browser/credit_card.h new file mode 100644 index 0000000..50700ad --- /dev/null +++ b/components/autofill/browser/credit_card.h @@ -0,0 +1,153 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_CREDIT_CARD_H_ +#define COMPONENTS_AUTOFILL_BROWSER_CREDIT_CARD_H_ + +#include <iosfwd> +#include <string> +#include <vector> + +#include "base/string16.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/form_group.h" + +struct FormFieldData; + +// A form group that stores credit card information. +class CreditCard : public FormGroup { + public: + explicit CreditCard(const std::string& guid); + + // For use in STL containers. + CreditCard(); + CreditCard(const CreditCard& credit_card); + virtual ~CreditCard(); + + // Returns a version of |number| that has any separator characters removed. + static const string16 StripSeparators(const string16& number); + + // FormGroup implementation: + virtual std::string GetGUID() const OVERRIDE; + virtual void GetMatchingTypes(const string16& text, + const std::string& app_locale, + FieldTypeSet* matching_types) const OVERRIDE; + virtual string16 GetRawInfo(AutofillFieldType type) const OVERRIDE; + virtual void SetRawInfo(AutofillFieldType type, + const string16& value) OVERRIDE; + virtual string16 GetInfo(AutofillFieldType type, + const std::string& app_locale) const OVERRIDE; + virtual bool SetInfo(AutofillFieldType type, + const string16& value, + const std::string& app_locale) OVERRIDE; + virtual void FillFormField(const AutofillField& field, + size_t variant, + FormFieldData* field_data) const OVERRIDE; + + // Credit card preview summary, for example: ******1234, Exp: 01/2020 + const string16 Label() const; + + // Special method to set value for HTML5 month input type. + void SetInfoForMonthInputType(const string16& value); + + // The number altered for display, for example: ******1234 + string16 ObfuscatedNumber() const; + // The last four digits of the credit card number. + string16 LastFourDigits() const; + // The user-visible type of the card, e.g. 'Mastercard'. + string16 TypeForDisplay() const; + // A label for this credit card formatted as 'Cardname - 2345'. + string16 TypeAndLastFourDigits() const; + // The ResourceBundle ID for the appropriate credit card image. + int IconResourceId() const; + + const std::string& type() const { return type_; } + + // The guid is the primary identifier for |CreditCard| objects. + // TODO(estade): remove this and just use GetGUID(). + const std::string guid() const { return guid_; } + void set_guid(const std::string& guid) { guid_ = guid; } + + // For use in STL containers. + void operator=(const CreditCard& credit_card); + + // If the card numbers for |this| and |imported_card| match, overwrites |this| + // card's data with the data in |credit_card| and returns true. Otherwise, + // returns false. + bool UpdateFromImportedCard(const CreditCard& imported_card, + const std::string& app_locale) WARN_UNUSED_RESULT; + + // Comparison for Sync. Returns 0 if the credit card is the same as |this|, + // or < 0, or > 0 if it is different. The implied ordering can be used for + // culling duplicates. The ordering is based on collation order of the + // textual contents of the fields. + // GUIDs, labels, and unique IDs are not compared, only the values of the + // credit cards themselves. + int Compare(const CreditCard& credit_card) const; + + // Used by tests. + bool operator==(const CreditCard& credit_card) const; + bool operator!=(const CreditCard& credit_card) const; + + // Returns true if there are no values (field types) set. + bool IsEmpty() const; + + // Returns true if all field types have valid values set. + bool IsComplete() const; + + // Returns the credit card number. + const string16& number() const { return number_; } + + private: + // FormGroup: + virtual void GetSupportedTypes(FieldTypeSet* supported_types) const OVERRIDE; + + // The month and year are zero if not present. + int Expiration4DigitYear() const { return expiration_year_; } + int Expiration2DigitYear() const { return expiration_year_ % 100; } + string16 ExpirationMonthAsString() const; + string16 Expiration4DigitYearAsString() const; + string16 Expiration2DigitYearAsString() const; + + // Sets |expiration_month_| to the integer conversion of |text|. + void SetExpirationMonthFromString(const string16& text, + const std::string& app_locale); + + // Sets |expiration_year_| to the integer conversion of |text|. + void SetExpirationYearFromString(const string16& text); + + // Sets |number_| to |number| and computes the appropriate card |type_|. + void SetNumber(const string16& number); + + // These setters verify that the month and year are within appropriate + // ranges. + void SetExpirationMonth(int expiration_month); + void SetExpirationYear(int expiration_year); + + string16 number_; // The credit card number. + string16 name_on_card_; // The cardholder's name. + std::string type_; // The type of the card. + + // These members are zero if not present. + int expiration_month_; + int expiration_year_; + + // The guid of this credit card. + std::string guid_; +}; + +// So we can compare CreditCards with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const CreditCard& credit_card); + +// The string identifiers for credit card icon resources. +extern const char* const kAmericanExpressCard; +extern const char* const kDinersCard; +extern const char* const kDiscoverCard; +extern const char* const kGenericCard; +extern const char* const kJCBCard; +extern const char* const kMasterCard; +extern const char* const kSoloCard; +extern const char* const kVisaCard; + +#endif // COMPONENTS_AUTOFILL_BROWSER_CREDIT_CARD_H_ diff --git a/components/autofill/browser/credit_card_field.cc b/components/autofill/browser/credit_card_field.cc new file mode 100644 index 0000000..8baa9f1 --- /dev/null +++ b/components/autofill/browser/credit_card_field.cc @@ -0,0 +1,229 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/credit_card_field.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_regex_constants.h" +#include "components/autofill/browser/autofill_scanner.h" +#include "components/autofill/browser/field_types.h" +#include "ui/base/l10n/l10n_util.h" + +// static +FormField* CreditCardField::Parse(AutofillScanner* scanner) { + if (scanner->IsEnd()) + return NULL; + + scoped_ptr<CreditCardField> credit_card_field(new CreditCardField); + size_t saved_cursor = scanner->SaveCursor(); + + // Credit card fields can appear in many different orders. + // We loop until no more credit card related fields are found, see |break| at + // bottom of the loop. + for (int fields = 0; !scanner->IsEnd(); ++fields) { + // Ignore gift card fields. + if (ParseField(scanner, UTF8ToUTF16(autofill::kGiftCardRe), NULL)) + break; + + // Sometimes the cardholder field is just labeled "name". Unfortunately this + // is a dangerously generic word to search for, since it will often match a + // name (not cardholder name) field before or after credit card fields. So + // we search for "name" only when we've already parsed at least one other + // credit card field and haven't yet parsed the expiration date (which + // usually appears at the end). + if (credit_card_field->cardholder_ == NULL) { + string16 name_pattern; + if (fields == 0 || credit_card_field->expiration_month_) { + // at beginning or end + name_pattern = UTF8ToUTF16(autofill::kNameOnCardRe); + } else { + name_pattern = UTF8ToUTF16(autofill::kNameOnCardContextualRe); + } + + if (ParseField(scanner, name_pattern, &credit_card_field->cardholder_)) + continue; + + // As a hard-coded hack for Expedia's billing pages (expedia_checkout.html + // and ExpediaBilling.html in our test suite), recognize separate fields + // for the cardholder's first and last name if they have the labels "cfnm" + // and "clnm". + scanner->SaveCursor(); + const AutofillField* first; + if (ParseField(scanner, ASCIIToUTF16("^cfnm"), &first) && + ParseField(scanner, ASCIIToUTF16("^clnm"), + &credit_card_field->cardholder_last_)) { + credit_card_field->cardholder_ = first; + continue; + } + scanner->Rewind(); + } + + // Check for a credit card type (Visa, MasterCard, etc.) field. + string16 type_pattern = UTF8ToUTF16(autofill::kCardTypeRe); + if (!credit_card_field->type_ && + ParseFieldSpecifics(scanner, type_pattern, + MATCH_DEFAULT | MATCH_SELECT, + &credit_card_field->type_)) { + continue; + } + + // We look for a card security code before we look for a credit + // card number and match the general term "number". The security code + // has a plethora of names; we've seen "verification #", + // "verification number", "card identification number" and others listed + // in the |pattern| below. + string16 pattern = UTF8ToUTF16(autofill::kCardCvcRe); + if (!credit_card_field->verification_ && + ParseField(scanner, pattern, &credit_card_field->verification_)) { + continue; + } + + pattern = UTF8ToUTF16(autofill::kCardNumberRe); + if (!credit_card_field->number_ && + ParseField(scanner, pattern, &credit_card_field->number_)) { + continue; + } + + if (LowerCaseEqualsASCII(scanner->Cursor()->form_control_type, "month")) { + credit_card_field->expiration_month_ = scanner->Cursor(); + scanner->Advance(); + } else { + // First try to parse split month/year expiration fields. + scanner->SaveCursor(); + pattern = UTF8ToUTF16(autofill::kExpirationMonthRe); + if (!credit_card_field->expiration_month_ && + ParseFieldSpecifics(scanner, pattern, MATCH_DEFAULT | MATCH_SELECT, + &credit_card_field->expiration_month_)) { + pattern = UTF8ToUTF16(autofill::kExpirationYearRe); + if (ParseFieldSpecifics(scanner, pattern, MATCH_DEFAULT | MATCH_SELECT, + &credit_card_field->expiration_year_)) { + continue; + } + } + + // If that fails, try to parse a combined expiration field. + if (!credit_card_field->expiration_date_) { + // Look for a 2-digit year first. + scanner->Rewind(); + pattern = UTF8ToUTF16(autofill::kExpirationDate2DigitYearRe); + // We allow <select> fields, because they're used e.g. on qvc.com. + if (ParseFieldSpecifics(scanner, pattern, + MATCH_LABEL | MATCH_VALUE | MATCH_TEXT | + MATCH_SELECT, + &credit_card_field->expiration_date_)) { + credit_card_field->is_two_digit_year_ = true; + continue; + } + + pattern = UTF8ToUTF16(autofill::kExpirationDateRe); + if (ParseFieldSpecifics(scanner, pattern, + MATCH_LABEL | MATCH_VALUE | MATCH_TEXT | + MATCH_SELECT, + &credit_card_field->expiration_date_)) { + continue; + } + } + + if (credit_card_field->expiration_month_ && + !credit_card_field->expiration_year_ && + !credit_card_field->expiration_date_) { + // Parsed a month but couldn't parse a year; give up. + scanner->RewindTo(saved_cursor); + return NULL; + } + } + + // Some pages (e.g. ExpediaBilling.html) have a "card description" + // field; we parse this field but ignore it. + // We also ignore any other fields within a credit card block that + // start with "card", under the assumption that they are related to + // the credit card section being processed but are uninteresting to us. + if (ParseField(scanner, UTF8ToUTF16(autofill::kCardIgnoredRe), NULL)) + continue; + + break; + } + + // Some pages have a billing address field after the cardholder name field. + // For that case, allow only just the cardholder name field. The remaining + // CC fields will be picked up in a following CreditCardField. + if (credit_card_field->cardholder_) + return credit_card_field.release(); + + // On some pages, the user selects a card type using radio buttons + // (e.g. test page Apple Store Billing.html). We can't handle that yet, + // so we treat the card type as optional for now. + // The existence of a number or cvc in combination with expiration date is + // a strong enough signal that this is a credit card. It is possible that + // the number and name were parsed in a separate part of the form. So if + // the cvc and date were found independently they are returned. + if ((credit_card_field->number_ || credit_card_field->verification_) && + (credit_card_field->expiration_date_ || + (credit_card_field->expiration_month_ && + (credit_card_field->expiration_year_ || + (LowerCaseEqualsASCII( + credit_card_field->expiration_month_->form_control_type, + "month")))))) { + return credit_card_field.release(); + } + + scanner->RewindTo(saved_cursor); + return NULL; +} + +CreditCardField::CreditCardField() + : cardholder_(NULL), + cardholder_last_(NULL), + type_(NULL), + number_(NULL), + verification_(NULL), + expiration_month_(NULL), + expiration_year_(NULL), + expiration_date_(NULL), + is_two_digit_year_(false) { +} + +bool CreditCardField::ClassifyField(FieldTypeMap* map) const { + bool ok = AddClassification(number_, CREDIT_CARD_NUMBER, map); + ok = ok && AddClassification(type_, CREDIT_CARD_TYPE, map); + ok = ok && AddClassification(verification_, CREDIT_CARD_VERIFICATION_CODE, + map); + + // If the heuristics detected first and last name in separate fields, + // then ignore both fields. Putting them into separate fields is probably + // wrong, because the credit card can also contain a middle name or middle + // initial. + if (cardholder_last_ == NULL) + ok = ok && AddClassification(cardholder_, CREDIT_CARD_NAME, map); + + if (expiration_date_) { + if (is_two_digit_year_) { + ok = ok && AddClassification(expiration_date_, + CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, map); + } else { + ok = ok && AddClassification(expiration_date_, + CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR, map); + } + } else { + ok = ok && AddClassification(expiration_month_, CREDIT_CARD_EXP_MONTH, map); + if (is_two_digit_year_) { + ok = ok && AddClassification(expiration_year_, + CREDIT_CARD_EXP_2_DIGIT_YEAR, + map); + } else { + ok = ok && AddClassification(expiration_year_, + CREDIT_CARD_EXP_4_DIGIT_YEAR, + map); + } + } + + return ok; +} diff --git a/components/autofill/browser/credit_card_field.h b/components/autofill/browser/credit_card_field.h new file mode 100644 index 0000000..777aa16 --- /dev/null +++ b/components/autofill/browser/credit_card_field.h @@ -0,0 +1,71 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_CREDIT_CARD_FIELD_H_ +#define COMPONENTS_AUTOFILL_BROWSER_CREDIT_CARD_FIELD_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/form_field.h" + +class AutofillField; +class AutofillScanner; + +class CreditCardField : public FormField { + public: + static FormField* Parse(AutofillScanner* scanner); + + protected: + // FormField: + virtual bool ClassifyField(FieldTypeMap* map) const OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(CreditCardFieldTest, ParseMiniumCreditCard); + FRIEND_TEST_ALL_PREFIXES(CreditCardFieldTest, ParseFullCreditCard); + FRIEND_TEST_ALL_PREFIXES(CreditCardFieldTest, ParseCreditCardType); + FRIEND_TEST_ALL_PREFIXES(CreditCardFieldTest, ParseExpMonthYear); + FRIEND_TEST_ALL_PREFIXES(CreditCardFieldTest, ParseExpMonthYear2); + FRIEND_TEST_ALL_PREFIXES(CreditCardFieldTest, ParseExpField); + FRIEND_TEST_ALL_PREFIXES(CreditCardFieldTest, ParseExpField2DigitYear); + FRIEND_TEST_ALL_PREFIXES(CreditCardFieldTest, + ParseCreditCardHolderNameWithCCFullName); + + CreditCardField(); + + const AutofillField* cardholder_; // Optional. + + // Occasionally pages have separate fields for the cardholder's first and + // last names; for such pages cardholder_ holds the first name field and + // we store the last name field here. + // (We could store an embedded NameField object here, but we don't do so + // because the text patterns for matching a cardholder name are different + // than for ordinary names, and because cardholder names never have titles, + // middle names or suffixes.) + const AutofillField* cardholder_last_; + + // TODO(jhawkins): Parse the select control. + const AutofillField* type_; // Optional. + const AutofillField* number_; // Required. + + // The 3-digit card verification number; we don't currently fill this. + const AutofillField* verification_; + + // Either |expiration_date_| or both |expiration_month_| and + // |expiration_year_| are required. + const AutofillField* expiration_month_; + const AutofillField* expiration_year_; + const AutofillField* expiration_date_; + + // True if the year is detected to be a 2-digit year; otherwise, we assume + // a 4-digit year. + bool is_two_digit_year_; + + DISALLOW_COPY_AND_ASSIGN(CreditCardField); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_CREDIT_CARD_FIELD_H_ diff --git a/components/autofill/browser/credit_card_field_unittest.cc b/components/autofill/browser/credit_card_field_unittest.cc new file mode 100644 index 0000000..b077b70 --- /dev/null +++ b/components/autofill/browser/credit_card_field_unittest.cc @@ -0,0 +1,318 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_scanner.h" +#include "components/autofill/browser/credit_card_field.h" +#include "components/autofill/common/form_field_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +class CreditCardFieldTest : public testing::Test { + public: + CreditCardFieldTest() {} + + protected: + ScopedVector<const AutofillField> list_; + scoped_ptr<CreditCardField> field_; + FieldTypeMap field_type_map_; + + // Downcast for tests. + static CreditCardField* Parse(AutofillScanner* scanner) { + return static_cast<CreditCardField*>(CreditCardField::Parse(scanner)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(CreditCardFieldTest); +}; + +TEST_F(CreditCardFieldTest, Empty) { + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_EQ(static_cast<CreditCardField*>(NULL), field_.get()); +} + +TEST_F(CreditCardFieldTest, NonParse) { + list_.push_back(new AutofillField); + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_EQ(static_cast<CreditCardField*>(NULL), field_.get()); +} + +TEST_F(CreditCardFieldTest, ParseCreditCardNoNumber) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Exp Month"); + field.name = ASCIIToUTF16("ccmonth"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("month1"))); + + field.label = ASCIIToUTF16("Exp Year"); + field.name = ASCIIToUTF16("ccyear"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("year2"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_EQ(static_cast<CreditCardField*>(NULL), field_.get()); +} + +TEST_F(CreditCardFieldTest, ParseCreditCardNoDate) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("number1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_EQ(static_cast<CreditCardField*>(NULL), field_.get()); +} + +TEST_F(CreditCardFieldTest, ParseMiniumCreditCard) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("number1"))); + + field.label = ASCIIToUTF16("Exp Month"); + field.name = ASCIIToUTF16("ccmonth"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("month2"))); + + field.label = ASCIIToUTF16("Exp Year"); + field.name = ASCIIToUTF16("ccyear"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("year3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<CreditCardField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("number1")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NUMBER, field_type_map_[ASCIIToUTF16("number1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("month2")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_MONTH, field_type_map_[ASCIIToUTF16("month2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("year3")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR, + field_type_map_[ASCIIToUTF16("year3")]); +} + +TEST_F(CreditCardFieldTest, ParseFullCreditCard) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Card Type"); + field.name = ASCIIToUTF16("card_type"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("type"))); + + field.label = ASCIIToUTF16("Name on Card"); + field.name = ASCIIToUTF16("name_on_card"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name"))); + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("number"))); + + field.label = ASCIIToUTF16("Exp Month"); + field.name = ASCIIToUTF16("ccmonth"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("month"))); + + field.label = ASCIIToUTF16("Exp Year"); + field.name = ASCIIToUTF16("ccyear"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("year"))); + + field.label = ASCIIToUTF16("Verification"); + field.name = ASCIIToUTF16("verification"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("cvc"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<CreditCardField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("type")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_TYPE, field_type_map_[ASCIIToUTF16("type")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NAME, field_type_map_[ASCIIToUTF16("name")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("number")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NUMBER, field_type_map_[ASCIIToUTF16("number")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("month")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_MONTH, field_type_map_[ASCIIToUTF16("month")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("year")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR, + field_type_map_[ASCIIToUTF16("year")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("cvc")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_VERIFICATION_CODE, + field_type_map_[ASCIIToUTF16("cvc")]); +} + +TEST_F(CreditCardFieldTest, ParseExpMonthYear) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name on Card"); + field.name = ASCIIToUTF16("name_on_card"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("number2"))); + + field.label = ASCIIToUTF16("ExpDate Month / Year"); + field.name = ASCIIToUTF16("ExpDate"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("month3"))); + + field.label = ASCIIToUTF16("ExpDate Month / Year"); + field.name = ASCIIToUTF16("ExpDate"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("year4"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<CreditCardField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NAME, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("number2")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NUMBER, field_type_map_[ASCIIToUTF16("number2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("month3")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_MONTH, field_type_map_[ASCIIToUTF16("month3")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("year4")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR, + field_type_map_[ASCIIToUTF16("year4")]); +} + +TEST_F(CreditCardFieldTest, ParseExpMonthYear2) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name on Card"); + field.name = ASCIIToUTF16("name_on_card"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("number2"))); + + field.label = ASCIIToUTF16("Expiration date Month / Year"); + field.name = ASCIIToUTF16("ExpDate"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("month3"))); + + field.label = ASCIIToUTF16("Expiration date Month / Year"); + field.name = ASCIIToUTF16("ExpDate"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("year4"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<CreditCardField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NAME, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("number2")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NUMBER, field_type_map_[ASCIIToUTF16("number2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("month3")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_MONTH, field_type_map_[ASCIIToUTF16("month3")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("year4")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR, + field_type_map_[ASCIIToUTF16("year4")]); +} + +TEST_F(CreditCardFieldTest, ParseExpField) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name on Card"); + field.name = ASCIIToUTF16("name_on_card"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("number2"))); + + field.label = ASCIIToUTF16("Expiration Date (MM/YYYY)"); + field.name = ASCIIToUTF16("cc_exp"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("exp3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<CreditCardField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NAME, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("number2")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NUMBER, field_type_map_[ASCIIToUTF16("number2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("exp3")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR, + field_type_map_[ASCIIToUTF16("exp3")]); +} + +TEST_F(CreditCardFieldTest, ParseExpField2DigitYear) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name on Card"); + field.name = ASCIIToUTF16("name_on_card"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("number2"))); + + field.label = ASCIIToUTF16("Expiration Date (MM/YY)"); + field.name = ASCIIToUTF16("cc_exp"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("exp3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<CreditCardField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NAME, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("number2")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NUMBER, field_type_map_[ASCIIToUTF16("number2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("exp3")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, + field_type_map_[ASCIIToUTF16("exp3")]); +} + +TEST_F(CreditCardFieldTest, ParseCreditCardHolderNameWithCCFullName) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name"); + field.name = ASCIIToUTF16("ccfullname"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<CreditCardField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(CREDIT_CARD_NAME, field_type_map_[ASCIIToUTF16("name1")]); +} diff --git a/components/autofill/browser/credit_card_unittest.cc b/components/autofill/browser/credit_card_unittest.cc new file mode 100644 index 0000000..0da4c13 --- /dev/null +++ b/components/autofill/browser/credit_card_unittest.cc @@ -0,0 +1,359 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/basictypes.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_common_test.h" +#include "components/autofill/browser/credit_card.h" +#include "components/autofill/common/form_field_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// From https://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm +const char* const kValidNumbers[] = { + "378282246310005", + "3714 4963 5398 431", + "3787-3449-3671-000", + "5610591081018250", + "3056 9309 0259 04", + "3852-0000-0232-37", + "6011111111111117", + "6011 0009 9013 9424", + "3530-1113-3330-0000", + "3566002020360505", + "5555 5555 5555 4444", + "5105-1051-0510-5100", + "4111111111111111", + "4012 8888 8888 1881", + "4222-2222-2222-2", + "5019717010103742", + "6331101999990016", +}; +const char* const kInvalidNumbers[] = { + "4111 1111 112", /* too short */ + "41111111111111111115", /* too long */ + "4111-1111-1111-1110", /* wrong Luhn checksum */ + "3056 9309 0259 04aa", /* non-digit characters */ +}; + +} // namespace + +// Tests credit card summary string generation. This test simulates a variety +// of different possible summary strings. Variations occur based on the +// existence of credit card number, month, and year fields. +TEST(CreditCardTest, PreviewSummaryAndObfuscatedNumberStrings) { + // Case 0: empty credit card. + CreditCard credit_card0; + string16 summary0 = credit_card0.Label(); + EXPECT_EQ(string16(), summary0); + string16 obfuscated0 = credit_card0.ObfuscatedNumber(); + EXPECT_EQ(string16(), obfuscated0); + + // Case 00: Empty credit card with empty strings. + CreditCard credit_card00; + autofill_test::SetCreditCardInfo(&credit_card00, + "John Dillinger", "", "", ""); + string16 summary00 = credit_card00.Label(); + EXPECT_EQ(string16(ASCIIToUTF16("John Dillinger")), summary00); + string16 obfuscated00 = credit_card00.ObfuscatedNumber(); + EXPECT_EQ(string16(), obfuscated00); + + // Case 1: No credit card number. + CreditCard credit_card1; + autofill_test::SetCreditCardInfo(&credit_card1, + "John Dillinger", "", "01", "2010"); + string16 summary1 = credit_card1.Label(); + EXPECT_EQ(string16(ASCIIToUTF16("John Dillinger")), summary1); + string16 obfuscated1 = credit_card1.ObfuscatedNumber(); + EXPECT_EQ(string16(), obfuscated1); + + // Case 2: No month. + CreditCard credit_card2; + autofill_test::SetCreditCardInfo(&credit_card2, + "John Dillinger", "5105 1051 0510 5100", "", "2010"); + string16 summary2 = credit_card2.Label(); + EXPECT_EQ(ASCIIToUTF16("************5100"), summary2); + string16 obfuscated2 = credit_card2.ObfuscatedNumber(); + EXPECT_EQ(ASCIIToUTF16("************5100"), obfuscated2); + + // Case 3: No year. + CreditCard credit_card3; + autofill_test::SetCreditCardInfo(&credit_card3, + "John Dillinger", "5105 1051 0510 5100", "01", ""); + string16 summary3 = credit_card3.Label(); + EXPECT_EQ(ASCIIToUTF16("************5100"), summary3); + string16 obfuscated3 = credit_card3.ObfuscatedNumber(); + EXPECT_EQ(ASCIIToUTF16("************5100"), obfuscated3); + + // Case 4: Have everything. + CreditCard credit_card4; + autofill_test::SetCreditCardInfo(&credit_card4, + "John Dillinger", "5105 1051 0510 5100", "01", "2010"); + string16 summary4 = credit_card4.Label(); + EXPECT_EQ(ASCIIToUTF16("************5100, Exp: 01/2010"), summary4); + string16 obfuscated4 = credit_card4.ObfuscatedNumber(); + EXPECT_EQ(ASCIIToUTF16("************5100"), obfuscated4); + + // Case 5: Very long credit card + CreditCard credit_card5; + autofill_test::SetCreditCardInfo(&credit_card5, + "John Dillinger", + "0123456789 0123456789 0123456789 5105 1051 0510 5100", "01", "2010"); + string16 summary5 = credit_card5.Label(); + EXPECT_EQ(ASCIIToUTF16("********************5100, Exp: 01/2010"), summary5); + string16 obfuscated5 = credit_card5.ObfuscatedNumber(); + EXPECT_EQ(ASCIIToUTF16("********************5100"), obfuscated5); +} + +TEST(CreditCardTest, AssignmentOperator) { + CreditCard a, b; + + // Result of assignment should be logically equal to the original profile. + autofill_test::SetCreditCardInfo(&a, "John Dillinger", + "123456789012", "01", "2010"); + b = a; + EXPECT_TRUE(a == b); + + // Assignment to self should not change the profile value. + a = a; + EXPECT_TRUE(a == b); +} + +TEST(CreditCardTest, IsComplete) { + CreditCard card; + EXPECT_FALSE(card.IsComplete()); + card.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Wally T. Walrus")); + EXPECT_FALSE(card.IsComplete()); + card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("01")); + EXPECT_FALSE(card.IsComplete()); + card.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("2014")); + + for (size_t i = 0; i < arraysize(kValidNumbers); ++i) { + SCOPED_TRACE(kValidNumbers[i]); + card.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16(kValidNumbers[i])); + EXPECT_TRUE(card.IsComplete()); + } + for (size_t i = 0; i < arraysize(kInvalidNumbers); ++i) { + SCOPED_TRACE(kInvalidNumbers[i]); + card.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16(kInvalidNumbers[i])); + EXPECT_FALSE(card.IsComplete()); + } +} + +TEST(CreditCardTest, InvalidMastercardNumber) { + CreditCard card; + + autofill_test::SetCreditCardInfo(&card, "Baby Face Nelson", + "5200000000000004", "01", "2010"); + EXPECT_EQ("genericCC", card.type()); +} + +// Verify that we preserve exactly what the user typed for credit card numbers. +TEST(CreditCardTest, SetRawInfoCreditCardNumber) { + CreditCard card; + + autofill_test::SetCreditCardInfo(&card, "Bob Dylan", + "4321-5432-6543-xxxx", "07", "2013"); + EXPECT_EQ(ASCIIToUTF16("4321-5432-6543-xxxx"), + card.GetRawInfo(CREDIT_CARD_NUMBER)); +} + +// Verify that we can handle both numeric and named months. +TEST(CreditCardTest, SetExpirationMonth) { + CreditCard card; + + card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("05")); + EXPECT_EQ(ASCIIToUTF16("05"), card.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + + card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("7")); + EXPECT_EQ(ASCIIToUTF16("07"), card.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + + // This should fail, and preserve the previous value. + card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("January")); + EXPECT_EQ(ASCIIToUTF16("07"), card.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + + card.SetInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("January"), "en-US"); + EXPECT_EQ(ASCIIToUTF16("01"), card.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + + card.SetInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("Apr"), "en-US"); + EXPECT_EQ(ASCIIToUTF16("04"), card.GetRawInfo(CREDIT_CARD_EXP_MONTH)); +} + +TEST(CreditCardTest, CreditCardType) { + CreditCard card; + + // The card type cannot be set directly. + card.SetRawInfo(CREDIT_CARD_TYPE, ASCIIToUTF16("Visa")); + EXPECT_EQ(string16(), card.GetRawInfo(CREDIT_CARD_TYPE)); + + // Setting the number should implicitly set the type. + card.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16("4111 1111 1111 1111")); + EXPECT_EQ(ASCIIToUTF16("Visa"), card.GetRawInfo(CREDIT_CARD_TYPE)); +} + +TEST(CreditCardTest, CreditCardVerificationCode) { + CreditCard card; + + // The verification code cannot be set, as Chrome does not store this data. + card.SetRawInfo(CREDIT_CARD_VERIFICATION_CODE, ASCIIToUTF16("999")); + EXPECT_EQ(string16(), card.GetRawInfo(CREDIT_CARD_VERIFICATION_CODE)); +} + + +TEST(CreditCardTest, CreditCardMonthExact) { + const char* const kMonthsNumeric[] = { + "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", + }; + std::vector<string16> options(arraysize(kMonthsNumeric)); + for (size_t i = 0; i < arraysize(kMonthsNumeric); ++i) { + options[i] = ASCIIToUTF16(kMonthsNumeric[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = options; + field.option_contents = options; + + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("01")); + credit_card.FillSelectControl(CREDIT_CARD_EXP_MONTH, &field); + EXPECT_EQ(ASCIIToUTF16("01"), field.value); +} + +TEST(CreditCardTest, CreditCardMonthAbbreviated) { + const char* const kMonthsAbbreviated[] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", + }; + std::vector<string16> options(arraysize(kMonthsAbbreviated)); + for (size_t i = 0; i < arraysize(kMonthsAbbreviated); ++i) { + options[i] = ASCIIToUTF16(kMonthsAbbreviated[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = options; + field.option_contents = options; + + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("01")); + credit_card.FillSelectControl(CREDIT_CARD_EXP_MONTH, &field); + EXPECT_EQ(ASCIIToUTF16("Jan"), field.value); +} + +TEST(CreditCardTest, CreditCardMonthFull) { + const char* const kMonthsFull[] = { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", + }; + std::vector<string16> options(arraysize(kMonthsFull)); + for (size_t i = 0; i < arraysize(kMonthsFull); ++i) { + options[i] = ASCIIToUTF16(kMonthsFull[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = options; + field.option_contents = options; + + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("01")); + credit_card.FillSelectControl(CREDIT_CARD_EXP_MONTH, &field); + EXPECT_EQ(ASCIIToUTF16("January"), field.value); +} + +TEST(CreditCardTest, CreditCardMonthNumeric) { + const char* const kMonthsNumeric[] = { + "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", + }; + std::vector<string16> options(arraysize(kMonthsNumeric)); + for (size_t i = 0; i < arraysize(kMonthsNumeric); ++i) { + options[i] = ASCIIToUTF16(kMonthsNumeric[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = options; + field.option_contents = options; + + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("01")); + credit_card.FillSelectControl(CREDIT_CARD_EXP_MONTH, &field); + EXPECT_EQ(ASCIIToUTF16("1"), field.value); +} + +TEST(CreditCardTest, CreditCardTwoDigitYear) { + const char* const kYears[] = { + "12", "13", "14", "15", "16", "17", "18", "19" + }; + std::vector<string16> options(arraysize(kYears)); + for (size_t i = 0; i < arraysize(kYears); ++i) { + options[i] = ASCIIToUTF16(kYears[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = options; + field.option_contents = options; + + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("2017")); + credit_card.FillSelectControl(CREDIT_CARD_EXP_4_DIGIT_YEAR, &field); + EXPECT_EQ(ASCIIToUTF16("17"), field.value); +} + +TEST(CreditCardTest, CreditCardTypeSelectControl) { + const char* const kCreditCardTypes[] = { + "Visa", "Master Card", "AmEx", "discover" + }; + std::vector<string16> options(arraysize(kCreditCardTypes)); + for (size_t i = 0; i < arraysize(kCreditCardTypes); ++i) { + options[i] = ASCIIToUTF16(kCreditCardTypes[i]); + } + + FormFieldData field; + field.form_control_type = "select-one"; + field.option_values = options; + field.option_contents = options; + + // Credit card types are inferred from the numbers, so we use test numbers for + // each card type. Test card numbers are drawn from + // http://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm + + { + // Normal case: + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_NUMBER, + ASCIIToUTF16("4111111111111111")); + credit_card.FillSelectControl(CREDIT_CARD_TYPE, &field); + EXPECT_EQ(ASCIIToUTF16("Visa"), field.value); + } + + { + // Filling should be able to handle intervening whitespace: + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_NUMBER, + ASCIIToUTF16("5105105105105100")); + credit_card.FillSelectControl(CREDIT_CARD_TYPE, &field); + EXPECT_EQ(ASCIIToUTF16("Master Card"), field.value); + } + + { + // American Express is sometimes abbreviated as AmEx: + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16("371449635398431")); + credit_card.FillSelectControl(CREDIT_CARD_TYPE, &field); + EXPECT_EQ(ASCIIToUTF16("AmEx"), field.value); + } + + { + // Case insensitivity: + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_NUMBER, + ASCIIToUTF16("6011111111111117")); + credit_card.FillSelectControl(CREDIT_CARD_TYPE, &field); + EXPECT_EQ(ASCIIToUTF16("discover"), field.value); + } +} diff --git a/components/autofill/browser/crypto/rc4_decryptor.h b/components/autofill/browser/crypto/rc4_decryptor.h new file mode 100644 index 0000000..2dff640 --- /dev/null +++ b/components/autofill/browser/crypto/rc4_decryptor.h @@ -0,0 +1,106 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_CRYPTO_RC4_DECRYPTOR_H_ +#define COMPONENTS_AUTOFILL_BROWSER_CRYPTO_RC4_DECRYPTOR_H_ + +#include <string> +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +// This is modified RC4 decryption used for import of Toolbar autofill data +// only. The difference from the Crypto Api implementation is twofold: +// First, it uses a non-standard key size (160 bit), not supported by Microsoft +// (it supports only 40 and 128 bit for RC4). Second, it codes 128 words with +// value 0x0020 at the beginning of the code to enhance security. +// +// This class used in +// components/autofill/browser/autofill_ie_toolbar_import_win.cc. +// +// This class should not be used anywhere else!!! +class RC4Decryptor { + public: + explicit RC4Decryptor(wchar_t const* password) { + PrepareKey(reinterpret_cast<const uint8 *>(password), + wcslen(password) * sizeof(wchar_t)); + std::wstring data; + // First 128 bytes should be spaces. + data.resize(128, L' '); + Run(data.c_str()); + } + + // Run the algorithm + std::wstring Run(const std::wstring& data) { + int data_size = data.length() * sizeof(wchar_t); + + scoped_array<wchar_t> buffer(new wchar_t[data.length() + 1]); + memset(buffer.get(), 0, (data.length() + 1) * sizeof(wchar_t)); + memcpy(buffer.get(), data.c_str(), data_size); + + RunInternal(reinterpret_cast<uint8 *>(buffer.get()), data_size); + + std::wstring result(buffer.get()); + + // Clear the memory + memset(buffer.get(), 0, data_size); + return result; + } + + private: + static const int kKeyDataSize = 256; + struct Rc4Key { + uint8 state[kKeyDataSize]; + uint8 x; + uint8 y; + }; + + void SwapByte(uint8* byte1, uint8* byte2) { + uint8 temp = *byte1; + *byte1 = *byte2; + *byte2 = temp; + } + + void PrepareKey(const uint8 *key_data, int key_data_len) { + uint8 index1 = 0; + uint8 index2 = 0; + uint8* state; + short counter; + + state = &key_.state[0]; + for (counter = 0; counter < kKeyDataSize; ++counter) + state[counter] = static_cast<uint8>(counter); + + key_.x = key_.y = 0; + + for (counter = 0; counter < kKeyDataSize; counter++) { + index2 = (key_data[index1] + state[counter] + index2) % kKeyDataSize; + SwapByte(&state[counter], &state[index2]); + index1 = (index1 + 1) % key_data_len; + } + } + + void RunInternal(uint8 *buffer, int buffer_len) { + uint8 x, y; + uint8 xor_index = 0; + uint8* state; + int counter; + + x = key_.x; + y = key_.y; + state = &key_.state[0]; + for (counter = 0; counter < buffer_len; ++counter) { + x = (x + 1) % kKeyDataSize; + y = (state[x] + y) % kKeyDataSize; + SwapByte(&state[x], &state[y]); + xor_index = (state[x] + state[y]) % kKeyDataSize; + buffer[counter] ^= state[xor_index]; + } + key_.x = x; + key_.y = y; + } + + Rc4Key key_; +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_CRYPTO_RC4_DECRYPTOR_H_ diff --git a/components/autofill/browser/data_driven_test.cc b/components/autofill/browser/data_driven_test.cc new file mode 100644 index 0000000..0936445 --- /dev/null +++ b/components/autofill/browser/data_driven_test.cc @@ -0,0 +1,90 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/data_driven_test.h" + +#include "base/file_util.h" +#include "base/path_service.h" +#include "base/string_util.h" +#include "chrome/common/chrome_paths.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// Reads |file| into |content|, and converts Windows line-endings to Unix ones. +// Returns true on success. +bool ReadFile(const base::FilePath& file, std::string* content) { + if (!file_util::ReadFileToString(file, content)) + return false; + + ReplaceSubstringsAfterOffset(content, 0, "\r\n", "\n"); + return true; +} + +// Write |content| to |file|. Returns true on success. +bool WriteFile(const base::FilePath& file, const std::string& content) { + int write_size = file_util::WriteFile(file, content.c_str(), + content.length()); + return write_size == static_cast<int>(content.length()); +} + +} // namespace + +void DataDrivenTest::RunDataDrivenTest( + const base::FilePath& input_directory, + const base::FilePath& output_directory, + const base::FilePath::StringType& file_name_pattern) { + file_util::FileEnumerator input_files(input_directory, + false, + file_util::FileEnumerator::FILES, + file_name_pattern); + + for (base::FilePath input_file = input_files.Next(); + !input_file.empty(); + input_file = input_files.Next()) { + SCOPED_TRACE(input_file.BaseName().value()); + + std::string input; + ASSERT_TRUE(ReadFile(input_file, &input)); + + std::string output; + GenerateResults(input, &output); + + base::FilePath output_file = output_directory.Append( + input_file.BaseName().StripTrailingSeparators().ReplaceExtension( + FILE_PATH_LITERAL(".out"))); + + std::string output_file_contents; + if (ReadFile(output_file, &output_file_contents)) + EXPECT_EQ(output_file_contents, output); + else + ASSERT_TRUE(WriteFile(output_file, output)); + } +} + +base::FilePath DataDrivenTest::GetInputDirectory( + const base::FilePath::StringType& test_name) { + base::FilePath test_data_dir_; + PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_); + test_data_dir_ = test_data_dir_.AppendASCII("autofill") + .Append(test_name) + .AppendASCII("input"); + return test_data_dir_; +} + +base::FilePath DataDrivenTest::GetOutputDirectory( + const base::FilePath::StringType& test_name) { + base::FilePath test_data_dir_; + PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir_); + test_data_dir_ = test_data_dir_.AppendASCII("autofill") + .Append(test_name) + .AppendASCII("output"); + return test_data_dir_; +} + +DataDrivenTest::DataDrivenTest() { +} + +DataDrivenTest::~DataDrivenTest() { +} diff --git a/components/autofill/browser/data_driven_test.h b/components/autofill/browser/data_driven_test.h new file mode 100644 index 0000000..36cf872 --- /dev/null +++ b/components/autofill/browser/data_driven_test.h @@ -0,0 +1,50 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_DATA_DRIVEN_TEST_H_ +#define COMPONENTS_AUTOFILL_BROWSER_DATA_DRIVEN_TEST_H_ + +#include <string> + +#include "base/files/file_path.h" +#include "base/string16.h" + +// A convenience class for implementing data-driven tests. Subclassers need only +// implement the conversion of serialized input data to serialized output data +// and provide a set of input files. For each input file, on the first run, a +// gold output file is generated; for subsequent runs, the test ouptut is +// compared to this gold output. +class DataDrivenTest { + public: + // For each file in |input_directory| whose filename matches + // |file_name_pattern|, slurps in the file contents and calls into + // |GenerateResults()|. If the corresponding output file already exists in + // the |output_directory|, verifies that the results match the file contents; + // otherwise, writes a gold result file to the |output_directory|. + void RunDataDrivenTest(const base::FilePath& input_directory, + const base::FilePath& output_directory, + const base::FilePath::StringType& file_name_pattern); + + // Given the |input| data, generates the |output| results. The output results + // must be stable across runs. + // Note: The return type is |void| so that googletest |ASSERT_*| macros will + // compile. + virtual void GenerateResults(const std::string& input, + std::string* output) = 0; + + // Return |base::FilePath|s to the test input and output subdirectories + // ../autofill/|test_name|/input and ../autofill/|test_name|/output. + base::FilePath GetInputDirectory(const base::FilePath::StringType& test_name); + base::FilePath GetOutputDirectory( + const base::FilePath::StringType& test_name); + + protected: + DataDrivenTest(); + virtual ~DataDrivenTest(); + + private: + DISALLOW_COPY_AND_ASSIGN(DataDrivenTest); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_DATA_DRIVEN_TEST_H_ diff --git a/components/autofill/browser/email_field.cc b/components/autofill/browser/email_field.cc new file mode 100644 index 0000000..bf01266 --- /dev/null +++ b/components/autofill/browser/email_field.cc @@ -0,0 +1,28 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/email_field.h" + +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_regex_constants.h" +#include "components/autofill/browser/autofill_scanner.h" +#include "ui/base/l10n/l10n_util.h" + +// static +FormField* EmailField::Parse(AutofillScanner* scanner) { + const AutofillField* field; + if (ParseFieldSpecifics(scanner, UTF8ToUTF16(autofill::kEmailRe), + MATCH_DEFAULT | MATCH_EMAIL, &field)) { + return new EmailField(field); + } + + return NULL; +} + +EmailField::EmailField(const AutofillField* field) : field_(field) { +} + +bool EmailField::ClassifyField(FieldTypeMap* map) const { + return AddClassification(field_, EMAIL_ADDRESS, map); +} diff --git a/components/autofill/browser/email_field.h b/components/autofill/browser/email_field.h new file mode 100644 index 0000000..d04a1a1 --- /dev/null +++ b/components/autofill/browser/email_field.h @@ -0,0 +1,28 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_EMAIL_FIELD_H_ +#define COMPONENTS_AUTOFILL_BROWSER_EMAIL_FIELD_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "components/autofill/browser/form_field.h" + +class EmailField : public FormField { + public: + static FormField* Parse(AutofillScanner* scanner); + + protected: + // FormField: + virtual bool ClassifyField(FieldTypeMap* map) const OVERRIDE; + + private: + explicit EmailField(const AutofillField* field); + + const AutofillField* field_; + + DISALLOW_COPY_AND_ASSIGN(EmailField); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_EMAIL_FIELD_H_ diff --git a/components/autofill/browser/field_types.h b/components/autofill/browser/field_types.h new file mode 100644 index 0000000..87dcfe8 --- /dev/null +++ b/components/autofill/browser/field_types.h @@ -0,0 +1,91 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_FIELD_TYPES_H_ +#define COMPONENTS_AUTOFILL_BROWSER_FIELD_TYPES_H_ + +#include <set> +#include <string> + +// NOTE: This list MUST not be modified. The server aggregates and stores these +// types over several versions, so we must remain fully compatible with the +// Autofill server, which is itself backward-compatible. The list must be kept +// up to date with the Autofill server list. +// +// This is the list of all valid field types. +enum AutofillFieldType { + // Server indication that it has no data for the requested field. + NO_SERVER_DATA = 0, + // Client indication that the text entered did not match anything in the + // personal data. + UNKNOWN_TYPE = 1, + // The "empty" type indicates that the user hasn't entered anything + // in this field. + EMPTY_TYPE = 2, + // Personal Information categorization types. + NAME_FIRST = 3, + NAME_MIDDLE = 4, + NAME_LAST = 5, + NAME_MIDDLE_INITIAL = 6, + NAME_FULL = 7, + NAME_SUFFIX = 8, + EMAIL_ADDRESS = 9, + PHONE_HOME_NUMBER = 10, + PHONE_HOME_CITY_CODE = 11, + PHONE_HOME_COUNTRY_CODE = 12, + PHONE_HOME_CITY_AND_NUMBER = 13, + PHONE_HOME_WHOLE_NUMBER = 14, + + // Work phone numbers (values [15,19]) are deprecated. + + // Fax numbers (values [20,24]) are deprecated in Chrome, but still supported + // by the server. + PHONE_FAX_NUMBER = 20, + PHONE_FAX_CITY_CODE = 21, + PHONE_FAX_COUNTRY_CODE = 22, + PHONE_FAX_CITY_AND_NUMBER = 23, + PHONE_FAX_WHOLE_NUMBER = 24, + + // Cell phone numbers (values [25, 29]) are deprecated. + + ADDRESS_HOME_LINE1 = 30, + ADDRESS_HOME_LINE2 = 31, + ADDRESS_HOME_APT_NUM = 32, + ADDRESS_HOME_CITY = 33, + ADDRESS_HOME_STATE = 34, + ADDRESS_HOME_ZIP = 35, + ADDRESS_HOME_COUNTRY = 36, + ADDRESS_BILLING_LINE1 = 37, + ADDRESS_BILLING_LINE2 = 38, + ADDRESS_BILLING_APT_NUM = 39, + ADDRESS_BILLING_CITY = 40, + ADDRESS_BILLING_STATE = 41, + ADDRESS_BILLING_ZIP = 42, + ADDRESS_BILLING_COUNTRY = 43, + + // ADDRESS_SHIPPING values [44,50] are deprecated. + + CREDIT_CARD_NAME = 51, + CREDIT_CARD_NUMBER = 52, + CREDIT_CARD_EXP_MONTH = 53, + CREDIT_CARD_EXP_2_DIGIT_YEAR = 54, + CREDIT_CARD_EXP_4_DIGIT_YEAR = 55, + CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR = 56, + CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR = 57, + CREDIT_CARD_TYPE = 58, + CREDIT_CARD_VERIFICATION_CODE = 59, + + COMPANY_NAME = 60, + + // Generic type whose default value is known. + FIELD_WITH_DEFAULT_VALUE = 61, + + // No new types can be added. + + MAX_VALID_FIELD_TYPE = 62, +}; + +typedef std::set<AutofillFieldType> FieldTypeSet; + +#endif // COMPONENTS_AUTOFILL_BROWSER_FIELD_TYPES_H_ diff --git a/components/autofill/browser/form_field.cc b/components/autofill/browser/form_field.cc new file mode 100644 index 0000000..2c1c5da --- /dev/null +++ b/components/autofill/browser/form_field.cc @@ -0,0 +1,197 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/form_field.h" + +#include <stddef.h> +#include <string> +#include <utility> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/address_field.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_regexes.h" +#include "components/autofill/browser/autofill_scanner.h" +#include "components/autofill/browser/credit_card_field.h" +#include "components/autofill/browser/email_field.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/browser/name_field.h" +#include "components/autofill/browser/phone_field.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +bool IsTextField(const std::string& type) { + return type == "text"; +} + +bool IsEmailField(const std::string& type) { + return type == "email"; +} + +bool IsTelephoneField(const std::string& type) { + return type == "tel"; +} + +bool IsSelectField(const std::string& type) { + return type == "select-one"; +} + +bool IsCheckable(const AutofillField* field) { + return field->is_checkable; +} + +} // namespace + +// static +void FormField::ParseFormFields(const std::vector<AutofillField*>& fields, + FieldTypeMap* map) { + // Set up a working copy of the fields to be processed. + std::vector<const AutofillField*> remaining_fields(fields.size()); + std::copy(fields.begin(), fields.end(), remaining_fields.begin()); + + // Ignore checkable fields as they interfere with parsers assuming context. + // Eg., while parsing address, "Is PO box" checkbox after ADDRESS_LINE1 + // interferes with correctly understanding ADDRESS_LINE2. + remaining_fields.erase( + std::remove_if(remaining_fields.begin(), remaining_fields.end(), + IsCheckable), + remaining_fields.end()); + + // Email pass. + ParseFormFieldsPass(EmailField::Parse, &remaining_fields, map); + + // Phone pass. + ParseFormFieldsPass(PhoneField::Parse, &remaining_fields, map); + + // Address pass. + ParseFormFieldsPass(AddressField::Parse, &remaining_fields, map); + + // Credit card pass. + ParseFormFieldsPass(CreditCardField::Parse, &remaining_fields, map); + + // Name pass. + ParseFormFieldsPass(NameField::Parse, &remaining_fields, map); +} + +// static +bool FormField::ParseField(AutofillScanner* scanner, + const string16& pattern, + const AutofillField** match) { + return ParseFieldSpecifics(scanner, pattern, MATCH_DEFAULT, match); +} + +// static +bool FormField::ParseFieldSpecifics(AutofillScanner* scanner, + const string16& pattern, + int match_type, + const AutofillField** match) { + if (scanner->IsEnd()) + return false; + + const AutofillField* field = scanner->Cursor(); + + if ((match_type & MATCH_TEXT) && IsTextField(field->form_control_type)) + return MatchAndAdvance(scanner, pattern, match_type, match); + + if ((match_type & MATCH_EMAIL) && IsEmailField(field->form_control_type)) + return MatchAndAdvance(scanner, pattern, match_type, match); + + if ((match_type & MATCH_TELEPHONE) && + IsTelephoneField(field->form_control_type)) { + return MatchAndAdvance(scanner, pattern, match_type, match); + } + + if ((match_type & MATCH_SELECT) && IsSelectField(field->form_control_type)) + return MatchAndAdvance(scanner, pattern, match_type, match); + + return false; +} + +// static +bool FormField::ParseEmptyLabel(AutofillScanner* scanner, + const AutofillField** match) { + return ParseFieldSpecifics(scanner, + ASCIIToUTF16("^$"), + MATCH_LABEL | MATCH_ALL_INPUTS, + match); +} + +// static +bool FormField::AddClassification(const AutofillField* field, + AutofillFieldType type, + FieldTypeMap* map) { + // Several fields are optional. + if (!field) + return true; + + return map->insert(make_pair(field->unique_name(), type)).second; +} + +// static. +bool FormField::MatchAndAdvance(AutofillScanner* scanner, + const string16& pattern, + int match_type, + const AutofillField** match) { + const AutofillField* field = scanner->Cursor(); + if (FormField::Match(field, pattern, match_type)) { + if (match) + *match = field; + scanner->Advance(); + return true; + } + + return false; +} + +// static +bool FormField::Match(const AutofillField* field, + const string16& pattern, + int match_type) { + if ((match_type & FormField::MATCH_LABEL) && + autofill::MatchesPattern(field->label, pattern)) { + return true; + } + + if ((match_type & FormField::MATCH_NAME) && + autofill::MatchesPattern(field->name, pattern)) { + return true; + } + + if ((match_type & FormField::MATCH_VALUE) && + autofill::MatchesPattern(field->value, pattern)) { + return true; + } + + return false; +} + +// static +void FormField::ParseFormFieldsPass(ParseFunction parse, + std::vector<const AutofillField*>* fields, + FieldTypeMap* map) { + // Store unmatched fields for further processing by the caller. + std::vector<const AutofillField*> remaining_fields; + + AutofillScanner scanner(*fields); + while (!scanner.IsEnd()) { + scoped_ptr<FormField> form_field(parse(&scanner)); + if (!form_field.get()) { + remaining_fields.push_back(scanner.Cursor()); + scanner.Advance(); + continue; + } + + // Add entries into the map for each field type found in |form_field|. + bool ok = form_field->ClassifyField(map); + DCHECK(ok); + } + + std::swap(*fields, remaining_fields); +} diff --git a/components/autofill/browser/form_field.h b/components/autofill/browser/form_field.h new file mode 100644 index 0000000..8b9765d --- /dev/null +++ b/components/autofill/browser/form_field.h @@ -0,0 +1,120 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_FORM_FIELD_H_ +#define COMPONENTS_AUTOFILL_BROWSER_FORM_FIELD_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/string16.h" +#include "components/autofill/browser/autofill_type.h" + +class AutofillField; +class AutofillScanner; + +// Represents a logical form field in a web form. Classes that implement this +// interface can identify themselves as a particular type of form field, e.g. +// name, phone number, or address field. +class FormField { + public: + virtual ~FormField() {} + + // Classifies each field in |fields| with its heuristically detected type. + // The association is stored into |map|. Each field has a derived unique name + // that is used as the key into the |map|. + static void ParseFormFields(const std::vector<AutofillField*>& fields, + FieldTypeMap* map); + + protected: + // A bit-field used for matching specific parts of a field in question. + enum MatchType { + // Attributes. + MATCH_LABEL = 1 << 0, + MATCH_NAME = 1 << 1, + MATCH_VALUE = 1 << 2, + + // Input types. + MATCH_TEXT = 1 << 3, + MATCH_EMAIL = 1 << 4, + MATCH_TELEPHONE = 1 << 5, + MATCH_SELECT = 1 << 6, + MATCH_ALL_INPUTS = + MATCH_TEXT | MATCH_EMAIL | MATCH_TELEPHONE | MATCH_SELECT, + + // By default match label and name for input/text types. + MATCH_DEFAULT = MATCH_LABEL | MATCH_NAME | MATCH_VALUE | MATCH_TEXT, + }; + + // Only derived classes may instantiate. + FormField() {} + + // Attempts to parse a form field with the given pattern. Returns true on + // success and fills |match| with a pointer to the field. + static bool ParseField(AutofillScanner* scanner, + const string16& pattern, + const AutofillField** match); + + // Parses the stream of fields in |scanner| with regular expression |pattern| + // as specified in the |match_type| bit field (see |MatchType|). If |match| + // is non-NULL and the pattern matches, the matched field is returned. + // A |true| result is returned in the case of a successful match, false + // otherwise. + static bool ParseFieldSpecifics(AutofillScanner* scanner, + const string16& pattern, + int match_type, + const AutofillField** match); + + // Attempts to parse a field with an empty label. Returns true + // on success and fills |match| with a pointer to the field. + static bool ParseEmptyLabel(AutofillScanner* scanner, + const AutofillField** match); + + // Adds an association between a field and a type to |map|. + static bool AddClassification(const AutofillField* field, + AutofillFieldType type, + FieldTypeMap* map); + + // Derived classes must implement this interface to supply field type + // information. |ParseFormFields| coordinates the parsing and extraction + // of types from an input vector of |AutofillField| objects and delegates + // the type extraction via this method. + virtual bool ClassifyField(FieldTypeMap* map) const = 0; + + private: + FRIEND_TEST_ALL_PREFIXES(FormFieldTest, Match); + + // Function pointer type for the parsing function that should be passed to the + // ParseFormFieldsPass() helper function. + typedef FormField* ParseFunction(AutofillScanner* scanner); + + // Matches |pattern| to the contents of the field at the head of the + // |scanner|. + // Returns |true| if a match is found according to |match_type|, and |false| + // otherwise. + static bool MatchAndAdvance(AutofillScanner* scanner, + const string16& pattern, + int match_type, + const AutofillField** match); + + // Matches the regular expression |pattern| against the components of |field| + // as specified in the |match_type| bit field (see |MatchType|). + static bool Match(const AutofillField* field, + const string16& pattern, + int match_type); + + // Perform a "pass" over the |fields| where each pass uses the supplied + // |parse| method to match content to a given field type. + // |fields| is both an input and an output parameter. Upon exit |fields| + // holds any remaining unclassified fields for further processing. + // Classification results of the processed fields are stored in |map|. + static void ParseFormFieldsPass(ParseFunction parse, + std::vector<const AutofillField*>* fields, + FieldTypeMap* map); + + DISALLOW_COPY_AND_ASSIGN(FormField); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_FORM_FIELD_H_ diff --git a/components/autofill/browser/form_field_unittest.cc b/components/autofill/browser/form_field_unittest.cc new file mode 100644 index 0000000..b8994bc --- /dev/null +++ b/components/autofill/browser/form_field_unittest.cc @@ -0,0 +1,146 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_vector.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/form_field.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(FormFieldTest, Match) { + AutofillField field; + + // Empty strings match. + EXPECT_TRUE(FormField::Match(&field, string16(), FormField::MATCH_LABEL)); + + // Empty pattern matches non-empty string. + field.label = ASCIIToUTF16("a"); + EXPECT_TRUE(FormField::Match(&field, string16(), FormField::MATCH_LABEL)); + + // Strictly empty pattern matches empty string. + field.label = string16(); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("^$"), + FormField::MATCH_LABEL)); + + // Strictly empty pattern does not match non-empty string. + field.label = ASCIIToUTF16("a"); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("^$"), + FormField::MATCH_LABEL)); + + // Non-empty pattern doesn't match empty string. + field.label = string16(); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("a"), + FormField::MATCH_LABEL)); + + // Beginning of line. + field.label = ASCIIToUTF16("head_tail"); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("^head"), + FormField::MATCH_LABEL)); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("^tail"), + FormField::MATCH_LABEL)); + + // End of line. + field.label = ASCIIToUTF16("head_tail"); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("head$"), + FormField::MATCH_LABEL)); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("tail$"), + FormField::MATCH_LABEL)); + + // Exact. + field.label = ASCIIToUTF16("head_tail"); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("^head$"), + FormField::MATCH_LABEL)); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("^tail$"), + FormField::MATCH_LABEL)); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("^head_tail$"), + FormField::MATCH_LABEL)); + + // Escaped dots. + field.label = ASCIIToUTF16("m.i."); + // Note: This pattern is misleading as the "." characters are wild cards. + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("m.i."), + FormField::MATCH_LABEL)); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("m\\.i\\."), + FormField::MATCH_LABEL)); + field.label = ASCIIToUTF16("mXiX"); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("m.i."), + FormField::MATCH_LABEL)); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("m\\.i\\."), + FormField::MATCH_LABEL)); + + // Repetition. + field.label = ASCIIToUTF16("headtail"); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("head.*tail"), + FormField::MATCH_LABEL)); + field.label = ASCIIToUTF16("headXtail"); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("head.*tail"), + FormField::MATCH_LABEL)); + field.label = ASCIIToUTF16("headXXXtail"); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("head.*tail"), + FormField::MATCH_LABEL)); + field.label = ASCIIToUTF16("headtail"); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("head.+tail"), + FormField::MATCH_LABEL)); + field.label = ASCIIToUTF16("headXtail"); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("head.+tail"), + FormField::MATCH_LABEL)); + field.label = ASCIIToUTF16("headXXXtail"); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("head.+tail"), + FormField::MATCH_LABEL)); + + // Alternation. + field.label = ASCIIToUTF16("head_tail"); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("head|other"), + FormField::MATCH_LABEL)); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("tail|other"), + FormField::MATCH_LABEL)); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("bad|good"), + FormField::MATCH_LABEL)); + + // Case sensitivity. + field.label = ASCIIToUTF16("xxxHeAd_tAiLxxx"); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("head_tail"), + FormField::MATCH_LABEL)); + + // Word boundaries. + field.label = ASCIIToUTF16("contains word:"); + EXPECT_TRUE(FormField::Match(&field, ASCIIToUTF16("\\bword\\b"), + FormField::MATCH_LABEL)); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("\\bcon\\b"), + FormField::MATCH_LABEL)); + // Make sure the circumflex in 'crepe' is not treated as a word boundary. + field.label = UTF8ToUTF16("cr" "\xC3\xAA" "pe"); + EXPECT_FALSE(FormField::Match(&field, ASCIIToUTF16("\\bcr\\b"), + FormField::MATCH_LABEL)); +} + +// Test that we ignore checkable elements. +TEST(FormFieldTest, ParseFormFields) { + ScopedVector<AutofillField> fields; + FormFieldData field_data; + field_data.form_control_type = "text"; + + field_data.label = ASCIIToUTF16("Address line1"); + fields.push_back(new AutofillField(field_data, field_data.label)); + + field_data.is_checkable = true; + field_data.label = ASCIIToUTF16("Is PO Box"); + fields.push_back(new AutofillField(field_data, field_data.label)); + + // reset is_checkable to false. + field_data.is_checkable = false; + field_data.label = ASCIIToUTF16("Address line2"); + fields.push_back(new AutofillField(field_data, field_data.label)); + + FieldTypeMap field_type_map; + FormField::ParseFormFields(fields.get(), &field_type_map); + // Checkable element shouldn't interfere with inference of Address line2. + EXPECT_EQ(2U, field_type_map.size()); + + EXPECT_EQ(ADDRESS_HOME_LINE1, + field_type_map.find(ASCIIToUTF16("Address line1"))->second); + EXPECT_EQ(ADDRESS_HOME_LINE2, + field_type_map.find(ASCIIToUTF16("Address line2"))->second); +} diff --git a/components/autofill/browser/form_group.cc b/components/autofill/browser/form_group.cc new file mode 100644 index 0000000..b2f21d2 --- /dev/null +++ b/components/autofill/browser/form_group.cc @@ -0,0 +1,322 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/form_group.h" + +#include <algorithm> +#include <iterator> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/common/form_field_data.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +// TODO(jhawkins): Add more states/provinces. See http://crbug.com/45039. + +class State { + public: + const char* name; + const char* abbreviation; + + static const State all_states[]; + + static string16 Abbreviation(const string16& name); + static string16 FullName(const string16& abbreviation); +}; + +const State State::all_states[] = { + { "alabama", "al" }, + { "alaska", "ak" }, + { "arizona", "az" }, + { "arkansas", "ar" }, + { "california", "ca" }, + { "colorado", "co" }, + { "connecticut", "ct" }, + { "delaware", "de" }, + { "district of columbia", "dc" }, + { "florida", "fl" }, + { "georgia", "ga" }, + { "hawaii", "hi" }, + { "idaho", "id" }, + { "illinois", "il" }, + { "indiana", "in" }, + { "iowa", "ia" }, + { "kansas", "ks" }, + { "kentucky", "ky" }, + { "louisiana", "la" }, + { "maine", "me" }, + { "maryland", "md" }, + { "massachusetts", "ma" }, + { "michigan", "mi" }, + { "minnesota", "mv" }, + { "mississippi", "ms" }, + { "missouri", "mo" }, + { "montana", "mt" }, + { "nebraska", "ne" }, + { "nevada", "nv" }, + { "new hampshire", "nh" }, + { "new jersey", "nj" }, + { "new mexico", "nm" }, + { "new york", "ny" }, + { "north carolina", "nc" }, + { "north dakota", "nd" }, + { "ohio", "oh" }, + { "oklahoma", "ok" }, + { "oregon", "or" }, + { "pennsylvania", "pa" }, + { "puerto rico", "pr" }, + { "rhode island", "ri" }, + { "south carolina", "sc" }, + { "south dakota", "sd" }, + { "tennessee", "tn" }, + { "texas", "tx" }, + { "utah", "ut" }, + { "vermont", "vt" }, + { "virginia", "va" }, + { "washington", "wa" }, + { "west virginia", "wv" }, + { "wisconsin", "wi" }, + { "wyoming", "wy" }, + { NULL, NULL } +}; + +string16 State::Abbreviation(const string16& name) { + for (const State* state = all_states; state->name; ++state) { + if (LowerCaseEqualsASCII(name, state->name)) + return ASCIIToUTF16(state->abbreviation); + } + return string16(); +} + +string16 State::FullName(const string16& abbreviation) { + for (const State* state = all_states; state->name; ++state) { + if (LowerCaseEqualsASCII(abbreviation, state->abbreviation)) + return ASCIIToUTF16(state->name); + } + return string16(); +} + +const char* const kMonthsAbbreviated[] = { + NULL, // Padding so index 1 = month 1 = January. + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", +}; + +const char* const kMonthsFull[] = { + NULL, // Padding so index 1 = month 1 = January. + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December", +}; + +const char* const kMonthsNumeric[] = { + NULL, // Padding so index 1 = month 1 = January. + "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", +}; + +// Returns true if the value was successfully set, meaning |value| was found in +// the list of select options in |field|. +bool SetSelectControlValue(const string16& value, + FormFieldData* field) { + string16 value_lowercase = StringToLowerASCII(value); + + DCHECK_EQ(field->option_values.size(), field->option_contents.size()); + for (size_t i = 0; i < field->option_values.size(); ++i) { + if (value_lowercase == StringToLowerASCII(field->option_values[i]) || + value_lowercase == StringToLowerASCII(field->option_contents[i])) { + field->value = field->option_values[i]; + return true; + } + } + + return false; +} + +bool FillStateSelectControl(const string16& value, + FormFieldData* field) { + string16 abbrev, full; + if (value.size() < 4U) { + abbrev = value; + full = State::FullName(value); + } else { + abbrev = State::Abbreviation(value); + full = value; + } + + // Try the abbreviation name first. + if (!abbrev.empty() && SetSelectControlValue(abbrev, field)) + return true; + + if (full.empty()) + return false; + + return SetSelectControlValue(full, field); +} + +bool FillExpirationMonthSelectControl(const string16& value, + FormFieldData* field) { + int index = 0; + if (!base::StringToInt(value, &index) || + index <= 0 || + static_cast<size_t>(index) >= arraysize(kMonthsFull)) + return false; + + bool filled = + SetSelectControlValue(ASCIIToUTF16(kMonthsAbbreviated[index]), field) || + SetSelectControlValue(ASCIIToUTF16(kMonthsFull[index]), field) || + SetSelectControlValue(ASCIIToUTF16(kMonthsNumeric[index]), field); + return filled; +} + +// Try to fill a credit card type |value| (Visa, MasterCard, etc.) into the +// given |field|. +bool FillCreditCardTypeSelectControl(const string16& value, + FormFieldData* field) { + // Try stripping off spaces. + string16 value_stripped; + RemoveChars(StringToLowerASCII(value), kWhitespaceUTF16, &value_stripped); + + for (size_t i = 0; i < field->option_values.size(); ++i) { + string16 option_value_lowercase; + RemoveChars(StringToLowerASCII(field->option_values[i]), kWhitespaceUTF16, + &option_value_lowercase); + string16 option_contents_lowercase; + RemoveChars(StringToLowerASCII(field->option_contents[i]), kWhitespaceUTF16, + &option_contents_lowercase); + + // Perform a case-insensitive comparison; but fill the form with the + // original text, not the lowercased version. + if (value_stripped == option_value_lowercase || + value_stripped == option_contents_lowercase) { + field->value = field->option_values[i]; + return true; + } + } + + // For American Express, also try filling as "AmEx". + if (value == l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_AMEX)) + return FillCreditCardTypeSelectControl(ASCIIToUTF16("AmEx"), field); + + return false; +} + +} // namespace + +std::string FormGroup::GetGUID() const { + NOTREACHED(); + return std::string(); +} + +void FormGroup::GetMatchingTypes(const string16& text, + const std::string& app_locale, + FieldTypeSet* matching_types) const { + if (text.empty()) { + matching_types->insert(EMPTY_TYPE); + return; + } + + FieldTypeSet types; + GetSupportedTypes(&types); + for (FieldTypeSet::const_iterator type = types.begin(); + type != types.end(); ++type) { + // TODO(isherman): Matches are case-sensitive for now. Let's keep an eye on + // this and decide whether there are compelling reasons to add case- + // insensitivity. + if (GetInfo(*type, app_locale) == text) + matching_types->insert(*type); + } +} + +void FormGroup::GetNonEmptyTypes(const std::string& app_locale, + FieldTypeSet* non_empty_types) const { + FieldTypeSet types; + GetSupportedTypes(&types); + for (FieldTypeSet::const_iterator type = types.begin(); + type != types.end(); ++type) { + if (!GetInfo(*type, app_locale).empty()) + non_empty_types->insert(*type); + } +} + +string16 FormGroup::GetInfo(AutofillFieldType type, + const std::string& app_locale) const { + return GetRawInfo(type); +} + +bool FormGroup::SetInfo(AutofillFieldType type, + const string16& value, + const std::string& app_locale) { + SetRawInfo(type, value); + return true; +} + +void FormGroup::FillFormField(const AutofillField& field, + size_t variant, + FormFieldData* field_data) const { + NOTREACHED(); +} + +void FormGroup::FillSelectControl(AutofillFieldType type, + FormFieldData* field) const { + DCHECK(field); + DCHECK_EQ("select-one", field->form_control_type); + DCHECK_EQ(field->option_values.size(), field->option_contents.size()); + + const std::string app_locale = AutofillCountry::ApplicationLocale(); + string16 field_text = GetInfo(type, app_locale); + string16 field_text_lower = StringToLowerASCII(field_text); + if (field_text.empty()) + return; + + string16 value; + for (size_t i = 0; i < field->option_values.size(); ++i) { + if (field_text == field->option_values[i] || + field_text == field->option_contents[i]) { + // An exact match, use it. + value = field->option_values[i]; + break; + } + + if (field_text_lower == StringToLowerASCII(field->option_values[i]) || + field_text_lower == StringToLowerASCII(field->option_contents[i])) { + // A match, but not in the same case. Save it in case an exact match is + // not found. + value = field->option_values[i]; + } + } + + if (!value.empty()) { + field->value = value; + return; + } + + if (type == ADDRESS_HOME_STATE || type == ADDRESS_BILLING_STATE) { + FillStateSelectControl(field_text, field); + } else if (type == ADDRESS_HOME_COUNTRY || type == ADDRESS_BILLING_COUNTRY) { + FillCountrySelectControl(field); + } else if (type == CREDIT_CARD_EXP_MONTH) { + FillExpirationMonthSelectControl(field_text, field); + } else if (type == CREDIT_CARD_EXP_4_DIGIT_YEAR) { + // Attempt to fill the year as a 2-digit year. This compensates for the + // fact that our heuristics do not always correctly detect when a website + // requests a 2-digit rather than a 4-digit year. + FillSelectControl(CREDIT_CARD_EXP_2_DIGIT_YEAR, field); + } else if (type == CREDIT_CARD_TYPE) { + FillCreditCardTypeSelectControl(field_text, field); + } +} + +bool FormGroup::FillCountrySelectControl(FormFieldData* field_data) const { + return false; +} + +// static +bool FormGroup::IsValidState(const string16& value) { + return !State::Abbreviation(value).empty() || !State::FullName(value).empty(); +} diff --git a/components/autofill/browser/form_group.h b/components/autofill/browser/form_group.h new file mode 100644 index 0000000..d7f0c8c --- /dev/null +++ b/components/autofill/browser/form_group.h @@ -0,0 +1,92 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_FORM_GROUP_H_ +#define COMPONENTS_AUTOFILL_BROWSER_FORM_GROUP_H_ + +#include <string> +#include <vector> + +#include "base/string16.h" +#include "base/string_util.h" +#include "components/autofill/browser/field_types.h" + +class AutofillField; +struct FormFieldData; + +// This class is an interface for collections of form fields, grouped by type. +// The information in objects of this class is managed by the +// PersonalDataManager. +class FormGroup { + public: + virtual ~FormGroup() {} + + // Returns a globally unique ID for this object. It is an error to call the + // default implementation. + virtual std::string GetGUID() const; + + // Used to determine the type of a field based on the text that a user enters + // into the field, interpreted in the given |app_locale| if appropriate. The + // field types can then be reported back to the server. This method is + // additive on |matching_types|. + virtual void GetMatchingTypes(const string16& text, + const std::string& app_locale, + FieldTypeSet* matching_types) const; + + // Returns a set of AutofillFieldTypes for which this FormGroup has non-empty + // data. This method is additive on |non_empty_types|. + virtual void GetNonEmptyTypes(const std::string& app_locale, + FieldTypeSet* non_empty_types) const; + + // Returns the string associated with |type|, without canonicalizing the + // returned value. For user-visible strings, use GetInfo() instead. + virtual string16 GetRawInfo(AutofillFieldType type) const = 0; + + // Sets this FormGroup object's data for |type| to |value|, without + // canonicalizing the |value|. For data that has not already been + // canonicalized, use SetInfo() instead. + virtual void SetRawInfo(AutofillFieldType type, const string16& value) = 0; + + // Returns the string that should be auto-filled into a text field given the + // type of that field, localized to the given |app_locale| if appropriate. + virtual string16 GetInfo(AutofillFieldType type, + const std::string& app_locale) const; + + // Used to populate this FormGroup object with data. Canonicalizes the data + // according to the specified |app_locale| prior to storing, if appropriate. + virtual bool SetInfo(AutofillFieldType type, + const string16& value, + const std::string& app_locale); + + // Set |field_data|'s value based on |field| and contents of |this| (using + // data variant |variant|). It is an error to call the default implementation. + virtual void FillFormField(const AutofillField& field, + size_t variant, + FormFieldData* field_data) const; + + // Fills in select control with data matching |type| from |this|. + // Public for testing purposes. + void FillSelectControl(AutofillFieldType type, + FormFieldData* field_data) const; + + // Returns true if |value| is a valid US state name or abbreviation. It is + // case insensitive. Valid for US states only. + // TODO(estade): this is a crappy place for this function. + static bool IsValidState(const string16& value); + + protected: + // AutofillProfile needs to call into GetSupportedTypes() for objects of + // non-AutofillProfile type, for which mere inheritance is insufficient. + friend class AutofillProfile; + + // Returns a set of AutofillFieldTypes for which this FormGroup can store + // data. This method is additive on |supported_types|. + virtual void GetSupportedTypes(FieldTypeSet* supported_types) const = 0; + + // Fills in a select control for a country from data in |this|. Returns true + // for success. + virtual bool FillCountrySelectControl(FormFieldData* field_data) const; +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_FORM_GROUP_H_ diff --git a/components/autofill/browser/form_structure.cc b/components/autofill/browser/form_structure.cc new file mode 100644 index 0000000..0b24bde --- /dev/null +++ b/components/autofill/browser/form_structure.cc @@ -0,0 +1,1156 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/form_structure.h" + +#include <utility> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/sha1.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/strings/string_number_conversions.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autocheckout_page_meta_data.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/autofill_xml_parser.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/form_field.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_data_predictions.h" +#include "components/autofill/common/form_field_data.h" +#include "components/autofill/common/form_field_data_predictions.h" +#include "third_party/libjingle/source/talk/xmllite/xmlelement.h" + +namespace { + +const char kFormMethodPost[] = "post"; + +// XML elements and attributes. +const char kAttributeAcceptedFeatures[] = "accepts"; +const char kAttributeAutofillUsed[] = "autofillused"; +const char kAttributeAutofillType[] = "autofilltype"; +const char kAttributeClientVersion[] = "clientversion"; +const char kAttributeDataPresent[] = "datapresent"; +const char kAttributeFormSignature[] = "formsignature"; +const char kAttributeSignature[] = "signature"; +const char kAttributeUrlprefixSignature[] = "urlprefixsignature"; +const char kAcceptedFeaturesExperiment[] = "e"; // e=experiments +const char kAcceptedFeaturesAutocheckoutExperiment[] = "a,e"; // a=autocheckout +const char kClientVersion[] = "6.1.1715.1442/en (GGLL)"; +const char kXMLDeclaration[] = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; +const char kXMLElementAutofillQuery[] = "autofillquery"; +const char kXMLElementAutofillUpload[] = "autofillupload"; +const char kXMLElementForm[] = "form"; +const char kXMLElementField[] = "field"; + +// The number of fillable fields necessary for a form to be fillable. +const size_t kRequiredFillableFields = 3; + +// Helper for |EncodeUploadRequest()| that creates a bit field corresponding to +// |available_field_types| and returns the hex representation as a string. +std::string EncodeFieldTypes(const FieldTypeSet& available_field_types) { + // There are |MAX_VALID_FIELD_TYPE| different field types and 8 bits per byte, + // so we need ceil(MAX_VALID_FIELD_TYPE / 8) bytes to encode the bit field. + const size_t kNumBytes = (MAX_VALID_FIELD_TYPE + 0x7) / 8; + + // Pack the types in |available_field_types| into |bit_field|. + std::vector<uint8> bit_field(kNumBytes, 0); + for (FieldTypeSet::const_iterator field_type = available_field_types.begin(); + field_type != available_field_types.end(); + ++field_type) { + // Set the appropriate bit in the field. The bit we set is the one + // |field_type| % 8 from the left of the byte. + const size_t byte = *field_type / 8; + const size_t bit = 0x80 >> (*field_type % 8); + DCHECK(byte < bit_field.size()); + bit_field[byte] |= bit; + } + + // Discard any trailing zeroes. + // If there are no available types, we return the empty string. + size_t data_end = bit_field.size(); + for (; data_end > 0 && !bit_field[data_end - 1]; --data_end) { + } + + // Print all meaningfull bytes into a string. + std::string data_presence; + data_presence.reserve(data_end * 2 + 1); + for (size_t i = 0; i < data_end; ++i) { + base::StringAppendF(&data_presence, "%02x", bit_field[i]); + } + + return data_presence; +} + +// Returns |true| iff the |token| is a type hint for a contact field, as +// specified in the implementation section of http://is.gd/whatwg_autocomplete +// Note that "fax" and "pager" are intentionally ignored, as Chrome does not +// support filling either type of information. +bool IsContactTypeHint(const std::string& token) { + return token == "home" || token == "work" || token == "mobile"; +} + +// Returns |true| iff the |token| is a type hint appropriate for a field of the +// given |field_type|, as specified in the implementation section of +// http://is.gd/whatwg_autocomplete +bool ContactTypeHintMatchesFieldType(const std::string& token, + AutofillFieldType field_type) { + // The "home" and "work" type hints are only appropriate for email and phone + // number field types. + if (token == "home" || token == "work") { + return field_type == EMAIL_ADDRESS || + (field_type >= PHONE_HOME_NUMBER && + field_type <= PHONE_HOME_WHOLE_NUMBER); + } + + // The "mobile" type hint is only appropriate for phone number field types. + // Note that "fax" and "pager" are intentionally ignored, as Chrome does not + // support filling either type of information. + if (token == "mobile") { + return field_type >= PHONE_HOME_NUMBER && + field_type <= PHONE_HOME_WHOLE_NUMBER; + } + + return false; +} + +// Returns the Chrome Autofill-supported field type corresponding to the given +// |autocomplete_type|, if there is one, in the context of the given |field|. +// Chrome Autofill supports a subset of the field types listed at +// http://is.gd/whatwg_autocomplete +AutofillFieldType FieldTypeFromAutocompleteType( + const std::string& autocomplete_type, + const AutofillField& field) { + if (autocomplete_type == "name") + return NAME_FULL; + + if (autocomplete_type == "given-name") + return NAME_FIRST; + + if (autocomplete_type == "additional-name") { + if (field.max_length == 1) + return NAME_MIDDLE_INITIAL; + else + return NAME_MIDDLE; + } + + if (autocomplete_type == "family-name") + return NAME_LAST; + + if (autocomplete_type == "honorific-suffix") + return NAME_SUFFIX; + + if (autocomplete_type == "organization") + return COMPANY_NAME; + + if (autocomplete_type == "street-address" || + autocomplete_type == "address-line1") + return ADDRESS_HOME_LINE1; + + if (autocomplete_type == "address-line2") + return ADDRESS_HOME_LINE2; + + if (autocomplete_type == "locality") + return ADDRESS_HOME_CITY; + + if (autocomplete_type == "region") + return ADDRESS_HOME_STATE; + + if (autocomplete_type == "country") + return ADDRESS_HOME_COUNTRY; + + if (autocomplete_type == "postal-code") + return ADDRESS_HOME_ZIP; + + if (autocomplete_type == "cc-name") + return CREDIT_CARD_NAME; + + if (autocomplete_type == "cc-number") + return CREDIT_CARD_NUMBER; + + if (autocomplete_type == "cc-exp") { + if (field.max_length == 5) + return CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR; + else + return CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR; + } + + if (autocomplete_type == "cc-exp-month") + return CREDIT_CARD_EXP_MONTH; + + if (autocomplete_type == "cc-exp-year") { + if (field.max_length == 2) + return CREDIT_CARD_EXP_2_DIGIT_YEAR; + else + return CREDIT_CARD_EXP_4_DIGIT_YEAR; + } + + if (autocomplete_type == "cc-csc") + return CREDIT_CARD_VERIFICATION_CODE; + + if (autocomplete_type == "cc-type") + return CREDIT_CARD_TYPE; + + if (autocomplete_type == "tel") + return PHONE_HOME_WHOLE_NUMBER; + + if (autocomplete_type == "tel-country-code") + return PHONE_HOME_COUNTRY_CODE; + + if (autocomplete_type == "tel-national") + return PHONE_HOME_CITY_AND_NUMBER; + + if (autocomplete_type == "tel-area-code") + return PHONE_HOME_CITY_CODE; + + if (autocomplete_type == "tel-local") + return PHONE_HOME_NUMBER; + + if (autocomplete_type == "tel-local-prefix") + return PHONE_HOME_NUMBER; + + if (autocomplete_type == "tel-local-suffix") + return PHONE_HOME_NUMBER; + + if (autocomplete_type == "email") + return EMAIL_ADDRESS; + + return UNKNOWN_TYPE; +} + +} // namespace + +FormStructure::FormStructure(const FormData& form, + const std::string& autocheckout_url_prefix) + : form_name_(form.name), + source_url_(form.origin), + target_url_(form.action), + autofill_count_(0), + checkable_field_count_(0), + upload_required_(USE_UPLOAD_RATES), + server_experiment_id_("no server response"), + has_author_specified_types_(false), + autocheckout_url_prefix_(autocheckout_url_prefix) { + // Copy the form fields. + std::map<string16, size_t> unique_names; + for (std::vector<FormFieldData>::const_iterator field = + form.fields.begin(); + field != form.fields.end(); field++) { + + // Skipping checkable elements when Autocheckout is not enabled, else + // these fields will interfere with existing field signatures with Autofill + // servers. + if (!field->is_checkable || IsAutocheckoutEnabled()) { + // Add all supported form fields (including with empty names) to the + // signature. This is a requirement for Autofill servers. + form_signature_field_names_.append("&"); + form_signature_field_names_.append(UTF16ToUTF8(field->name)); + } + + // Generate a unique name for this field by appending a counter to the name. + // Make sure to prepend the counter with a non-numeric digit so that we are + // guaranteed to avoid collisions. + if (!unique_names.count(field->name)) + unique_names[field->name] = 1; + else + ++unique_names[field->name]; + string16 unique_name = field->name + ASCIIToUTF16("_") + + base::IntToString16(unique_names[field->name]); + fields_.push_back(new AutofillField(*field, unique_name)); + + if (field->is_checkable) + ++checkable_field_count_; + } + + std::string method = UTF16ToUTF8(form.method); + if (StringToLowerASCII(method) == kFormMethodPost) { + method_ = POST; + } else { + // Either the method is 'get', or we don't know. In this case we default + // to GET. + method_ = GET; + } +} + +FormStructure::~FormStructure() {} + +void FormStructure::DetermineHeuristicTypes( + const AutofillMetrics& metric_logger) { + // First, try to detect field types based on each field's |autocomplete| + // attribute value. If there is at least one form field that specifies an + // autocomplete type hint, don't try to apply other heuristics to match fields + // in this form. + bool has_author_specified_sections; + ParseFieldTypesFromAutocompleteAttributes(&has_author_specified_types_, + &has_author_specified_sections); + + if (!has_author_specified_types_) { + FieldTypeMap field_type_map; + FormField::ParseFormFields(fields_.get(), &field_type_map); + for (size_t index = 0; index < field_count(); index++) { + AutofillField* field = fields_[index]; + FieldTypeMap::iterator iter = field_type_map.find(field->unique_name()); + if (iter != field_type_map.end()) + field->set_heuristic_type(iter->second); + } + } + + UpdateAutofillCount(); + IdentifySections(has_author_specified_sections); + + if (IsAutofillable(true)) { + metric_logger.LogDeveloperEngagementMetric( + AutofillMetrics::FILLABLE_FORM_PARSED); + if (has_author_specified_types_) { + metric_logger.LogDeveloperEngagementMetric( + AutofillMetrics::FILLABLE_FORM_CONTAINS_TYPE_HINTS); + } + } +} + +bool FormStructure::EncodeUploadRequest( + const FieldTypeSet& available_field_types, + bool form_was_autofilled, + std::string* encoded_xml) const { + if (!ShouldBeCrowdsourced()) { + NOTREACHED(); + return false; + } + + // Verify that |available_field_types| agrees with the possible field types we + // are uploading. + for (std::vector<AutofillField*>::const_iterator field = begin(); + field != end(); + ++field) { + for (FieldTypeSet::const_iterator type = (*field)->possible_types().begin(); + type != (*field)->possible_types().end(); + ++type) { + DCHECK(*type == UNKNOWN_TYPE || + *type == EMPTY_TYPE || + available_field_types.count(*type)); + } + } + + // Set up the <autofillupload> element and its attributes. + buzz::XmlElement autofill_request_xml( + (buzz::QName(kXMLElementAutofillUpload))); + autofill_request_xml.SetAttr(buzz::QName(kAttributeClientVersion), + kClientVersion); + autofill_request_xml.SetAttr(buzz::QName(kAttributeFormSignature), + FormSignature()); + autofill_request_xml.SetAttr(buzz::QName(kAttributeAutofillUsed), + form_was_autofilled ? "true" : "false"); + autofill_request_xml.SetAttr(buzz::QName(kAttributeDataPresent), + EncodeFieldTypes(available_field_types).c_str()); + + if (!EncodeFormRequest(FormStructure::UPLOAD, &autofill_request_xml)) + return false; // Malformed form, skip it. + + // Obtain the XML structure as a string. + encoded_xml->clear(); + *encoded_xml = kXMLDeclaration; + *encoded_xml += autofill_request_xml.Str().c_str(); + + // To enable this logging, run with the flag --vmodule="form_structure=2". + VLOG(2) << "\n" << *encoded_xml; + + return true; +} + +// static +bool FormStructure::EncodeQueryRequest( + const std::vector<FormStructure*>& forms, + std::vector<std::string>* encoded_signatures, + std::string* encoded_xml) { + DCHECK(encoded_signatures); + DCHECK(encoded_xml); + encoded_xml->clear(); + encoded_signatures->clear(); + encoded_signatures->reserve(forms.size()); + + // Set up the <autofillquery> element and attributes. + buzz::XmlElement autofill_request_xml( + (buzz::QName(kXMLElementAutofillQuery))); + autofill_request_xml.SetAttr(buzz::QName(kAttributeClientVersion), + kClientVersion); + + // autocheckout_url_prefix tells the Autofill server where the forms in the + // request came from, and the the Autofill server checks internal status and + // decide to enable Autocheckout or not and may return Autocheckout related + // data in the response accordingly. + // There is no page/frame level object associated with FormStructure that + // we could extract URL prefix from. But, all the forms should come from the + // same frame, so they should have the same Autocheckout URL prefix. Thus we + // use URL prefix from the first form with Autocheckout enabled. + std::string autocheckout_url_prefix; + + // Some badly formatted web sites repeat forms - detect that and encode only + // one form as returned data would be the same for all the repeated forms. + std::set<std::string> processed_forms; + for (ScopedVector<FormStructure>::const_iterator it = forms.begin(); + it != forms.end(); + ++it) { + std::string signature((*it)->FormSignature()); + if (processed_forms.find(signature) != processed_forms.end()) + continue; + processed_forms.insert(signature); + scoped_ptr<buzz::XmlElement> encompassing_xml_element( + new buzz::XmlElement(buzz::QName(kXMLElementForm))); + encompassing_xml_element->SetAttr(buzz::QName(kAttributeSignature), + signature); + + if (!(*it)->EncodeFormRequest(FormStructure::QUERY, + encompassing_xml_element.get())) + continue; // Malformed form, skip it. + + if ((*it)->IsAutocheckoutEnabled()) { + if (autocheckout_url_prefix.empty()) { + autocheckout_url_prefix = (*it)->autocheckout_url_prefix_; + } else { + // Making sure all the forms in the request has the same url_prefix. + DCHECK_EQ(autocheckout_url_prefix, (*it)->autocheckout_url_prefix_); + } + } + + autofill_request_xml.AddElement(encompassing_xml_element.release()); + encoded_signatures->push_back(signature); + } + + if (!encoded_signatures->size()) + return false; + + if (autocheckout_url_prefix.empty()) { + autofill_request_xml.SetAttr(buzz::QName(kAttributeAcceptedFeatures), + kAcceptedFeaturesExperiment); + } else { + autofill_request_xml.SetAttr(buzz::QName(kAttributeAcceptedFeatures), + kAcceptedFeaturesAutocheckoutExperiment); + autofill_request_xml.SetAttr(buzz::QName(kAttributeUrlprefixSignature), + Hash64Bit(autocheckout_url_prefix)); + } + + // Obtain the XML structure as a string. + *encoded_xml = kXMLDeclaration; + *encoded_xml += autofill_request_xml.Str().c_str(); + + return true; +} + +// static +void FormStructure::ParseQueryResponse( + const std::string& response_xml, + const std::vector<FormStructure*>& forms, + autofill::AutocheckoutPageMetaData* page_meta_data, + const AutofillMetrics& metric_logger) { + metric_logger.LogServerQueryMetric(AutofillMetrics::QUERY_RESPONSE_RECEIVED); + + // Parse the field types from the server response to the query. + std::vector<AutofillServerFieldInfo> field_infos; + UploadRequired upload_required; + std::string experiment_id; + AutofillQueryXmlParser parse_handler(&field_infos, &upload_required, + &experiment_id); + buzz::XmlParser parser(&parse_handler); + parser.Parse(response_xml.c_str(), response_xml.length(), true); + if (!parse_handler.succeeded()) + return; + + page_meta_data->current_page_number = parse_handler.current_page_number(); + page_meta_data->total_pages = parse_handler.total_pages(); + if (parse_handler.proceed_element_descriptor()) { + page_meta_data->proceed_element_descriptor.reset( + new autofill::WebElementDescriptor( + *parse_handler.proceed_element_descriptor())); + } else { + page_meta_data->proceed_element_descriptor.reset(); + } + + metric_logger.LogServerQueryMetric(AutofillMetrics::QUERY_RESPONSE_PARSED); + metric_logger.LogServerExperimentIdForQuery(experiment_id); + + bool heuristics_detected_fillable_field = false; + bool query_response_overrode_heuristics = false; + + // Copy the field types into the actual form. + std::vector<AutofillServerFieldInfo>::iterator current_info = + field_infos.begin(); + for (std::vector<FormStructure*>::const_iterator iter = forms.begin(); + iter != forms.end(); ++iter) { + FormStructure* form = *iter; + form->upload_required_ = upload_required; + form->server_experiment_id_ = experiment_id; + + for (std::vector<AutofillField*>::iterator field = form->fields_.begin(); + field != form->fields_.end(); ++field, ++current_info) { + // In some cases *successful* response does not return all the fields. + // Quit the update of the types then. + if (current_info == field_infos.end()) + break; + + // UNKNOWN_TYPE is reserved for use by the client. + DCHECK_NE(current_info->field_type, UNKNOWN_TYPE); + + AutofillFieldType heuristic_type = (*field)->type(); + if (heuristic_type != UNKNOWN_TYPE) + heuristics_detected_fillable_field = true; + + (*field)->set_server_type(current_info->field_type); + if (heuristic_type != (*field)->type()) + query_response_overrode_heuristics = true; + + // Copy default value into the field if available. + if (!current_info->default_value.empty()) + (*field)->set_default_value(current_info->default_value); + } + + form->UpdateAutofillCount(); + form->IdentifySections(false); + } + + AutofillMetrics::ServerQueryMetric metric; + if (query_response_overrode_heuristics) { + if (heuristics_detected_fillable_field) { + metric = AutofillMetrics::QUERY_RESPONSE_OVERRODE_LOCAL_HEURISTICS; + } else { + metric = AutofillMetrics::QUERY_RESPONSE_WITH_NO_LOCAL_HEURISTICS; + } + } else { + metric = AutofillMetrics::QUERY_RESPONSE_MATCHED_LOCAL_HEURISTICS; + } + metric_logger.LogServerQueryMetric(metric); +} + +// static +void FormStructure::GetFieldTypePredictions( + const std::vector<FormStructure*>& form_structures, + std::vector<FormDataPredictions>* forms) { + forms->clear(); + forms->reserve(form_structures.size()); + for (size_t i = 0; i < form_structures.size(); ++i) { + FormStructure* form_structure = form_structures[i]; + FormDataPredictions form; + form.data.name = form_structure->form_name_; + form.data.method = + ASCIIToUTF16((form_structure->method_ == POST) ? "POST" : "GET"); + form.data.origin = form_structure->source_url_; + form.data.action = form_structure->target_url_; + form.signature = form_structure->FormSignature(); + form.experiment_id = form_structure->server_experiment_id_; + + for (std::vector<AutofillField*>::const_iterator field = + form_structure->fields_.begin(); + field != form_structure->fields_.end(); ++field) { + form.data.fields.push_back(FormFieldData(**field)); + + FormFieldDataPredictions annotated_field; + annotated_field.signature = (*field)->FieldSignature(); + annotated_field.heuristic_type = + AutofillType::FieldTypeToString((*field)->heuristic_type()); + annotated_field.server_type = + AutofillType::FieldTypeToString((*field)->server_type()); + annotated_field.overall_type = + AutofillType::FieldTypeToString((*field)->type()); + form.fields.push_back(annotated_field); + } + + forms->push_back(form); + } +} + +std::string FormStructure::FormSignature() const { + std::string scheme(target_url_.scheme()); + std::string host(target_url_.host()); + + // If target host or scheme is empty, set scheme and host of source url. + // This is done to match the Toolbar's behavior. + if (scheme.empty() || host.empty()) { + scheme = source_url_.scheme(); + host = source_url_.host(); + } + + std::string form_string = scheme + "://" + host + "&" + + UTF16ToUTF8(form_name_) + + form_signature_field_names_; + + return Hash64Bit(form_string); +} + +bool FormStructure::IsAutocheckoutEnabled() const { + return !autocheckout_url_prefix_.empty(); +} + +size_t FormStructure::RequiredFillableFields() const { + return IsAutocheckoutEnabled() ? 0 : kRequiredFillableFields; +} + +bool FormStructure::IsAutofillable(bool require_method_post) const { + if (autofill_count() < RequiredFillableFields()) + return false; + + return ShouldBeParsed(require_method_post); +} + +void FormStructure::UpdateAutofillCount() { + autofill_count_ = 0; + for (std::vector<AutofillField*>::const_iterator iter = begin(); + iter != end(); ++iter) { + AutofillField* field = *iter; + if (field && field->IsFieldFillable()) + ++autofill_count_; + } +} + +bool FormStructure::ShouldBeParsed(bool require_method_post) const { + // Ignore counting checkable elements towards minimum number of elements + // required to parse. This avoids trying to crowdsource forms with few text + // or select elements. + if ((field_count() - checkable_field_count()) < RequiredFillableFields()) + return false; + + // Rule out http(s)://*/search?... + // e.g. http://www.google.com/search?q=... + // http://search.yahoo.com/search?p=... + if (target_url_.path() == "/search") + return false; + + if (!IsAutocheckoutEnabled()) { + // Make sure there is at least one text field when Autocheckout is + // not enabled. + bool has_text_field = false; + for (std::vector<AutofillField*>::const_iterator it = begin(); + it != end() && !has_text_field; ++it) { + has_text_field |= (*it)->form_control_type != "select-one"; + } + if (!has_text_field) + return false; + } + + return !require_method_post || (method_ == POST); +} + +bool FormStructure::ShouldBeCrowdsourced() const { + return !has_author_specified_types_ && ShouldBeParsed(true); +} + +void FormStructure::UpdateFromCache(const FormStructure& cached_form) { + // Map from field signatures to cached fields. + std::map<std::string, const AutofillField*> cached_fields; + for (size_t i = 0; i < cached_form.field_count(); ++i) { + const AutofillField* field = cached_form.field(i); + cached_fields[field->FieldSignature()] = field; + } + + for (std::vector<AutofillField*>::const_iterator iter = begin(); + iter != end(); ++iter) { + AutofillField* field = *iter; + + std::map<std::string, const AutofillField*>::const_iterator + cached_field = cached_fields.find(field->FieldSignature()); + if (cached_field != cached_fields.end()) { + if (field->form_control_type != "select-one" && + field->value == cached_field->second->value) { + // From the perspective of learning user data, text fields containing + // default values are equivalent to empty fields. + field->value = string16(); + } + + field->set_heuristic_type(cached_field->second->heuristic_type()); + field->set_server_type(cached_field->second->server_type()); + } + } + + UpdateAutofillCount(); + + server_experiment_id_ = cached_form.server_experiment_id(); + + // The form signature should match between query and upload requests to the + // server. On many websites, form elements are dynamically added, removed, or + // rearranged via JavaScript between page load and form submission, so we + // copy over the |form_signature_field_names_| corresponding to the query + // request. + DCHECK_EQ(cached_form.form_name_, form_name_); + DCHECK_EQ(cached_form.source_url_, source_url_); + DCHECK_EQ(cached_form.target_url_, target_url_); + form_signature_field_names_ = cached_form.form_signature_field_names_; +} + +void FormStructure::LogQualityMetrics( + const AutofillMetrics& metric_logger, + const base::TimeTicks& load_time, + const base::TimeTicks& interaction_time, + const base::TimeTicks& submission_time) const { + std::string experiment_id = server_experiment_id(); + metric_logger.LogServerExperimentIdForUpload(experiment_id); + + size_t num_detected_field_types = 0; + bool did_autofill_all_possible_fields = true; + bool did_autofill_some_possible_fields = false; + for (size_t i = 0; i < field_count(); ++i) { + const AutofillField* field = this->field(i); + metric_logger.LogQualityMetric(AutofillMetrics::FIELD_SUBMITTED, + experiment_id); + + // No further logging for empty fields nor for fields where the entered data + // does not appear to already exist in the user's stored Autofill data. + const FieldTypeSet& field_types = field->possible_types(); + DCHECK(!field_types.empty()); + if (field_types.count(EMPTY_TYPE) || field_types.count(UNKNOWN_TYPE)) + continue; + + ++num_detected_field_types; + if (field->is_autofilled) + did_autofill_some_possible_fields = true; + else + did_autofill_all_possible_fields = false; + + // Collapse field types that Chrome treats as identical, e.g. home and + // billing address fields. + FieldTypeSet collapsed_field_types; + for (FieldTypeSet::const_iterator it = field_types.begin(); + it != field_types.end(); + ++it) { + // Since we currently only support US phone numbers, the (city code + main + // digits) number is almost always identical to the whole phone number. + // TODO(isherman): Improve this logic once we add support for + // international numbers. + if (*it == PHONE_HOME_CITY_AND_NUMBER) + collapsed_field_types.insert(PHONE_HOME_WHOLE_NUMBER); + else + collapsed_field_types.insert(AutofillType::GetEquivalentFieldType(*it)); + } + + // Capture the field's type, if it is unambiguous. + AutofillFieldType field_type = UNKNOWN_TYPE; + if (collapsed_field_types.size() == 1) + field_type = *collapsed_field_types.begin(); + + AutofillFieldType heuristic_type = field->heuristic_type(); + AutofillFieldType server_type = field->server_type(); + AutofillFieldType predicted_type = field->type(); + + // Log heuristic, server, and overall type quality metrics, independently of + // whether the field was autofilled. + if (heuristic_type == UNKNOWN_TYPE) { + metric_logger.LogHeuristicTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + field_type, experiment_id); + } else if (field_types.count(heuristic_type)) { + metric_logger.LogHeuristicTypePrediction(AutofillMetrics::TYPE_MATCH, + field_type, experiment_id); + } else { + metric_logger.LogHeuristicTypePrediction(AutofillMetrics::TYPE_MISMATCH, + field_type, experiment_id); + } + + if (server_type == NO_SERVER_DATA) { + metric_logger.LogServerTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + field_type, experiment_id); + } else if (field_types.count(server_type)) { + metric_logger.LogServerTypePrediction(AutofillMetrics::TYPE_MATCH, + field_type, experiment_id); + } else { + metric_logger.LogServerTypePrediction(AutofillMetrics::TYPE_MISMATCH, + field_type, experiment_id); + } + + if (predicted_type == UNKNOWN_TYPE) { + metric_logger.LogOverallTypePrediction(AutofillMetrics::TYPE_UNKNOWN, + field_type, experiment_id); + } else if (field_types.count(predicted_type)) { + metric_logger.LogOverallTypePrediction(AutofillMetrics::TYPE_MATCH, + field_type, experiment_id); + } else { + metric_logger.LogOverallTypePrediction(AutofillMetrics::TYPE_MISMATCH, + field_type, experiment_id); + } + + // TODO(isherman): <select> fields don't support |is_autofilled()|, so we + // have to skip them for the remaining metrics. + if (field->form_control_type == "select-one") + continue; + + if (field->is_autofilled) { + metric_logger.LogQualityMetric(AutofillMetrics::FIELD_AUTOFILLED, + experiment_id); + } else { + metric_logger.LogQualityMetric(AutofillMetrics::FIELD_NOT_AUTOFILLED, + experiment_id); + + if (heuristic_type == UNKNOWN_TYPE) { + metric_logger.LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_UNKNOWN, + experiment_id); + } else if (field_types.count(heuristic_type)) { + metric_logger.LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MATCH, + experiment_id); + } else { + metric_logger.LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_HEURISTIC_TYPE_MISMATCH, + experiment_id); + } + + if (server_type == NO_SERVER_DATA) { + metric_logger.LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_UNKNOWN, + experiment_id); + } else if (field_types.count(server_type)) { + metric_logger.LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MATCH, + experiment_id); + } else { + metric_logger.LogQualityMetric( + AutofillMetrics::NOT_AUTOFILLED_SERVER_TYPE_MISMATCH, + experiment_id); + } + } + } + + if (num_detected_field_types < RequiredFillableFields()) { + metric_logger.LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_NON_FILLABLE_FORM); + } else { + if (did_autofill_all_possible_fields) { + metric_logger.LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_ALL); + } else if (did_autofill_some_possible_fields) { + metric_logger.LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_SOME); + } else { + metric_logger.LogUserHappinessMetric( + AutofillMetrics::SUBMITTED_FILLABLE_FORM_AUTOFILLED_NONE); + } + + // Unlike the other times, the |submission_time| should always be available. + DCHECK(!submission_time.is_null()); + + // The |load_time| might be unset, in the case that the form was dynamically + // added to the DOM. + if (!load_time.is_null()) { + // Submission should always chronologically follow form load. + DCHECK(submission_time > load_time); + base::TimeDelta elapsed = submission_time - load_time; + if (did_autofill_some_possible_fields) + metric_logger.LogFormFillDurationFromLoadWithAutofill(elapsed); + else + metric_logger.LogFormFillDurationFromLoadWithoutAutofill(elapsed); + } + + // The |interaction_time| might be unset, in the case that the user + // submitted a blank form. + if (!interaction_time.is_null()) { + // Submission should always chronologically follow interaction. + DCHECK(submission_time > interaction_time); + base::TimeDelta elapsed = submission_time - interaction_time; + if (did_autofill_some_possible_fields) { + metric_logger.LogFormFillDurationFromInteractionWithAutofill(elapsed); + } else { + metric_logger.LogFormFillDurationFromInteractionWithoutAutofill( + elapsed); + } + } + } +} + +const AutofillField* FormStructure::field(size_t index) const { + if (index >= fields_.size()) { + NOTREACHED(); + return NULL; + } + + return fields_[index]; +} + +AutofillField* FormStructure::field(size_t index) { + return const_cast<AutofillField*>( + static_cast<const FormStructure*>(this)->field(index)); +} + +size_t FormStructure::field_count() const { + return fields_.size(); +} + +size_t FormStructure::checkable_field_count() const { + return checkable_field_count_; +} + +std::string FormStructure::server_experiment_id() const { + return server_experiment_id_; +} + +FormData FormStructure::ToFormData() const { + // |data.user_submitted| will always be false. + FormData data; + data.name = form_name_; + data.origin = source_url_; + data.action = target_url_; + data.method = ASCIIToUTF16(method_ == POST ? "POST" : "GET"); + + for (size_t i = 0; i < fields_.size(); ++i) { + data.fields.push_back(FormFieldData(*fields_[i])); + } + + return data; +} + +bool FormStructure::operator==(const FormData& form) const { + // TODO(jhawkins): Is this enough to differentiate a form? + if (form_name_ == form.name && + source_url_ == form.origin && + target_url_ == form.action) { + return true; + } + + // TODO(jhawkins): Compare field names, IDs and labels once we have labels + // set up. + + return false; +} + +bool FormStructure::operator!=(const FormData& form) const { + return !operator==(form); +} + +std::string FormStructure::Hash64Bit(const std::string& str) { + std::string hash_bin = base::SHA1HashString(str); + DCHECK_EQ(20U, hash_bin.length()); + + uint64 hash64 = (((static_cast<uint64>(hash_bin[0])) & 0xFF) << 56) | + (((static_cast<uint64>(hash_bin[1])) & 0xFF) << 48) | + (((static_cast<uint64>(hash_bin[2])) & 0xFF) << 40) | + (((static_cast<uint64>(hash_bin[3])) & 0xFF) << 32) | + (((static_cast<uint64>(hash_bin[4])) & 0xFF) << 24) | + (((static_cast<uint64>(hash_bin[5])) & 0xFF) << 16) | + (((static_cast<uint64>(hash_bin[6])) & 0xFF) << 8) | + ((static_cast<uint64>(hash_bin[7])) & 0xFF); + + return base::Uint64ToString(hash64); +} + +bool FormStructure::EncodeFormRequest( + FormStructure::EncodeRequestType request_type, + buzz::XmlElement* encompassing_xml_element) const { + if (!field_count()) // Nothing to add. + return false; + + // Some badly formatted web sites repeat fields - limit number of fields to + // 48, which is far larger than any valid form and XML still fits into 2K. + // Do not send requests for forms with more than this many fields, as they are + // near certainly not valid/auto-fillable. + const size_t kMaxFieldsOnTheForm = 48; + if (field_count() > kMaxFieldsOnTheForm) + return false; + + // Add the child nodes for the form fields. + for (size_t index = 0; index < field_count(); ++index) { + const AutofillField* field = fields_[index]; + if (request_type == FormStructure::UPLOAD) { + // Don't upload checkable fields. + if (field->is_checkable) + continue; + + FieldTypeSet types = field->possible_types(); + // |types| could be empty in unit-tests only. + for (FieldTypeSet::iterator field_type = types.begin(); + field_type != types.end(); ++field_type) { + buzz::XmlElement *field_element = new buzz::XmlElement( + buzz::QName(kXMLElementField)); + + field_element->SetAttr(buzz::QName(kAttributeSignature), + field->FieldSignature()); + field_element->SetAttr(buzz::QName(kAttributeAutofillType), + base::IntToString(*field_type)); + encompassing_xml_element->AddElement(field_element); + } + } else { + // Skip putting checkable fields in the request if Autocheckout is not + // enabled. + if (field->is_checkable && !IsAutocheckoutEnabled()) + continue; + + buzz::XmlElement *field_element = new buzz::XmlElement( + buzz::QName(kXMLElementField)); + field_element->SetAttr(buzz::QName(kAttributeSignature), + field->FieldSignature()); + encompassing_xml_element->AddElement(field_element); + } + } + return true; +} + +void FormStructure::ParseFieldTypesFromAutocompleteAttributes( + bool* found_types, + bool* found_sections) { + const std::string kDefaultSection = "-default"; + + *found_types = false; + *found_sections = false; + for (std::vector<AutofillField*>::iterator it = fields_.begin(); + it != fields_.end(); ++it) { + AutofillField* field = *it; + + // To prevent potential section name collisions, add a default suffix for + // other fields. Without this, 'autocomplete' attribute values + // "section--shipping street-address" and "shipping street-address" would be + // parsed identically, given the section handling code below. We do this + // before any validation so that fields with invalid attributes still end up + // in the default section. These default section names will be overridden + // by subsequent heuristic parsing steps if there are no author-specified + // section names. + field->set_section(kDefaultSection); + + // Canonicalize the attribute value by trimming whitespace, collapsing + // non-space characters (e.g. tab) to spaces, and converting to lowercase. + std::string autocomplete_attribute = + CollapseWhitespaceASCII(field->autocomplete_attribute, false); + autocomplete_attribute = StringToLowerASCII(autocomplete_attribute); + + // The autocomplete attribute is overloaded: it can specify either a field + // type hint or whether autocomplete should be enabled at all. Ignore the + // latter type of attribute value. + if (autocomplete_attribute.empty() || + autocomplete_attribute == "on" || + autocomplete_attribute == "off") { + continue; + } + + // Any other value, even it is invalid, is considered to be a type hint. + // This allows a website's author to specify an attribute like + // autocomplete="other" on a field to disable all Autofill heuristics for + // the form. + *found_types = true; + + // Tokenize the attribute value. Per the spec, the tokens are parsed in + // reverse order. + std::vector<std::string> tokens; + Tokenize(autocomplete_attribute, " ", &tokens); + + // The final token must be the field type. + // If it is not one of the known types, abort. + DCHECK(!tokens.empty()); + std::string field_type_token = tokens.back(); + tokens.pop_back(); + AutofillFieldType field_type = + FieldTypeFromAutocompleteType(field_type_token, *field); + if (field_type == UNKNOWN_TYPE) + continue; + + // The preceding token, if any, may be a type hint. + if (!tokens.empty() && IsContactTypeHint(tokens.back())) { + // If it is, it must match the field type; otherwise, abort. + // Note that an invalid token invalidates the entire attribute value, even + // if the other tokens are valid. + if (!ContactTypeHintMatchesFieldType(tokens.back(), field_type)) + continue; + + // Chrome Autofill ignores these type hints. + tokens.pop_back(); + } + + // The preceding token, if any, may be a fixed string that is either + // "shipping" or "billing". Chrome Autofill treats these as implicit + // section name suffixes. + DCHECK_EQ(kDefaultSection, field->section()); + std::string section = field->section(); + if (!tokens.empty() && + (tokens.back() == "shipping" || tokens.back() == "billing")) { + section = "-" + tokens.back(); + tokens.pop_back(); + } + + // The preceding token, if any, may be a named section. + const std::string kSectionPrefix = "section-"; + if (!tokens.empty() && + StartsWithASCII(tokens.back(), kSectionPrefix, true)) { + // Prepend this section name to the suffix set in the preceding block. + section = tokens.back().substr(kSectionPrefix.size()) + section; + tokens.pop_back(); + } + + // No other tokens are allowed. If there are any remaining, abort. + if (!tokens.empty()) + continue; + + if (section != kDefaultSection) { + *found_sections = true; + field->set_section(section); + } + + // No errors encountered while parsing! + // Update the |field|'s type based on what was parsed from the attribute. + field->set_heuristic_type(field_type); + if (field_type_token == "tel-local-prefix") + field->set_phone_part(AutofillField::PHONE_PREFIX); + else if (field_type_token == "tel-local-suffix") + field->set_phone_part(AutofillField::PHONE_SUFFIX); + } +} + +void FormStructure::IdentifySections(bool has_author_specified_sections) { + if (fields_.empty()) + return; + + if (!has_author_specified_sections) { + // Name sections after the first field in the section. + string16 current_section = fields_.front()->unique_name(); + + // Keep track of the types we've seen in this section. + std::set<AutofillFieldType> seen_types; + AutofillFieldType previous_type = UNKNOWN_TYPE; + + for (std::vector<AutofillField*>::iterator field = fields_.begin(); + field != fields_.end(); ++field) { + const AutofillFieldType current_type = + AutofillType::GetEquivalentFieldType((*field)->type()); + + bool already_saw_current_type = seen_types.count(current_type) > 0; + + // Forms often ask for multiple phone numbers -- e.g. both a daytime and + // evening phone number. Our phone number detection is also generally a + // little off. Hence, ignore this field type as a signal here. + if (AutofillType(current_type).group() == AutofillType::PHONE) + already_saw_current_type = false; + + // Some forms have adjacent fields of the same type. Two common examples: + // * Forms with two email fields, where the second is meant to "confirm" + // the first. + // * Forms with a <select> menu for states in some countries, and a + // freeform <input> field for states in other countries. (Usually, + // only one of these two will be visible for any given choice of + // country.) + // Generally, adjacent fields of the same type belong in the same logical + // section. + if (current_type == previous_type) + already_saw_current_type = false; + + previous_type = current_type; + + if (current_type != UNKNOWN_TYPE && already_saw_current_type) { + // We reached the end of a section, so start a new section. + seen_types.clear(); + current_section = (*field)->unique_name(); + } + + seen_types.insert(current_type); + (*field)->set_section(UTF16ToUTF8(current_section)); + } + } + + // Ensure that credit card and address fields are in separate sections. + // This simplifies the section-aware logic in autofill_manager.cc. + for (std::vector<AutofillField*>::iterator field = fields_.begin(); + field != fields_.end(); ++field) { + AutofillType::FieldTypeGroup field_type_group = + AutofillType((*field)->type()).group(); + if (field_type_group == AutofillType::CREDIT_CARD) + (*field)->set_section((*field)->section() + "-cc"); + else + (*field)->set_section((*field)->section() + "-default"); + } +} diff --git a/components/autofill/browser/form_structure.h b/components/autofill/browser/form_structure.h new file mode 100644 index 0000000..731fe5c --- /dev/null +++ b/components/autofill/browser/form_structure.h @@ -0,0 +1,241 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_FORM_STRUCTURE_H_ +#define COMPONENTS_AUTOFILL_BROWSER_FORM_STRUCTURE_H_ + +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/common/web_element_descriptor.h" +#include "googleurl/src/gurl.h" + +struct FormData; +struct FormDataPredictions; + +enum RequestMethod { + GET, + POST +}; + +enum UploadRequired { + UPLOAD_NOT_REQUIRED, + UPLOAD_REQUIRED, + USE_UPLOAD_RATES +}; + +class AutofillMetrics; + +namespace autofill { +struct AutocheckoutPageMetaData; +} + +namespace base { +class TimeTicks; +} + +namespace buzz { +class XmlElement; +} + +// FormStructure stores a single HTML form together with the values entered +// in the fields along with additional information needed by Autofill. +class FormStructure { + public: + FormStructure(const FormData& form, + const std::string& autocheckout_url_prefix); + virtual ~FormStructure(); + + // Runs several heuristics against the form fields to determine their possible + // types. + void DetermineHeuristicTypes(const AutofillMetrics& metric_logger); + + // Encodes the XML upload request from this FormStructure. + bool EncodeUploadRequest(const FieldTypeSet& available_field_types, + bool form_was_autofilled, + std::string* encoded_xml) const; + + // Encodes the XML query request for the set of forms. + // All fields are returned in one XML. For example, there are three forms, + // with 2, 4, and 3 fields. The returned XML would have type info for 9 + // fields, first two of which would be for the first form, next 4 for the + // second, and the rest is for the third. + static bool EncodeQueryRequest(const std::vector<FormStructure*>& forms, + std::vector<std::string>* encoded_signatures, + std::string* encoded_xml); + + // Parses the field types from the server query response. |forms| must be the + // same as the one passed to EncodeQueryRequest when constructing the query. + static void ParseQueryResponse( + const std::string& response_xml, + const std::vector<FormStructure*>& forms, + autofill::AutocheckoutPageMetaData* page_meta_data, + const AutofillMetrics& metric_logger); + + // Fills |forms| with the details from the given |form_structures| and their + // fields' predicted types. + static void GetFieldTypePredictions( + const std::vector<FormStructure*>& form_structures, + std::vector<FormDataPredictions>* forms); + + // The unique signature for this form, composed of the target url domain, + // the form name, and the form field names in a 64-bit hash. + std::string FormSignature() const; + + // Runs a quick heuristic to rule out forms that are obviously not + // auto-fillable, like google/yahoo/msn search, etc. The requirement that the + // form's method be POST is only applied if |require_method_post| is true. + bool IsAutofillable(bool require_method_post) const; + + // Resets |autofill_count_| and counts the number of auto-fillable fields. + // This is used when we receive server data for form fields. At that time, + // we may have more known fields than just the number of fields we matched + // heuristically. + void UpdateAutofillCount(); + + // Returns true if this form matches the structural requirements for Autofill. + // The requirement that the form's method be POST is only applied if + // |require_method_post| is true. + bool ShouldBeParsed(bool require_method_post) const; + + // Returns true if we should query the crowdsourcing server to determine this + // form's field types. If the form includes author-specified types, this will + // return false. + bool ShouldBeCrowdsourced() const; + + // Sets the field types and experiment id to be those set for |cached_form|. + void UpdateFromCache(const FormStructure& cached_form); + + // Logs quality metrics for |this|, which should be a user-submitted form. + // This method should only be called after the possible field types have been + // set for each field. |interaction_time| should be a timestamp corresponding + // to the user's first interaction with the form. |submission_time| should be + // a timestamp corresponding to the form's submission. + void LogQualityMetrics(const AutofillMetrics& metric_logger, + const base::TimeTicks& load_time, + const base::TimeTicks& interaction_time, + const base::TimeTicks& submission_time) const; + + // Classifies each field in |fields_| based upon its |autocomplete| attribute, + // if the attribute is available. The association is stored into the field's + // |heuristic_type|. + // Fills |found_types| with |true| if the attribute is available and neither + // empty nor set to the special values "on" or "off" for at least one field. + // Fills |found_sections| with |true| if the attribute specifies a section for + // at least one field. + void ParseFieldTypesFromAutocompleteAttributes(bool* found_types, + bool* found_sections); + + const AutofillField* field(size_t index) const; + AutofillField* field(size_t index); + size_t field_count() const; + size_t checkable_field_count() const; + + // Returns the number of fields that are able to be autofilled. + size_t autofill_count() const { return autofill_count_; } + + // Used for iterating over the fields. + std::vector<AutofillField*>::const_iterator begin() const { + return fields_.begin(); + } + std::vector<AutofillField*>::const_iterator end() const { + return fields_.end(); + } + + const GURL& source_url() const { return source_url_; } + + UploadRequired upload_required() const { return upload_required_; } + + virtual std::string server_experiment_id() const; + + // Returns a FormData containing the data this form structure knows about. + // |user_submitted| is currently always false. + FormData ToFormData() const; + + bool operator==(const FormData& form) const; + bool operator!=(const FormData& form) const; + + private: + friend class FormStructureTest; + FRIEND_TEST_ALL_PREFIXES(AutofillDownloadTest, QueryAndUploadTest); + + // 64-bit hash of the string - used in FormSignature and unit-tests. + static std::string Hash64Bit(const std::string& str); + + enum EncodeRequestType { + QUERY, + UPLOAD, + }; + + // Adds form info to |encompassing_xml_element|. |request_type| indicates if + // it is a query or upload. + bool EncodeFormRequest(EncodeRequestType request_type, + buzz::XmlElement* encompassing_xml_element) const; + + // Classifies each field in |fields_| into a logical section. + // Sections are identified by the heuristic that a logical section should not + // include multiple fields of the same autofill type (with some exceptions, as + // described in the implementation). Sections are furthermore distinguished + // as either credit card or non-credit card sections. + // If |has_author_specified_sections| is true, only the second pass -- + // distinguishing credit card sections from non-credit card ones -- is made. + void IdentifySections(bool has_author_specified_sections); + + bool IsAutocheckoutEnabled() const; + + // Returns the minimal number of fillable fields required to start autofill. + size_t RequiredFillableFields() const; + + // The name of the form. + string16 form_name_; + + // The source URL. + GURL source_url_; + + // The target URL. + GURL target_url_; + + // The number of fields able to be auto-filled. + size_t autofill_count_; + + // A vector of all the input fields in the form. + ScopedVector<AutofillField> fields_; + + // The number of fields able to be checked. + size_t checkable_field_count_; + + // The names of the form input elements, that are part of the form signature. + // The string starts with "&" and the names are also separated by the "&" + // character. E.g.: "&form_input1_name&form_input2_name&...&form_inputN_name" + std::string form_signature_field_names_; + + // Whether the server expects us to always upload, never upload, or default + // to the stored upload rates. + UploadRequired upload_required_; + + // The server experiment corresponding to the server types returned for this + // form. + std::string server_experiment_id_; + + // GET or POST. + RequestMethod method_; + + // Whether the form includes any field types explicitly specified by the site + // author, via the |autocompletetype| attribute. + bool has_author_specified_types_; + + // The URL prefix matched in autocheckout whitelist. An empty string implies + // autocheckout is not enabled for this form. + std::string autocheckout_url_prefix_; + + DISALLOW_COPY_AND_ASSIGN(FormStructure); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_FORM_STRUCTURE_H_ diff --git a/components/autofill/browser/form_structure_unittest.cc b/components/autofill/browser/form_structure_unittest.cc new file mode 100644 index 0000000..abbe1aa --- /dev/null +++ b/components/autofill/browser/form_structure_unittest.cc @@ -0,0 +1,2202 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/form_structure.h" + +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_field_data.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" + +using WebKit::WebInputElement; + +namespace { + +// Unlike the base AutofillMetrics, exposes copy and assignment constructors, +// which are handy for briefer test code. The AutofillMetrics class is +// stateless, so this is safe. +class TestAutofillMetrics : public AutofillMetrics { + public: + TestAutofillMetrics() {} + virtual ~TestAutofillMetrics() {} +}; + +} // anonymous namespace + + +namespace content { + +std::ostream& operator<<(std::ostream& os, const FormData& form) { + os << UTF16ToUTF8(form.name) + << " " + << UTF16ToUTF8(form.method) + << " " + << form.origin.spec() + << " " + << form.action.spec() + << " "; + + for (std::vector<FormFieldData>::const_iterator iter = + form.fields.begin(); + iter != form.fields.end(); ++iter) { + os << *iter + << " "; + } + + return os; +} + +} // namespace content + +class FormStructureTest { + public: + static std::string Hash64Bit(const std::string& str) { + return FormStructure::Hash64Bit(str); + } +}; + +TEST(FormStructureTest, FieldCount) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.label = ASCIIToUTF16("username"); + field.name = ASCIIToUTF16("username"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("password"); + field.name = ASCIIToUTF16("password"); + field.form_control_type = "password"; + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("address1"); + field.name = ASCIIToUTF16("address1"); + field.form_control_type = "text"; + field.should_autocomplete = false; + form.fields.push_back(field); + + // The render process sends all fields to browser including fields with + // autocomplete=off + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_EQ(4U, form_structure->field_count()); + + // We expect the same count when autocheckout is enabled. + form_structure.reset(new FormStructure(form, "http://fake_url")); + EXPECT_EQ(4U, form_structure->field_count()); +} + +TEST(FormStructureTest, AutofillCount) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.label = ASCIIToUTF16("username"); + field.name = ASCIIToUTF16("username"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("password"); + field.name = ASCIIToUTF16("password"); + field.form_control_type = "password"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("state"); + field.name = ASCIIToUTF16("state"); + field.form_control_type = "select-one"; + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + // Only text and select fields that are heuristically matched are counted. + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_EQ(1U, form_structure->autofill_count()); + + // Add a field with should_autocomplete=false. + field.label = ASCIIToUTF16("address1"); + field.name = ASCIIToUTF16("address1"); + field.form_control_type = "text"; + field.should_autocomplete = false; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + // DetermineHeuristicTypes also assign field type for fields with + // autocomplete=off thus autofill_count includes them. This is a bug, + // and they should not be counted. See http://crbug.com/176432 for details. + // TODO(benquan): change it to EXPECT_EQ(1U, ... when the bug is fixed. + EXPECT_EQ(2U, form_structure->autofill_count()); + + // All fields should be counted when Autocheckout is enabled. + form_structure.reset(new FormStructure(form, "http://fake_url")); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_EQ(2U, form_structure->autofill_count()); +} + +TEST(FormStructureTest, SourceURL) { + FormData form; + form.origin = GURL("http://www.foo.com/"); + form.method = ASCIIToUTF16("post"); + FormStructure form_structure(form, std::string()); + + EXPECT_EQ(form.origin, form_structure.source_url()); +} + +TEST(FormStructureTest, IsAutofillable) { + scoped_ptr<FormStructure> form_structure; + FormData form; + + // We need at least three text fields to be auto-fillable. + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + // When autocheckout is enabled, we enable autofill even the form has + // no fields + form_structure.reset(new FormStructure(form, "http://fake_url")); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + + field.label = ASCIIToUTF16("username"); + field.name = ASCIIToUTF16("username"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("password"); + field.name = ASCIIToUTF16("password"); + field.form_control_type = "password"; + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_FALSE(form_structure->IsAutofillable(true)); + + // We do not limit to three text fields when autocheckout is enabled. + form_structure.reset(new FormStructure(form, "http://fake_url")); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + + // We now have three text fields, but only two auto-fillable fields. + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("firstname"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("lastname"); + field.form_control_type = "text"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_FALSE(form_structure->IsAutofillable(true)); + + // We now have three auto-fillable fields. + field.label = ASCIIToUTF16("Email"); + field.name = ASCIIToUTF16("email"); + field.form_control_type = "email"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + + // The method must be 'post', though we can intentionally ignore this + // criterion for the sake of providing a helpful warning message to the user. + form.method = ASCIIToUTF16("get"); + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_FALSE(form_structure->IsAutofillable(true)); + EXPECT_TRUE(form_structure->IsAutofillable(false)); + + // The target cannot include http(s)://*/search... + form.method = ASCIIToUTF16("post"); + form.action = GURL("http://google.com/search?q=hello"); + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_FALSE(form_structure->IsAutofillable(true)); + + // But search can be in the URL. + form.action = GURL("http://search.com/?q=hello"); + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); +} + +TEST(FormStructureTest, ShouldBeParsed) { + scoped_ptr<FormStructure> form_structure; + FormData form; + + // We need at least three text fields to be parseable. + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.label = ASCIIToUTF16("username"); + field.name = ASCIIToUTF16("username"); + field.form_control_type = "text"; + form.fields.push_back(field); + + FormFieldData checkable_field; + checkable_field.is_checkable = true; + checkable_field.name = ASCIIToUTF16("radiobtn"); + checkable_field.form_control_type = "radio"; + form.fields.push_back(checkable_field); + + checkable_field.name = ASCIIToUTF16("checkbox"); + checkable_field.form_control_type = "checkbox"; + form.fields.push_back(checkable_field); + + // We have only one text field, should not be parsed. + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_FALSE(form_structure->ShouldBeParsed(true)); + + // The form should be parsed for autocheckout even it has less than three + // text fields. + form_structure.reset(new FormStructure(form, "http://fake_url")); + EXPECT_TRUE(form_structure->ShouldBeParsed(true)); + + // We now have three text fields, though only two are auto-fillable. + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("firstname"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("lastname"); + field.form_control_type = "text"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_TRUE(form_structure->ShouldBeParsed(true)); + + // The method must be 'post', though we can intentionally ignore this + // criterion for the sake of providing a helpful warning message to the user. + form.method = ASCIIToUTF16("get"); + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_FALSE(form_structure->IsAutofillable(true)); + EXPECT_TRUE(form_structure->ShouldBeParsed(false)); + + // The target cannot include http(s)://*/search... + form.method = ASCIIToUTF16("post"); + form.action = GURL("http://google.com/search?q=hello"); + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_FALSE(form_structure->ShouldBeParsed(true)); + + // But search can be in the URL. + form.action = GURL("http://search.com/?q=hello"); + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_TRUE(form_structure->ShouldBeParsed(true)); + + // The form need only have three fields, but at least one must be a text + // field. + form.fields.clear(); + + field.label = ASCIIToUTF16("Email"); + field.name = ASCIIToUTF16("email"); + field.form_control_type = "email"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("State"); + field.name = ASCIIToUTF16("state"); + field.form_control_type = "select-one"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Country"); + field.name = ASCIIToUTF16("country"); + field.form_control_type = "select-one"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_TRUE(form_structure->ShouldBeParsed(true)); + + form.fields[0].form_control_type = "select-one"; + // Now, no text fields. + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_FALSE(form_structure->ShouldBeParsed(true)); + + // It should be parsed when autocheckout is enabled. + form_structure.reset(new FormStructure(form, "http://fake_url")); + EXPECT_TRUE(form_structure->ShouldBeParsed(true)); +} + +TEST(FormStructureTest, HeuristicsContactInfo) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("firstname"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("lastname"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Email"); + field.name = ASCIIToUTF16("email"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Phone"); + field.name = ASCIIToUTF16("phone"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("address"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("City"); + field.name = ASCIIToUTF16("city"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Zip code"); + field.name = ASCIIToUTF16("zipcode"); + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + + // Expect the correct number of fields. + ASSERT_EQ(8U, form_structure->field_count()); + ASSERT_EQ(7U, form_structure->autofill_count()); + + // First name. + EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type()); + // Last name. + EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type()); + // Email. + EXPECT_EQ(EMAIL_ADDRESS, form_structure->field(2)->heuristic_type()); + // Phone. + EXPECT_EQ(PHONE_HOME_WHOLE_NUMBER, + form_structure->field(3)->heuristic_type()); + // Address. + EXPECT_EQ(ADDRESS_HOME_LINE1, form_structure->field(4)->heuristic_type()); + // City. + EXPECT_EQ(ADDRESS_HOME_CITY, form_structure->field(5)->heuristic_type()); + // Zip. + EXPECT_EQ(ADDRESS_HOME_ZIP, form_structure->field(6)->heuristic_type()); + // Submit. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(7)->heuristic_type()); +} + +// Verify that we can correctly process the |autocomplete| attribute. +TEST(FormStructureTest, HeuristicsAutocompleteAttribute) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = string16(); + field.name = ASCIIToUTF16("field1"); + field.autocomplete_attribute = "given-name"; + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("field2"); + field.autocomplete_attribute = "family-name"; + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("field3"); + field.autocomplete_attribute = "email"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + + // Expect the correct number of fields. + ASSERT_EQ(3U, form_structure->field_count()); + ASSERT_EQ(3U, form_structure->autofill_count()); + + EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type()); + EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type()); + EXPECT_EQ(EMAIL_ADDRESS, form_structure->field(2)->heuristic_type()); +} + +// Verify that we can correctly process the 'autocomplete' attribute for phone +// number types (especially phone prefixes and suffixes). +TEST(FormStructureTest, HeuristicsAutocompleteAttributePhoneTypes) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = string16(); + field.name = ASCIIToUTF16("field1"); + field.autocomplete_attribute = "tel-local"; + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("field2"); + field.autocomplete_attribute = "tel-local-prefix"; + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("field3"); + field.autocomplete_attribute = "tel-local-suffix"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + + // Expect the correct number of fields. + ASSERT_EQ(3U, form_structure->field_count()); + EXPECT_EQ(3U, form_structure->autofill_count()); + + EXPECT_EQ(PHONE_HOME_NUMBER, form_structure->field(0)->heuristic_type()); + EXPECT_EQ(AutofillField::IGNORED, form_structure->field(0)->phone_part()); + EXPECT_EQ(PHONE_HOME_NUMBER, form_structure->field(1)->heuristic_type()); + EXPECT_EQ(AutofillField::PHONE_PREFIX, + form_structure->field(1)->phone_part()); + EXPECT_EQ(PHONE_HOME_NUMBER, form_structure->field(2)->heuristic_type()); + EXPECT_EQ(AutofillField::PHONE_SUFFIX, + form_structure->field(2)->phone_part()); +} + +// If at least one field includes type hints in the 'autocomplete' attribute, we +// should not try to apply any other heuristics. +TEST(FormStructureTest, AutocompleteAttributeOverridesOtherHeuristics) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + // Start with a regular contact form. + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("firstname"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("lastname"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Email"); + field.name = ASCIIToUTF16("email"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + EXPECT_TRUE(form_structure->ShouldBeCrowdsourced()); + + ASSERT_EQ(3U, form_structure->field_count()); + ASSERT_EQ(3U, form_structure->autofill_count()); + + EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type()); + EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type()); + EXPECT_EQ(EMAIL_ADDRESS, form_structure->field(2)->heuristic_type()); + + // Now update the first form field to include an 'autocomplete' attribute. + form.fields.front().autocomplete_attribute = "x-other"; + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_FALSE(form_structure->IsAutofillable(true)); + EXPECT_FALSE(form_structure->ShouldBeCrowdsourced()); + + ASSERT_EQ(3U, form_structure->field_count()); + ASSERT_EQ(0U, form_structure->autofill_count()); + + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(0)->heuristic_type()); + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(1)->heuristic_type()); + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(2)->heuristic_type()); +} + +// Verify that we can correctly process sections listed in the |autocomplete| +// attribute. +TEST(FormStructureTest, HeuristicsAutocompleteAttributeWithSections) { + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + // Some fields will have no section specified. These fall into the default + // section. + field.autocomplete_attribute = "email"; + form.fields.push_back(field); + + // We allow arbitrary section names. + field.autocomplete_attribute = "section-foo email"; + form.fields.push_back(field); + + // "shipping" and "billing" are special section tokens that don't require the + // "section-" prefix. + field.autocomplete_attribute = "shipping email"; + form.fields.push_back(field); + field.autocomplete_attribute = "billing email"; + form.fields.push_back(field); + + // "shipping" and "billing" can be combined with other section names. + field.autocomplete_attribute = "section-foo shipping email"; + form.fields.push_back(field); + field.autocomplete_attribute = "section-foo billing email"; + form.fields.push_back(field); + + // We don't do anything clever to try to coalesce sections; it's up to site + // authors to avoid typos. + field.autocomplete_attribute = "section--foo email"; + form.fields.push_back(field); + + // "shipping email" and "section--shipping" email should be parsed as + // different sections. This is only an interesting test due to how we + // implement implicit section names from attributes like "shipping email"; see + // the implementation for more details. + field.autocomplete_attribute = "section--shipping email"; + form.fields.push_back(field); + + // Credit card fields are implicitly in a separate section from other fields. + field.autocomplete_attribute = "section-foo cc-number"; + form.fields.push_back(field); + + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure.IsAutofillable(true)); + + // Expect the correct number of fields. + ASSERT_EQ(9U, form_structure.field_count()); + EXPECT_EQ(9U, form_structure.autofill_count()); + + // All of the fields in this form should be parsed as belonging to different + // sections. + std::set<std::string> section_names; + for (size_t i = 0; i < 9; ++i) { + section_names.insert(form_structure.field(i)->section()); + } + EXPECT_EQ(9U, section_names.size()); +} + +// Verify that we can correctly process a degenerate section listed in the +// |autocomplete| attribute. +TEST(FormStructureTest, HeuristicsAutocompleteAttributeWithSectionsDegenerate) { + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + // Some fields will have no section specified. These fall into the default + // section. + field.autocomplete_attribute = "email"; + form.fields.push_back(field); + + // Specifying "section-" is equivalent to not specifying a section. + field.autocomplete_attribute = "section- email"; + form.fields.push_back(field); + + // Invalid tokens should prevent us from setting a section name. + field.autocomplete_attribute = "garbage section-foo email"; + form.fields.push_back(field); + field.autocomplete_attribute = "garbage section-bar email"; + form.fields.push_back(field); + field.autocomplete_attribute = "garbage shipping email"; + form.fields.push_back(field); + field.autocomplete_attribute = "garbage billing email"; + form.fields.push_back(field); + + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + + // Expect the correct number of fields. + ASSERT_EQ(6U, form_structure.field_count()); + EXPECT_EQ(2U, form_structure.autofill_count()); + + // All of the fields in this form should be parsed as belonging to the same + // section. + std::set<std::string> section_names; + for (size_t i = 0; i < 6; ++i) { + section_names.insert(form_structure.field(i)->section()); + } + EXPECT_EQ(1U, section_names.size()); +} + +// Verify that we can correctly process repeated sections listed in the +// |autocomplete| attribute. +TEST(FormStructureTest, HeuristicsAutocompleteAttributeWithSectionsRepeated) { + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.autocomplete_attribute = "section-foo email"; + form.fields.push_back(field); + field.autocomplete_attribute = "section-foo street-address"; + form.fields.push_back(field); + + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + + // Expect the correct number of fields. + ASSERT_EQ(2U, form_structure.field_count()); + EXPECT_EQ(2U, form_structure.autofill_count()); + + // All of the fields in this form should be parsed as belonging to the same + // section. + std::set<std::string> section_names; + for (size_t i = 0; i < 2; ++i) { + section_names.insert(form_structure.field(i)->section()); + } + EXPECT_EQ(1U, section_names.size()); +} + +// Verify that we do not override the author-specified sections from a form with +// local heuristics. +TEST(FormStructureTest, HeuristicsDontOverrideAutocompleteAttributeSections) { + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.name = ASCIIToUTF16("one"); + field.autocomplete_attribute = "street-address"; + form.fields.push_back(field); + field.name = string16(); + field.autocomplete_attribute = "section-foo email"; + form.fields.push_back(field); + field.name = string16(); + field.autocomplete_attribute = "name"; + form.fields.push_back(field); + field.name = ASCIIToUTF16("two"); + field.autocomplete_attribute = "street-address"; + form.fields.push_back(field); + + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + + // Expect the correct number of fields. + ASSERT_EQ(4U, form_structure.field_count()); + EXPECT_EQ(4U, form_structure.autofill_count()); + + // Normally, the two separate address fields would cause us to detect two + // separate sections; but because there is an author-specified section in this + // form, we do not apply these usual heuristics. + EXPECT_EQ(ASCIIToUTF16("one"), form_structure.field(0)->name); + EXPECT_EQ(ASCIIToUTF16("two"), form_structure.field(3)->name); + EXPECT_EQ(form_structure.field(0)->section(), + form_structure.field(3)->section()); +} + +TEST(FormStructureTest, HeuristicsSample8) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Your First Name:"); + field.name = ASCIIToUTF16("bill.first"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Your Last Name:"); + field.name = ASCIIToUTF16("bill.last"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Street Address Line 1:"); + field.name = ASCIIToUTF16("bill.street1"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Street Address Line 2:"); + field.name = ASCIIToUTF16("bill.street2"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("City"); + field.name = ASCIIToUTF16("bill.city"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("State (U.S.):"); + field.name = ASCIIToUTF16("bill.state"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Zip/Postal Code:"); + field.name = ASCIIToUTF16("BillTo.PostalCode"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Country:"); + field.name = ASCIIToUTF16("bill.country"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Phone Number:"); + field.name = ASCIIToUTF16("BillTo.Phone"); + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(10U, form_structure->field_count()); + ASSERT_EQ(9U, form_structure->autofill_count()); + + // First name. + EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type()); + // Last name. + EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type()); + // Address. + EXPECT_EQ(ADDRESS_BILLING_LINE1, form_structure->field(2)->heuristic_type()); + // Address. + EXPECT_EQ(ADDRESS_BILLING_LINE2, form_structure->field(3)->heuristic_type()); + // City. + EXPECT_EQ(ADDRESS_BILLING_CITY, form_structure->field(4)->heuristic_type()); + // State. + EXPECT_EQ(ADDRESS_BILLING_STATE, form_structure->field(5)->heuristic_type()); + // Zip. + EXPECT_EQ(ADDRESS_BILLING_ZIP, form_structure->field(6)->heuristic_type()); + // Country. + EXPECT_EQ(ADDRESS_BILLING_COUNTRY, + form_structure->field(7)->heuristic_type()); + // Phone. + EXPECT_EQ(PHONE_HOME_WHOLE_NUMBER, + form_structure->field(8)->heuristic_type()); + // Submit. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(9)->heuristic_type()); +} + +TEST(FormStructureTest, HeuristicsSample6) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("E-mail address"); + field.name = ASCIIToUTF16("email"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Full name"); + field.name = ASCIIToUTF16("name"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Company"); + field.name = ASCIIToUTF16("company"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("address"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("City"); + field.name = ASCIIToUTF16("city"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Zip Code"); + field.name = ASCIIToUTF16("Home.PostalCode"); + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.value = ASCIIToUTF16("continue"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(7U, form_structure->field_count()); + ASSERT_EQ(6U, form_structure->autofill_count()); + + // Email. + EXPECT_EQ(EMAIL_ADDRESS, form_structure->field(0)->heuristic_type()); + // Full name. + EXPECT_EQ(NAME_FULL, form_structure->field(1)->heuristic_type()); + // Company + EXPECT_EQ(COMPANY_NAME, form_structure->field(2)->heuristic_type()); + // Address. + EXPECT_EQ(ADDRESS_HOME_LINE1, form_structure->field(3)->heuristic_type()); + // City. + EXPECT_EQ(ADDRESS_HOME_CITY, form_structure->field(4)->heuristic_type()); + // Zip. + EXPECT_EQ(ADDRESS_HOME_ZIP, form_structure->field(5)->heuristic_type()); + // Submit. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(6)->heuristic_type()); +} + +// Tests a sequence of FormFields where only labels are supplied to heuristics +// for matching. This works because FormFieldData labels are matched in the +// case that input element ids (or |name| fields) are missing. +TEST(FormStructureTest, HeuristicsLabelsOnly) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name"); + field.name = string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name"); + field.name = string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Email"); + field.name = string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Phone"); + field.name = string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address"); + field.name = string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address"); + field.name = string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Zip code"); + field.name = string16(); + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(8U, form_structure->field_count()); + ASSERT_EQ(7U, form_structure->autofill_count()); + + // First name. + EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type()); + // Last name. + EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type()); + // Email. + EXPECT_EQ(EMAIL_ADDRESS, form_structure->field(2)->heuristic_type()); + // Phone. + EXPECT_EQ(PHONE_HOME_WHOLE_NUMBER, + form_structure->field(3)->heuristic_type()); + // Address. + EXPECT_EQ(ADDRESS_HOME_LINE1, form_structure->field(4)->heuristic_type()); + // Address Line 2. + EXPECT_EQ(ADDRESS_HOME_LINE2, form_structure->field(5)->heuristic_type()); + // Zip. + EXPECT_EQ(ADDRESS_HOME_ZIP, form_structure->field(6)->heuristic_type()); + // Submit. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(7)->heuristic_type()); +} + +TEST(FormStructureTest, HeuristicsCreditCardInfo) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name on Card"); + field.name = ASCIIToUTF16("name_on_card"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Exp Month"); + field.name = ASCIIToUTF16("ccmonth"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Exp Year"); + field.name = ASCIIToUTF16("ccyear"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Verification"); + field.name = ASCIIToUTF16("verification"); + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(6U, form_structure->field_count()); + ASSERT_EQ(5U, form_structure->autofill_count()); + + // Credit card name. + EXPECT_EQ(CREDIT_CARD_NAME, form_structure->field(0)->heuristic_type()); + // Credit card number. + EXPECT_EQ(CREDIT_CARD_NUMBER, form_structure->field(1)->heuristic_type()); + // Credit card expiration month. + EXPECT_EQ(CREDIT_CARD_EXP_MONTH, form_structure->field(2)->heuristic_type()); + // Credit card expiration year. + EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR, + form_structure->field(3)->heuristic_type()); + // CVV. + EXPECT_EQ(CREDIT_CARD_VERIFICATION_CODE, + form_structure->field(4)->heuristic_type()); + // Submit. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(5)->heuristic_type()); +} + +TEST(FormStructureTest, HeuristicsCreditCardInfoWithUnknownCardField) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name on Card"); + field.name = ASCIIToUTF16("name_on_card"); + form.fields.push_back(field); + + // This is not a field we know how to process. But we should skip over it + // and process the other fields in the card block. + field.label = ASCIIToUTF16("Card image"); + field.name = ASCIIToUTF16("card_image"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Exp Month"); + field.name = ASCIIToUTF16("ccmonth"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Exp Year"); + field.name = ASCIIToUTF16("ccyear"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Verification"); + field.name = ASCIIToUTF16("verification"); + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(7U, form_structure->field_count()); + ASSERT_EQ(5U, form_structure->autofill_count()); + + // Credit card name. + EXPECT_EQ(CREDIT_CARD_NAME, form_structure->field(0)->heuristic_type()); + // Credit card type. This is an unknown type but related to the credit card. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(1)->heuristic_type()); + // Credit card number. + EXPECT_EQ(CREDIT_CARD_NUMBER, form_structure->field(2)->heuristic_type()); + // Credit card expiration month. + EXPECT_EQ(CREDIT_CARD_EXP_MONTH, form_structure->field(3)->heuristic_type()); + // Credit card expiration year. + EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR, + form_structure->field(4)->heuristic_type()); + // CVV. + EXPECT_EQ(CREDIT_CARD_VERIFICATION_CODE, + form_structure->field(5)->heuristic_type()); + // Submit. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(6)->heuristic_type()); +} + +TEST(FormStructureTest, ThreeAddressLines) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Address Line1"); + field.name = ASCIIToUTF16("Address"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address Line2"); + field.name = ASCIIToUTF16("Address"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address Line3"); + field.name = ASCIIToUTF16("Address"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("City"); + field.name = ASCIIToUTF16("city"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(4U, form_structure->field_count()); + ASSERT_EQ(3U, form_structure->autofill_count()); + + // Address Line 1. + EXPECT_EQ(ADDRESS_HOME_LINE1, form_structure->field(0)->heuristic_type()); + // Address Line 2. + EXPECT_EQ(ADDRESS_HOME_LINE2, form_structure->field(1)->heuristic_type()); + // Address Line 3. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(2)->heuristic_type()); + // City. + EXPECT_EQ(ADDRESS_HOME_CITY, form_structure->field(3)->heuristic_type()); +} + +// This test verifies that "addressLine1" and "addressLine2" matches heuristics. +// This occured in https://www.gorillaclothing.com/. http://crbug.com/52126. +TEST(FormStructureTest, BillingAndShippingAddresses) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Address Line1"); + field.name = ASCIIToUTF16("shipping.address.addressLine1"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address Line2"); + field.name = ASCIIToUTF16("shipping.address.addressLine2"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address Line1"); + field.name = ASCIIToUTF16("billing.address.addressLine1"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address Line2"); + field.name = ASCIIToUTF16("billing.address.addressLine2"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(4U, form_structure->field_count()); + ASSERT_EQ(4U, form_structure->autofill_count()); + + // Address Line 1. + EXPECT_EQ(ADDRESS_HOME_LINE1, form_structure->field(0)->heuristic_type()); + // Address Line 2. + EXPECT_EQ(ADDRESS_HOME_LINE2, form_structure->field(1)->heuristic_type()); + // Address Line 1. + EXPECT_EQ(ADDRESS_BILLING_LINE1, form_structure->field(2)->heuristic_type()); + // Address Line 2. + EXPECT_EQ(ADDRESS_BILLING_LINE2, form_structure->field(3)->heuristic_type()); +} + + +// This example comes from expedia.com where they use a "Suite" label to +// indicate a suite or apartment number. We interpret this as address line 2. +// And the following "Street address second line" we interpret as address line +// 3 and discard. +// See http://crbug.com/48197 for details. +TEST(FormStructureTest, ThreeAddressLinesExpedia) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Street:"); + field.name = ASCIIToUTF16("FOPIH_RgWebCC_0_IHAddress_ads1"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Suite or Apt:"); + field.name = ASCIIToUTF16("FOPIH_RgWebCC_0_IHAddress_adap"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Street address second line"); + field.name = ASCIIToUTF16("FOPIH_RgWebCC_0_IHAddress_ads2"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("City:"); + field.name = ASCIIToUTF16("FOPIH_RgWebCC_0_IHAddress_adct"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(4U, form_structure->field_count()); + EXPECT_EQ(3U, form_structure->autofill_count()); + + // Address Line 1. + EXPECT_EQ(ADDRESS_HOME_LINE1, form_structure->field(0)->heuristic_type()); + // Suite / Apt. + EXPECT_EQ(ADDRESS_HOME_LINE2, form_structure->field(1)->heuristic_type()); + // Address Line 3. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(2)->heuristic_type()); + // City. + EXPECT_EQ(ADDRESS_HOME_CITY, form_structure->field(3)->heuristic_type()); +} + +// This example comes from ebay.com where the word "suite" appears in the label +// and the name "address2" clearly indicates that this is the address line 2. +// See http://crbug.com/48197 for details. +TEST(FormStructureTest, TwoAddressLinesEbay) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Address Line1"); + field.name = ASCIIToUTF16("address1"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Floor number, suite number, etc"); + field.name = ASCIIToUTF16("address2"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("City:"); + field.name = ASCIIToUTF16("city"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(3U, form_structure->field_count()); + ASSERT_EQ(3U, form_structure->autofill_count()); + + // Address Line 1. + EXPECT_EQ(ADDRESS_HOME_LINE1, form_structure->field(0)->heuristic_type()); + // Address Line 2. + EXPECT_EQ(ADDRESS_HOME_LINE2, form_structure->field(1)->heuristic_type()); + // City. + EXPECT_EQ(ADDRESS_HOME_CITY, form_structure->field(2)->heuristic_type()); +} + +TEST(FormStructureTest, HeuristicsStateWithProvince) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Address Line1"); + field.name = ASCIIToUTF16("Address"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address Line2"); + field.name = ASCIIToUTF16("Address"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("State/Province/Region"); + field.name = ASCIIToUTF16("State"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(3U, form_structure->field_count()); + ASSERT_EQ(3U, form_structure->autofill_count()); + + // Address Line 1. + EXPECT_EQ(ADDRESS_HOME_LINE1, form_structure->field(0)->heuristic_type()); + // Address Line 2. + EXPECT_EQ(ADDRESS_HOME_LINE2, form_structure->field(1)->heuristic_type()); + // State. + EXPECT_EQ(ADDRESS_HOME_STATE, form_structure->field(2)->heuristic_type()); +} + +// This example comes from lego.com's checkout page. +TEST(FormStructureTest, HeuristicsWithBilling) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name*:"); + field.name = ASCIIToUTF16("editBillingAddress$firstNameBox"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name*:"); + field.name = ASCIIToUTF16("editBillingAddress$lastNameBox"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Company Name:"); + field.name = ASCIIToUTF16("editBillingAddress$companyBox"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address*:"); + field.name = ASCIIToUTF16("editBillingAddress$addressLine1Box"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Apt/Suite :"); + field.name = ASCIIToUTF16("editBillingAddress$addressLine2Box"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("City*:"); + field.name = ASCIIToUTF16("editBillingAddress$cityBox"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("State/Province*:"); + field.name = ASCIIToUTF16("editBillingAddress$stateDropDown"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Country*:"); + field.name = ASCIIToUTF16("editBillingAddress$countryDropDown"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Postal Code*:"); + field.name = ASCIIToUTF16("editBillingAddress$zipCodeBox"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Phone*:"); + field.name = ASCIIToUTF16("editBillingAddress$phoneBox"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Email Address*:"); + field.name = ASCIIToUTF16("email$emailBox"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(11U, form_structure->field_count()); + ASSERT_EQ(11U, form_structure->autofill_count()); + + EXPECT_EQ(NAME_FIRST, form_structure->field(0)->heuristic_type()); + EXPECT_EQ(NAME_LAST, form_structure->field(1)->heuristic_type()); + EXPECT_EQ(COMPANY_NAME, form_structure->field(2)->heuristic_type()); + EXPECT_EQ(ADDRESS_BILLING_LINE1, form_structure->field(3)->heuristic_type()); + EXPECT_EQ(ADDRESS_BILLING_LINE2, form_structure->field(4)->heuristic_type()); + EXPECT_EQ(ADDRESS_BILLING_CITY, form_structure->field(5)->heuristic_type()); + EXPECT_EQ(ADDRESS_BILLING_STATE, form_structure->field(6)->heuristic_type()); + EXPECT_EQ(ADDRESS_BILLING_COUNTRY, + form_structure->field(7)->heuristic_type()); + EXPECT_EQ(ADDRESS_BILLING_ZIP, form_structure->field(8)->heuristic_type()); + EXPECT_EQ(PHONE_HOME_WHOLE_NUMBER, + form_structure->field(9)->heuristic_type()); + EXPECT_EQ(EMAIL_ADDRESS, form_structure->field(10)->heuristic_type()); +} + +TEST(FormStructureTest, ThreePartPhoneNumber) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Phone:"); + field.name = ASCIIToUTF16("dayphone1"); + field.max_length = 0; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("-"); + field.name = ASCIIToUTF16("dayphone2"); + field.max_length = 3; // Size of prefix is 3. + form.fields.push_back(field); + + field.label = ASCIIToUTF16("-"); + field.name = ASCIIToUTF16("dayphone3"); + field.max_length = 4; // Size of suffix is 4. If unlimited size is + // passed, phone will be parsed as + // <country code> - <area code> - <phone>. + form.fields.push_back(field); + + field.label = ASCIIToUTF16("ext.:"); + field.name = ASCIIToUTF16("dayphone4"); + field.max_length = 0; + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + ASSERT_EQ(4U, form_structure->field_count()); + ASSERT_EQ(3U, form_structure->autofill_count()); + + // Area code. + EXPECT_EQ(PHONE_HOME_CITY_CODE, form_structure->field(0)->heuristic_type()); + // Phone number suffix. + EXPECT_EQ(PHONE_HOME_NUMBER, + form_structure->field(1)->heuristic_type()); + // Phone number suffix. + EXPECT_EQ(PHONE_HOME_NUMBER, + form_structure->field(2)->heuristic_type()); + // Unknown. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(3)->heuristic_type()); +} + +TEST(FormStructureTest, HeuristicsInfernoCC) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name on Card"); + field.name = ASCIIToUTF16("name_on_card"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("billing_address"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Expiration Date"); + field.name = ASCIIToUTF16("expiration_month"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Expiration Year"); + field.name = ASCIIToUTF16("expiration_year"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + + // Expect the correct number of fields. + ASSERT_EQ(5U, form_structure->field_count()); + EXPECT_EQ(5U, form_structure->autofill_count()); + + // Name on Card. + EXPECT_EQ(CREDIT_CARD_NAME, form_structure->field(0)->heuristic_type()); + // Address. + EXPECT_EQ(ADDRESS_BILLING_LINE1, form_structure->field(1)->heuristic_type()); + // Card Number. + EXPECT_EQ(CREDIT_CARD_NUMBER, form_structure->field(2)->heuristic_type()); + // Expiration Date. + EXPECT_EQ(CREDIT_CARD_EXP_MONTH, form_structure->field(3)->heuristic_type()); + // Expiration Year. + EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR, + form_structure->field(4)->heuristic_type()); +} + +TEST(FormStructureTest, CVCCodeClash) { + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Card number"); + field.name = ASCIIToUTF16("ccnumber"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("First name"); + field.name = ASCIIToUTF16("first_name"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last name"); + field.name = ASCIIToUTF16("last_name"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Expiration date"); + field.name = ASCIIToUTF16("ccexpiresmonth"); + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("ccexpiresyear"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("cvc number"); + field.name = ASCIIToUTF16("csc"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + + // Expect the correct number of fields. + ASSERT_EQ(6U, form_structure->field_count()); + ASSERT_EQ(5U, form_structure->autofill_count()); + + // Card Number. + EXPECT_EQ(CREDIT_CARD_NUMBER, form_structure->field(0)->heuristic_type()); + // First name, taken as name on card. + EXPECT_EQ(CREDIT_CARD_NAME, form_structure->field(1)->heuristic_type()); + // Last name is not merged. + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(2)->heuristic_type()); + // Expiration Date. + EXPECT_EQ(CREDIT_CARD_EXP_MONTH, form_structure->field(3)->heuristic_type()); + // Expiration Year. + EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR, + form_structure->field(4)->heuristic_type()); + // CVC code. + EXPECT_EQ(CREDIT_CARD_VERIFICATION_CODE, + form_structure->field(5)->heuristic_type()); +} + +TEST(FormStructureTest, EncodeQueryRequest) { + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name on Card"); + field.name = ASCIIToUTF16("name_on_card"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("billing_address"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Card Number"); + field.name = ASCIIToUTF16("card_number"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Expiration Date"); + field.name = ASCIIToUTF16("expiration_month"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Expiration Year"); + field.name = ASCIIToUTF16("expiration_year"); + form.fields.push_back(field); + + // Add checkable field. + FormFieldData checkable_field; + checkable_field.is_checkable = true; + checkable_field.label = ASCIIToUTF16("Checkable1"); + checkable_field.name = ASCIIToUTF16("Checkable1"); + form.fields.push_back(checkable_field); + + ScopedVector<FormStructure> forms; + forms.push_back(new FormStructure(form, std::string())); + std::vector<std::string> encoded_signatures; + std::string encoded_xml; + const char * const kSignature1 = "11337937696949187602"; + const char * const kResponse1 = + "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?><autofillquery " + "clientversion=\"6.1.1715.1442/en (GGLL)\" accepts=\"e\"><form " + "signature=\"11337937696949187602\"><field signature=\"412125936\"/>" + "<field signature=\"1917667676\"/><field signature=\"2226358947\"/>" + "<field signature=\"747221617\"/><field signature=\"4108155786\"/></form>" + "</autofillquery>"; + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms.get(), + &encoded_signatures, + &encoded_xml)); + ASSERT_EQ(1U, encoded_signatures.size()); + EXPECT_EQ(kSignature1, encoded_signatures[0]); + EXPECT_EQ(kResponse1, encoded_xml); + + // Add the same form, only one will be encoded, so EncodeQueryRequest() should + // return the same data. + forms.push_back(new FormStructure(form, std::string())); + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms.get(), + &encoded_signatures, + &encoded_xml)); + ASSERT_EQ(1U, encoded_signatures.size()); + EXPECT_EQ(kSignature1, encoded_signatures[0]); + EXPECT_EQ(kResponse1, encoded_xml); + // Add 5 address fields - this should be still a valid form. + for (size_t i = 0; i < 5; ++i) { + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("address"); + form.fields.push_back(field); + } + + forms.push_back(new FormStructure(form, std::string())); + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms.get(), + &encoded_signatures, + &encoded_xml)); + ASSERT_EQ(2U, encoded_signatures.size()); + EXPECT_EQ(kSignature1, encoded_signatures[0]); + const char * const kSignature2 = "8308881815906226214"; + EXPECT_EQ(kSignature2, encoded_signatures[1]); + const char * const kResponse2 = + "<\?xml version=\"1.0\" encoding=\"UTF-8\"\?><autofillquery " + "clientversion=\"6.1.1715.1442/en (GGLL)\" accepts=\"e\"><form " + "signature=\"11337937696949187602\"><field signature=\"412125936\"/>" + "<field signature=\"1917667676\"/><field signature=\"2226358947\"/>" + "<field signature=\"747221617\"/><field signature=\"4108155786\"/></form>" + "<form signature=\"8308881815906226214\"><field signature=\"412125936\"/>" + "<field signature=\"1917667676\"/><field signature=\"2226358947\"/>" + "<field signature=\"747221617\"/><field signature=\"4108155786\"/><field " + "signature=\"509334676\"/><field signature=\"509334676\"/><field " + "signature=\"509334676\"/><field signature=\"509334676\"/><field " + "signature=\"509334676\"/></form></autofillquery>"; + EXPECT_EQ(kResponse2, encoded_xml); + + FormData malformed_form(form); + // Add 50 address fields - the form is not valid anymore, but previous ones + // are. The result should be the same as in previous test. + for (size_t i = 0; i < 50; ++i) { + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("address"); + malformed_form.fields.push_back(field); + } + + forms.push_back(new FormStructure(malformed_form, std::string())); + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms.get(), + &encoded_signatures, + &encoded_xml)); + ASSERT_EQ(2U, encoded_signatures.size()); + EXPECT_EQ(kSignature1, encoded_signatures[0]); + EXPECT_EQ(kSignature2, encoded_signatures[1]); + EXPECT_EQ(kResponse2, encoded_xml); + + // Check that we fail if there are only bad form(s). + ScopedVector<FormStructure> bad_forms; + bad_forms.push_back(new FormStructure(malformed_form, std::string())); + EXPECT_FALSE(FormStructure::EncodeQueryRequest(bad_forms.get(), + &encoded_signatures, + &encoded_xml)); + EXPECT_EQ(0U, encoded_signatures.size()); + EXPECT_EQ("", encoded_xml); + + // Check the behaviour with autocheckout enabled. + ScopedVector<FormStructure> checkable_forms; + checkable_forms.push_back( + new FormStructure(form, "https://www.sample1.com/query/path")); + + ASSERT_TRUE(FormStructure::EncodeQueryRequest(checkable_forms.get(), + &encoded_signatures, + &encoded_xml)); + const char * const kSignature3 = "7747357776717901584"; + const char * const kResponse3 = + "<?xml version=\"1.0\" encoding=\"UTF-8\"?><autofillquery " + "clientversion=\"6.1.1715.1442/en (GGLL)\" accepts=\"a,e\" " + "urlprefixsignature=\"7648393911063090788\">" + "<form signature=\"7747357776717901584\">" + "<field signature=\"412125936\"/>" + "<field signature=\"1917667676\"/><field signature=\"2226358947\"/><field" + " signature=\"747221617\"/><field signature=\"4108155786\"/><field " + "signature=\"3410250678\"/><field signature=\"509334676\"/><field " + "signature=\"509334676\"/><field signature=\"509334676\"/><field " + "signature=\"509334676\"/><field signature=\"509334676\"/></form>" + "</autofillquery>"; + ASSERT_EQ(1U, encoded_signatures.size()); + EXPECT_EQ(kSignature3, encoded_signatures[0]); + EXPECT_EQ(kResponse3, encoded_xml); +} + +TEST(FormStructureTest, EncodeUploadRequest) { + scoped_ptr<FormStructure> form_structure; + std::vector<FieldTypeSet> possible_field_types; + FormData form; + form.method = ASCIIToUTF16("post"); + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("firstname"); + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(NAME_FIRST); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("lastname"); + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(NAME_LAST); + + field.label = ASCIIToUTF16("Email"); + field.name = ASCIIToUTF16("email"); + field.form_control_type = "email"; + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(EMAIL_ADDRESS); + + field.label = ASCIIToUTF16("Phone"); + field.name = ASCIIToUTF16("phone"); + field.form_control_type = "number"; + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(PHONE_HOME_WHOLE_NUMBER); + + field.label = ASCIIToUTF16("Country"); + field.name = ASCIIToUTF16("country"); + field.form_control_type = "select-one"; + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(ADDRESS_HOME_COUNTRY); + + // Add checkable field. + FormFieldData checkable_field; + checkable_field.is_checkable = true; + checkable_field.label = ASCIIToUTF16("Checkable1"); + checkable_field.name = ASCIIToUTF16("Checkable1"); + form.fields.push_back(checkable_field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(ADDRESS_HOME_COUNTRY); + + form_structure.reset(new FormStructure(form, std::string())); + + ASSERT_EQ(form_structure->field_count(), possible_field_types.size()); + for (size_t i = 0; i < form_structure->field_count(); ++i) + form_structure->field(i)->set_possible_types(possible_field_types[i]); + + FieldTypeSet available_field_types; + available_field_types.insert(NAME_FIRST); + available_field_types.insert(NAME_LAST); + available_field_types.insert(ADDRESS_HOME_LINE1); + available_field_types.insert(ADDRESS_HOME_LINE2); + available_field_types.insert(ADDRESS_HOME_COUNTRY); + available_field_types.insert(ADDRESS_BILLING_LINE1); + available_field_types.insert(ADDRESS_BILLING_LINE2); + available_field_types.insert(EMAIL_ADDRESS); + available_field_types.insert(PHONE_HOME_WHOLE_NUMBER); + + std::string encoded_xml; + EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\" " + "formsignature=\"8736493185895608956\" autofillused=\"false\" " + "datapresent=\"144200030e\">" + "<field signature=\"3763331450\" autofilltype=\"3\"/>" + "<field signature=\"3494530716\" autofilltype=\"5\"/>" + "<field signature=\"1029417091\" autofilltype=\"9\"/>" + "<field signature=\"466116101\" autofilltype=\"14\"/>" + "<field signature=\"2799270304\" autofilltype=\"36\"/>" + "</autofillupload>", + encoded_xml); + EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, true, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\" " + "formsignature=\"8736493185895608956\" autofillused=\"true\" " + "datapresent=\"144200030e\">" + "<field signature=\"3763331450\" autofilltype=\"3\"/>" + "<field signature=\"3494530716\" autofilltype=\"5\"/>" + "<field signature=\"1029417091\" autofilltype=\"9\"/>" + "<field signature=\"466116101\" autofilltype=\"14\"/>" + "<field signature=\"2799270304\" autofilltype=\"36\"/>" + "</autofillupload>", + encoded_xml); + + // Add 2 address fields - this should be still a valid form. + for (size_t i = 0; i < 2; ++i) { + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("address"); + field.form_control_type = "text"; + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(ADDRESS_HOME_LINE1); + possible_field_types.back().insert(ADDRESS_HOME_LINE2); + possible_field_types.back().insert(ADDRESS_BILLING_LINE1); + possible_field_types.back().insert(ADDRESS_BILLING_LINE2); + } + + form_structure.reset(new FormStructure(form, std::string())); + ASSERT_EQ(form_structure->field_count(), possible_field_types.size()); + for (size_t i = 0; i < form_structure->field_count(); ++i) + form_structure->field(i)->set_possible_types(possible_field_types[i]); + + EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\" " + "formsignature=\"7816485729218079147\" autofillused=\"false\" " + "datapresent=\"144200030e\">" + "<field signature=\"3763331450\" autofilltype=\"3\"/>" + "<field signature=\"3494530716\" autofilltype=\"5\"/>" + "<field signature=\"1029417091\" autofilltype=\"9\"/>" + "<field signature=\"466116101\" autofilltype=\"14\"/>" + "<field signature=\"2799270304\" autofilltype=\"36\"/>" + "<field signature=\"509334676\" autofilltype=\"30\"/>" + "<field signature=\"509334676\" autofilltype=\"31\"/>" + "<field signature=\"509334676\" autofilltype=\"37\"/>" + "<field signature=\"509334676\" autofilltype=\"38\"/>" + "<field signature=\"509334676\" autofilltype=\"30\"/>" + "<field signature=\"509334676\" autofilltype=\"31\"/>" + "<field signature=\"509334676\" autofilltype=\"37\"/>" + "<field signature=\"509334676\" autofilltype=\"38\"/>" + "</autofillupload>", + encoded_xml); + + // Add 50 address fields - now the form is invalid, as it has too many fields. + for (size_t i = 0; i < 50; ++i) { + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("address"); + field.form_control_type = "text"; + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(ADDRESS_HOME_LINE1); + possible_field_types.back().insert(ADDRESS_HOME_LINE2); + possible_field_types.back().insert(ADDRESS_BILLING_LINE1); + possible_field_types.back().insert(ADDRESS_BILLING_LINE2); + } + form_structure.reset(new FormStructure(form, std::string())); + ASSERT_EQ(form_structure->field_count(), possible_field_types.size()); + for (size_t i = 0; i < form_structure->field_count(); ++i) + form_structure->field(i)->set_possible_types(possible_field_types[i]); + EXPECT_FALSE(form_structure->EncodeUploadRequest(available_field_types, false, + &encoded_xml)); +} + +// Check that we compute the "datapresent" string correctly for the given +// |available_types|. +TEST(FormStructureTest, CheckDataPresence) { + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("first"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("last"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Email"); + field.name = ASCIIToUTF16("email"); + form.fields.push_back(field); + + FormStructure form_structure(form, std::string()); + + FieldTypeSet unknown_type; + unknown_type.insert(UNKNOWN_TYPE); + for (size_t i = 0; i < form_structure.field_count(); ++i) + form_structure.field(i)->set_possible_types(unknown_type); + + // No available types. + // datapresent should be "" == trimmmed(0x0000000000000000) == + // 0b0000000000000000000000000000000000000000000000000000000000000000 + FieldTypeSet available_field_types; + + std::string encoded_xml; + EXPECT_TRUE(form_structure.EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\"" + " formsignature=\"6402244543831589061\" autofillused=\"false\"" + " datapresent=\"\">" + "<field signature=\"1089846351\" autofilltype=\"1\"/>" + "<field signature=\"2404144663\" autofilltype=\"1\"/>" + "<field signature=\"420638584\" autofilltype=\"1\"/>" + "</autofillupload>", + encoded_xml); + + // Only a few types available. + // datapresent should be "1540000240" == trimmmed(0x1540000240000000) == + // 0b0001010101000000000000000000001001000000000000000000000000000000 + // The set bits are: + // 3 == NAME_FIRST + // 5 == NAME_LAST + // 7 == NAME_FULL + // 9 == EMAIL_ADDRESS + // 30 == ADDRESS_HOME_LINE1 + // 33 == ADDRESS_HOME_CITY + available_field_types.clear(); + available_field_types.insert(NAME_FIRST); + available_field_types.insert(NAME_LAST); + available_field_types.insert(NAME_FULL); + available_field_types.insert(EMAIL_ADDRESS); + available_field_types.insert(ADDRESS_HOME_LINE1); + available_field_types.insert(ADDRESS_HOME_CITY); + + EXPECT_TRUE(form_structure.EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\"" + " formsignature=\"6402244543831589061\" autofillused=\"false\"" + " datapresent=\"1540000240\">" + "<field signature=\"1089846351\" autofilltype=\"1\"/>" + "<field signature=\"2404144663\" autofilltype=\"1\"/>" + "<field signature=\"420638584\" autofilltype=\"1\"/>" + "</autofillupload>", + encoded_xml); + + // All supported non-credit card types available. + // datapresent should be "1f7e000378000008" == trimmmed(0x1f7e000378000008) == + // 0b0001111101111110000000000000001101111000000000000000000000001000 + // The set bits are: + // 3 == NAME_FIRST + // 4 == NAME_MIDDLE + // 5 == NAME_LAST + // 6 == NAME_MIDDLE_INITIAL + // 7 == NAME_FULL + // 9 == EMAIL_ADDRESS + // 10 == PHONE_HOME_NUMBER, + // 11 == PHONE_HOME_CITY_CODE, + // 12 == PHONE_HOME_COUNTRY_CODE, + // 13 == PHONE_HOME_CITY_AND_NUMBER, + // 14 == PHONE_HOME_WHOLE_NUMBER, + // 30 == ADDRESS_HOME_LINE1 + // 31 == ADDRESS_HOME_LINE2 + // 33 == ADDRESS_HOME_CITY + // 34 == ADDRESS_HOME_STATE + // 35 == ADDRESS_HOME_ZIP + // 36 == ADDRESS_HOME_COUNTRY + // 60 == COMPANY_NAME + available_field_types.clear(); + available_field_types.insert(NAME_FIRST); + available_field_types.insert(NAME_MIDDLE); + available_field_types.insert(NAME_LAST); + available_field_types.insert(NAME_MIDDLE_INITIAL); + available_field_types.insert(NAME_FULL); + available_field_types.insert(EMAIL_ADDRESS); + available_field_types.insert(PHONE_HOME_NUMBER); + available_field_types.insert(PHONE_HOME_CITY_CODE); + available_field_types.insert(PHONE_HOME_COUNTRY_CODE); + available_field_types.insert(PHONE_HOME_CITY_AND_NUMBER); + available_field_types.insert(PHONE_HOME_WHOLE_NUMBER); + available_field_types.insert(ADDRESS_HOME_LINE1); + available_field_types.insert(ADDRESS_HOME_LINE2); + available_field_types.insert(ADDRESS_HOME_CITY); + available_field_types.insert(ADDRESS_HOME_STATE); + available_field_types.insert(ADDRESS_HOME_ZIP); + available_field_types.insert(ADDRESS_HOME_COUNTRY); + available_field_types.insert(COMPANY_NAME); + + EXPECT_TRUE(form_structure.EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\"" + " formsignature=\"6402244543831589061\" autofillused=\"false\"" + " datapresent=\"1f7e000378000008\">" + "<field signature=\"1089846351\" autofilltype=\"1\"/>" + "<field signature=\"2404144663\" autofilltype=\"1\"/>" + "<field signature=\"420638584\" autofilltype=\"1\"/>" + "</autofillupload>", + encoded_xml); + + // All supported credit card types available. + // datapresent should be "0000000000001fc0" == trimmmed(0x0000000000001fc0) == + // 0b0000000000000000000000000000000000000000000000000001111111000000 + // The set bits are: + // 51 == CREDIT_CARD_NAME + // 52 == CREDIT_CARD_NUMBER + // 53 == CREDIT_CARD_EXP_MONTH + // 54 == CREDIT_CARD_EXP_2_DIGIT_YEAR + // 55 == CREDIT_CARD_EXP_4_DIGIT_YEAR + // 56 == CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR + // 57 == CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR + available_field_types.clear(); + available_field_types.insert(CREDIT_CARD_NAME); + available_field_types.insert(CREDIT_CARD_NUMBER); + available_field_types.insert(CREDIT_CARD_EXP_MONTH); + available_field_types.insert(CREDIT_CARD_EXP_2_DIGIT_YEAR); + available_field_types.insert(CREDIT_CARD_EXP_4_DIGIT_YEAR); + available_field_types.insert(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR); + available_field_types.insert(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR); + + EXPECT_TRUE(form_structure.EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\"" + " formsignature=\"6402244543831589061\" autofillused=\"false\"" + " datapresent=\"0000000000001fc0\">" + "<field signature=\"1089846351\" autofilltype=\"1\"/>" + "<field signature=\"2404144663\" autofilltype=\"1\"/>" + "<field signature=\"420638584\" autofilltype=\"1\"/>" + "</autofillupload>", + encoded_xml); + + // All supported types available. + // datapresent should be "1f7e000378001fc8" == trimmmed(0x1f7e000378001fc8) == + // 0b0001111101111110000000000000001101111000000000000001111111001000 + // The set bits are: + // 3 == NAME_FIRST + // 4 == NAME_MIDDLE + // 5 == NAME_LAST + // 6 == NAME_MIDDLE_INITIAL + // 7 == NAME_FULL + // 9 == EMAIL_ADDRESS + // 10 == PHONE_HOME_NUMBER, + // 11 == PHONE_HOME_CITY_CODE, + // 12 == PHONE_HOME_COUNTRY_CODE, + // 13 == PHONE_HOME_CITY_AND_NUMBER, + // 14 == PHONE_HOME_WHOLE_NUMBER, + // 30 == ADDRESS_HOME_LINE1 + // 31 == ADDRESS_HOME_LINE2 + // 33 == ADDRESS_HOME_CITY + // 34 == ADDRESS_HOME_STATE + // 35 == ADDRESS_HOME_ZIP + // 36 == ADDRESS_HOME_COUNTRY + // 51 == CREDIT_CARD_NAME + // 52 == CREDIT_CARD_NUMBER + // 53 == CREDIT_CARD_EXP_MONTH + // 54 == CREDIT_CARD_EXP_2_DIGIT_YEAR + // 55 == CREDIT_CARD_EXP_4_DIGIT_YEAR + // 56 == CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR + // 57 == CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR + // 60 == COMPANY_NAME + available_field_types.clear(); + available_field_types.insert(NAME_FIRST); + available_field_types.insert(NAME_MIDDLE); + available_field_types.insert(NAME_LAST); + available_field_types.insert(NAME_MIDDLE_INITIAL); + available_field_types.insert(NAME_FULL); + available_field_types.insert(EMAIL_ADDRESS); + available_field_types.insert(PHONE_HOME_NUMBER); + available_field_types.insert(PHONE_HOME_CITY_CODE); + available_field_types.insert(PHONE_HOME_COUNTRY_CODE); + available_field_types.insert(PHONE_HOME_CITY_AND_NUMBER); + available_field_types.insert(PHONE_HOME_WHOLE_NUMBER); + available_field_types.insert(ADDRESS_HOME_LINE1); + available_field_types.insert(ADDRESS_HOME_LINE2); + available_field_types.insert(ADDRESS_HOME_CITY); + available_field_types.insert(ADDRESS_HOME_STATE); + available_field_types.insert(ADDRESS_HOME_ZIP); + available_field_types.insert(ADDRESS_HOME_COUNTRY); + available_field_types.insert(CREDIT_CARD_NAME); + available_field_types.insert(CREDIT_CARD_NUMBER); + available_field_types.insert(CREDIT_CARD_EXP_MONTH); + available_field_types.insert(CREDIT_CARD_EXP_2_DIGIT_YEAR); + available_field_types.insert(CREDIT_CARD_EXP_4_DIGIT_YEAR); + available_field_types.insert(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR); + available_field_types.insert(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR); + available_field_types.insert(COMPANY_NAME); + + EXPECT_TRUE(form_structure.EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\"" + " formsignature=\"6402244543831589061\" autofillused=\"false\"" + " datapresent=\"1f7e000378001fc8\">" + "<field signature=\"1089846351\" autofilltype=\"1\"/>" + "<field signature=\"2404144663\" autofilltype=\"1\"/>" + "<field signature=\"420638584\" autofilltype=\"1\"/>" + "</autofillupload>", + encoded_xml); +} + +TEST(FormStructureTest, CheckMultipleTypes) { + // Throughout this test, datapresent should be + // 0x1440000360000008 == + // 0b0001010001000000000000000000001101100000000000000000000000001000 + // The set bits are: + // 3 == NAME_FIRST + // 5 == NAME_LAST + // 9 == EMAIL_ADDRESS + // 30 == ADDRESS_HOME_LINE1 + // 31 == ADDRESS_HOME_LINE2 + // 33 == ADDRESS_HOME_CITY + // 34 == ADDRESS_HOME_STATE + // 60 == COMPANY_NAME + FieldTypeSet available_field_types; + available_field_types.insert(NAME_FIRST); + available_field_types.insert(NAME_LAST); + available_field_types.insert(EMAIL_ADDRESS); + available_field_types.insert(ADDRESS_HOME_LINE1); + available_field_types.insert(ADDRESS_HOME_LINE2); + available_field_types.insert(ADDRESS_HOME_CITY); + available_field_types.insert(ADDRESS_HOME_STATE); + available_field_types.insert(COMPANY_NAME); + + // Check that multiple types for the field are processed correctly. + scoped_ptr<FormStructure> form_structure; + std::vector<FieldTypeSet> possible_field_types; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("email"); + field.name = ASCIIToUTF16("email"); + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(EMAIL_ADDRESS); + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("first"); + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(NAME_FIRST); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("last"); + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(NAME_LAST); + + field.label = ASCIIToUTF16("Address"); + field.name = ASCIIToUTF16("address"); + form.fields.push_back(field); + possible_field_types.push_back(FieldTypeSet()); + possible_field_types.back().insert(ADDRESS_HOME_LINE1); + + form_structure.reset(new FormStructure(form, std::string())); + + for (size_t i = 0; i < form_structure->field_count(); ++i) + form_structure->field(i)->set_possible_types(possible_field_types[i]); + std::string encoded_xml; + + // Now we matched both fields singularly. + EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\"" + " formsignature=\"18062476096658145866\" autofillused=\"false\"" + " datapresent=\"1440000360000008\">" + "<field signature=\"420638584\" autofilltype=\"9\"/>" + "<field signature=\"1089846351\" autofilltype=\"3\"/>" + "<field signature=\"2404144663\" autofilltype=\"5\"/>" + "<field signature=\"509334676\" autofilltype=\"30\"/>" + "</autofillupload>", + encoded_xml); + // Match third field as both first and last. + possible_field_types[2].insert(NAME_FIRST); + form_structure->field(2)->set_possible_types(possible_field_types[2]); + EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\"" + " formsignature=\"18062476096658145866\" autofillused=\"false\"" + " datapresent=\"1440000360000008\">" + "<field signature=\"420638584\" autofilltype=\"9\"/>" + "<field signature=\"1089846351\" autofilltype=\"3\"/>" + "<field signature=\"2404144663\" autofilltype=\"3\"/>" + "<field signature=\"2404144663\" autofilltype=\"5\"/>" + "<field signature=\"509334676\" autofilltype=\"30\"/>" + "</autofillupload>", + encoded_xml); + possible_field_types[3].insert(ADDRESS_HOME_LINE2); + form_structure->field(form_structure->field_count() - 1)->set_possible_types( + possible_field_types[form_structure->field_count() - 1]); + EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\"" + " formsignature=\"18062476096658145866\" autofillused=\"false\"" + " datapresent=\"1440000360000008\">" + "<field signature=\"420638584\" autofilltype=\"9\"/>" + "<field signature=\"1089846351\" autofilltype=\"3\"/>" + "<field signature=\"2404144663\" autofilltype=\"3\"/>" + "<field signature=\"2404144663\" autofilltype=\"5\"/>" + "<field signature=\"509334676\" autofilltype=\"30\"/>" + "<field signature=\"509334676\" autofilltype=\"31\"/>" + "</autofillupload>", + encoded_xml); + possible_field_types[3].clear(); + possible_field_types[3].insert(ADDRESS_HOME_LINE1); + possible_field_types[3].insert(COMPANY_NAME); + form_structure->field(form_structure->field_count() - 1)->set_possible_types( + possible_field_types[form_structure->field_count() - 1]); + EXPECT_TRUE(form_structure->EncodeUploadRequest(available_field_types, false, + &encoded_xml)); + EXPECT_EQ("<\?xml version=\"1.0\" encoding=\"UTF-8\"\?>" + "<autofillupload clientversion=\"6.1.1715.1442/en (GGLL)\"" + " formsignature=\"18062476096658145866\" autofillused=\"false\"" + " datapresent=\"1440000360000008\">" + "<field signature=\"420638584\" autofilltype=\"9\"/>" + "<field signature=\"1089846351\" autofilltype=\"3\"/>" + "<field signature=\"2404144663\" autofilltype=\"3\"/>" + "<field signature=\"2404144663\" autofilltype=\"5\"/>" + "<field signature=\"509334676\" autofilltype=\"30\"/>" + "<field signature=\"509334676\" autofilltype=\"60\"/>" + "</autofillupload>", + encoded_xml); +} + +TEST(FormStructureTest, CheckFormSignature) { + // Check that form signature is created correctly. + scoped_ptr<FormStructure> form_structure; + FormData form; + form.method = ASCIIToUTF16("post"); + + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("email"); + field.name = ASCIIToUTF16("email"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("first"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + + EXPECT_EQ(FormStructureTest::Hash64Bit( + std::string("://&&email&first")), + form_structure->FormSignature()); + + form.origin = GURL(std::string("http://www.facebook.com")); + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_EQ(FormStructureTest::Hash64Bit( + std::string("http://www.facebook.com&&email&first")), + form_structure->FormSignature()); + + form.action = GURL(std::string("https://login.facebook.com/path")); + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_EQ(FormStructureTest::Hash64Bit( + std::string("https://login.facebook.com&&email&first")), + form_structure->FormSignature()); + + form.name = ASCIIToUTF16("login_form"); + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_EQ(FormStructureTest::Hash64Bit( + std::string("https://login.facebook.com&login_form&email&first")), + form_structure->FormSignature()); +} + +TEST(FormStructureTest, ToFormData) { + FormData form; + form.name = ASCIIToUTF16("the-name"); + form.method = ASCIIToUTF16("POST"); + form.origin = GURL("http://cool.com"); + form.action = form.origin.Resolve("/login"); + + FormFieldData field; + field.label = ASCIIToUTF16("username"); + field.name = ASCIIToUTF16("username"); + field.form_control_type = "text"; + form.fields.push_back(field); + + field.label = ASCIIToUTF16("password"); + field.name = ASCIIToUTF16("password"); + field.form_control_type = "password"; + form.fields.push_back(field); + + field.label = string16(); + field.name = ASCIIToUTF16("Submit"); + field.form_control_type = "submit"; + form.fields.push_back(field); + + EXPECT_EQ(form, FormStructure(form, std::string()).ToFormData()); + + // Currently |FormStructure(form_data)ToFormData().user_submitted| is always + // false. This forces a future author that changes this to update this test. + form.user_submitted = true; + EXPECT_NE(form, FormStructure(form, std::string()).ToFormData()); +} diff --git a/components/autofill/browser/name_field.cc b/components/autofill/browser/name_field.cc new file mode 100644 index 0000000..8d29111 --- /dev/null +++ b/components/autofill/browser/name_field.cc @@ -0,0 +1,214 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/name_field.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_regex_constants.h" +#include "components/autofill/browser/autofill_scanner.h" +#include "components/autofill/browser/autofill_type.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +// A form field that can parse a full name field. +class FullNameField : public NameField { + public: + static FullNameField* Parse(AutofillScanner* scanner); + + protected: + // FormField: + virtual bool ClassifyField(FieldTypeMap* map) const OVERRIDE; + + private: + explicit FullNameField(const AutofillField* field); + + const AutofillField* field_; + + DISALLOW_COPY_AND_ASSIGN(FullNameField); +}; + +// A form field that can parse a first and last name field. +class FirstLastNameField : public NameField { + public: + static FirstLastNameField* ParseSpecificName(AutofillScanner* scanner); + static FirstLastNameField* ParseComponentNames(AutofillScanner* scanner); + static FirstLastNameField* Parse(AutofillScanner* scanner); + + protected: + // FormField: + virtual bool ClassifyField(FieldTypeMap* map) const OVERRIDE; + + private: + FirstLastNameField(); + + const AutofillField* first_name_; + const AutofillField* middle_name_; // Optional. + const AutofillField* last_name_; + bool middle_initial_; // True if middle_name_ is a middle initial. + + DISALLOW_COPY_AND_ASSIGN(FirstLastNameField); +}; + +} // namespace + +FormField* NameField::Parse(AutofillScanner* scanner) { + if (scanner->IsEnd()) + return NULL; + + // Try FirstLastNameField first since it's more specific. + NameField* field = FirstLastNameField::Parse(scanner); + if (!field) + field = FullNameField::Parse(scanner); + return field; +} + +// This is overriden in concrete subclasses. +bool NameField::ClassifyField(FieldTypeMap* map) const { + return false; +} + +FullNameField* FullNameField::Parse(AutofillScanner* scanner) { + // Exclude e.g. "username" or "nickname" fields. + scanner->SaveCursor(); + bool should_ignore = ParseField(scanner, + UTF8ToUTF16(autofill::kNameIgnoredRe), NULL); + scanner->Rewind(); + if (should_ignore) + return NULL; + + // Searching for any label containing the word "name" is too general; + // for example, Travelocity_Edit travel profile.html contains a field + // "Travel Profile Name". + const AutofillField* field = NULL; + if (ParseField(scanner, UTF8ToUTF16(autofill::kNameRe), &field)) + return new FullNameField(field); + + return NULL; +} + +bool FullNameField::ClassifyField(FieldTypeMap* map) const { + return AddClassification(field_, NAME_FULL, map); +} + +FullNameField::FullNameField(const AutofillField* field) + : field_(field) { +} + +FirstLastNameField* FirstLastNameField::ParseSpecificName( + AutofillScanner* scanner) { + // Some pages (e.g. Overstock_comBilling.html, SmithsonianCheckout.html) + // have the label "Name" followed by two or three text fields. + scoped_ptr<FirstLastNameField> v(new FirstLastNameField); + scanner->SaveCursor(); + + const AutofillField* next = NULL; + if (ParseField(scanner, + UTF8ToUTF16(autofill::kNameSpecificRe), &v->first_name_) && + ParseEmptyLabel(scanner, &next)) { + if (ParseEmptyLabel(scanner, &v->last_name_)) { + // There are three name fields; assume that the middle one is a + // middle initial (it is, at least, on SmithsonianCheckout.html). + v->middle_name_ = next; + v->middle_initial_ = true; + } else { // only two name fields + v->last_name_ = next; + } + + return v.release(); + } + + scanner->Rewind(); + return NULL; +} + +FirstLastNameField* FirstLastNameField::ParseComponentNames( + AutofillScanner* scanner) { + scoped_ptr<FirstLastNameField> v(new FirstLastNameField); + scanner->SaveCursor(); + + // A fair number of pages use the names "fname" and "lname" for naming + // first and last name fields (examples from the test suite: + // BESTBUY_COM - Sign In2.html; Crate and Barrel Check Out.html; + // dell_checkout1.html). At least one UK page (The China Shop2.html) + // asks, in stuffy English style, for just initials and a surname, + // so we match "initials" here (and just fill in a first name there, + // American-style). + // The ".*first$" matches fields ending in "first" (example in sample8.html). + // The ".*last$" matches fields ending in "last" (example in sample8.html). + + // Allow name fields to appear in any order. + while (!scanner->IsEnd()) { + // Skip over any unrelated fields, e.g. "username" or "nickname". + if (ParseFieldSpecifics(scanner, UTF8ToUTF16(autofill::kNameIgnoredRe), + MATCH_DEFAULT | MATCH_SELECT, NULL)) { + continue; + } + + if (!v->first_name_ && + ParseField(scanner, UTF8ToUTF16(autofill::kFirstNameRe), + &v->first_name_)) { + continue; + } + + // We check for a middle initial before checking for a middle name + // because at least one page (PC Connection.html) has a field marked + // as both (the label text is "MI" and the element name is + // "txtmiddlename"); such a field probably actually represents a + // middle initial. + if (!v->middle_name_ && + ParseField(scanner, UTF8ToUTF16(autofill::kMiddleInitialRe), + &v->middle_name_)) { + v->middle_initial_ = true; + continue; + } + + if (!v->middle_name_ && + ParseField(scanner, UTF8ToUTF16(autofill::kMiddleNameRe), + &v->middle_name_)) { + continue; + } + + if (!v->last_name_ && + ParseField(scanner, UTF8ToUTF16(autofill::kLastNameRe), + &v->last_name_)) { + continue; + } + + break; + } + + // Consider the match to be successful if we detected both first and last name + // fields. + if (v->first_name_ && v->last_name_) + return v.release(); + + scanner->Rewind(); + return NULL; +} + +FirstLastNameField* FirstLastNameField::Parse(AutofillScanner* scanner) { + FirstLastNameField* field = ParseSpecificName(scanner); + if (!field) + field = ParseComponentNames(scanner); + return field; +} + +FirstLastNameField::FirstLastNameField() + : first_name_(NULL), + middle_name_(NULL), + last_name_(NULL), + middle_initial_(false) { +} + +bool FirstLastNameField::ClassifyField(FieldTypeMap* map) const { + bool ok = AddClassification(first_name_, NAME_FIRST, map); + ok = ok && AddClassification(last_name_, NAME_LAST, map); + AutofillFieldType type = middle_initial_ ? NAME_MIDDLE_INITIAL : NAME_MIDDLE; + ok = ok && AddClassification(middle_name_, type, map); + return ok; +} diff --git a/components/autofill/browser/name_field.h b/components/autofill/browser/name_field.h new file mode 100644 index 0000000..e62b62f --- /dev/null +++ b/components/autofill/browser/name_field.h @@ -0,0 +1,42 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_NAME_FIELD_H_ +#define COMPONENTS_AUTOFILL_BROWSER_NAME_FIELD_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/form_field.h" + +class AutofillScanner; + +// A form field that can parse either a FullNameField or a FirstLastNameField. +class NameField : public FormField { + public: + static FormField* Parse(AutofillScanner* scanner); + + protected: + NameField() {} + + // FormField: + virtual bool ClassifyField(FieldTypeMap* map) const OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(NameFieldTest, FirstMiddleLast); + FRIEND_TEST_ALL_PREFIXES(NameFieldTest, FirstMiddleLast2); + FRIEND_TEST_ALL_PREFIXES(NameFieldTest, FirstLast); + FRIEND_TEST_ALL_PREFIXES(NameFieldTest, FirstLast2); + FRIEND_TEST_ALL_PREFIXES(NameFieldTest, FirstLastMiddleWithSpaces); + FRIEND_TEST_ALL_PREFIXES(NameFieldTest, FirstLastEmpty); + FRIEND_TEST_ALL_PREFIXES(NameFieldTest, FirstMiddleLastEmpty); + FRIEND_TEST_ALL_PREFIXES(NameFieldTest, MiddleInitial); + FRIEND_TEST_ALL_PREFIXES(NameFieldTest, MiddleInitialAtEnd); + + DISALLOW_COPY_AND_ASSIGN(NameField); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_NAME_FIELD_H_ diff --git a/components/autofill/browser/name_field_unittest.cc b/components/autofill/browser/name_field_unittest.cc new file mode 100644 index 0000000..2689191 --- /dev/null +++ b/components/autofill/browser/name_field_unittest.cc @@ -0,0 +1,307 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_scanner.h" +#include "components/autofill/browser/name_field.h" +#include "components/autofill/common/form_field_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +class NameFieldTest : public testing::Test { + public: + NameFieldTest() {} + + protected: + ScopedVector<const AutofillField> list_; + scoped_ptr<NameField> field_; + FieldTypeMap field_type_map_; + + // Downcast for tests. + static NameField* Parse(AutofillScanner* scanner) { + return static_cast<NameField*>(NameField::Parse(scanner)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(NameFieldTest); +}; + +TEST_F(NameFieldTest, FirstMiddleLast) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("First"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = ASCIIToUTF16("Middle Name"); + field.name = ASCIIToUTF16("Middle"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("Last"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<NameField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(NAME_FIRST, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name2")) != field_type_map_.end()); + EXPECT_EQ(NAME_MIDDLE, field_type_map_[ASCIIToUTF16("name2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name3")) != field_type_map_.end()); + EXPECT_EQ(NAME_LAST, field_type_map_[ASCIIToUTF16("name3")]); +} + +TEST_F(NameFieldTest, FirstMiddleLast2) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = string16(); + field.name = ASCIIToUTF16("firstName"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = string16(); + field.name = ASCIIToUTF16("middleName"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + field.label = string16(); + field.name = ASCIIToUTF16("lastName"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<NameField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(NAME_FIRST, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name2")) != field_type_map_.end()); + EXPECT_EQ(NAME_MIDDLE, field_type_map_[ASCIIToUTF16("name2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name3")) != field_type_map_.end()); + EXPECT_EQ(NAME_LAST, field_type_map_[ASCIIToUTF16("name3")]); +} + +TEST_F(NameFieldTest, FirstLast) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = string16(); + field.name = ASCIIToUTF16("first_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = string16(); + field.name = ASCIIToUTF16("last_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<NameField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(NAME_FIRST, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name2")) != field_type_map_.end()); + EXPECT_EQ(NAME_LAST, field_type_map_[ASCIIToUTF16("name2")]); +} + +TEST_F(NameFieldTest, FirstLast2) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name"); + field.name = ASCIIToUTF16("first_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = ASCIIToUTF16("Name"); + field.name = ASCIIToUTF16("last_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<NameField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(NAME_FIRST, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name2")) != field_type_map_.end()); + EXPECT_EQ(NAME_LAST, field_type_map_[ASCIIToUTF16("name2")]); +} + +TEST_F(NameFieldTest, FirstLastMiddleWithSpaces) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("first_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = ASCIIToUTF16("Middle Name"); + field.name = ASCIIToUTF16("middle_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("last_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<NameField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(NAME_FIRST, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name2")) != field_type_map_.end()); + EXPECT_EQ(NAME_MIDDLE, field_type_map_[ASCIIToUTF16("name2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name3")) != field_type_map_.end()); + EXPECT_EQ(NAME_LAST, field_type_map_[ASCIIToUTF16("name3")]); +} + +TEST_F(NameFieldTest, FirstLastEmpty) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name"); + field.name = ASCIIToUTF16("first_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = string16(); + field.name = ASCIIToUTF16("last_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<NameField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(NAME_FIRST, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name2")) != field_type_map_.end()); + EXPECT_EQ(NAME_LAST, field_type_map_[ASCIIToUTF16("name2")]); +} + +TEST_F(NameFieldTest, FirstMiddleLastEmpty) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Name"); + field.name = ASCIIToUTF16("first_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = string16(); + field.name = ASCIIToUTF16("middle_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + field.label = string16(); + field.name = ASCIIToUTF16("last_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<NameField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(NAME_FIRST, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name2")) != field_type_map_.end()); + EXPECT_EQ(NAME_MIDDLE_INITIAL, field_type_map_[ASCIIToUTF16("name2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name3")) != field_type_map_.end()); + EXPECT_EQ(NAME_LAST, field_type_map_[ASCIIToUTF16("name3")]); +} + +TEST_F(NameFieldTest, MiddleInitial) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("first_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = ASCIIToUTF16("MI"); + field.name = ASCIIToUTF16("middle_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + field.label = ASCIIToUTF16("Last Name"); + field.name = ASCIIToUTF16("last_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<NameField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(NAME_FIRST, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name2")) != field_type_map_.end()); + EXPECT_EQ(NAME_MIDDLE_INITIAL, field_type_map_[ASCIIToUTF16("name2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name3")) != field_type_map_.end()); + EXPECT_EQ(NAME_LAST, field_type_map_[ASCIIToUTF16("name3")]); +} + +TEST_F(NameFieldTest, MiddleInitialNoLastName) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("First Name"); + field.name = ASCIIToUTF16("first_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = ASCIIToUTF16("MI"); + field.name = ASCIIToUTF16("middle_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_EQ(static_cast<NameField*>(NULL), field_.get()); +} + +// This case is from the dell.com checkout page. The middle initial "mi" string +// came at the end following other descriptive text. http://crbug.com/45123. +TEST_F(NameFieldTest, MiddleInitialAtEnd) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = string16(); + field.name = ASCIIToUTF16("XXXnameXXXfirst"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = string16(); + field.name = ASCIIToUTF16("XXXnameXXXmi"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + field.label = string16(); + field.name = ASCIIToUTF16("XXXnameXXXlast"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<NameField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name1")) != field_type_map_.end()); + EXPECT_EQ(NAME_FIRST, field_type_map_[ASCIIToUTF16("name1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name2")) != field_type_map_.end()); + EXPECT_EQ(NAME_MIDDLE_INITIAL, field_type_map_[ASCIIToUTF16("name2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("name3")) != field_type_map_.end()); + EXPECT_EQ(NAME_LAST, field_type_map_[ASCIIToUTF16("name3")]); +} diff --git a/components/autofill/browser/password_autofill_manager.cc b/components/autofill/browser/password_autofill_manager.cc new file mode 100644 index 0000000..6a641f6 --- /dev/null +++ b/components/autofill/browser/password_autofill_manager.cc @@ -0,0 +1,83 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/password_autofill_manager.h" +#include "components/autofill/common/autofill_messages.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "ui/base/keycodes/keyboard_codes.h" + +//////////////////////////////////////////////////////////////////////////////// +// PasswordAutofillManager, public: + +PasswordAutofillManager::PasswordAutofillManager( + content::WebContents* web_contents) : web_contents_(web_contents) { +} + +PasswordAutofillManager::~PasswordAutofillManager() { +} + +bool PasswordAutofillManager::DidAcceptAutofillSuggestion( + const FormFieldData& field, + const string16& value) { + PasswordFormFillData password; + if (!FindLoginInfo(field, &password)) + return false; + + if (WillFillUserNameAndPassword(value, password)) { + if (web_contents_) { + content::RenderViewHost* render_view_host = + web_contents_->GetRenderViewHost(); + render_view_host->Send(new AutofillMsg_AcceptPasswordAutofillSuggestion( + render_view_host->GetRoutingID(), + value)); + } + return true; + } + + return false; +} + +void PasswordAutofillManager::AddPasswordFormMapping( + const FormFieldData& username_element, + const PasswordFormFillData& password) { + login_to_password_info_[username_element] = password; +} + +void PasswordAutofillManager::Reset() { + login_to_password_info_.clear(); +} + +//////////////////////////////////////////////////////////////////////////////// +// PasswordAutofillManager, private: + +bool PasswordAutofillManager::WillFillUserNameAndPassword( + const string16& current_username, + const PasswordFormFillData& fill_data) { + // Look for any suitable matches to current field text. + if (fill_data.basic_data.fields[0].value == current_username) { + return true; + } else { + // Scan additional logins for a match. + PasswordFormFillData::LoginCollection::const_iterator iter; + for (iter = fill_data.additional_logins.begin(); + iter != fill_data.additional_logins.end(); ++iter) { + if (iter->first == current_username) + return true; + } + } + + return false; +} + +bool PasswordAutofillManager::FindLoginInfo( + const FormFieldData& field, + PasswordFormFillData* found_password) { + LoginToPasswordInfoMap::iterator iter = login_to_password_info_.find(field); + if (iter == login_to_password_info_.end()) + return false; + + *found_password = iter->second; + return true; +} diff --git a/components/autofill/browser/password_autofill_manager.h b/components/autofill/browser/password_autofill_manager.h new file mode 100644 index 0000000..3d16054 --- /dev/null +++ b/components/autofill/browser/password_autofill_manager.h @@ -0,0 +1,71 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_PASSWORD_AUTOFILL_MANAGER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_PASSWORD_AUTOFILL_MANAGER_H_ + +// This file was contains some repeated code from +// chrome/renderer/autofill/password_autofill_manager because as we move to the +// new Autofill UI we needs these functions in both the browser and renderer. +// Once the move is completed the repeated code in the renderer half should be +// removed. +// http://crbug.com/51644 + +#include <map> + +#include "components/autofill/common/password_form_fill_data.h" + +namespace content { +class WebContents; +} // namespace content + +// This class is responsible for filling password forms. +class PasswordAutofillManager { + public: + explicit PasswordAutofillManager(content::WebContents* web_contents); + virtual ~PasswordAutofillManager(); + + // Fills the password associated with user name |value|. Returns true if the + // username and password fields were filled, false otherwise. + bool DidAcceptAutofillSuggestion(const FormFieldData& field, + const string16& value); + + // Invoked when a password mapping is added. + void AddPasswordFormMapping( + const FormFieldData& username_element, + const PasswordFormFillData& password); + + // Invoked to clear any page specific cached values. + void Reset(); + + private: + // TODO(csharp): Modify the AutofillExternalDeletegate code so that it can + // figure out if a entry is a password one without using this mapping. + // crbug.com/118601 + typedef std::map<FormFieldData, + PasswordFormFillData> + LoginToPasswordInfoMap; + + // Returns true if |current_username| matches a username for one of the + // login mappings in |password|. + bool WillFillUserNameAndPassword( + const string16& current_username, + const PasswordFormFillData& password); + + // Finds login information for a |node| that was previously filled. + bool FindLoginInfo(const FormFieldData& field, + PasswordFormFillData* found_password); + + // The logins we have filled so far with their associated info. + LoginToPasswordInfoMap login_to_password_info_; + + // We only need the RenderViewHost pointer in WebContents, but if we attempt + // to just store RenderViewHost on creation, it becomes invalid once we start + // using it. By having the WebContents we can always get a valid pointer. + content::WebContents* web_contents_; // Weak reference. + + DISALLOW_COPY_AND_ASSIGN(PasswordAutofillManager); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_PASSWORD_AUTOFILL_MANAGER_H_ diff --git a/components/autofill/browser/password_autofill_manager_unittest.cc b/components/autofill/browser/password_autofill_manager_unittest.cc new file mode 100644 index 0000000..bcdd6f0 --- /dev/null +++ b/components/autofill/browser/password_autofill_manager_unittest.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/compiler_specific.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/password_autofill_manager.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// The name of the username/password element in the form. +const char* const kUsernameName = "username"; +const char* const kInvalidUsername = "no-username"; +const char* const kPasswordName = "password"; + +const char* const kAliceUsername = "alice"; +const char* const kAlicePassword = "password"; + +const char* const kValue = "password"; + +} // namespace + +class PasswordAutofillManagerTest : public testing::Test { + protected: + PasswordAutofillManagerTest() : password_autofill_manager_(NULL) {} + + virtual void SetUp() OVERRIDE { + // Add a preferred login and an additional login to the FillData. + string16 username1 = ASCIIToUTF16(kAliceUsername); + string16 password1 = ASCIIToUTF16(kAlicePassword); + + username_field_.name = ASCIIToUTF16(kUsernameName); + username_field_.value = username1; + fill_data_.basic_data.fields.push_back(username_field_); + + FormFieldData password_field; + password_field.name = ASCIIToUTF16(kPasswordName); + password_field.value = password1; + fill_data_.basic_data.fields.push_back(password_field); + + password_autofill_manager_.AddPasswordFormMapping(username_field_, + fill_data_); + } + + PasswordAutofillManager* password_autofill_manager() { + return &password_autofill_manager_; + } + + const FormFieldData& username_field() { return username_field_; } + + private: + PasswordFormFillData fill_data_; + FormFieldData username_field_; + + PasswordAutofillManager password_autofill_manager_; +}; + +TEST_F(PasswordAutofillManagerTest, DidAcceptAutofillSuggestion) { + EXPECT_TRUE(password_autofill_manager()->DidAcceptAutofillSuggestion( + username_field(), ASCIIToUTF16(kAliceUsername))); + EXPECT_FALSE(password_autofill_manager()->DidAcceptAutofillSuggestion( + username_field(), ASCIIToUTF16(kInvalidUsername))); + + FormFieldData invalid_username_field; + invalid_username_field.name = ASCIIToUTF16(kInvalidUsername); + + EXPECT_FALSE(password_autofill_manager()->DidAcceptAutofillSuggestion( + invalid_username_field, ASCIIToUTF16(kAliceUsername))); + + password_autofill_manager()->Reset(); + EXPECT_FALSE(password_autofill_manager()->DidAcceptAutofillSuggestion( + username_field(), ASCIIToUTF16(kAliceUsername))); +} diff --git a/components/autofill/browser/password_generator.cc b/components/autofill/browser/password_generator.cc new file mode 100644 index 0000000..1967706 --- /dev/null +++ b/components/autofill/browser/password_generator.cc @@ -0,0 +1,125 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/password_generator.h" + +#include <algorithm> +#include <vector> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/rand_util.h" + +const int kMinUpper = 65; // First upper case letter 'A' +const int kMaxUpper = 90; // Last upper case letter 'Z' +const int kMinLower = 97; // First lower case letter 'a' +const int kMaxLower = 122; // Last lower case letter 'z' +const int kMinDigit = 48; // First digit '0' +const int kMaxDigit = 57; // Last digit '9' +// Copy of the other printable symbols from the ASCII table since they are +// disjointed. +const char kOtherSymbols[] = + {'!', '\"', '#', '$', '%', '&', '\'', '(', + ')', '*', '+', ',', '-', '.', '/', ':', + ';', '<', '=', '>', '?', '@', '[', '\\', + ']', '^', '_', '`', '{', '|', '}', '~'}; +const size_t kMinPasswordLength = 4; +const size_t kMaxPasswordLength = 15; + +namespace { + +// A helper function to get the length of the generated password from +// |max_length| retrieved from input password field. +size_t GetLengthFromHint(size_t max_length, size_t default_length) { + if (max_length >= kMinPasswordLength && max_length <= kMaxPasswordLength) + return max_length; + else + return default_length; +} + +void InitializeAlphaNumericCharacters(std::vector<char>* characters) { + for (int i = kMinDigit; i <= kMaxDigit; ++i) + characters->push_back(static_cast<char>(i)); + for (int i = kMinUpper; i <= kMaxUpper; ++i) + characters->push_back(static_cast<char>(i)); + for (int i = kMinLower; i <= kMaxLower; ++i) + characters->push_back(static_cast<char>(i)); +} + +// Classic algorithm to randomly select |num_select| elements out of +// |num_total| elements. One description can be found at: +// "http://stackoverflow.com/questions/48087/select-a-random-n-elements-from-listt-in-c-sharp/48089#48089" +void GetRandomSelection(size_t num_to_select, + size_t num_total, + std::vector<size_t>* selections) { + DCHECK_GE(num_total, num_to_select); + size_t num_left = num_total; + size_t num_needed = num_to_select; + for (size_t i = 0; i < num_total && num_needed > 0; ++i) { + // we have probability = |num_needed| / |num_left| to select + // this position. + size_t probability = base::RandInt(0, num_left - 1); + if (probability < num_needed) { + selections->push_back(i); + --num_needed; + } + --num_left; + } + DCHECK_EQ(num_to_select, selections->size()); +} + +} // namespace + +namespace autofill { + +const size_t PasswordGenerator::kDefaultPasswordLength = 12; + +PasswordGenerator::PasswordGenerator(size_t max_length) + : password_length_(GetLengthFromHint(max_length, kDefaultPasswordLength)) {} +PasswordGenerator::~PasswordGenerator() {} + +std::string PasswordGenerator::Generate() const { + std::string ret; + CR_DEFINE_STATIC_LOCAL(std::vector<char>, alphanumeric_characters, ()); + if (alphanumeric_characters.empty()) + InitializeAlphaNumericCharacters(&alphanumeric_characters); + + // First, randomly select 4 positions to hold one upper case letter, + // one lower case letter, one digit, and one other symbol respectively, + // to make sure at least one of each category of characters will be + // included in the password. + std::vector<size_t> positions; + GetRandomSelection(4u, password_length_, &positions); + + // To enhance the strengh of the password, we random suffle the positions so + // that the 4 catagories can be put at a random position in it. + std::random_shuffle(positions.begin(), positions.end()); + + // Next, generate each character of the password. + for (size_t i = 0; i < password_length_; ++i) { + if (i == positions[0]) { + // Generate random upper case letter. + ret.push_back(static_cast<char>(base::RandInt(kMinUpper, kMaxUpper))); + } else if (i == positions[1]) { + // Generate random lower case letter. + ret.push_back(static_cast<char>(base::RandInt(kMinLower, kMaxLower))); + } else if (i == positions[2]) { + // Generate random digit. + ret.push_back(static_cast<char>(base::RandInt(kMinDigit, kMaxDigit))); + } else if (i == positions[3]) { + // Generate random other symbol. + ret.push_back( + kOtherSymbols[base::RandInt(0, arraysize(kOtherSymbols) - 1)]); + } else { + // Generate random alphanumeric character. We don't use other symbols + // here as most sites don't allow a lot of non-alphanumeric characters. + ret.push_back( + alphanumeric_characters.at( + base::RandInt(0, alphanumeric_characters.size() - 1))); + } + } + return ret; +} + +} // namespace autofill diff --git a/components/autofill/browser/password_generator.h b/components/autofill/browser/password_generator.h new file mode 100644 index 0000000..0872bee --- /dev/null +++ b/components/autofill/browser/password_generator.h @@ -0,0 +1,48 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_PASSWORD_GENERATOR_H_ +#define COMPONENTS_AUTOFILL_BROWSER_PASSWORD_GENERATOR_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" + +namespace autofill { + +// Class to generate random passwords. Currently we just use a generic algorithm +// for all sites, but eventually we can incorporate additional information to +// determine passwords that are likely to be accepted (i.e. use pattern field, +// previous generated passwords, crowdsourcing, etc.) +class PasswordGenerator { + public: + // |max_length| is used as a hint for the generated password's length. + explicit PasswordGenerator(size_t max_length); + ~PasswordGenerator(); + + // Returns a random password such that: + // (1) Each character is guaranteed to be a non-whitespace printable ASCII + // character. + // (2) The generated password will contain AT LEAST one upper case letter, one + // lower case letter, one digit, and EXACTLY one other symbol. + // (3) The password length will be equal to |password_length_| (see comment + // for the constructor). + // Not thread safe. + std::string Generate() const; + + private: + // Unit test also need to access |kDefaultPasswordLength|. + static const size_t kDefaultPasswordLength; + FRIEND_TEST_ALL_PREFIXES(PasswordGeneratorTest, PasswordLength); + + // The length of the generated password. + const size_t password_length_; + + DISALLOW_COPY_AND_ASSIGN(PasswordGenerator); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_PASSWORD_GENERATOR_H_ diff --git a/components/autofill/browser/password_generator_unittest.cc b/components/autofill/browser/password_generator_unittest.cc new file mode 100644 index 0000000..3be72a5 --- /dev/null +++ b/components/autofill/browser/password_generator_unittest.cc @@ -0,0 +1,58 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <locale> + +#include "components/autofill/browser/password_generator.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +TEST(PasswordGeneratorTest, PasswordLength) { + PasswordGenerator pg1(10); + std::string password = pg1.Generate(); + EXPECT_EQ(password.size(), 10u); + + PasswordGenerator pg2(-1); + password = pg2.Generate(); + EXPECT_EQ(password.size(), PasswordGenerator::kDefaultPasswordLength); + + PasswordGenerator pg3(100); + password = pg3.Generate(); + EXPECT_EQ(password.size(), PasswordGenerator::kDefaultPasswordLength); +} + +TEST(PasswordGeneratorTest, PasswordPattern) { + PasswordGenerator pg(12); + std::string password = pg.Generate(); + int num_upper_case_letters = 0; + int num_lower_case_letters = 0; + int num_digits = 0; + int num_other_symbols = 0; + for (size_t i = 0; i < password.size(); i++) { + if (isupper(password[i])) + ++num_upper_case_letters; + else if (islower(password[i])) + ++num_lower_case_letters; + else if (isdigit(password[i])) + ++num_digits; + else + ++num_other_symbols; + } + EXPECT_GT(num_upper_case_letters, 0); + EXPECT_GT(num_lower_case_letters, 0); + EXPECT_GT(num_digits, 0); + EXPECT_EQ(num_other_symbols, 1); +} + +TEST(PasswordGeneratorTest, Printable) { + PasswordGenerator pg(12); + std::string password = pg.Generate(); + for (size_t i = 0; i < password.size(); i++) { + // Make sure that the character is printable. + EXPECT_TRUE(isgraph(password[i])); + } +} + +} // namespace autofill diff --git a/components/autofill/browser/personal_data_manager.cc b/components/autofill/browser/personal_data_manager.cc new file mode 100644 index 0000000..7317a6b --- /dev/null +++ b/components/autofill/browser/personal_data_manager.cc @@ -0,0 +1,972 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/personal_data_manager.h" + +#include <algorithm> +#include <functional> +#include <iterator> + +#include "base/logging.h" +#include "base/prefs/pref_service.h" +#include "base/strings/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/api/webdata/autofill_web_data_service.h" +#include "chrome/common/chrome_notification_types.h" +#include "components/autofill/browser/autofill-inl.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/browser/form_group.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/browser/personal_data_manager_observer.h" +#include "components/autofill/browser/phone_number.h" +#include "components/autofill/browser/phone_number_i18n.h" +#include "components/autofill/browser/validation.h" +#include "components/autofill/common/autofill_pref_names.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/notification_source.h" + +using content::BrowserContext; + +namespace { + +const string16::value_type kCreditCardPrefix[] = {'*', 0}; + +template<typename T> +class FormGroupMatchesByGUIDFunctor { + public: + explicit FormGroupMatchesByGUIDFunctor(const std::string& guid) + : guid_(guid) { + } + + bool operator()(const T& form_group) { + return form_group.guid() == guid_; + } + + bool operator()(const T* form_group) { + return form_group->guid() == guid_; + } + + private: + std::string guid_; +}; + +template<typename T, typename C> +bool FindByGUID(const C& container, const std::string& guid) { + return std::find_if( + container.begin(), + container.end(), + FormGroupMatchesByGUIDFunctor<T>(guid)) != container.end(); +} + +template<typename T> +class DereferenceFunctor { + public: + template<typename T_Iterator> + const T& operator()(const T_Iterator& iterator) { + return *iterator; + } +}; + +template<typename T> +T* address_of(T& v) { + return &v; +} + +// Returns true if minimum requirements for import of a given |profile| have +// been met. An address submitted via a form must have at least these fields +// filled. No verification of validity of the contents is preformed. This is +// and existence check only. +bool IsMinimumAddress(const AutofillProfile& profile) { + return !profile.GetRawInfo(ADDRESS_HOME_LINE1).empty() && + !profile.GetRawInfo(ADDRESS_HOME_CITY).empty() && + !profile.GetRawInfo(ADDRESS_HOME_STATE).empty() && + !profile.GetRawInfo(ADDRESS_HOME_ZIP).empty(); +} + +// Return true if the |field_type| and |value| are valid within the context +// of importing a form. +bool IsValidFieldTypeAndValue(const std::set<AutofillFieldType>& types_seen, + AutofillFieldType field_type, + const string16& value) { + // Abandon the import if two fields of the same type are encountered. + // This indicates ambiguous data or miscategorization of types. + // Make an exception for PHONE_HOME_NUMBER however as both prefix and + // suffix are stored against this type. + if (types_seen.count(field_type) && field_type != PHONE_HOME_NUMBER) + return false; + + // Abandon the import if an email address value shows up in a field that is + // not an email address. + if (field_type != EMAIL_ADDRESS && autofill::IsValidEmailAddress(value)) + return false; + + return true; +} + +} // namespace + +PersonalDataManager::PersonalDataManager() + : browser_context_(NULL), + is_data_loaded_(false), + pending_profiles_query_(0), + pending_creditcards_query_(0), + metric_logger_(new AutofillMetrics), + has_logged_profile_count_(false) {} + +void PersonalDataManager::Init(BrowserContext* browser_context) { + browser_context_ = browser_context; + metric_logger_->LogIsAutofillEnabledAtStartup(IsAutofillEnabled()); + + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + + // WebDataService may not be available in tests. + if (!autofill_data.get()) + return; + + LoadProfiles(); + LoadCreditCards(); + + notification_registrar_.Add( + this, + chrome::NOTIFICATION_AUTOFILL_MULTIPLE_CHANGED, + autofill_data->GetNotificationSource()); +} + +PersonalDataManager::~PersonalDataManager() { + CancelPendingQuery(&pending_profiles_query_); + CancelPendingQuery(&pending_creditcards_query_); +} + +void PersonalDataManager::OnWebDataServiceRequestDone( + WebDataServiceBase::Handle h, + const WDTypedResult* result) { + DCHECK(pending_profiles_query_ || pending_creditcards_query_); + + if (!result) { + // Error from the web database. + if (h == pending_creditcards_query_) + pending_creditcards_query_ = 0; + else if (h == pending_profiles_query_) + pending_profiles_query_ = 0; + return; + } + + DCHECK(result->GetType() == AUTOFILL_PROFILES_RESULT || + result->GetType() == AUTOFILL_CREDITCARDS_RESULT); + + switch (result->GetType()) { + case AUTOFILL_PROFILES_RESULT: + ReceiveLoadedProfiles(h, result); + break; + case AUTOFILL_CREDITCARDS_RESULT: + ReceiveLoadedCreditCards(h, result); + break; + default: + NOTREACHED(); + } + + // If both requests have responded, then all personal data is loaded. + if (pending_profiles_query_ == 0 && pending_creditcards_query_ == 0) { + is_data_loaded_ = true; + std::vector<AutofillProfile*> profile_pointers(web_profiles_.size()); + std::copy(web_profiles_.begin(), web_profiles_.end(), + profile_pointers.begin()); + AutofillProfile::AdjustInferredLabels(&profile_pointers); + FOR_EACH_OBSERVER(PersonalDataManagerObserver, observers_, + OnPersonalDataChanged()); + } +} + +void PersonalDataManager::AddObserver(PersonalDataManagerObserver* observer) { + observers_.AddObserver(observer); +} + +void PersonalDataManager::RemoveObserver( + PersonalDataManagerObserver* observer) { + observers_.RemoveObserver(observer); +} + +void PersonalDataManager::Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + DCHECK_EQ(type, chrome::NOTIFICATION_AUTOFILL_MULTIPLE_CHANGED); + + if (DCHECK_IS_ON()) { + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + + DCHECK(autofill_data.get() && + autofill_data->GetNotificationSource() == source); + } + + Refresh(); +} + +bool PersonalDataManager::ImportFormData( + const FormStructure& form, + const CreditCard** imported_credit_card) { + scoped_ptr<AutofillProfile> imported_profile(new AutofillProfile); + scoped_ptr<CreditCard> local_imported_credit_card(new CreditCard); + + // Parse the form and construct a profile based on the information that is + // possible to import. + int importable_credit_card_fields = 0; + + // Detect and discard forms with multiple fields of the same type. + std::set<AutofillFieldType> types_seen; + + // We only set complete phone, so aggregate phone parts in these vars and set + // complete at the end. + PhoneNumber::PhoneCombineHelper home; + + const std::string app_locale = AutofillCountry::ApplicationLocale(); + for (size_t i = 0; i < form.field_count(); ++i) { + const AutofillField* field = form.field(i); + string16 value = CollapseWhitespace(field->value, false); + + // If we don't know the type of the field, or the user hasn't entered any + // information into the field, then skip it. + if (!field->IsFieldFillable() || value.empty()) + continue; + + AutofillFieldType field_type = field->type(); + FieldTypeGroup group(AutofillType(field_type).group()); + + // If the |field_type| and |value| don't pass basic validity checks then + // abandon the import. + if (!IsValidFieldTypeAndValue(types_seen, field_type, value)) { + imported_profile.reset(); + local_imported_credit_card.reset(); + break; + } + + types_seen.insert(field_type); + + if (group == AutofillType::CREDIT_CARD) { + if (LowerCaseEqualsASCII(field->form_control_type, "month")) { + DCHECK_EQ(CREDIT_CARD_EXP_MONTH, field_type); + local_imported_credit_card->SetInfoForMonthInputType(value); + } else { + local_imported_credit_card->SetInfo(field_type, value, app_locale); + } + ++importable_credit_card_fields; + } else { + // We need to store phone data in the variables, before building the whole + // number at the end. The rest of the fields are set "as is". + // If the fields are not the phone fields in question home.SetInfo() is + // going to return false. + if (!home.SetInfo(field_type, value)) + imported_profile->SetInfo(field_type, value, app_locale); + + // Reject profiles with invalid country information. + if (field_type == ADDRESS_HOME_COUNTRY && + !value.empty() && imported_profile->CountryCode().empty()) { + imported_profile.reset(); + break; + } + } + } + + // Construct the phone number. Reject the profile if the number is invalid. + if (imported_profile.get() && !home.IsEmpty()) { + string16 constructed_number; + if (!home.ParseNumber(*imported_profile, app_locale, &constructed_number) || + !imported_profile->SetInfo(PHONE_HOME_WHOLE_NUMBER, constructed_number, + app_locale)) { + imported_profile.reset(); + } + } + + // Reject the profile if minimum address and validation requirements are not + // met. + if (imported_profile.get() && !IsValidLearnableProfile(*imported_profile)) + imported_profile.reset(); + + // Reject the credit card if we did not detect enough filled credit card + // fields or if the credit card number does not seem to be valid. + if (local_imported_credit_card.get() && + !local_imported_credit_card->IsComplete()) { + local_imported_credit_card.reset(); + } + + // Don't import if we already have this info. + // Don't present an infobar if we have already saved this card number. + bool merged_credit_card = false; + if (local_imported_credit_card.get()) { + for (std::vector<CreditCard*>::const_iterator iter = credit_cards_.begin(); + iter != credit_cards_.end(); + ++iter) { + if ((*iter)->UpdateFromImportedCard(*local_imported_credit_card.get(), + app_locale)) { + merged_credit_card = true; + UpdateCreditCard(**iter); + local_imported_credit_card.reset(); + break; + } + } + } + + if (imported_profile.get()) { + // We always save imported profiles. + SaveImportedProfile(*imported_profile); + } + *imported_credit_card = local_imported_credit_card.release(); + + if (imported_profile.get() || *imported_credit_card || merged_credit_card) { + return true; + } else { + FOR_EACH_OBSERVER(PersonalDataManagerObserver, observers_, + OnInsufficientFormData()); + return false; + } +} + +void PersonalDataManager::AddProfile(const AutofillProfile& profile) { + if (browser_context_->IsOffTheRecord()) + return; + + if (profile.IsEmpty()) + return; + + // Don't add an existing profile. + if (FindByGUID<AutofillProfile>(web_profiles_, profile.guid())) + return; + + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (!autofill_data.get()) + return; + + // Don't add a duplicate. + if (FindByContents(web_profiles_, profile)) + return; + + // Add the new profile to the web database. + autofill_data->AddAutofillProfile(profile); + + // Refresh our local cache and send notifications to observers. + Refresh(); +} + +void PersonalDataManager::UpdateProfile(const AutofillProfile& profile) { + if (browser_context_->IsOffTheRecord()) + return; + + if (!FindByGUID<AutofillProfile>(web_profiles_, profile.guid())) + return; + + if (profile.IsEmpty()) { + RemoveByGUID(profile.guid()); + return; + } + + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (!autofill_data.get()) + return; + + // Make the update. + autofill_data->UpdateAutofillProfile(profile); + + // Refresh our local cache and send notifications to observers. + Refresh(); +} + +AutofillProfile* PersonalDataManager::GetProfileByGUID( + const std::string& guid) { + const std::vector<AutofillProfile*>& profiles = GetProfiles(); + for (std::vector<AutofillProfile*>::const_iterator iter = profiles.begin(); + iter != profiles.end(); ++iter) { + if ((*iter)->guid() == guid) + return *iter; + } + return NULL; +} + +void PersonalDataManager::AddCreditCard(const CreditCard& credit_card) { + if (browser_context_->IsOffTheRecord()) + return; + + if (credit_card.IsEmpty()) + return; + + if (FindByGUID<CreditCard>(credit_cards_, credit_card.guid())) + return; + + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (!autofill_data.get()) + return; + + // Don't add a duplicate. + if (FindByContents(credit_cards_, credit_card)) + return; + + // Add the new credit card to the web database. + autofill_data->AddCreditCard(credit_card); + + // Refresh our local cache and send notifications to observers. + Refresh(); +} + +void PersonalDataManager::UpdateCreditCard(const CreditCard& credit_card) { + if (browser_context_->IsOffTheRecord()) + return; + + if (!FindByGUID<CreditCard>(credit_cards_, credit_card.guid())) + return; + + if (credit_card.IsEmpty()) { + RemoveByGUID(credit_card.guid()); + return; + } + + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (!autofill_data.get()) + return; + + // Make the update. + autofill_data->UpdateCreditCard(credit_card); + + // Refresh our local cache and send notifications to observers. + Refresh(); +} + +void PersonalDataManager::RemoveByGUID(const std::string& guid) { + if (browser_context_->IsOffTheRecord()) + return; + + bool is_credit_card = FindByGUID<CreditCard>(credit_cards_, guid); + bool is_profile = !is_credit_card && + FindByGUID<AutofillProfile>(web_profiles_, guid); + if (!is_credit_card && !is_profile) + return; + + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (!autofill_data.get()) + return; + + if (is_credit_card) + autofill_data->RemoveCreditCard(guid); + else + autofill_data->RemoveAutofillProfile(guid); + + // Refresh our local cache and send notifications to observers. + Refresh(); +} + +CreditCard* PersonalDataManager::GetCreditCardByGUID(const std::string& guid) { + for (std::vector<CreditCard*>::iterator iter = credit_cards_.begin(); + iter != credit_cards_.end(); ++iter) { + if ((*iter)->guid() == guid) + return *iter; + } + return NULL; +} + +void PersonalDataManager::GetNonEmptyTypes( + FieldTypeSet* non_empty_types) { + const std::string app_locale = AutofillCountry::ApplicationLocale(); + const std::vector<AutofillProfile*>& profiles = GetProfiles(); + for (std::vector<AutofillProfile*>::const_iterator iter = profiles.begin(); + iter != profiles.end(); ++iter) { + (*iter)->GetNonEmptyTypes(app_locale, non_empty_types); + } + + for (ScopedVector<CreditCard>::const_iterator iter = credit_cards_.begin(); + iter != credit_cards_.end(); ++iter) { + (*iter)->GetNonEmptyTypes(app_locale, non_empty_types); + } +} + +bool PersonalDataManager::IsDataLoaded() const { + return is_data_loaded_; +} + +const std::vector<AutofillProfile*>& PersonalDataManager::GetProfiles() { + if (!components::UserPrefs::Get(browser_context_)->GetBoolean( + prefs::kAutofillAuxiliaryProfilesEnabled)) { + return web_profiles(); + } + + profiles_.clear(); + + // Populates |auxiliary_profiles_|. + LoadAuxiliaryProfiles(); + + profiles_.insert(profiles_.end(), web_profiles_.begin(), web_profiles_.end()); + profiles_.insert(profiles_.end(), + auxiliary_profiles_.begin(), auxiliary_profiles_.end()); + return profiles_; +} + +const std::vector<AutofillProfile*>& PersonalDataManager::web_profiles() const { + return web_profiles_.get(); +} + +const std::vector<CreditCard*>& PersonalDataManager::credit_cards() const { + return credit_cards_.get(); +} + +void PersonalDataManager::Refresh() { + LoadProfiles(); + LoadCreditCards(); +} + +void PersonalDataManager::GetProfileSuggestions( + AutofillFieldType type, + const string16& field_contents, + bool field_is_autofilled, + std::vector<AutofillFieldType> other_field_types, + std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<GUIDPair>* guid_pairs) { + values->clear(); + labels->clear(); + icons->clear(); + guid_pairs->clear(); + + const std::vector<AutofillProfile*>& profiles = GetProfiles(); + const std::string app_locale = AutofillCountry::ApplicationLocale(); + std::vector<AutofillProfile*> matched_profiles; + for (std::vector<AutofillProfile*>::const_iterator iter = profiles.begin(); + iter != profiles.end(); ++iter) { + AutofillProfile* profile = *iter; + + // The value of the stored data for this field type in the |profile|. + std::vector<string16> multi_values; + profile->GetMultiInfo(type, app_locale, &multi_values); + + for (size_t i = 0; i < multi_values.size(); ++i) { + if (!field_is_autofilled) { + // Suggest data that starts with what the user has typed. + if (!multi_values[i].empty() && + StartsWith(multi_values[i], field_contents, false)) { + matched_profiles.push_back(profile); + values->push_back(multi_values[i]); + guid_pairs->push_back(GUIDPair(profile->guid(), i)); + } + } else { + if (multi_values[i].empty()) + continue; + + string16 profile_value_lower_case( + StringToLowerASCII(multi_values[i])); + string16 field_value_lower_case(StringToLowerASCII(field_contents)); + // Phone numbers could be split in US forms, so field value could be + // either prefix or suffix of the phone. + bool matched_phones = false; + if (type == PHONE_HOME_NUMBER && !field_value_lower_case.empty() && + (profile_value_lower_case.find(field_value_lower_case) != + string16::npos)) { + matched_phones = true; + } + + // Suggest variants of the profile that's already been filled in. + if (matched_phones || + profile_value_lower_case == field_value_lower_case) { + for (size_t j = 0; j < multi_values.size(); ++j) { + if (!multi_values[j].empty()) { + values->push_back(multi_values[j]); + guid_pairs->push_back(GUIDPair(profile->guid(), j)); + } + } + + // We've added all the values for this profile so move on to the + // next. + break; + } + } + } + } + + if (!field_is_autofilled) { + AutofillProfile::CreateInferredLabels( + &matched_profiles, &other_field_types, + type, 1, labels); + } else { + // No sub-labels for previously filled fields. + labels->resize(values->size()); + } + + // No icons for profile suggestions. + icons->resize(values->size()); +} + +void PersonalDataManager::GetCreditCardSuggestions( + AutofillFieldType type, + const string16& field_contents, + std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<GUIDPair>* guid_pairs) { + const std::string app_locale = AutofillCountry::ApplicationLocale(); + for (std::vector<CreditCard*>::const_iterator iter = credit_cards().begin(); + iter != credit_cards().end(); ++iter) { + CreditCard* credit_card = *iter; + + // The value of the stored data for this field type in the |credit_card|. + string16 creditcard_field_value = credit_card->GetInfo(type, app_locale); + if (!creditcard_field_value.empty() && + StartsWith(creditcard_field_value, field_contents, false)) { + if (type == CREDIT_CARD_NUMBER) + creditcard_field_value = credit_card->ObfuscatedNumber(); + + string16 label; + if (credit_card->number().empty()) { + // If there is no CC number, return name to show something. + label = credit_card->GetInfo(CREDIT_CARD_NAME, app_locale); + } else { + label = kCreditCardPrefix; + label.append(credit_card->LastFourDigits()); + } + + values->push_back(creditcard_field_value); + labels->push_back(label); + icons->push_back(UTF8ToUTF16(credit_card->type())); + guid_pairs->push_back(GUIDPair(credit_card->guid(), 0)); + } + } +} + +bool PersonalDataManager::IsAutofillEnabled() const { + return components::UserPrefs::Get(browser_context_)->GetBoolean( + prefs::kAutofillEnabled); +} + +// static +bool PersonalDataManager::IsValidLearnableProfile( + const AutofillProfile& profile) { + if (!IsMinimumAddress(profile)) + return false; + + string16 email = profile.GetRawInfo(EMAIL_ADDRESS); + if (!email.empty() && !autofill::IsValidEmailAddress(email)) + return false; + + // Reject profiles with invalid US state information. + string16 state = profile.GetRawInfo(ADDRESS_HOME_STATE); + if (profile.CountryCode() == "US" && + !state.empty() && !FormGroup::IsValidState(state)) { + return false; + } + + // Reject profiles with invalid US zip information. + string16 zip = profile.GetRawInfo(ADDRESS_HOME_ZIP); + if (profile.CountryCode() == "US" && !zip.empty() && + !autofill::IsValidZip(zip)) + return false; + + return true; +} + +// static +bool PersonalDataManager::MergeProfile( + const AutofillProfile& profile, + const std::vector<AutofillProfile*>& existing_profiles, + std::vector<AutofillProfile>* merged_profiles) { + merged_profiles->clear(); + + // Set to true if |profile| is merged into |existing_profiles|. + bool merged = false; + + // If we have already saved this address, merge in any missing values. + // Only merge with the first match. + for (std::vector<AutofillProfile*>::const_iterator iter = + existing_profiles.begin(); + iter != existing_profiles.end(); ++iter) { + if (!merged) { + if (!profile.PrimaryValue().empty() && + StringToLowerASCII((*iter)->PrimaryValue()) == + StringToLowerASCII(profile.PrimaryValue())) { + merged = true; + (*iter)->OverwriteWithOrAddTo(profile); + } + } + merged_profiles->push_back(**iter); + } + + // If the new profile was not merged with an existing one, add it to the list. + if (!merged) + merged_profiles->push_back(profile); + + return merged; +} + +void PersonalDataManager::SetProfiles(std::vector<AutofillProfile>* profiles) { + if (browser_context_->IsOffTheRecord()) + return; + + // Remove empty profiles from input. + profiles->erase( + std::remove_if(profiles->begin(), profiles->end(), + std::mem_fun_ref(&AutofillProfile::IsEmpty)), + profiles->end()); + + // Ensure that profile labels are up to date. Currently, sync relies on + // labels to identify a profile. + // TODO(dhollowa): We need to deprecate labels and update the way sync + // identifies profiles. + std::vector<AutofillProfile*> profile_pointers(profiles->size()); + std::transform(profiles->begin(), profiles->end(), profile_pointers.begin(), + address_of<AutofillProfile>); + AutofillProfile::AdjustInferredLabels(&profile_pointers); + + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (!autofill_data.get()) + return; + + // Any profiles that are not in the new profile list should be removed from + // the web database. + for (std::vector<AutofillProfile*>::const_iterator iter = + web_profiles_.begin(); + iter != web_profiles_.end(); ++iter) { + if (!FindByGUID<AutofillProfile>(*profiles, (*iter)->guid())) + autofill_data->RemoveAutofillProfile((*iter)->guid()); + } + + // Update the web database with the existing profiles. + for (std::vector<AutofillProfile>::iterator iter = profiles->begin(); + iter != profiles->end(); ++iter) { + if (FindByGUID<AutofillProfile>(web_profiles_, iter->guid())) + autofill_data->UpdateAutofillProfile(*iter); + } + + // Add the new profiles to the web database. Don't add a duplicate. + for (std::vector<AutofillProfile>::iterator iter = profiles->begin(); + iter != profiles->end(); ++iter) { + if (!FindByGUID<AutofillProfile>(web_profiles_, iter->guid()) && + !FindByContents(web_profiles_, *iter)) + autofill_data->AddAutofillProfile(*iter); + } + + // Copy in the new profiles. + web_profiles_.clear(); + for (std::vector<AutofillProfile>::iterator iter = profiles->begin(); + iter != profiles->end(); ++iter) { + web_profiles_.push_back(new AutofillProfile(*iter)); + } + + // Refresh our local cache and send notifications to observers. + Refresh(); +} + +void PersonalDataManager::SetCreditCards( + std::vector<CreditCard>* credit_cards) { + if (browser_context_->IsOffTheRecord()) + return; + + // Remove empty credit cards from input. + credit_cards->erase( + std::remove_if( + credit_cards->begin(), credit_cards->end(), + std::mem_fun_ref(&CreditCard::IsEmpty)), + credit_cards->end()); + + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (!autofill_data.get()) + return; + + // Any credit cards that are not in the new credit card list should be + // removed. + for (std::vector<CreditCard*>::const_iterator iter = credit_cards_.begin(); + iter != credit_cards_.end(); ++iter) { + if (!FindByGUID<CreditCard>(*credit_cards, (*iter)->guid())) + autofill_data->RemoveCreditCard((*iter)->guid()); + } + + // Update the web database with the existing credit cards. + for (std::vector<CreditCard>::iterator iter = credit_cards->begin(); + iter != credit_cards->end(); ++iter) { + if (FindByGUID<CreditCard>(credit_cards_, iter->guid())) + autofill_data->UpdateCreditCard(*iter); + } + + // Add the new credit cards to the web database. Don't add a duplicate. + for (std::vector<CreditCard>::iterator iter = credit_cards->begin(); + iter != credit_cards->end(); ++iter) { + if (!FindByGUID<CreditCard>(credit_cards_, iter->guid()) && + !FindByContents(credit_cards_, *iter)) + autofill_data->AddCreditCard(*iter); + } + + // Copy in the new credit cards. + credit_cards_.clear(); + for (std::vector<CreditCard>::iterator iter = credit_cards->begin(); + iter != credit_cards->end(); ++iter) { + credit_cards_.push_back(new CreditCard(*iter)); + } + + // Refresh our local cache and send notifications to observers. + Refresh(); +} + +void PersonalDataManager::LoadProfiles() { + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (!autofill_data.get()) { + NOTREACHED(); + return; + } + + CancelPendingQuery(&pending_profiles_query_); + + pending_profiles_query_ = autofill_data->GetAutofillProfiles(this); +} + +// Win and Linux implementations do nothing. Mac implementation fills in the +// contents of |auxiliary_profiles_|. +#if !defined(OS_MACOSX) +void PersonalDataManager::LoadAuxiliaryProfiles() { +} +#endif + +void PersonalDataManager::LoadCreditCards() { + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (!autofill_data.get()) { + NOTREACHED(); + return; + } + + CancelPendingQuery(&pending_creditcards_query_); + + pending_creditcards_query_ = autofill_data->GetCreditCards(this); +} + +void PersonalDataManager::ReceiveLoadedProfiles(WebDataServiceBase::Handle h, + const WDTypedResult* result) { + DCHECK_EQ(pending_profiles_query_, h); + + pending_profiles_query_ = 0; + web_profiles_.clear(); + + const WDResult<std::vector<AutofillProfile*> >* r = + static_cast<const WDResult<std::vector<AutofillProfile*> >*>(result); + + std::vector<AutofillProfile*> profiles = r->GetValue(); + for (std::vector<AutofillProfile*>::iterator iter = profiles.begin(); + iter != profiles.end(); ++iter) { + web_profiles_.push_back(*iter); + } + + LogProfileCount(); +} + +void PersonalDataManager::ReceiveLoadedCreditCards( + WebDataServiceBase::Handle h, const WDTypedResult* result) { + DCHECK_EQ(pending_creditcards_query_, h); + + pending_creditcards_query_ = 0; + credit_cards_.clear(); + + const WDResult<std::vector<CreditCard*> >* r = + static_cast<const WDResult<std::vector<CreditCard*> >*>(result); + + std::vector<CreditCard*> credit_cards = r->GetValue(); + for (std::vector<CreditCard*>::iterator iter = credit_cards.begin(); + iter != credit_cards.end(); ++iter) { + credit_cards_.push_back(*iter); + } +} + +void PersonalDataManager::CancelPendingQuery( + WebDataServiceBase::Handle* handle) { + if (*handle) { + scoped_ptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (!autofill_data.get()) { + NOTREACHED(); + return; + } + autofill_data->CancelRequest(*handle); + } + *handle = 0; +} + +void PersonalDataManager::SaveImportedProfile( + const AutofillProfile& imported_profile) { + if (browser_context_->IsOffTheRecord()) { + // The |IsOffTheRecord| check should happen earlier in the import process, + // upon form submission. + NOTREACHED(); + return; + } + + // Don't save a web profile if the data in the profile is a subset of an + // auxiliary profile. + for (std::vector<AutofillProfile*>::const_iterator iter = + auxiliary_profiles_.begin(); + iter != auxiliary_profiles_.end(); ++iter) { + if (imported_profile.IsSubsetOf(**iter)) + return; + } + + std::vector<AutofillProfile> profiles; + MergeProfile(imported_profile, web_profiles_.get(), &profiles); + SetProfiles(&profiles); +} + + +void PersonalDataManager::SaveImportedCreditCard( + const CreditCard& imported_card) { + DCHECK(!imported_card.number().empty()); + if (browser_context_->IsOffTheRecord()) { + // The |IsOffTheRecord| check should happen earlier in the import process, + // upon form submission. + NOTREACHED(); + return; + } + + // Set to true if |imported_card| is merged into the credit card list. + bool merged = false; + + const std::string app_locale = AutofillCountry::ApplicationLocale(); + std::vector<CreditCard> credit_cards; + for (std::vector<CreditCard*>::const_iterator card = credit_cards_.begin(); + card != credit_cards_.end(); + ++card) { + // If |imported_card| has not yet been merged, check whether it should be + // with the current |card|. + if (!merged && (*card)->UpdateFromImportedCard(imported_card, app_locale)) + merged = true; + + credit_cards.push_back(**card); + } + + if (!merged) + credit_cards.push_back(imported_card); + + SetCreditCards(&credit_cards); +} + +void PersonalDataManager::LogProfileCount() const { + if (!has_logged_profile_count_) { + metric_logger_->LogStoredProfileCount(web_profiles_.size()); + has_logged_profile_count_ = true; + } +} + +const AutofillMetrics* PersonalDataManager::metric_logger() const { + return metric_logger_.get(); +} + +void PersonalDataManager::set_metric_logger( + const AutofillMetrics* metric_logger) { + metric_logger_.reset(metric_logger); +} + +void PersonalDataManager::set_browser_context( + content::BrowserContext* context) { + browser_context_ = context; +} diff --git a/components/autofill/browser/personal_data_manager.h b/components/autofill/browser/personal_data_manager.h new file mode 100644 index 0000000..4a1292a --- /dev/null +++ b/components/autofill/browser/personal_data_manager.h @@ -0,0 +1,282 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_PERSONAL_DATA_MANAGER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_PERSONAL_DATA_MANAGER_H_ + +#include <set> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/observer_list.h" +#include "base/string16.h" +#include "chrome/browser/api/webdata/web_data_service_consumer.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/credit_card.h" +#include "components/autofill/browser/field_types.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" + +class AutofillMetrics; +class FormStructure; +class PersonalDataManagerObserver; + +namespace autofill_helper { +void SetProfiles(int, std::vector<AutofillProfile>*); +void SetCreditCards(int, std::vector<CreditCard>*); +} + +namespace content { +class BrowserContext; +} + +// Handles loading and saving Autofill profile information to the web database. +// This class also stores the profiles loaded from the database for use during +// Autofill. +class PersonalDataManager : public WebDataServiceConsumer, + public content::NotificationObserver { + public: + // A pair of GUID and variant index. Represents a single FormGroup and a + // specific data variant. + typedef std::pair<std::string, size_t> GUIDPair; + + PersonalDataManager(); + virtual ~PersonalDataManager(); + + // Kicks off asynchronous loading of profiles and credit cards. + void Init(content::BrowserContext* context); + + // WebDataServiceConsumer: + virtual void OnWebDataServiceRequestDone( + WebDataServiceBase::Handle h, + const WDTypedResult* result) OVERRIDE; + + // Adds a listener to be notified of PersonalDataManager events. + virtual void AddObserver(PersonalDataManagerObserver* observer); + + // Removes |observer| as an observer of this PersonalDataManager. + virtual void RemoveObserver(PersonalDataManagerObserver* observer); + + // content::NotificationObserver: + // Observes "batch" changes made by Sync and refreshes data from the + // WebDataServiceBase in response. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // Scans the given |form| for importable Autofill data. If the form includes + // sufficient address data, it is immediately imported. If the form includes + // sufficient credit card data, it is stored into |credit_card|, so that we + // can prompt the user whether to save this data. + // Returns |true| if sufficient address or credit card data was found. + bool ImportFormData(const FormStructure& form, + const CreditCard** credit_card); + + // Saves |imported_profile| to the WebDB if it exists. + virtual void SaveImportedProfile(const AutofillProfile& imported_profile); + + // Saves a credit card value detected in |ImportedFormData|. + virtual void SaveImportedCreditCard(const CreditCard& imported_credit_card); + + // Adds |profile| to the web database. + void AddProfile(const AutofillProfile& profile); + + // Updates |profile| which already exists in the web database. + void UpdateProfile(const AutofillProfile& profile); + + // Removes the profile or credit card represented by |guid|. + virtual void RemoveByGUID(const std::string& guid); + + // Returns the profile with the specified |guid|, or NULL if there is no + // profile with the specified |guid|. Both web and auxiliary profiles may + // be returned. + AutofillProfile* GetProfileByGUID(const std::string& guid); + + // Adds |credit_card| to the web database. + void AddCreditCard(const CreditCard& credit_card); + + // Updates |credit_card| which already exists in the web database. + void UpdateCreditCard(const CreditCard& credit_card); + + // Returns the credit card with the specified |guid|, or NULL if there is + // no credit card with the specified |guid|. + CreditCard* GetCreditCardByGUID(const std::string& guid); + + // Gets the field types availabe in the stored address and credit card data. + void GetNonEmptyTypes(FieldTypeSet* non_empty_types); + + // Returns true if the credit card information is stored with a password. + bool HasPassword(); + + // Returns whether the personal data has been loaded from the web database. + virtual bool IsDataLoaded() const; + + // This PersonalDataManager owns these profiles and credit cards. Their + // lifetime is until the web database is updated with new profile and credit + // card information, respectively. |profiles()| returns both web and + // auxiliary profiles. |web_profiles()| returns only web profiles. + virtual const std::vector<AutofillProfile*>& GetProfiles(); + virtual const std::vector<AutofillProfile*>& web_profiles() const; + virtual const std::vector<CreditCard*>& credit_cards() const; + + // Loads profiles that can suggest data for |type|. |field_contents| is the + // part the user has already typed. |field_is_autofilled| is true if the field + // has already been autofilled. |other_field_types| represents the rest of + // form. Identifying info is loaded into the last four outparams. + void GetProfileSuggestions( + AutofillFieldType type, + const string16& field_contents, + bool field_is_autofilled, + std::vector<AutofillFieldType> other_field_types, + std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<GUIDPair>* guid_pairs); + + // Gets credit cards that can suggest data for |type|. See + // GetProfileSuggestions for argument descriptions. The variant in each + // GUID pair should be ignored. + void GetCreditCardSuggestions( + AutofillFieldType type, + const string16& field_contents, + std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<GUIDPair>* guid_pairs); + + // Re-loads profiles and credit cards from the WebDatabase asynchronously. + // In the general case, this is a no-op and will re-create the same + // in-memory model as existed prior to the call. If any change occurred to + // profiles in the WebDatabase directly, as is the case if the browser sync + // engine processed a change from the cloud, we will learn of these as a + // result of this call. + // + // Also see SetProfile for more details. + virtual void Refresh(); + + // Checks suitability of |profile| for adding to the user's set of profiles. + static bool IsValidLearnableProfile(const AutofillProfile& profile); + + // Merges |profile| into one of the |existing_profiles| if possible; otherwise + // appends |profile| to the end of that list. Fills |merged_profiles| with the + // result. + static bool MergeProfile( + const AutofillProfile& profile, + const std::vector<AutofillProfile*>& existing_profiles, + std::vector<AutofillProfile>* merged_profiles); + + protected: + // Only PersonalDataManagerFactory and certain tests can create instances of + // PersonalDataManager. + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, FirstMiddleLast); + FRIEND_TEST_ALL_PREFIXES(AutofillMetricsTest, AutofillIsEnabledAtStartup); + FRIEND_TEST_ALL_PREFIXES(PersonalDataManagerTest, + AggregateExistingAuxiliaryProfile); + friend class AutofillTest; + friend class PersonalDataManagerFactory; + friend class PersonalDataManagerTest; + friend class ProfileSyncServiceAutofillTest; + friend class RemoveAutofillTester; + friend class TestingAutomationProvider; + friend struct base::DefaultDeleter<PersonalDataManager>; + friend void autofill_helper::SetProfiles(int, std::vector<AutofillProfile>*); + friend void autofill_helper::SetCreditCards(int, std::vector<CreditCard>*); + + // Sets |web_profiles_| to the contents of |profiles| and updates the web + // database by adding, updating and removing profiles. + // The relationship between this and Refresh is subtle. + // A call to |SetProfiles| could include out-of-date data that may conflict + // if we didn't refresh-to-latest before an Autofill window was opened for + // editing. |SetProfiles| is implemented to make a "best effort" to apply the + // changes, but in extremely rare edge cases it is possible not all of the + // updates in |profiles| make it to the DB. This is why SetProfiles will + // invoke Refresh after finishing, to ensure we get into a + // consistent state. See Refresh for details. + void SetProfiles(std::vector<AutofillProfile>* profiles); + + // Sets |credit_cards_| to the contents of |credit_cards| and updates the web + // database by adding, updating and removing credit cards. + void SetCreditCards(std::vector<CreditCard>* credit_cards); + + // Loads the saved profiles from the web database. + virtual void LoadProfiles(); + + // Loads the auxiliary profiles. Currently Mac only. + virtual void LoadAuxiliaryProfiles(); + + // Loads the saved credit cards from the web database. + virtual void LoadCreditCards(); + + // Receives the loaded profiles from the web data service and stores them in + // |credit_cards_|. + void ReceiveLoadedProfiles(WebDataServiceBase::Handle h, + const WDTypedResult* result); + + // Receives the loaded credit cards from the web data service and stores them + // in |credit_cards_|. + void ReceiveLoadedCreditCards(WebDataServiceBase::Handle h, + const WDTypedResult* result); + + // Cancels a pending query to the web database. |handle| is a pointer to the + // query handle. + void CancelPendingQuery(WebDataServiceBase::Handle* handle); + + // The first time this is called, logs an UMA metrics for the number of + // profiles the user has. On subsequent calls, does nothing. + void LogProfileCount() const; + + // Returns the value of the AutofillEnabled pref. + virtual bool IsAutofillEnabled() const; + + // For tests. + const AutofillMetrics* metric_logger() const; + void set_metric_logger(const AutofillMetrics* metric_logger); + void set_browser_context(content::BrowserContext* context); + + // The browser context this PersonalDataManager is in. + content::BrowserContext* browser_context_; + + // True if personal data has been loaded from the web database. + bool is_data_loaded_; + + // The loaded web profiles. + ScopedVector<AutofillProfile> web_profiles_; + + // Auxiliary profiles. + mutable ScopedVector<AutofillProfile> auxiliary_profiles_; + + // Storage for combined web and auxiliary profiles. Contents are weak + // references. Lifetime managed by |web_profiles_| and |auxiliary_profiles_|. + mutable std::vector<AutofillProfile*> profiles_; + + // The loaded credit cards. + ScopedVector<CreditCard> credit_cards_; + + // When the manager makes a request from WebDataServiceBase, the database + // is queried on another thread, we record the query handle until we + // get called back. We store handles for both profile and credit card queries + // so they can be loaded at the same time. + WebDataServiceBase::Handle pending_profiles_query_; + WebDataServiceBase::Handle pending_creditcards_query_; + + // The observers. + ObserverList<PersonalDataManagerObserver> observers_; + + private: + + // For logging UMA metrics. Overridden by metrics tests. + scoped_ptr<const AutofillMetrics> metric_logger_; + + // Whether we have already logged the number of profiles this session. + mutable bool has_logged_profile_count_; + + // Manages registration lifetime for NotificationObserver implementation. + content::NotificationRegistrar notification_registrar_; + + DISALLOW_COPY_AND_ASSIGN(PersonalDataManager); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_PERSONAL_DATA_MANAGER_H_ diff --git a/components/autofill/browser/personal_data_manager_mac.mm b/components/autofill/browser/personal_data_manager_mac.mm new file mode 100644 index 0000000..dbab8ed --- /dev/null +++ b/components/autofill/browser/personal_data_manager_mac.mm @@ -0,0 +1,262 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/personal_data_manager.h" + +#include <math.h> + +#import <AddressBook/AddressBook.h> + +#include "base/format_macros.h" +#include "base/guid.h" +#include "base/logging.h" +#import "base/mac/scoped_nsexception_enabler.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/stringprintf.h" +#include "base/sys_string_conversions.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/phone_number.h" +#include "grit/generated_resources.h" +#include "ui/base/l10n/l10n_util_mac.h" + +namespace { + +// This implementation makes use of the Address Book API. Profiles are +// generated that correspond to addresses in the "me" card that reside in the +// user's Address Book. The caller passes a vector of profiles into the +// the constructer and then initiate the fetch from the Mac Address Book "me" +// card using the main |GetAddressBookMeCard()| method. This clears any +// existing addresses and populates new addresses derived from the data found +// in the "me" card. +class AuxiliaryProfilesImpl { + public: + // Constructor takes a reference to the |profiles| that will be filled in + // by the subsequent call to |GetAddressBookMeCard()|. |profiles| may not + // be NULL. + explicit AuxiliaryProfilesImpl(ScopedVector<AutofillProfile>* profiles) + : profiles_(*profiles) { + } + virtual ~AuxiliaryProfilesImpl() {} + + // Import the "me" card from the Mac Address Book and fill in |profiles_|. + void GetAddressBookMeCard(); + + private: + void GetAddressBookNames(ABPerson* me, + NSString* addressLabelRaw, + AutofillProfile* profile); + void GetAddressBookAddress(NSDictionary* address, AutofillProfile* profile); + void GetAddressBookEmail(ABPerson* me, + NSString* addressLabelRaw, + AutofillProfile* profile); + void GetAddressBookPhoneNumbers(ABPerson* me, + NSString* addressLabelRaw, + AutofillProfile* profile); + + private: + // A reference to the profiles this class populates. + ScopedVector<AutofillProfile>& profiles_; + + DISALLOW_COPY_AND_ASSIGN(AuxiliaryProfilesImpl); +}; + +// This method uses the |ABAddressBook| system service to fetch the "me" card +// from the active user's address book. It looks for the user address +// information and translates it to the internal list of |AutofillProfile| data +// structures. +void AuxiliaryProfilesImpl::GetAddressBookMeCard() { + profiles_.clear(); + + // +[ABAddressBook sharedAddressBook] throws an exception internally in + // circumstances that aren't clear. The exceptions are only observed in crash + // reports, so it is unknown whether they would be caught by AppKit and nil + // returned, or if they would take down the app. In either case, avoid + // crashing. http://crbug.com/129022 + ABAddressBook* addressBook = base::mac::RunBlockIgnoringExceptions(^{ + return [ABAddressBook sharedAddressBook]; + }); + ABPerson* me = [addressBook me]; + if (!me) + return; + + ABMultiValue* addresses = [me valueForProperty:kABAddressProperty]; + + // The number of characters at the end of the GUID to reserve for + // distinguishing addresses within the "me" card. Cap the number of addresses + // we will fetch to the number that can be distinguished by this fragment of + // the GUID. + const size_t kNumAddressGUIDChars = 2; + const size_t kNumHexDigits = 16; + const size_t kMaxAddressCount = pow(kNumHexDigits, kNumAddressGUIDChars); + NSUInteger count = MIN([addresses count], kMaxAddressCount); + for (NSUInteger i = 0; i < count; i++) { + NSDictionary* address = [addresses valueAtIndex:i]; + NSString* addressLabelRaw = [addresses labelAtIndex:i]; + + // Create a new profile where the guid is set to the guid portion of the + // |kABUIDProperty| taken from from the "me" address. The format of + // the |kABUIDProperty| is "<guid>:ABPerson", so we're stripping off the + // raw guid here and using it directly, with one modification: we update the + // last |kNumAddressGUIDChars| characters in the GUID to reflect the address + // variant. Note that we capped the number of addresses above, so this is + // safe. + const size_t kGUIDLength = 36U; + const size_t kTrimmedGUIDLength = kGUIDLength - kNumAddressGUIDChars; + std::string guid = base::SysNSStringToUTF8( + [me valueForProperty:kABUIDProperty]).substr(0, kTrimmedGUIDLength); + + // The format string to print |kNumAddressGUIDChars| hexadecimal characters, + // left-padded with 0's. + const std::string kAddressGUIDFormat = + base::StringPrintf("%%0%" PRIuS "X", kNumAddressGUIDChars); + guid += base::StringPrintf(kAddressGUIDFormat.c_str(), i); + DCHECK_EQ(kGUIDLength, guid.size()); + + scoped_ptr<AutofillProfile> profile(new AutofillProfile(guid)); + DCHECK(base::IsValidGUID(profile->guid())); + + // Fill in name and company information. + GetAddressBookNames(me, addressLabelRaw, profile.get()); + + // Fill in address information. + GetAddressBookAddress(address, profile.get()); + + // Fill in email information. + GetAddressBookEmail(me, addressLabelRaw, profile.get()); + + // Fill in phone number information. + GetAddressBookPhoneNumbers(me, addressLabelRaw, profile.get()); + + profiles_.push_back(profile.release()); + } +} + +// Name and company information is stored once in the Address Book against +// multiple addresses. We replicate that information for each profile. +// We only propagate the company name to work profiles. +void AuxiliaryProfilesImpl::GetAddressBookNames( + ABPerson* me, + NSString* addressLabelRaw, + AutofillProfile* profile) { + NSString* firstName = [me valueForProperty:kABFirstNameProperty]; + NSString* middleName = [me valueForProperty:kABMiddleNameProperty]; + NSString* lastName = [me valueForProperty:kABLastNameProperty]; + NSString* companyName = [me valueForProperty:kABOrganizationProperty]; + + profile->SetRawInfo(NAME_FIRST, base::SysNSStringToUTF16(firstName)); + profile->SetRawInfo(NAME_MIDDLE, base::SysNSStringToUTF16(middleName)); + profile->SetRawInfo(NAME_LAST, base::SysNSStringToUTF16(lastName)); + if ([addressLabelRaw isEqualToString:kABAddressWorkLabel]) + profile->SetRawInfo(COMPANY_NAME, base::SysNSStringToUTF16(companyName)); +} + +// Addresss information from the Address Book may span multiple lines. +// If it does then we represent the address with two lines in the profile. The +// second line we join with commas. +// For example: "c/o John Doe\n1122 Other Avenue\nApt #7" translates to +// line 1: "c/o John Doe", line 2: "1122 Other Avenue, Apt #7". +void AuxiliaryProfilesImpl::GetAddressBookAddress(NSDictionary* address, + AutofillProfile* profile) { + if (NSString* addressField = [address objectForKey:kABAddressStreetKey]) { + // If there are newlines in the address, split into two lines. + if ([addressField rangeOfCharacterFromSet: + [NSCharacterSet newlineCharacterSet]].location != NSNotFound) { + NSArray* chunks = [addressField componentsSeparatedByCharactersInSet: + [NSCharacterSet newlineCharacterSet]]; + DCHECK([chunks count] > 1); + + NSString* separator = l10n_util::GetNSString( + IDS_AUTOFILL_MAC_ADDRESS_LINE_SEPARATOR); + + NSString* addressField1 = [chunks objectAtIndex:0]; + NSString* addressField2 = + [[chunks subarrayWithRange:NSMakeRange(1, [chunks count] - 1)] + componentsJoinedByString:separator]; + profile->SetRawInfo(ADDRESS_HOME_LINE1, + base::SysNSStringToUTF16(addressField1)); + profile->SetRawInfo(ADDRESS_HOME_LINE2, + base::SysNSStringToUTF16(addressField2)); + } else { + profile->SetRawInfo(ADDRESS_HOME_LINE1, + base::SysNSStringToUTF16(addressField)); + } + } + + if (NSString* city = [address objectForKey:kABAddressCityKey]) + profile->SetRawInfo(ADDRESS_HOME_CITY, base::SysNSStringToUTF16(city)); + + if (NSString* state = [address objectForKey:kABAddressStateKey]) + profile->SetRawInfo(ADDRESS_HOME_STATE, base::SysNSStringToUTF16(state)); + + if (NSString* zip = [address objectForKey:kABAddressZIPKey]) + profile->SetRawInfo(ADDRESS_HOME_ZIP, base::SysNSStringToUTF16(zip)); + + if (NSString* country = [address objectForKey:kABAddressCountryKey]) { + profile->SetInfo(ADDRESS_HOME_COUNTRY, + base::SysNSStringToUTF16(country), + AutofillCountry::ApplicationLocale()); + } +} + +// Fills in email address matching current address label. Note that there may +// be multiple matching email addresses for a given label. We take the +// first we find (topmost) as preferred. +void AuxiliaryProfilesImpl::GetAddressBookEmail( + ABPerson* me, + NSString* addressLabelRaw, + AutofillProfile* profile) { + ABMultiValue* emailAddresses = [me valueForProperty:kABEmailProperty]; + NSString* emailAddress = nil; + for (NSUInteger j = 0, emailCount = [emailAddresses count]; + j < emailCount; j++) { + NSString* emailAddressLabelRaw = [emailAddresses labelAtIndex:j]; + if ([emailAddressLabelRaw isEqualToString:addressLabelRaw]) { + emailAddress = [emailAddresses valueAtIndex:j]; + break; + } + } + profile->SetRawInfo(EMAIL_ADDRESS, base::SysNSStringToUTF16(emailAddress)); +} + +// Fills in telephone numbers. Each of these are special cases. +// We match two cases: home/tel, work/tel. +// Note, we traverse in reverse order so that top values in address book +// take priority. +void AuxiliaryProfilesImpl::GetAddressBookPhoneNumbers( + ABPerson* me, + NSString* addressLabelRaw, + AutofillProfile* profile) { + ABMultiValue* phoneNumbers = [me valueForProperty:kABPhoneProperty]; + for (NSUInteger k = 0, phoneCount = [phoneNumbers count]; + k < phoneCount; k++) { + NSUInteger reverseK = phoneCount - k - 1; + NSString* phoneLabelRaw = [phoneNumbers labelAtIndex:reverseK]; + if ([addressLabelRaw isEqualToString:kABAddressHomeLabel] && + [phoneLabelRaw isEqualToString:kABPhoneHomeLabel]) { + string16 homePhone = base::SysNSStringToUTF16( + [phoneNumbers valueAtIndex:reverseK]); + profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, homePhone); + } else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] && + [phoneLabelRaw isEqualToString:kABPhoneWorkLabel]) { + string16 workPhone = base::SysNSStringToUTF16( + [phoneNumbers valueAtIndex:reverseK]); + profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, workPhone); + } else if ([phoneLabelRaw isEqualToString:kABPhoneMobileLabel] || + [phoneLabelRaw isEqualToString:kABPhoneMainLabel]) { + string16 phone = base::SysNSStringToUTF16( + [phoneNumbers valueAtIndex:reverseK]); + profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, phone); + } + } +} + +} // namespace + +// Populate |auxiliary_profiles_| with the Address Book data. +void PersonalDataManager::LoadAuxiliaryProfiles() { + AuxiliaryProfilesImpl impl(&auxiliary_profiles_); + impl.GetAddressBookMeCard(); +} diff --git a/components/autofill/browser/personal_data_manager_observer.h b/components/autofill/browser/personal_data_manager_observer.h new file mode 100644 index 0000000..f2becb6 --- /dev/null +++ b/components/autofill/browser/personal_data_manager_observer.h @@ -0,0 +1,21 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_PERSONAL_DATA_MANAGER_OBSERVER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_PERSONAL_DATA_MANAGER_OBSERVER_H_ + +// An interface the PersonalDataManager uses to notify its clients (observers) +// when it has finished loading personal data from the web database. Register +// observers via PersonalDataManager::AddObserver. +class PersonalDataManagerObserver { + public: + // Notifies the observer that the PersonalDataManager changed in some way. + virtual void OnPersonalDataChanged() = 0; + virtual void OnInsufficientFormData() {} + + protected: + virtual ~PersonalDataManagerObserver() {} +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_PERSONAL_DATA_MANAGER_OBSERVER_H_ diff --git a/components/autofill/browser/personal_data_manager_unittest.cc b/components/autofill/browser/personal_data_manager_unittest.cc new file mode 100644 index 0000000..db0146b --- /dev/null +++ b/components/autofill/browser/personal_data_manager_unittest.cc @@ -0,0 +1,1975 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include <string> + +#include "base/basictypes.h" +#include "base/guid.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/password_manager/encryptor.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "chrome/browser/webdata/web_data_service_factory.h" +#include "chrome/test/base/testing_browser_process.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/autofill_common_test.h" +#include "components/autofill/browser/autofill_metrics.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/form_structure.h" +#include "components/autofill/browser/personal_data_manager.h" +#include "components/autofill/browser/personal_data_manager_observer.h" +#include "components/autofill/common/form_data.h" +#include "content/public/browser/notification_details.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/notification_source.h" +#include "content/public/browser/notification_types.h" +#include "content/public/test/mock_notification_observer.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; + +namespace { + +ACTION(QuitUIMessageLoop) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + MessageLoop::current()->Quit(); +} + +class PersonalDataLoadedObserverMock : public PersonalDataManagerObserver { + public: + PersonalDataLoadedObserverMock() {} + virtual ~PersonalDataLoadedObserverMock() {} + + MOCK_METHOD0(OnPersonalDataChanged, void()); +}; + +// Unlike the base AutofillMetrics, exposes copy and assignment constructors, +// which are handy for briefer test code. The AutofillMetrics class is +// stateless, so this is safe. +class TestAutofillMetrics : public AutofillMetrics { + public: + TestAutofillMetrics() {} + virtual ~TestAutofillMetrics() {} +}; + +} // anonymous namespace + +class PersonalDataManagerTest : public testing::Test { + protected: + PersonalDataManagerTest() + : ui_thread_(BrowserThread::UI, &message_loop_), + db_thread_(BrowserThread::DB) { + } + + virtual void SetUp() { + db_thread_.Start(); + + profile_.reset(new TestingProfile); + profile_->CreateWebDatabaseService(); + profile_->CreateWebDataService(); + + autofill_test::DisableSystemServices(profile_.get()); + ResetPersonalDataManager(); + } + + virtual void TearDown() { + // Destruction order is imposed explicitly here. + personal_data_.reset(NULL); + profile_.reset(NULL); + + db_thread_.Stop(); + MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure()); + MessageLoop::current()->Run(); + } + + void ResetPersonalDataManager() { + personal_data_.reset(new PersonalDataManager); + personal_data_->Init(profile_.get()); + personal_data_->AddObserver(&personal_data_observer_); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + } + + MessageLoopForUI message_loop_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread db_thread_; + scoped_ptr<TestingProfile> profile_; + scoped_ptr<PersonalDataManager> personal_data_; + content::NotificationRegistrar registrar_; + content::MockNotificationObserver observer_; + PersonalDataLoadedObserverMock personal_data_observer_; +}; + +TEST_F(PersonalDataManagerTest, AddProfile) { + AutofillProfile profile0; + autofill_test::SetProfileInfo(&profile0, + "John", "Mitchell", "Smith", + "j@s.com", "Acme Inc.", "1 Main", "Apt A", "San Francisco", "CA", + "94102", "US", "4158889999"); + + // Add profile0 to the database. + personal_data_->AddProfile(profile0); + + // Reload the database. + ResetPersonalDataManager(); + + // Verify the addition. + const std::vector<AutofillProfile*>& results1 = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results1.size()); + EXPECT_EQ(0, profile0.Compare(*results1[0])); + + // Add profile with identical values. Duplicates should not get saved. + AutofillProfile profile0a = profile0; + profile0a.set_guid(base::GenerateGUID()); + personal_data_->AddProfile(profile0a); + + // Reload the database. + ResetPersonalDataManager(); + + // Verify the non-addition. + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, profile0.Compare(*results2[0])); + + // New profile with different email. + AutofillProfile profile1 = profile0; + profile1.set_guid(base::GenerateGUID()); + profile1.SetRawInfo(EMAIL_ADDRESS, ASCIIToUTF16("john@smith.com")); + + // Add the different profile. This should save as a separate profile. + // Note that if this same profile was "merged" it would collapse to one + // profile with a multi-valued entry for email. + personal_data_->AddProfile(profile1); + + // Reload the database. + ResetPersonalDataManager(); + + // Verify the addition. + const std::vector<AutofillProfile*>& results3 = personal_data_->GetProfiles(); + ASSERT_EQ(2U, results3.size()); + EXPECT_EQ(0, profile0.Compare(*results3[0])); + EXPECT_EQ(0, profile1.Compare(*results3[1])); +} + +TEST_F(PersonalDataManagerTest, AddUpdateRemoveProfiles) { + AutofillProfile profile0; + autofill_test::SetProfileInfo(&profile0, + "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910"); + + AutofillProfile profile1; + autofill_test::SetProfileInfo(&profile1, + "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "903 Apple Ct.", NULL, "Orlando", "FL", "32801", + "US", "19482937549"); + + AutofillProfile profile2; + autofill_test::SetProfileInfo(&profile2, + "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549"); + + // Add two test profiles to the database. + personal_data_->AddProfile(profile0); + personal_data_->AddProfile(profile1); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results1 = personal_data_->GetProfiles(); + ASSERT_EQ(2U, results1.size()); + EXPECT_EQ(0, profile0.Compare(*results1[0])); + EXPECT_EQ(0, profile1.Compare(*results1[1])); + + // Update, remove, and add. + profile0.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John")); + personal_data_->UpdateProfile(profile0); + personal_data_->RemoveByGUID(profile1.guid()); + personal_data_->AddProfile(profile2); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + ASSERT_EQ(2U, results2.size()); + EXPECT_EQ(0, profile0.Compare(*results2[0])); + EXPECT_EQ(0, profile2.Compare(*results2[1])); + + // Reset the PersonalDataManager. This tests that the personal data was saved + // to the web database, and that we can load the profiles from the web + // database. + ResetPersonalDataManager(); + + // Verify that we've loaded the profiles from the web database. + const std::vector<AutofillProfile*>& results3 = personal_data_->GetProfiles(); + ASSERT_EQ(2U, results3.size()); + EXPECT_EQ(0, profile0.Compare(*results3[0])); + EXPECT_EQ(0, profile2.Compare(*results3[1])); +} + +TEST_F(PersonalDataManagerTest, AddUpdateRemoveCreditCards) { + CreditCard credit_card0; + autofill_test::SetCreditCardInfo(&credit_card0, + "John Dillinger", "423456789012" /* Visa */, "01", "2010"); + + CreditCard credit_card1; + autofill_test::SetCreditCardInfo(&credit_card1, + "Bonnie Parker", "518765432109" /* Mastercard */, "12", "2012"); + + CreditCard credit_card2; + autofill_test::SetCreditCardInfo(&credit_card2, + "Clyde Barrow", "347666888555" /* American Express */, "04", "2015"); + + // Add two test credit cards to the database. + personal_data_->AddCreditCard(credit_card0); + personal_data_->AddCreditCard(credit_card1); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<CreditCard*>& results1 = personal_data_->credit_cards(); + ASSERT_EQ(2U, results1.size()); + EXPECT_EQ(0, credit_card0.Compare(*results1[0])); + EXPECT_EQ(0, credit_card1.Compare(*results1[1])); + + // Update, remove, and add. + credit_card0.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Joe")); + personal_data_->UpdateCreditCard(credit_card0); + personal_data_->RemoveByGUID(credit_card1.guid()); + personal_data_->AddCreditCard(credit_card2); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<CreditCard*>& results2 = personal_data_->credit_cards(); + ASSERT_EQ(2U, results2.size()); + EXPECT_EQ(credit_card0, *results2[0]); + EXPECT_EQ(credit_card2, *results2[1]); + + // Reset the PersonalDataManager. This tests that the personal data was saved + // to the web database, and that we can load the credit cards from the web + // database. + ResetPersonalDataManager(); + + // Verify that we've loaded the credit cards from the web database. + const std::vector<CreditCard*>& results3 = personal_data_->credit_cards(); + ASSERT_EQ(2U, results3.size()); + EXPECT_EQ(credit_card0, *results3[0]); + EXPECT_EQ(credit_card2, *results3[1]); +} + +TEST_F(PersonalDataManagerTest, AddProfilesAndCreditCards) { + AutofillProfile profile0; + autofill_test::SetProfileInfo(&profile0, + "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910"); + + AutofillProfile profile1; + autofill_test::SetProfileInfo(&profile1, + "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "903 Apple Ct.", NULL, "Orlando", "FL", "32801", + "US", "19482937549"); + + CreditCard credit_card0; + autofill_test::SetCreditCardInfo(&credit_card0, + "John Dillinger", "423456789012" /* Visa */, "01", "2010"); + + CreditCard credit_card1; + autofill_test::SetCreditCardInfo(&credit_card1, + "Bonnie Parker", "518765432109" /* Mastercard */, "12", "2012"); + + // Add two test profiles to the database. + personal_data_->AddProfile(profile0); + personal_data_->AddProfile(profile1); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results1 = personal_data_->GetProfiles(); + ASSERT_EQ(2U, results1.size()); + EXPECT_EQ(0, profile0.Compare(*results1[0])); + EXPECT_EQ(0, profile1.Compare(*results1[1])); + + // Add two test credit cards to the database. + personal_data_->AddCreditCard(credit_card0); + personal_data_->AddCreditCard(credit_card1); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<CreditCard*>& results2 = personal_data_->credit_cards(); + ASSERT_EQ(2U, results2.size()); + EXPECT_EQ(credit_card0, *results2[0]); + EXPECT_EQ(credit_card1, *results2[1]); + + // Determine uniqueness by inserting all of the GUIDs into a set and verifying + // the size of the set matches the number of GUIDs. + std::set<std::string> guids; + guids.insert(profile0.guid()); + guids.insert(profile1.guid()); + guids.insert(credit_card0.guid()); + guids.insert(credit_card1.guid()); + EXPECT_EQ(4U, guids.size()); +} + +// Test for http://crbug.com/50047. Makes sure that guids are populated +// correctly on load. +TEST_F(PersonalDataManagerTest, PopulateUniqueIDsOnLoad) { + AutofillProfile profile0; + autofill_test::SetProfileInfo(&profile0, + "y", "", "", "", "", "", "", "", "", "", "", ""); + + // Add the profile0 to the db. + personal_data_->AddProfile(profile0); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + // Verify that we've loaded the profiles from the web database. + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, profile0.Compare(*results2[0])); + + // Add a new profile. + AutofillProfile profile1; + autofill_test::SetProfileInfo(&profile1, + "z", "", "", "", "", "", "", "", "", "", "", ""); + personal_data_->AddProfile(profile1); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + // Make sure the two profiles have different GUIDs, both valid. + const std::vector<AutofillProfile*>& results3 = personal_data_->GetProfiles(); + ASSERT_EQ(2U, results3.size()); + EXPECT_NE(results3[0]->guid(), results3[1]->guid()); + EXPECT_TRUE(base::IsValidGUID(results3[0]->guid())); + EXPECT_TRUE(base::IsValidGUID(results3[1]->guid())); +} + +TEST_F(PersonalDataManagerTest, SetEmptyProfile) { + AutofillProfile profile0; + autofill_test::SetProfileInfo(&profile0, + "", "", "", "", "", "", "", "", "", "", "", ""); + + // Add the empty profile to the database. + personal_data_->AddProfile(profile0); + + // Note: no refresh here. + + // Reset the PersonalDataManager. This tests that the personal data was saved + // to the web database, and that we can load the profiles from the web + // database. + ResetPersonalDataManager(); + + // Verify that we've loaded the profiles from the web database. + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + ASSERT_EQ(0U, results2.size()); +} + +TEST_F(PersonalDataManagerTest, SetEmptyCreditCard) { + CreditCard credit_card0; + autofill_test::SetCreditCardInfo(&credit_card0, "", "", "", ""); + + // Add the empty credit card to the database. + personal_data_->AddCreditCard(credit_card0); + + // Note: no refresh here. + + // Reset the PersonalDataManager. This tests that the personal data was saved + // to the web database, and that we can load the credit cards from the web + // database. + ResetPersonalDataManager(); + + // Verify that we've loaded the credit cards from the web database. + const std::vector<CreditCard*>& results2 = personal_data_->credit_cards(); + ASSERT_EQ(0U, results2.size()); +} + +TEST_F(PersonalDataManagerTest, Refresh) { + AutofillProfile profile0; + autofill_test::SetProfileInfo(&profile0, + "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910"); + + AutofillProfile profile1; + autofill_test::SetProfileInfo(&profile1, + "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "903 Apple Ct.", NULL, "Orlando", "FL", "32801", + "US", "19482937549"); + + // Add the test profiles to the database. + personal_data_->AddProfile(profile0); + personal_data_->AddProfile(profile1); + + // Labels depend on other profiles in the list - update labels manually. + std::vector<AutofillProfile *> profile_pointers; + profile_pointers.push_back(&profile0); + profile_pointers.push_back(&profile1); + AutofillProfile::AdjustInferredLabels(&profile_pointers); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results1 = personal_data_->GetProfiles(); + ASSERT_EQ(2U, results1.size()); + EXPECT_EQ(profile0, *results1[0]); + EXPECT_EQ(profile1, *results1[1]); + + AutofillProfile profile2; + autofill_test::SetProfileInfo(&profile2, + "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549"); + + // Adjust all labels. + profile_pointers.push_back(&profile2); + AutofillProfile::AdjustInferredLabels(&profile_pointers); + + scoped_refptr<WebDataService> wds = WebDataServiceFactory::GetForProfile( + profile_.get(), Profile::EXPLICIT_ACCESS); + ASSERT_TRUE(wds.get()); + wds->AddAutofillProfile(profile2); + + personal_data_->Refresh(); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + ASSERT_EQ(3U, results2.size()); + EXPECT_EQ(profile0, *results2[0]); + EXPECT_EQ(profile1, *results2[1]); + EXPECT_EQ(profile2, *results2[2]); + + wds->RemoveAutofillProfile(profile1.guid()); + wds->RemoveAutofillProfile(profile2.guid()); + + // Before telling the PDM to refresh, simulate an edit to one of the profiles + // via a SetProfile update (this would happen if the Autofill window was + // open with a previous snapshot of the profiles, and something [e.g. sync] + // removed a profile from the browser. In this edge case, we will end up + // in a consistent state by dropping the write). + profile2.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Jo")); + personal_data_->UpdateProfile(profile0); + personal_data_->AddProfile(profile1); + personal_data_->AddProfile(profile2); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results3 = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results3.size()); + EXPECT_EQ(profile0, *results2[0]); +} + +TEST_F(PersonalDataManagerTest, ImportFormData) { + FormData form; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form.fields.push_back(field); + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + AutofillProfile expected; + autofill_test::SetProfileInfo(&expected, "George", NULL, + "Washington", "theprez@gmail.com", NULL, "21 Laussat St", NULL, + "San Francisco", "California", "94102", NULL, NULL); + const std::vector<AutofillProfile*>& results = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); +} + +TEST_F(PersonalDataManagerTest, ImportFormDataBadEmail) { + FormData form; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "bogus", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form.fields.push_back(field); + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_FALSE(personal_data_->ImportFormData(form_structure, + &imported_credit_card)); + ASSERT_EQ(static_cast<CreditCard*>(NULL), imported_credit_card); + + const std::vector<AutofillProfile*>& results = personal_data_->GetProfiles(); + ASSERT_EQ(0U, results.size()); +} + +TEST_F(PersonalDataManagerTest, ImportFormDataNotEnoughFilledFields) { + FormData form; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card number:", "card_number", "4111 1111 1111 1111", "text", &field); + form.fields.push_back(field); + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_FALSE(personal_data_->ImportFormData(form_structure, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + const std::vector<AutofillProfile*>& profiles = personal_data_->GetProfiles(); + ASSERT_EQ(0U, profiles.size()); + const std::vector<CreditCard*>& credit_cards = personal_data_->credit_cards(); + ASSERT_EQ(0U, credit_cards.size()); +} + +TEST_F(PersonalDataManagerTest, ImportPhoneNumberSplitAcrossMultipleFields) { + FormData form; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Phone #:", "home_phone_area_code", "650", "text", &field); + field.max_length = 3; + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Phone #:", "home_phone_prefix", "555", "text", &field); + field.max_length = 3; + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Phone #:", "home_phone_suffix", "0000", "text", &field); + field.max_length = 4; + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form.fields.push_back(field); + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + AutofillProfile expected; + autofill_test::SetProfileInfo(&expected, "George", NULL, + "Washington", NULL, NULL, "21 Laussat St", NULL, + "San Francisco", "California", "94102", NULL, "(650) 555-0000"); + const std::vector<AutofillProfile*>& results = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); +} + +TEST_F(PersonalDataManagerTest, SetUniqueCreditCardLabels) { + CreditCard credit_card0; + credit_card0.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("John")); + CreditCard credit_card1; + credit_card1.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Paul")); + CreditCard credit_card2; + credit_card2.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Ringo")); + CreditCard credit_card3; + credit_card3.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Other")); + CreditCard credit_card4; + credit_card4.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Ozzy")); + CreditCard credit_card5; + credit_card5.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Dio")); + + // Add the test credit cards to the database. + personal_data_->AddCreditCard(credit_card0); + personal_data_->AddCreditCard(credit_card1); + personal_data_->AddCreditCard(credit_card2); + personal_data_->AddCreditCard(credit_card3); + personal_data_->AddCreditCard(credit_card4); + personal_data_->AddCreditCard(credit_card5); + + // Reset the PersonalDataManager. This tests that the personal data was saved + // to the web database, and that we can load the credit cards from the web + // database. + ResetPersonalDataManager(); + + const std::vector<CreditCard*>& results = personal_data_->credit_cards(); + ASSERT_EQ(6U, results.size()); + EXPECT_EQ(credit_card0.guid(), results[0]->guid()); + EXPECT_EQ(credit_card1.guid(), results[1]->guid()); + EXPECT_EQ(credit_card2.guid(), results[2]->guid()); + EXPECT_EQ(credit_card3.guid(), results[3]->guid()); + EXPECT_EQ(credit_card4.guid(), results[4]->guid()); + EXPECT_EQ(credit_card5.guid(), results[5]->guid()); +} + +TEST_F(PersonalDataManagerTest, AggregateTwoDifferentProfiles) { + FormData form1; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + AutofillProfile expected; + autofill_test::SetProfileInfo(&expected, "George", NULL, + "Washington", "theprez@gmail.com", NULL, "21 Laussat St", NULL, + "San Francisco", "California", "94102", NULL, NULL); + const std::vector<AutofillProfile*>& results1 = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results1.size()); + EXPECT_EQ(0, expected.Compare(*results1[0])); + + // Now create a completely different profile. + FormData form2; + autofill_test::CreateTestFormField( + "First name:", "first_name", "John", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Adams", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "second@gmail.com", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address1", "22 Laussat St", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + AutofillProfile expected2; + autofill_test::SetProfileInfo(&expected2, "John", NULL, + "Adams", "second@gmail.com", NULL, "22 Laussat St", NULL, + "San Francisco", "California", "94102", NULL, NULL); + ASSERT_EQ(2U, results2.size()); + EXPECT_EQ(0, expected.Compare(*results2[0])); + EXPECT_EQ(0, expected2.Compare(*results2[1])); +} + +TEST_F(PersonalDataManagerTest, AggregateTwoProfilesWithMultiValue) { + FormData form1; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + AutofillProfile expected; + autofill_test::SetProfileInfo(&expected, "George", NULL, + "Washington", "theprez@gmail.com", NULL, "21 Laussat St", NULL, + "San Francisco", "California", "94102", NULL, NULL); + const std::vector<AutofillProfile*>& results1 = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results1.size()); + EXPECT_EQ(0, expected.Compare(*results1[0])); + + // Now create a completely different profile. + FormData form2; + autofill_test::CreateTestFormField( + "First name:", "first_name", "John", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Adams", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "second@gmail.com", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + // Modify expected to include multi-valued fields. + std::vector<string16> values; + expected.GetRawMultiInfo(NAME_FULL, &values); + values.push_back(ASCIIToUTF16("John Adams")); + expected.SetRawMultiInfo(NAME_FULL, values); + expected.GetRawMultiInfo(EMAIL_ADDRESS, &values); + values.push_back(ASCIIToUTF16("second@gmail.com")); + expected.SetRawMultiInfo(EMAIL_ADDRESS, values); + + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected.Compare(*results2[0])); +} + +TEST_F(PersonalDataManagerTest, AggregateSameProfileWithConflict) { + FormData form1; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address", "1600 Pennsylvania Avenue", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address Line 2:", "address2", "Suite A", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Phone:", "phone", "6505556666", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + AutofillProfile expected; + autofill_test::SetProfileInfo( + &expected, "George", NULL, "Washington", "theprez@gmail.com", NULL, + "1600 Pennsylvania Avenue", "Suite A", "San Francisco", "California", + "94102", NULL, "(650) 555-6666"); + const std::vector<AutofillProfile*>& results1 = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results1.size()); + EXPECT_EQ(0, expected.Compare(*results1[0])); + + // Now create an updated profile. + FormData form2; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address", "1600 Pennsylvania Avenue", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address Line 2:", "address2", "Suite A", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form2.fields.push_back(field); + // Country gets added. + autofill_test::CreateTestFormField( + "Country:", "country", "USA", "text", &field); + form2.fields.push_back(field); + // Phone gets updated. + autofill_test::CreateTestFormField( + "Phone:", "phone", "6502231234", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + // Add multi-valued phone number to expectation. Also, country gets added. + std::vector<string16> values; + expected.GetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, &values); + values.push_back(ASCIIToUTF16("(650) 223-1234")); + expected.SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, values); + expected.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected.Compare(*results2[0])); +} + +TEST_F(PersonalDataManagerTest, AggregateProfileWithMissingInfoInOld) { + FormData form1; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address Line 1:", "address", "190 High Street", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "Philadelphia", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "Pennsylvania", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zipcode", "19106", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + EXPECT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + AutofillProfile expected; + autofill_test::SetProfileInfo(&expected, "George", NULL, + "Washington", NULL, NULL, "190 High Street", NULL, + "Philadelphia", "Pennsylvania", "19106", NULL, NULL); + const std::vector<AutofillProfile*>& results1 = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results1.size()); + EXPECT_EQ(0, expected.Compare(*results1[0])); + + // Submit a form with new data for the first profile. + FormData form2; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address Line 1:", "address", "190 High Street", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "Philadelphia", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "Pennsylvania", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zipcode", "19106", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + AutofillProfile expected2; + autofill_test::SetProfileInfo(&expected2, "George", NULL, + "Washington", "theprez@gmail.com", NULL, "190 High Street", NULL, + "Philadelphia", "Pennsylvania", "19106", NULL, NULL); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected2.Compare(*results2[0])); +} + +TEST_F(PersonalDataManagerTest, AggregateProfileWithMissingInfoInNew) { + FormData form1; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Company:", "company", "Government", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address Line 1:", "address", "190 High Street", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "Philadelphia", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "Pennsylvania", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zipcode", "19106", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + AutofillProfile expected; + autofill_test::SetProfileInfo(&expected, "George", NULL, + "Washington", "theprez@gmail.com", "Government", "190 High Street", NULL, + "Philadelphia", "Pennsylvania", "19106", NULL, NULL); + const std::vector<AutofillProfile*>& results1 = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results1.size()); + EXPECT_EQ(0, expected.Compare(*results1[0])); + + // Submit a form with new data for the first profile. + FormData form2; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form2.fields.push_back(field); + // Note missing Company field. + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address Line 1:", "address", "190 High Street", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "Philadelphia", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "Pennsylvania", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zipcode", "19106", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + // Expect no change. + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected.Compare(*results2[0])); +} + +TEST_F(PersonalDataManagerTest, AggregateProfileWithInsufficientAddress) { + FormData form1; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Company:", "company", "Government", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address Line 1:", "address", "190 High Street", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "Philadelphia", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_FALSE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Note: no refresh here. + + const std::vector<AutofillProfile*>& profiles = personal_data_->GetProfiles(); + ASSERT_EQ(0U, profiles.size()); + const std::vector<CreditCard*>& credit_cards = personal_data_->credit_cards(); + ASSERT_EQ(0U, credit_cards.size()); +} + +TEST_F(PersonalDataManagerTest, AggregateExistingAuxiliaryProfile) { + // Simulate having access to an auxiliary profile. + // |auxiliary_profile| will be owned by |personal_data_|. + AutofillProfile* auxiliary_profile = new AutofillProfile; + autofill_test::SetProfileInfo(auxiliary_profile, + "Tester", "Frederick", "McAddressBookTesterson", + "tester@example.com", "Acme Inc.", "1 Main", "Apt A", "San Francisco", + "CA", "94102", "US", "1.415.888.9999"); + ScopedVector<AutofillProfile>& auxiliary_profiles = + personal_data_->auxiliary_profiles_; + auxiliary_profiles.push_back(auxiliary_profile); + + // Simulate a form submission with a subset of the info. + // Note that the phone number format is different from the saved format. + FormData form; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "Tester", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "McAddressBookTesterson", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "tester@example.com", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address1", "1 Main", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "CA", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Phone:", "phone", "4158889999", "text", &field); + form.fields.push_back(field); + + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure, + &imported_credit_card)); + EXPECT_FALSE(imported_credit_card); + + // Note: No refresh. + + // Expect no change. + const std::vector<AutofillProfile*>& web_profiles = + personal_data_->web_profiles(); + EXPECT_EQ(0U, web_profiles.size()); + ASSERT_EQ(1U, auxiliary_profiles.size()); + EXPECT_EQ(0, auxiliary_profile->Compare(*auxiliary_profiles[0])); +} + +TEST_F(PersonalDataManagerTest, AggregateTwoDifferentCreditCards) { + FormData form1; + + // Start with a single valid credit card form. + FormFieldData field; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "01", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2011", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_TRUE(imported_credit_card); + personal_data_->SaveImportedCreditCard(*imported_credit_card); + delete imported_credit_card; + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + CreditCard expected; + autofill_test::SetCreditCardInfo(&expected, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results = personal_data_->credit_cards(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); + + // Add a second different valid credit card. + FormData form2; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "5500 0000 0000 0004", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "02", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2012", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + ASSERT_TRUE(imported_credit_card); + personal_data_->SaveImportedCreditCard(*imported_credit_card); + delete imported_credit_card; + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + CreditCard expected2; + autofill_test::SetCreditCardInfo(&expected2, + "", "5500000000000004", "02", "2012"); + const std::vector<CreditCard*>& results2 = personal_data_->credit_cards(); + ASSERT_EQ(2U, results2.size()); + EXPECT_EQ(0, expected.Compare(*results2[0])); + EXPECT_EQ(0, expected2.Compare(*results2[1])); +} + +TEST_F(PersonalDataManagerTest, AggregateInvalidCreditCard) { + FormData form1; + + // Start with a single valid credit card form. + FormFieldData field; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "01", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2011", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_TRUE(imported_credit_card); + personal_data_->SaveImportedCreditCard(*imported_credit_card); + delete imported_credit_card; + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + CreditCard expected; + autofill_test::SetCreditCardInfo(&expected, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results = personal_data_->credit_cards(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); + + // Add a second different invalid credit card. + FormData form2; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Jim Johansen", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "1000000000000000", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "02", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2012", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_FALSE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Note: no refresh here. + + const std::vector<CreditCard*>& results2 = personal_data_->credit_cards(); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected.Compare(*results2[0])); +} + +TEST_F(PersonalDataManagerTest, AggregateSameCreditCardWithConflict) { + FormData form1; + + // Start with a single valid credit card form. + FormFieldData field; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "01", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2011", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_TRUE(imported_credit_card); + personal_data_->SaveImportedCreditCard(*imported_credit_card); + delete imported_credit_card; + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + CreditCard expected; + autofill_test::SetCreditCardInfo(&expected, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results = personal_data_->credit_cards(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); + + // Add a second different valid credit card where the year is different but + // the credit card number matches. + FormData form2; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "4111 1111 1111 1111", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "01", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2012", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + EXPECT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + // Expect that the newer information is saved. In this case the year is + // updated to "2012". + CreditCard expected2; + autofill_test::SetCreditCardInfo(&expected2, + "Biggie Smalls", "4111111111111111", "01", "2012"); + const std::vector<CreditCard*>& results2 = personal_data_->credit_cards(); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected2.Compare(*results2[0])); +} + +TEST_F(PersonalDataManagerTest, AggregateEmptyCreditCardWithConflict) { + FormData form1; + + // Start with a single valid credit card form. + FormFieldData field; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "01", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2011", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_TRUE(imported_credit_card); + personal_data_->SaveImportedCreditCard(*imported_credit_card); + delete imported_credit_card; + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + CreditCard expected; + autofill_test::SetCreditCardInfo(&expected, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results = personal_data_->credit_cards(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); + + // Add a second credit card with no number. + FormData form2; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "01", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2012", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_FALSE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + EXPECT_FALSE(imported_credit_card); + + // Note: no refresh here. + + // No change is expected. + CreditCard expected2; + autofill_test::SetCreditCardInfo(&expected2, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results2 = personal_data_->credit_cards(); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected2.Compare(*results2[0])); +} + +TEST_F(PersonalDataManagerTest, AggregateCreditCardWithMissingInfoInNew) { + FormData form1; + + // Start with a single valid credit card form. + FormFieldData field; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "01", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2011", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_TRUE(imported_credit_card); + personal_data_->SaveImportedCreditCard(*imported_credit_card); + delete imported_credit_card; + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + CreditCard expected; + autofill_test::SetCreditCardInfo(&expected, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results = personal_data_->credit_cards(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); + + // Add a second different valid credit card where the name is missing but + // the credit card number matches. + FormData form2; + // Note missing name. + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "4111111111111111", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "01", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2011", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + EXPECT_FALSE(imported_credit_card); + + // Wait for the refresh, which in this case is a no-op. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + // No change is expected. + CreditCard expected2; + autofill_test::SetCreditCardInfo(&expected2, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results2 = personal_data_->credit_cards(); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected2.Compare(*results2[0])); + + // Add a third credit card where the expiration date is missing. + FormData form3; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Johnny McEnroe", "text", &field); + form3.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "5555555555554444", "text", &field); + form3.fields.push_back(field); + // Note missing expiration month and year.. + + FormStructure form_structure3(form3, std::string()); + form_structure3.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_FALSE(personal_data_->ImportFormData(form_structure3, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Note: no refresh here. + + // No change is expected. + CreditCard expected3; + autofill_test::SetCreditCardInfo(&expected3, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results3 = personal_data_->credit_cards(); + ASSERT_EQ(1U, results3.size()); + EXPECT_EQ(0, expected3.Compare(*results2[0])); +} + +TEST_F(PersonalDataManagerTest, AggregateCreditCardWithMissingInfoInOld) { + // Start with a single valid credit card stored via the preferences. + // Note the empty name. + CreditCard saved_credit_card; + autofill_test::SetCreditCardInfo(&saved_credit_card, + "", "4111111111111111" /* Visa */, "01", "2011"); + personal_data_->AddCreditCard(saved_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<CreditCard*>& results1 = personal_data_->credit_cards(); + ASSERT_EQ(1U, results1.size()); + EXPECT_EQ(saved_credit_card, *results1[0]); + + + // Add a second different valid credit card where the year is different but + // the credit card number matches. + FormData form; + FormFieldData field; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "01", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2012", "text", &field); + form.fields.push_back(field); + + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure, + &imported_credit_card)); + EXPECT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + // Expect that the newer information is saved. In this case the year is + // added to the existing credit card. + CreditCard expected2; + autofill_test::SetCreditCardInfo(&expected2, + "Biggie Smalls", "4111111111111111", "01", "2012"); + const std::vector<CreditCard*>& results2 = personal_data_->credit_cards(); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected2.Compare(*results2[0])); +} + +// We allow the user to store a credit card number with separators via the UI. +// We should not try to re-aggregate the same card with the separators stripped. +TEST_F(PersonalDataManagerTest, AggregateSameCreditCardWithSeparators) { + // Start with a single valid credit card stored via the preferences. + // Note the separators in the credit card number. + CreditCard saved_credit_card; + autofill_test::SetCreditCardInfo(&saved_credit_card, + "Biggie Smalls", "4111 1111 1111 1111" /* Visa */, "01", "2011"); + personal_data_->AddCreditCard(saved_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<CreditCard*>& results1 = personal_data_->credit_cards(); + ASSERT_EQ(1U, results1.size()); + EXPECT_EQ(0, saved_credit_card.Compare(*results1[0])); + + // Import the same card info, but with different separators in the number. + FormData form; + FormFieldData field; + autofill_test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Month:", "exp_month", "01", "text", &field); + form.fields.push_back(field); + autofill_test::CreateTestFormField( + "Exp Year:", "exp_year", "2011", "text", &field); + form.fields.push_back(field); + + FormStructure form_structure(form, std::string()); + form_structure.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure, + &imported_credit_card)); + EXPECT_FALSE(imported_credit_card); + + // Wait for the refresh, which in this case is a no-op. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + // Expect that no new card is saved. + const std::vector<CreditCard*>& results2 = personal_data_->credit_cards(); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, saved_credit_card.Compare(*results2[0])); +} + +TEST_F(PersonalDataManagerTest, GetNonEmptyTypes) { + // Check that there are no available types with no profiles stored. + FieldTypeSet non_empty_types; + personal_data_->GetNonEmptyTypes(&non_empty_types); + EXPECT_EQ(0U, non_empty_types.size()); + + // Test with one profile stored. + AutofillProfile profile0; + autofill_test::SetProfileInfo(&profile0, + "Marion", NULL, "Morrison", + "johnwayne@me.xyz", NULL, "123 Zoo St.", NULL, "Hollywood", "CA", + "91601", "US", "14155678910"); + + personal_data_->AddProfile(profile0); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + personal_data_->GetNonEmptyTypes(&non_empty_types); + EXPECT_EQ(14U, non_empty_types.size()); + EXPECT_TRUE(non_empty_types.count(NAME_FIRST)); + EXPECT_TRUE(non_empty_types.count(NAME_LAST)); + EXPECT_TRUE(non_empty_types.count(NAME_FULL)); + EXPECT_TRUE(non_empty_types.count(EMAIL_ADDRESS)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_LINE1)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_CITY)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_STATE)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_ZIP)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_COUNTRY)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_NUMBER)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_COUNTRY_CODE)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_CITY_CODE)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_CITY_AND_NUMBER)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_WHOLE_NUMBER)); + + // Test with multiple profiles stored. + AutofillProfile profile1; + autofill_test::SetProfileInfo(&profile1, + "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "903 Apple Ct.", NULL, "Orlando", "FL", "32801", + "US", "16502937549"); + + AutofillProfile profile2; + autofill_test::SetProfileInfo(&profile2, + "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "16502937549"); + + personal_data_->AddProfile(profile1); + personal_data_->AddProfile(profile2); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + personal_data_->GetNonEmptyTypes(&non_empty_types); + EXPECT_EQ(18U, non_empty_types.size()); + EXPECT_TRUE(non_empty_types.count(NAME_FIRST)); + EXPECT_TRUE(non_empty_types.count(NAME_MIDDLE)); + EXPECT_TRUE(non_empty_types.count(NAME_MIDDLE_INITIAL)); + EXPECT_TRUE(non_empty_types.count(NAME_LAST)); + EXPECT_TRUE(non_empty_types.count(NAME_FULL)); + EXPECT_TRUE(non_empty_types.count(EMAIL_ADDRESS)); + EXPECT_TRUE(non_empty_types.count(COMPANY_NAME)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_LINE1)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_LINE2)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_CITY)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_STATE)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_ZIP)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_COUNTRY)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_NUMBER)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_CITY_CODE)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_COUNTRY_CODE)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_CITY_AND_NUMBER)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_WHOLE_NUMBER)); + + // Test with credit card information also stored. + CreditCard credit_card; + autofill_test::SetCreditCardInfo(&credit_card, + "John Dillinger", "423456789012" /* Visa */, + "01", "2010"); + personal_data_->AddCreditCard(credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + personal_data_->GetNonEmptyTypes(&non_empty_types); + EXPECT_EQ(25U, non_empty_types.size()); + EXPECT_TRUE(non_empty_types.count(NAME_FIRST)); + EXPECT_TRUE(non_empty_types.count(NAME_MIDDLE)); + EXPECT_TRUE(non_empty_types.count(NAME_MIDDLE_INITIAL)); + EXPECT_TRUE(non_empty_types.count(NAME_LAST)); + EXPECT_TRUE(non_empty_types.count(NAME_FULL)); + EXPECT_TRUE(non_empty_types.count(EMAIL_ADDRESS)); + EXPECT_TRUE(non_empty_types.count(COMPANY_NAME)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_LINE1)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_LINE2)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_CITY)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_STATE)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_ZIP)); + EXPECT_TRUE(non_empty_types.count(ADDRESS_HOME_COUNTRY)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_NUMBER)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_CITY_CODE)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_COUNTRY_CODE)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_CITY_AND_NUMBER)); + EXPECT_TRUE(non_empty_types.count(PHONE_HOME_WHOLE_NUMBER)); + EXPECT_TRUE(non_empty_types.count(CREDIT_CARD_NAME)); + EXPECT_TRUE(non_empty_types.count(CREDIT_CARD_NUMBER)); + EXPECT_TRUE(non_empty_types.count(CREDIT_CARD_EXP_MONTH)); + EXPECT_TRUE(non_empty_types.count(CREDIT_CARD_EXP_2_DIGIT_YEAR)); + EXPECT_TRUE(non_empty_types.count(CREDIT_CARD_EXP_4_DIGIT_YEAR)); + EXPECT_TRUE(non_empty_types.count(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR)); + EXPECT_TRUE(non_empty_types.count(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR)); +} + +TEST_F(PersonalDataManagerTest, CaseInsensitiveMultiValueAggregation) { + FormData form1; + FormFieldData field; + autofill_test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form1.fields.push_back(field); + autofill_test::CreateTestFormField( + "Phone number:", "phone_number", "817-555-6789", "text", &field); + form1.fields.push_back(field); + + FormStructure form_structure1(form1, std::string()); + form_structure1.DetermineHeuristicTypes(TestAutofillMetrics()); + const CreditCard* imported_credit_card; + EXPECT_TRUE(personal_data_->ImportFormData(form_structure1, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + AutofillProfile expected; + autofill_test::SetProfileInfo(&expected, "George", NULL, + "Washington", "theprez@gmail.com", NULL, "21 Laussat St", NULL, + "San Francisco", "California", "94102", NULL, "(817) 555-6789"); + const std::vector<AutofillProfile*>& results1 = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results1.size()); + EXPECT_EQ(0, expected.Compare(*results1[0])); + + // Upper-case the first name and change the phone number. + FormData form2; + autofill_test::CreateTestFormField( + "First name:", "first_name", "GEORGE", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "State:", "state", "California", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form2.fields.push_back(field); + autofill_test::CreateTestFormField( + "Phone number:", "phone_number", "214-555-1234", "text", &field); + form2.fields.push_back(field); + + FormStructure form_structure2(form2, std::string()); + form_structure2.DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(personal_data_->ImportFormData(form_structure2, + &imported_credit_card)); + ASSERT_FALSE(imported_credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + // Modify expected to include multi-valued fields. + std::vector<string16> values; + expected.GetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, &values); + values.push_back(ASCIIToUTF16("(214) 555-1234")); + expected.SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, values); + + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, expected.Compare(*results2[0])); +} diff --git a/components/autofill/browser/phone_field.cc b/components/autofill/browser/phone_field.cc new file mode 100644 index 0000000..e7415ba --- /dev/null +++ b/components/autofill/browser/phone_field.cc @@ -0,0 +1,273 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/phone_field.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_regex_constants.h" +#include "components/autofill/browser/autofill_scanner.h" +#include "ui/base/l10n/l10n_util.h" + +namespace { + +// This string includes all area code separators, including NoText. +string16 GetAreaRegex() { + string16 area_code = UTF8ToUTF16(autofill::kAreaCodeRe); + area_code.append(ASCIIToUTF16("|")); // Regexp separator. + area_code.append(UTF8ToUTF16(autofill::kAreaCodeNotextRe)); + return area_code; +} + +} // namespace + +PhoneField::~PhoneField() {} + +// Phone field grammars - first matched grammar will be parsed. Grammars are +// separated by { REGEX_SEPARATOR, FIELD_NONE, 0 }. Suffix and extension are +// parsed separately unless they are necessary parts of the match. +// The following notation is used to describe the patterns: +// <cc> - country code field. +// <ac> - area code field. +// <phone> - phone or prefix. +// <suffix> - suffix. +// <ext> - extension. +// :N means field is limited to N characters, otherwise it is unlimited. +// (pattern <field>)? means pattern is optional and matched separately. +const PhoneField::Parser PhoneField::kPhoneFieldGrammars[] = { + // Country code: <cc> Area Code: <ac> Phone: <phone> (- <suffix> + // (Ext: <ext>)?)? + { REGEX_COUNTRY, FIELD_COUNTRY_CODE, 0 }, + { REGEX_AREA, FIELD_AREA_CODE, 0 }, + { REGEX_PHONE, FIELD_PHONE, 0 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // \( <ac> \) <phone>:3 <suffix>:4 (Ext: <ext>)? + { REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 3 }, + { REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3 }, + { REGEX_PHONE, FIELD_SUFFIX, 4 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <cc> <ac>:3 - <phone>:3 - <suffix>:4 (Ext: <ext>)? + { REGEX_PHONE, FIELD_COUNTRY_CODE, 0 }, + { REGEX_PHONE, FIELD_AREA_CODE, 3 }, + { REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3 }, + { REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 4 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <cc>:3 <ac>:3 <phone>:3 <suffix>:4 (Ext: <ext>)? + { REGEX_PHONE, FIELD_COUNTRY_CODE, 3 }, + { REGEX_PHONE, FIELD_AREA_CODE, 3 }, + { REGEX_PHONE, FIELD_PHONE, 3 }, + { REGEX_PHONE, FIELD_SUFFIX, 4 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Area Code: <ac> Phone: <phone> (- <suffix> (Ext: <ext>)?)? + { REGEX_AREA, FIELD_AREA_CODE, 0 }, + { REGEX_PHONE, FIELD_PHONE, 0 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <ac> <phone>:3 <suffix>:4 (Ext: <ext>)? + { REGEX_PHONE, FIELD_AREA_CODE, 0 }, + { REGEX_PHONE, FIELD_PHONE, 3 }, + { REGEX_PHONE, FIELD_SUFFIX, 4 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <cc> \( <ac> \) <phone> (- <suffix> (Ext: <ext>)?)? + { REGEX_PHONE, FIELD_COUNTRY_CODE, 0 }, + { REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 0 }, + { REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: \( <ac> \) <phone> (- <suffix> (Ext: <ext>)?)? + { REGEX_PHONE, FIELD_COUNTRY_CODE, 0 }, + { REGEX_AREA_NOTEXT, FIELD_AREA_CODE, 0 }, + { REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <cc> - <ac> - <phone> - <suffix> (Ext: <ext>)? + { REGEX_PHONE, FIELD_COUNTRY_CODE, 0 }, + { REGEX_PREFIX_SEPARATOR, FIELD_AREA_CODE, 0 }, + { REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 0 }, + { REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 0 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <ac> Prefix: <phone> Suffix: <suffix> (Ext: <ext>)? + { REGEX_PHONE, FIELD_AREA_CODE, 0 }, + { REGEX_PREFIX, FIELD_PHONE, 0 }, + { REGEX_SUFFIX, FIELD_SUFFIX, 0 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <ac> - <phone>:3 - <suffix>:4 (Ext: <ext>)? + { REGEX_PHONE, FIELD_AREA_CODE, 0 }, + { REGEX_PREFIX_SEPARATOR, FIELD_PHONE, 3 }, + { REGEX_SUFFIX_SEPARATOR, FIELD_SUFFIX, 4 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <cc> - <ac> - <phone> (Ext: <ext>)? + { REGEX_PHONE, FIELD_COUNTRY_CODE, 0 }, + { REGEX_PREFIX_SEPARATOR, FIELD_AREA_CODE, 0 }, + { REGEX_SUFFIX_SEPARATOR, FIELD_PHONE, 0 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <ac> - <phone> (Ext: <ext>)? + { REGEX_AREA, FIELD_AREA_CODE, 0 }, + { REGEX_PHONE, FIELD_PHONE, 0 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <cc>:3 - <phone>:10 (Ext: <ext>)? + { REGEX_PHONE, FIELD_COUNTRY_CODE, 3 }, + { REGEX_PHONE, FIELD_PHONE, 10 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, + // Phone: <phone> (Ext: <ext>)? + { REGEX_PHONE, FIELD_PHONE, 0 }, + { REGEX_SEPARATOR, FIELD_NONE, 0 }, +}; + +// static +FormField* PhoneField::Parse(AutofillScanner* scanner) { + if (scanner->IsEnd()) + return NULL; + + scanner->SaveCursor(); + + // The form owns the following variables, so they should not be deleted. + const AutofillField* parsed_fields[FIELD_MAX]; + + for (size_t i = 0; i < arraysize(kPhoneFieldGrammars); ++i) { + memset(parsed_fields, 0, sizeof(parsed_fields)); + scanner->SaveCursor(); + + // Attempt to parse according to the next grammar. + for (; i < arraysize(kPhoneFieldGrammars) && + kPhoneFieldGrammars[i].regex != REGEX_SEPARATOR; ++i) { + if (!ParseFieldSpecifics( + scanner, + GetRegExp(kPhoneFieldGrammars[i].regex), + MATCH_DEFAULT | MATCH_TELEPHONE, + &parsed_fields[kPhoneFieldGrammars[i].phone_part])) + break; + if (kPhoneFieldGrammars[i].max_size && + (!parsed_fields[kPhoneFieldGrammars[i].phone_part]->max_length || + kPhoneFieldGrammars[i].max_size < + parsed_fields[kPhoneFieldGrammars[i].phone_part]->max_length)) { + break; + } + } + + if (i >= arraysize(kPhoneFieldGrammars)) { + scanner->Rewind(); + return NULL; // Parsing failed. + } + if (kPhoneFieldGrammars[i].regex == REGEX_SEPARATOR) + break; // Parsing succeeded. + + // Proceed to the next grammar. + do { + ++i; + } while (i < arraysize(kPhoneFieldGrammars) && + kPhoneFieldGrammars[i].regex != REGEX_SEPARATOR); + + if (i + 1 == arraysize(kPhoneFieldGrammars)) { + scanner->Rewind(); + return NULL; // Tried through all the possibilities - did not match. + } + + scanner->Rewind(); + } + + if (!parsed_fields[FIELD_PHONE]) { + scanner->Rewind(); + return NULL; + } + + scoped_ptr<PhoneField> phone_field(new PhoneField); + for (int i = 0; i < FIELD_MAX; ++i) + phone_field->parsed_phone_fields_[i] = parsed_fields[i]; + + // Look for optional fields. + + // Look for a third text box. + if (!phone_field->parsed_phone_fields_[FIELD_SUFFIX]) { + if (!ParseField(scanner, UTF8ToUTF16(autofill::kPhoneSuffixRe), + &phone_field->parsed_phone_fields_[FIELD_SUFFIX])) { + ParseField(scanner, UTF8ToUTF16(autofill::kPhoneSuffixSeparatorRe), + &phone_field->parsed_phone_fields_[FIELD_SUFFIX]); + } + } + + // Now look for an extension. + ParseField(scanner, UTF8ToUTF16(autofill::kPhoneExtensionRe), + &phone_field->parsed_phone_fields_[FIELD_EXTENSION]); + + return phone_field.release(); +} + +bool PhoneField::ClassifyField(FieldTypeMap* map) const { + bool ok = true; + + DCHECK(parsed_phone_fields_[FIELD_PHONE]); // Phone was correctly parsed. + + if ((parsed_phone_fields_[FIELD_COUNTRY_CODE] != NULL) || + (parsed_phone_fields_[FIELD_AREA_CODE] != NULL) || + (parsed_phone_fields_[FIELD_SUFFIX] != NULL)) { + if (parsed_phone_fields_[FIELD_COUNTRY_CODE] != NULL) { + ok = ok && AddClassification(parsed_phone_fields_[FIELD_COUNTRY_CODE], + PHONE_HOME_COUNTRY_CODE, + map); + } + + AutofillFieldType field_number_type = PHONE_HOME_NUMBER; + if (parsed_phone_fields_[FIELD_AREA_CODE] != NULL) { + ok = ok && AddClassification(parsed_phone_fields_[FIELD_AREA_CODE], + PHONE_HOME_CITY_CODE, + map); + } else if (parsed_phone_fields_[FIELD_COUNTRY_CODE] != NULL) { + // Only if we can find country code without city code, it means the phone + // number include city code. + field_number_type = PHONE_HOME_CITY_AND_NUMBER; + } + // We tag the prefix as PHONE_HOME_NUMBER, then when filling the form + // we fill only the prefix depending on the size of the input field. + ok = ok && AddClassification(parsed_phone_fields_[FIELD_PHONE], + field_number_type, + map); + // We tag the suffix as PHONE_HOME_NUMBER, then when filling the form + // we fill only the suffix depending on the size of the input field. + if (parsed_phone_fields_[FIELD_SUFFIX] != NULL) { + ok = ok && AddClassification(parsed_phone_fields_[FIELD_SUFFIX], + PHONE_HOME_NUMBER, + map); + } + } else { + ok = AddClassification(parsed_phone_fields_[FIELD_PHONE], + PHONE_HOME_WHOLE_NUMBER, + map); + } + + return ok; +} + +PhoneField::PhoneField() { + memset(parsed_phone_fields_, 0, sizeof(parsed_phone_fields_)); +} + +// static +string16 PhoneField::GetRegExp(RegexType regex_id) { + switch (regex_id) { + case REGEX_COUNTRY: + return UTF8ToUTF16(autofill::kCountryCodeRe); + case REGEX_AREA: + return GetAreaRegex(); + case REGEX_AREA_NOTEXT: + return UTF8ToUTF16(autofill::kAreaCodeNotextRe); + case REGEX_PHONE: + return UTF8ToUTF16(autofill::kPhoneRe); + case REGEX_PREFIX_SEPARATOR: + return UTF8ToUTF16(autofill::kPhonePrefixSeparatorRe); + case REGEX_PREFIX: + return UTF8ToUTF16(autofill::kPhonePrefixRe); + case REGEX_SUFFIX_SEPARATOR: + return UTF8ToUTF16(autofill::kPhoneSuffixSeparatorRe); + case REGEX_SUFFIX: + return UTF8ToUTF16(autofill::kPhoneSuffixRe); + case REGEX_EXTENSION: + return UTF8ToUTF16(autofill::kPhoneExtensionRe); + default: + NOTREACHED(); + break; + } + return string16(); +} diff --git a/components/autofill/browser/phone_field.h b/components/autofill/browser/phone_field.h new file mode 100644 index 0000000..ee3cb0a --- /dev/null +++ b/components/autofill/browser/phone_field.h @@ -0,0 +1,89 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_PHONE_FIELD_H_ +#define COMPONENTS_AUTOFILL_BROWSER_PHONE_FIELD_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/form_field.h" +#include "components/autofill/browser/phone_number.h" + +class AutofillField; +class AutofillScanner; + +// A phone number in one of the following formats: +// - area code, prefix, suffix +// - area code, number +// - number +class PhoneField : public FormField { + public: + virtual ~PhoneField(); + + static FormField* Parse(AutofillScanner* scanner); + + protected: + // FormField: + virtual bool ClassifyField(FieldTypeMap* map) const OVERRIDE; + + private: + FRIEND_TEST_ALL_PREFIXES(PhoneFieldTest, ParseOneLinePhone); + FRIEND_TEST_ALL_PREFIXES(PhoneFieldTest, ParseTwoLinePhone); + FRIEND_TEST_ALL_PREFIXES(PhoneFieldTest, ThreePartPhoneNumber); + FRIEND_TEST_ALL_PREFIXES(PhoneFieldTest, ThreePartPhoneNumberPrefixSuffix); + FRIEND_TEST_ALL_PREFIXES(PhoneFieldTest, ThreePartPhoneNumberPrefixSuffix2); + FRIEND_TEST_ALL_PREFIXES(PhoneFieldTest, CountryAndCityAndPhoneNumber); + + // This is for easy description of the possible parsing paths of the phone + // fields. + enum RegexType { + REGEX_COUNTRY, + REGEX_AREA, + REGEX_AREA_NOTEXT, + REGEX_PHONE, + REGEX_PREFIX_SEPARATOR, + REGEX_PREFIX, + REGEX_SUFFIX_SEPARATOR, + REGEX_SUFFIX, + REGEX_EXTENSION, + + // Separates regexps in grammar. + REGEX_SEPARATOR, + }; + + // Parsed fields. + enum PhonePart { + FIELD_NONE = -1, + FIELD_COUNTRY_CODE, + FIELD_AREA_CODE, + FIELD_PHONE, + FIELD_SUFFIX, + FIELD_EXTENSION, + + FIELD_MAX, + }; + + struct Parser { + RegexType regex; // Field matching reg-ex. + PhonePart phone_part; // Index of the field. + size_t max_size; // Max size of the field to match. 0 means any. + }; + + static const Parser kPhoneFieldGrammars[]; + + PhoneField(); + + // Returns the regular expression string correspoding to |regex_id| + static string16 GetRegExp(RegexType regex_id); + + // FIELD_PHONE is always present; holds suffix if prefix is present. + // The rest could be NULL. + const AutofillField* parsed_phone_fields_[FIELD_MAX]; + + DISALLOW_COPY_AND_ASSIGN(PhoneField); +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_PHONE_FIELD_H_ diff --git a/components/autofill/browser/phone_field_unittest.cc b/components/autofill/browser/phone_field_unittest.cc new file mode 100644 index 0000000..cec2f84 --- /dev/null +++ b/components/autofill/browser/phone_field_unittest.cc @@ -0,0 +1,226 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_field.h" +#include "components/autofill/browser/autofill_scanner.h" +#include "components/autofill/browser/phone_field.h" +#include "components/autofill/common/form_field_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +class PhoneFieldTest : public testing::Test { + public: + PhoneFieldTest() {} + + protected: + ScopedVector<const AutofillField> list_; + scoped_ptr<PhoneField> field_; + FieldTypeMap field_type_map_; + + // Downcast for tests. + static PhoneField* Parse(AutofillScanner* scanner) { + return static_cast<PhoneField*>(PhoneField::Parse(scanner)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(PhoneFieldTest); +}; + +TEST_F(PhoneFieldTest, Empty) { + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_EQ(static_cast<PhoneField*>(NULL), field_.get()); +} + +TEST_F(PhoneFieldTest, NonParse) { + list_.push_back(new AutofillField); + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_EQ(static_cast<PhoneField*>(NULL), field_.get()); +} + +TEST_F(PhoneFieldTest, ParseOneLinePhone) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Phone"); + field.name = ASCIIToUTF16("phone"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("phone1"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<PhoneField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("phone1")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_WHOLE_NUMBER, field_type_map_[ASCIIToUTF16("phone1")]); +} + +TEST_F(PhoneFieldTest, ParseTwoLinePhone) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Area Code"); + field.name = ASCIIToUTF16("area code"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("areacode1"))); + + field.label = ASCIIToUTF16("Phone"); + field.name = ASCIIToUTF16("phone"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("phone2"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<PhoneField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("areacode1")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_CITY_CODE, field_type_map_[ASCIIToUTF16("areacode1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("phone2")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_NUMBER, field_type_map_[ASCIIToUTF16("phone2")]); +} + +TEST_F(PhoneFieldTest, ThreePartPhoneNumber) { + // Phone in format <field> - <field> - <field> could be either + // <area code> - <prefix> - <suffix>, or + // <country code> - <area code> - <phone>. The only distinguishing feature is + // size: <prefix> is no bigger than 3 characters, and <suffix> is no bigger + // than 4. + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Phone:"); + field.name = ASCIIToUTF16("dayphone1"); + field.max_length = 0; + list_.push_back(new AutofillField(field, ASCIIToUTF16("areacode1"))); + + field.label = ASCIIToUTF16("-"); + field.name = ASCIIToUTF16("dayphone2"); + field.max_length = 3; + list_.push_back(new AutofillField(field, ASCIIToUTF16("prefix2"))); + + field.label = ASCIIToUTF16("-"); + field.name = ASCIIToUTF16("dayphone3"); + field.max_length = 4; + list_.push_back(new AutofillField(field, ASCIIToUTF16("suffix3"))); + + field.label = ASCIIToUTF16("ext.:"); + field.name = ASCIIToUTF16("dayphone4"); + field.max_length = 0; + list_.push_back(new AutofillField(field, ASCIIToUTF16("ext4"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<PhoneField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("areacode1")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_CITY_CODE, field_type_map_[ASCIIToUTF16("areacode1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("prefix2")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_NUMBER, field_type_map_[ASCIIToUTF16("prefix2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("suffix3")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_NUMBER, field_type_map_[ASCIIToUTF16("suffix3")]); + EXPECT_TRUE( + field_type_map_.find(ASCIIToUTF16("ext4")) == field_type_map_.end()); +} + +// This scenario of explicitly labeled "prefix" and "suffix" phone numbers +// encountered in http://crbug.com/40694 with page +// https://www.wrapables.com/jsp/Signup.jsp. +TEST_F(PhoneFieldTest, ThreePartPhoneNumberPrefixSuffix) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Phone:"); + field.name = ASCIIToUTF16("area"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("areacode1"))); + + field.label = string16(); + field.name = ASCIIToUTF16("prefix"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("prefix2"))); + + field.label = string16(); + field.name = ASCIIToUTF16("suffix"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("suffix3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<PhoneField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("areacode1")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_CITY_CODE, field_type_map_[ASCIIToUTF16("areacode1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("prefix2")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_NUMBER, field_type_map_[ASCIIToUTF16("prefix2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("suffix3")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_NUMBER, field_type_map_[ASCIIToUTF16("suffix3")]); +} + +TEST_F(PhoneFieldTest, ThreePartPhoneNumberPrefixSuffix2) { + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("("); + field.name = ASCIIToUTF16("phone1"); + field.max_length = 3; + list_.push_back(new AutofillField(field, ASCIIToUTF16("phone1"))); + + field.label = ASCIIToUTF16(")"); + field.name = ASCIIToUTF16("phone2"); + field.max_length = 3; + list_.push_back(new AutofillField(field, ASCIIToUTF16("phone2"))); + + field.label = string16(); + field.name = ASCIIToUTF16("phone3"); + field.max_length = 4; + list_.push_back(new AutofillField(field, ASCIIToUTF16("phone3"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<PhoneField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("phone1")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_CITY_CODE, field_type_map_[ASCIIToUTF16("phone1")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("phone2")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_NUMBER, field_type_map_[ASCIIToUTF16("phone2")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("phone3")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_NUMBER, field_type_map_[ASCIIToUTF16("phone3")]); +} + +TEST_F(PhoneFieldTest, CountryAndCityAndPhoneNumber) { + // Phone in format <country code>:3 - <city and number>:10 + // The |maxlength| is considered, otherwise it's too broad. + FormFieldData field; + field.form_control_type = "text"; + + field.label = ASCIIToUTF16("Phone Number"); + field.name = ASCIIToUTF16("CountryCode"); + field.max_length = 3; + list_.push_back(new AutofillField(field, ASCIIToUTF16("country"))); + + field.label = ASCIIToUTF16("Phone Number"); + field.name = ASCIIToUTF16("PhoneNumber"); + field.max_length = 10; + list_.push_back(new AutofillField(field, ASCIIToUTF16("phone"))); + + AutofillScanner scanner(list_.get()); + field_.reset(Parse(&scanner)); + ASSERT_NE(static_cast<PhoneField*>(NULL), field_.get()); + ASSERT_TRUE(field_->ClassifyField(&field_type_map_)); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("country")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_COUNTRY_CODE, field_type_map_[ASCIIToUTF16("country")]); + ASSERT_TRUE( + field_type_map_.find(ASCIIToUTF16("phone")) != field_type_map_.end()); + EXPECT_EQ(PHONE_HOME_CITY_AND_NUMBER, field_type_map_[ASCIIToUTF16("phone")]); +} diff --git a/components/autofill/browser/phone_number.cc b/components/autofill/browser/phone_number.cc new file mode 100644 index 0000000..0a785e5 --- /dev/null +++ b/components/autofill/browser/phone_number.cc @@ -0,0 +1,238 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/phone_number.h" + +#include "base/basictypes.h" +#include "base/string_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_country.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/phone_number_i18n.h" + +namespace { + +const char16 kPhoneNumberSeparators[] = { ' ', '.', '(', ')', '-', 0 }; + +// The number of digits in a phone number. +const size_t kPhoneNumberLength = 7; + +// The number of digits in an area code. +const size_t kPhoneCityCodeLength = 3; + +void StripPunctuation(string16* number) { + RemoveChars(*number, kPhoneNumberSeparators, number); +} + +// Returns the region code for this phone number, which is an ISO 3166 2-letter +// country code. The returned value is based on the |profile|; if the |profile| +// does not have a country code associated with it, falls back to the country +// code corresponding to the |app_locale|. +std::string GetRegion(const AutofillProfile& profile, + const std::string& app_locale) { + std::string country_code = profile.CountryCode(); + if (!country_code.empty()) + return country_code; + + return AutofillCountry::CountryCodeForLocale(app_locale); +} + +} // namespace + +PhoneNumber::PhoneNumber(AutofillProfile* profile) + : profile_(profile) { +} + +PhoneNumber::PhoneNumber(const PhoneNumber& number) + : profile_(NULL) { + *this = number; +} + +PhoneNumber::~PhoneNumber() {} + +PhoneNumber& PhoneNumber::operator=(const PhoneNumber& number) { + if (this == &number) + return *this; + + number_ = number.number_; + profile_ = number.profile_; + cached_parsed_phone_ = number.cached_parsed_phone_; + return *this; +} + +void PhoneNumber::GetSupportedTypes(FieldTypeSet* supported_types) const { + supported_types->insert(PHONE_HOME_WHOLE_NUMBER); + supported_types->insert(PHONE_HOME_NUMBER); + supported_types->insert(PHONE_HOME_CITY_CODE); + supported_types->insert(PHONE_HOME_CITY_AND_NUMBER); + supported_types->insert(PHONE_HOME_COUNTRY_CODE); +} + +string16 PhoneNumber::GetRawInfo(AutofillFieldType type) const { + if (type == PHONE_HOME_WHOLE_NUMBER) + return number_; + + // Only the whole number is available as raw data. All of the other types are + // parsed from this raw info, and parsing requires knowledge of the phone + // number's region, which is only available via GetInfo(). + return string16(); +} + +void PhoneNumber::SetRawInfo(AutofillFieldType type, const string16& value) { + if (type != PHONE_HOME_CITY_AND_NUMBER && + type != PHONE_HOME_WHOLE_NUMBER) { + // Only full phone numbers should be set directly. The remaining field + // field types are read-only. + return; + } + + number_ = value; + + // Invalidate the cached number. + cached_parsed_phone_ = autofill_i18n::PhoneObject(); +} + +// Normalize phones if |type| is a whole number: +// (650)2345678 -> 6502345678 +// 1-800-FLOWERS -> 18003569377 +// If the phone cannot be normalized, returns the stored value verbatim. +string16 PhoneNumber::GetInfo(AutofillFieldType type, + const std::string& app_locale) const { + UpdateCacheIfNeeded(app_locale); + + // Queries for whole numbers will return the non-normalized number if + // normalization for the number fails. All other field types require + // normalization. + if (type != PHONE_HOME_WHOLE_NUMBER && !cached_parsed_phone_.IsValidNumber()) + return string16(); + + switch (type) { + case PHONE_HOME_WHOLE_NUMBER: + return cached_parsed_phone_.GetWholeNumber(); + + case PHONE_HOME_NUMBER: + return cached_parsed_phone_.number(); + + case PHONE_HOME_CITY_CODE: + return cached_parsed_phone_.city_code(); + + case PHONE_HOME_COUNTRY_CODE: + return cached_parsed_phone_.country_code(); + + case PHONE_HOME_CITY_AND_NUMBER: + return + cached_parsed_phone_.city_code() + cached_parsed_phone_.number(); + + default: + NOTREACHED(); + return string16(); + } +} + +bool PhoneNumber::SetInfo(AutofillFieldType type, + const string16& value, + const std::string& app_locale) { + SetRawInfo(type, value); + + if (number_.empty()) + return true; + + // Store a formatted (i.e., pretty printed) version of the number. + UpdateCacheIfNeeded(app_locale); + number_ = cached_parsed_phone_.GetFormattedNumber(); + return !number_.empty(); +} + +void PhoneNumber::GetMatchingTypes(const string16& text, + const std::string& app_locale, + FieldTypeSet* matching_types) const { + string16 stripped_text = text; + StripPunctuation(&stripped_text); + FormGroup::GetMatchingTypes(stripped_text, app_locale, matching_types); + + // For US numbers, also compare to the three-digit prefix and the four-digit + // suffix, since web sites often split numbers into these two fields. + string16 number = GetInfo(PHONE_HOME_NUMBER, app_locale); + if (GetRegion(*profile_, app_locale) == "US" && + number.size() == (kPrefixLength + kSuffixLength)) { + string16 prefix = number.substr(kPrefixOffset, kPrefixLength); + string16 suffix = number.substr(kSuffixOffset, kSuffixLength); + if (text == prefix || text == suffix) + matching_types->insert(PHONE_HOME_NUMBER); + } + + string16 whole_number = GetInfo(PHONE_HOME_WHOLE_NUMBER, app_locale); + if (!whole_number.empty()) { + string16 normalized_number = + autofill_i18n::NormalizePhoneNumber(text, + GetRegion(*profile_, app_locale)); + if (normalized_number == whole_number) + matching_types->insert(PHONE_HOME_WHOLE_NUMBER); + } +} + +void PhoneNumber::UpdateCacheIfNeeded(const std::string& app_locale) const { + std::string region = GetRegion(*profile_, app_locale); + if (!number_.empty() && cached_parsed_phone_.region() != region) + cached_parsed_phone_ = autofill_i18n::PhoneObject(number_, region); +} + +PhoneNumber::PhoneCombineHelper::PhoneCombineHelper() { +} + +PhoneNumber::PhoneCombineHelper::~PhoneCombineHelper() { +} + +bool PhoneNumber::PhoneCombineHelper::SetInfo(AutofillFieldType field_type, + const string16& value) { + if (field_type == PHONE_HOME_COUNTRY_CODE) { + country_ = value; + return true; + } + + if (field_type == PHONE_HOME_CITY_CODE) { + city_ = value; + return true; + } + + if (field_type == PHONE_HOME_CITY_AND_NUMBER) { + phone_ = value; + return true; + } + + if (field_type == PHONE_HOME_WHOLE_NUMBER) { + whole_number_ = value; + return true; + } + + if (field_type == PHONE_HOME_NUMBER) { + phone_.append(value); + return true; + } + + return false; +} + +bool PhoneNumber::PhoneCombineHelper::ParseNumber( + const AutofillProfile& profile, + const std::string& app_locale, + string16* value) { + if (IsEmpty()) + return false; + + if (!whole_number_.empty()) { + *value = whole_number_; + return true; + } + + return autofill_i18n::ConstructPhoneNumber( + country_, city_, phone_, GetRegion(profile, app_locale), value); +} + +bool PhoneNumber::PhoneCombineHelper::IsEmpty() const { + return phone_.empty() && whole_number_.empty(); +} diff --git a/components/autofill/browser/phone_number.h b/components/autofill/browser/phone_number.h new file mode 100644 index 0000000..79b780d --- /dev/null +++ b/components/autofill/browser/phone_number.h @@ -0,0 +1,94 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_PHONE_NUMBER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_PHONE_NUMBER_H_ + +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/string16.h" +#include "components/autofill/browser/autofill_type.h" +#include "components/autofill/browser/form_group.h" +#include "components/autofill/browser/phone_number_i18n.h" + +class AutofillProfile; + +// A form group that stores phone number information. +class PhoneNumber : public FormGroup { + public: + explicit PhoneNumber(AutofillProfile* profile); + PhoneNumber(const PhoneNumber& number); + virtual ~PhoneNumber(); + + PhoneNumber& operator=(const PhoneNumber& number); + + void set_profile(AutofillProfile* profile) { profile_ = profile; } + + // FormGroup implementation: + virtual void GetMatchingTypes(const string16& text, + const std::string& app_locale, + FieldTypeSet* matching_types) const OVERRIDE; + virtual string16 GetRawInfo(AutofillFieldType type) const OVERRIDE; + virtual void SetRawInfo(AutofillFieldType type, + const string16& value) OVERRIDE; + virtual string16 GetInfo(AutofillFieldType type, + const std::string& app_locale) const OVERRIDE; + virtual bool SetInfo(AutofillFieldType type, + const string16& value, + const std::string& app_locale) OVERRIDE; + + // Size and offset of the prefix and suffix portions of phone numbers. + static const size_t kPrefixOffset = 0; + static const size_t kPrefixLength = 3; + static const size_t kSuffixOffset = 3; + static const size_t kSuffixLength = 4; + + // The class used to combine home phone parts into a whole number. + class PhoneCombineHelper { + public: + PhoneCombineHelper(); + ~PhoneCombineHelper(); + + // If |type| is a phone field type, saves the |value| accordingly and + // returns true. For all other field types returs false. + bool SetInfo(AutofillFieldType type, const string16& value); + + // Parses the number built up from pieces stored via SetInfo() according to + // the specified |profile|'s country code, falling back to the given + // |app_locale| if the |profile| has no associated country code. Returns + // true if parsing was successful, false otherwise. + bool ParseNumber(const AutofillProfile& profile, + const std::string& app_locale, + string16* value); + + // Returns true if both |phone_| and |whole_number_| are empty. + bool IsEmpty() const; + + private: + string16 country_; + string16 city_; + string16 phone_; + string16 whole_number_; + }; + + private: + // FormGroup: + virtual void GetSupportedTypes(FieldTypeSet* supported_types) const OVERRIDE; + + // Updates the cached parsed number if the profile's region has changed + // since the last time the cache was updated. + void UpdateCacheIfNeeded(const std::string& app_locale) const; + + // The phone number. + string16 number_; + // Profile which stores the region used as hint when normalizing the number. + const AutofillProfile* profile_; // WEAK + + // Cached number. + mutable autofill_i18n::PhoneObject cached_parsed_phone_; +}; + +#endif // COMPONENTS_AUTOFILL_BROWSER_PHONE_NUMBER_H_ diff --git a/components/autofill/browser/phone_number_i18n.cc b/components/autofill/browser/phone_number_i18n.cc new file mode 100644 index 0000000..0f71deb --- /dev/null +++ b/components/autofill/browser/phone_number_i18n.cc @@ -0,0 +1,296 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/phone_number_i18n.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/string_util.h" +#include "base/stringprintf.h" +#include "base/strings/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_country.h" +#include "third_party/libphonenumber/src/phonenumber_api.h" + +using i18n::phonenumbers::PhoneNumber; +using i18n::phonenumbers::PhoneNumberUtil; + +namespace { + +std::string SanitizeRegion(const std::string& region) { + if (region.length() == 2) + return region; + + return AutofillCountry::CountryCodeForLocale( + AutofillCountry::ApplicationLocale()); +} + +// Returns true if |phone_number| is valid. +bool IsValidPhoneNumber(const PhoneNumber& phone_number) { + PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance(); + if (!phone_util->IsPossibleNumber(phone_number)) + return false; + + // Verify that the number has a valid area code (that in some cases could be + // empty) for the parsed country code. Also verify that this is a valid + // number (for example, in the US 1234567 is not valid, because numbers do not + // start with 1). + if (!phone_util->IsValidNumber(phone_number)) + return false; + + return true; +} + +// Formats the given |number| as a human-readable string, and writes the result +// into |formatted_number|. Also, normalizes the formatted number, and writes +// that result into |normalized_number|. This function should only be called +// with numbers already known to be valid, i.e. validation should be done prior +// to calling this function. Note that the |country_code|, which determines +// whether to format in the national or in the international format, is passed +// in explicitly, as |number| might have an implicit country code set, even +// though the original input lacked a country code. +void FormatValidatedNumber(const PhoneNumber& number, + const string16& country_code, + string16* formatted_number, + string16* normalized_number) { + PhoneNumberUtil::PhoneNumberFormat format = + country_code.empty() ? + PhoneNumberUtil::NATIONAL : + PhoneNumberUtil::INTERNATIONAL; + + PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance(); + std::string processed_number; + phone_util->Format(number, format, &processed_number); + + if (formatted_number) + *formatted_number = UTF8ToUTF16(processed_number); + + if (normalized_number) { + phone_util->NormalizeDigitsOnly(&processed_number); + *normalized_number = UTF8ToUTF16(processed_number); + } +} + +} // namespace + +namespace autofill_i18n { + +// Parses the number stored in |value| as it should be interpreted in the given +// |region|, and stores the results into the remaining arguments. The |region| +// should be sanitized prior to calling this function. +bool ParsePhoneNumber(const string16& value, + const std::string& region, + string16* country_code, + string16* city_code, + string16* number, + PhoneNumber* i18n_number) { + country_code->clear(); + city_code->clear(); + number->clear(); + *i18n_number = PhoneNumber(); + + std::string number_text(UTF16ToUTF8(value)); + + // Parse phone number based on the region. + PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance(); + + // The |region| should already be sanitized. + DCHECK_EQ(2U, region.size()); + if (phone_util->Parse(number_text, region.c_str(), i18n_number) != + PhoneNumberUtil::NO_PARSING_ERROR) { + return false; + } + + if (!IsValidPhoneNumber(*i18n_number)) + return false; + + std::string national_significant_number; + phone_util->GetNationalSignificantNumber(*i18n_number, + &national_significant_number); + + int area_length = phone_util->GetLengthOfGeographicalAreaCode(*i18n_number); + int destination_length = + phone_util->GetLengthOfNationalDestinationCode(*i18n_number); + // Some phones have a destination code in lieu of area code: mobile operators + // in Europe, toll and toll-free numbers in USA, etc. From our point of view + // these two types of codes are the same. + if (destination_length > area_length) + area_length = destination_length; + + std::string area_code; + std::string subscriber_number; + if (area_length > 0) { + area_code = national_significant_number.substr(0, area_length); + subscriber_number = national_significant_number.substr(area_length); + } else { + subscriber_number = national_significant_number; + } + *number = UTF8ToUTF16(subscriber_number); + *city_code = UTF8ToUTF16(area_code); + *country_code = string16(); + + phone_util->NormalizeDigitsOnly(&number_text); + string16 normalized_number(UTF8ToUTF16(number_text)); + + // Check if parsed number has a country code that was not inferred from the + // region. + if (i18n_number->has_country_code()) { + *country_code = UTF8ToUTF16( + base::StringPrintf("%d", i18n_number->country_code())); + if (normalized_number.length() <= national_significant_number.length() && + !StartsWith(normalized_number, *country_code, + true /* case_sensitive */)) { + country_code->clear(); + } + } + + return true; +} + +string16 NormalizePhoneNumber(const string16& value, + std::string const& region) { + string16 country_code; + string16 unused_city_code; + string16 unused_number; + PhoneNumber phone_number; + if (!ParsePhoneNumber(value, SanitizeRegion(region), &country_code, + &unused_city_code, &unused_number, &phone_number)) { + return string16(); // Parsing failed - do not store phone. + } + + string16 normalized_number; + FormatValidatedNumber(phone_number, country_code, NULL, &normalized_number); + return normalized_number; +} + +bool ConstructPhoneNumber(const string16& country_code, + const string16& city_code, + const string16& number, + const std::string& region, + string16* whole_number) { + whole_number->clear(); + + string16 unused_country_code; + string16 unused_city_code; + string16 unused_number; + PhoneNumber phone_number; + if (!ParsePhoneNumber(country_code + city_code + number, + SanitizeRegion(region), + &unused_country_code, &unused_city_code, &unused_number, + &phone_number)) { + return false; + } + + FormatValidatedNumber(phone_number, country_code, whole_number, NULL); + return true; +} + +bool PhoneNumbersMatch(const string16& number_a, + const string16& number_b, + const std::string& raw_region) { + // Sanitize the provided |raw_region| before trying to use it for parsing. + const std::string region = SanitizeRegion(raw_region); + + PhoneNumberUtil* phone_util = PhoneNumberUtil::GetInstance(); + + // Parse phone numbers based on the region + PhoneNumber i18n_number1; + if (phone_util->Parse(UTF16ToUTF8(number_a), region.c_str(), &i18n_number1) != + PhoneNumberUtil::NO_PARSING_ERROR) { + return false; + } + + PhoneNumber i18n_number2; + if (phone_util->Parse(UTF16ToUTF8(number_b), region.c_str(), &i18n_number2) != + PhoneNumberUtil::NO_PARSING_ERROR) { + return false; + } + + switch (phone_util->IsNumberMatch(i18n_number1, i18n_number2)) { + case PhoneNumberUtil::INVALID_NUMBER: + case PhoneNumberUtil::NO_MATCH: + return false; + case PhoneNumberUtil::SHORT_NSN_MATCH: + return false; + case PhoneNumberUtil::NSN_MATCH: + case PhoneNumberUtil::EXACT_MATCH: + return true; + } + + NOTREACHED(); + return false; +} + +PhoneObject::PhoneObject(const string16& number, const std::string& region) + : region_(SanitizeRegion(region)), + i18n_number_(NULL) { + // TODO(isherman): Autofill profiles should always have a |region| set, but in + // some cases it should be marked as implicit. Otherwise, phone numbers + // might behave differently when they are synced across computers: + // [ http://crbug.com/100845 ]. Once the bug is fixed, add a DCHECK here to + // verify. + + scoped_ptr<PhoneNumber> i18n_number(new PhoneNumber); + if (ParsePhoneNumber(number, region_, &country_code_, &city_code_, &number_, + i18n_number.get())) { + // The phone number was successfully parsed, so store the parsed version. + // The formatted and normalized versions will be set on the first call to + // the coresponding methods. + i18n_number_.reset(i18n_number.release()); + } else { + // Parsing failed. Store passed phone "as is" into |whole_number_|. + whole_number_ = number; + } +} + +PhoneObject::PhoneObject(const PhoneObject& other) : i18n_number_(NULL) { + *this = other; +} + +PhoneObject::PhoneObject() : i18n_number_(NULL) { +} + +PhoneObject::~PhoneObject() { +} + +string16 PhoneObject::GetFormattedNumber() const { + if (i18n_number_ && formatted_number_.empty()) { + FormatValidatedNumber(*i18n_number_, country_code_, &formatted_number_, + &whole_number_); + } + + return formatted_number_; +} + +string16 PhoneObject::GetWholeNumber() const { + if (i18n_number_ && whole_number_.empty()) { + FormatValidatedNumber(*i18n_number_, country_code_, &formatted_number_, + &whole_number_); + } + + return whole_number_; +} + +PhoneObject& PhoneObject::operator=(const PhoneObject& other) { + if (this == &other) + return *this; + + region_ = other.region_; + + if (other.i18n_number_.get()) + i18n_number_.reset(new PhoneNumber(*other.i18n_number_)); + else + i18n_number_.reset(); + + country_code_ = other.country_code_; + city_code_ = other.city_code_; + number_ = other.number_; + + formatted_number_ = other.formatted_number_; + whole_number_ = other.whole_number_; + + return *this; +} + +} // namespace autofill_i18n diff --git a/components/autofill/browser/phone_number_i18n.h b/components/autofill/browser/phone_number_i18n.h new file mode 100644 index 0000000..3a35c3b --- /dev/null +++ b/components/autofill/browser/phone_number_i18n.h @@ -0,0 +1,106 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_PHONE_NUMBER_I18N_H_ +#define COMPONENTS_AUTOFILL_BROWSER_PHONE_NUMBER_I18N_H_ + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" + +namespace i18n { +namespace phonenumbers { +class PhoneNumber; +} +} + +// Utilities to process, normalize and compare international phone numbers. +namespace autofill_i18n { + +// Most of the following functions require |region| to operate. The |region| is +// a ISO 3166 standard code ("US" for USA, "CZ" for Czech Republic, etc.). + +// Parses the number stored in |value| as a phone number interpreted in the +// given |region|, and stores the results into the remaining arguments. The +// |region| should be a 2-letter country code. This is an internal function, +// exposed in the header file so that it can be tested. +bool ParsePhoneNumber( + const string16& value, + const std::string& region, + string16* country_code, + string16* city_code, + string16* number, + i18n::phonenumbers::PhoneNumber* i18n_number) WARN_UNUSED_RESULT; + +// Normalizes phone number, by changing digits in the extended fonts +// (such as \xFF1x) into '0'-'9'. Also strips out non-digit characters. +string16 NormalizePhoneNumber(const string16& value, + const std::string& region); + +// Constructs whole phone number from parts. +// |city_code| - area code, could be empty. +// |country_code| - country code, could be empty. +// |number| - local number, should not be empty. +// |region| - current region, the parsing is based on. +// |whole_number| - constructed whole number. +// Separator characters are stripped before parsing the digits. +// Returns true if parsing was successful, false otherwise. +bool ConstructPhoneNumber(const string16& country_code, + const string16& city_code, + const string16& number, + const std::string& region, + string16* whole_number) WARN_UNUSED_RESULT; + +// Returns true if |number_a| and |number_b| parse to the same phone number in +// the given |region|. +bool PhoneNumbersMatch(const string16& number_a, + const string16& number_b, + const std::string& region); + +// The cached phone number, does parsing only once, improves performance. +class PhoneObject { + public: + PhoneObject(const string16& number, const std::string& region); + PhoneObject(const PhoneObject&); + PhoneObject(); + ~PhoneObject(); + + std::string region() const { return region_; } + + string16 country_code() const { return country_code_; } + string16 city_code() const { return city_code_; } + string16 number() const { return number_; } + + string16 GetFormattedNumber() const; + string16 GetWholeNumber() const; + + PhoneObject& operator=(const PhoneObject& other); + + bool IsValidNumber() const { return i18n_number_ != NULL; } + + private: + // The region code used to parse this number. + std::string region_; + + // The parsed number and its components. + scoped_ptr<i18n::phonenumbers::PhoneNumber> i18n_number_; + string16 city_code_; + string16 country_code_; + string16 number_; + + // Pretty printed version of the whole number, or empty if parsing failed. + // Set on first request. + mutable string16 formatted_number_; + + // The whole number, normalized to contain only digits if possible. + // Set on first request. + mutable string16 whole_number_; +}; + +} // namespace autofill_i18n + +#endif // COMPONENTS_AUTOFILL_BROWSER_PHONE_NUMBER_I18N_H_ diff --git a/components/autofill/browser/phone_number_i18n_unittest.cc b/components/autofill/browser/phone_number_i18n_unittest.cc new file mode 100644 index 0000000..8e25520 --- /dev/null +++ b/components/autofill/browser/phone_number_i18n_unittest.cc @@ -0,0 +1,372 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/message_loop.h" +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/phone_number_i18n.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libphonenumber/src/phonenumber_api.h" + +using autofill_i18n::NormalizePhoneNumber; +using autofill_i18n::ParsePhoneNumber; +using autofill_i18n::ConstructPhoneNumber; +using autofill_i18n::PhoneNumbersMatch; +using content::BrowserThread; + +TEST(PhoneNumberI18NTest, NormalizePhoneNumber) { + // "Large" digits. + string16 phone1(UTF8ToUTF16("\xEF\xBC\x91\xEF\xBC\x96\xEF\xBC\x95\xEF\xBC\x90" + "\xEF\xBC\x97\xEF\xBC\x94\xEF\xBC\x99\xEF\xBC\x98" + "\xEF\xBC\x93\xEF\xBC\x92\xEF\xBC\x93")); + EXPECT_EQ(NormalizePhoneNumber(phone1, "US"), ASCIIToUTF16("16507498323")); + + // Devanagari script digits. + string16 phone2(UTF8ToUTF16("\xD9\xA1\xD9\xA6\xD9\xA5\xD9\xA0\xD9\xA8\xD9\xA3" + "\xD9\xA2\xD9\xA3\xD9\xA7\xD9\xA4\xD9\xA9")); + EXPECT_EQ(NormalizePhoneNumber(phone2, "US"), ASCIIToUTF16("16508323749")); + + string16 phone3(UTF8ToUTF16("16503334\xef\xbc\x92\x35\xd9\xa5")); + EXPECT_EQ(NormalizePhoneNumber(phone3, "US"), ASCIIToUTF16("16503334255")); + + string16 phone4(UTF8ToUTF16("+1(650)2346789")); + EXPECT_EQ(NormalizePhoneNumber(phone4, "US"), ASCIIToUTF16("16502346789")); + + string16 phone5(UTF8ToUTF16("6502346789")); + EXPECT_EQ(NormalizePhoneNumber(phone5, "US"), ASCIIToUTF16("6502346789")); +} + +TEST(PhoneNumberI18NTest, ParsePhoneNumber) { + string16 number; + string16 city_code; + string16 country_code; + i18n::phonenumbers::PhoneNumber unused_i18n_number; + + // Test for empty string. Should give back empty strings. + string16 phone0; + EXPECT_FALSE(ParsePhoneNumber(phone0, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(string16(), number); + EXPECT_EQ(string16(), city_code); + EXPECT_EQ(string16(), country_code); + + // Test for string with less than 7 digits. Should give back empty strings. + string16 phone1(ASCIIToUTF16("1234")); + EXPECT_FALSE(ParsePhoneNumber(phone1, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(string16(), number); + EXPECT_EQ(string16(), city_code); + EXPECT_EQ(string16(), country_code); + + // Test for string with exactly 7 digits. + // Not a valid number - starts with 1 + string16 phone2(ASCIIToUTF16("1234567")); + EXPECT_FALSE(ParsePhoneNumber(phone2, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(string16(), number); + EXPECT_EQ(string16(), city_code); + EXPECT_EQ(string16(), country_code); + + // Not a valid number - does not have area code. + string16 phone3(ASCIIToUTF16("2234567")); + EXPECT_FALSE(ParsePhoneNumber(phone3, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(string16(), number); + EXPECT_EQ(string16(), city_code); + EXPECT_EQ(string16(), country_code); + + // Test for string with greater than 7 digits but less than 10 digits. + // Should fail parsing in US. + string16 phone4(ASCIIToUTF16("123456789")); + EXPECT_FALSE(ParsePhoneNumber(phone4, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(string16(), number); + EXPECT_EQ(string16(), city_code); + EXPECT_EQ(string16(), country_code); + + // Test for string with greater than 7 digits but less than 10 digits and + // separators. + // Should fail parsing in US. + string16 phone_separator4(ASCIIToUTF16("12.345-6789")); + EXPECT_FALSE(ParsePhoneNumber(phone_separator4, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(string16(), number); + EXPECT_EQ(string16(), city_code); + EXPECT_EQ(string16(), country_code); + + // Test for string with exactly 10 digits. + // Should give back phone number and city code. + // This one going to fail because of the incorrect area code. + string16 phone5(ASCIIToUTF16("1234567890")); + EXPECT_FALSE(ParsePhoneNumber(phone5, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(string16(), number); + EXPECT_EQ(string16(), city_code); + EXPECT_EQ(string16(), country_code); + + string16 phone6(ASCIIToUTF16("6501567890")); + // This one going to fail because of the incorrect number (starts with 1). + EXPECT_FALSE(ParsePhoneNumber(phone6, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(string16(), number); + EXPECT_EQ(string16(), city_code); + EXPECT_EQ(string16(), country_code); + + string16 phone7(ASCIIToUTF16("6504567890")); + EXPECT_TRUE(ParsePhoneNumber(phone7, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(ASCIIToUTF16("4567890"), number); + EXPECT_EQ(ASCIIToUTF16("650"), city_code); + EXPECT_EQ(string16(), country_code); + + // Test for string with exactly 10 digits and separators. + // Should give back phone number and city code. + string16 phone_separator7(ASCIIToUTF16("(650) 456-7890")); + EXPECT_TRUE(ParsePhoneNumber(phone_separator7, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(ASCIIToUTF16("4567890"), number); + EXPECT_EQ(ASCIIToUTF16("650"), city_code); + EXPECT_EQ(string16(), country_code); + + // Tests for string with over 10 digits. + // 01 is incorrect prefix in the USA, and if we interpret 011 as prefix, the + // rest is too short for international number - the parsing should fail. + string16 phone8(ASCIIToUTF16("0116504567890")); + EXPECT_FALSE(ParsePhoneNumber(phone8, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(string16(), number); + EXPECT_EQ(string16(), city_code); + EXPECT_EQ(string16(), country_code); + + // 011 is a correct "dial out" prefix in the USA - the parsing should succeed. + string16 phone9(ASCIIToUTF16("01116504567890")); + EXPECT_TRUE(ParsePhoneNumber(phone9, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(ASCIIToUTF16("4567890"), number); + EXPECT_EQ(ASCIIToUTF16("650"), city_code); + EXPECT_EQ(ASCIIToUTF16("1"), country_code); + + // 011 is a correct "dial out" prefix in the USA - the parsing should succeed. + string16 phone10(ASCIIToUTF16("01178124567890")); + EXPECT_TRUE(ParsePhoneNumber(phone10, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(ASCIIToUTF16("4567890"), number); + EXPECT_EQ(ASCIIToUTF16("812"), city_code); + EXPECT_EQ(ASCIIToUTF16("7"), country_code); + + // Test for string with over 10 digits with separator characters. + // Should give back phone number, city code, and country code. "011" is + // US "dial out" code, which is discarded. + string16 phone11(ASCIIToUTF16("(0111) 650-456.7890")); + EXPECT_TRUE(ParsePhoneNumber(phone11, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(ASCIIToUTF16("4567890"), number); + EXPECT_EQ(ASCIIToUTF16("650"), city_code); + EXPECT_EQ(ASCIIToUTF16("1"), country_code); + + // Now try phone from Chech republic - it has 00 dial out code, 420 country + // code and variable length area codes. + string16 phone12(ASCIIToUTF16("+420 27-89.10.112")); + EXPECT_TRUE(ParsePhoneNumber(phone12, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(ASCIIToUTF16("910112"), number); + EXPECT_EQ(ASCIIToUTF16("278"), city_code); + EXPECT_EQ(ASCIIToUTF16("420"), country_code); + + EXPECT_TRUE(ParsePhoneNumber(phone12, "CZ", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(ASCIIToUTF16("910112"), number); + EXPECT_EQ(ASCIIToUTF16("278"), city_code); + EXPECT_EQ(ASCIIToUTF16("420"), country_code); + + string16 phone13(ASCIIToUTF16("420 57-89.10.112")); + EXPECT_FALSE(ParsePhoneNumber(phone13, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_TRUE(ParsePhoneNumber(phone13, "CZ", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(ASCIIToUTF16("910112"), number); + EXPECT_EQ(ASCIIToUTF16("578"), city_code); + EXPECT_EQ(ASCIIToUTF16("420"), country_code); + + string16 phone14(ASCIIToUTF16("1-650-FLOWERS")); + EXPECT_TRUE(ParsePhoneNumber(phone14, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(ASCIIToUTF16("3569377"), number); + EXPECT_EQ(ASCIIToUTF16("650"), city_code); + EXPECT_EQ(ASCIIToUTF16("1"), country_code); + + // 800 is not an area code, but the destination code. In our library these + // codes should be treated the same as area codes. + string16 phone15(ASCIIToUTF16("1-800-FLOWERS")); + EXPECT_TRUE(ParsePhoneNumber(phone15, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(ASCIIToUTF16("3569377"), number); + EXPECT_EQ(ASCIIToUTF16("800"), city_code); + EXPECT_EQ(ASCIIToUTF16("1"), country_code); +} + +TEST(PhoneNumberI18NTest, ConstructPhoneNumber) { + string16 number; + EXPECT_TRUE(ConstructPhoneNumber(ASCIIToUTF16("1"), + ASCIIToUTF16("650"), + ASCIIToUTF16("2345678"), + "US", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("+1 650-234-5678")); + EXPECT_TRUE(ConstructPhoneNumber(string16(), + ASCIIToUTF16("650"), + ASCIIToUTF16("2345678"), + "US", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("(650) 234-5678")); + EXPECT_TRUE(ConstructPhoneNumber(ASCIIToUTF16("1"), + string16(), + ASCIIToUTF16("6502345678"), + "US", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("+1 650-234-5678")); + EXPECT_TRUE(ConstructPhoneNumber(string16(), + string16(), + ASCIIToUTF16("6502345678"), + "US", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("(650) 234-5678")); + + EXPECT_FALSE(ConstructPhoneNumber(string16(), + ASCIIToUTF16("650"), + ASCIIToUTF16("234567890"), + "US", + &number)); + EXPECT_EQ(number, string16()); + // Italian number + EXPECT_TRUE(ConstructPhoneNumber(ASCIIToUTF16("39"), + ASCIIToUTF16("347"), + ASCIIToUTF16("2345678"), + "IT", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("+39 347 234 5678")); + EXPECT_TRUE(ConstructPhoneNumber(string16(), + ASCIIToUTF16("347"), + ASCIIToUTF16("2345678"), + "IT", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("347 234 5678")); + // German number. + EXPECT_TRUE(ConstructPhoneNumber(ASCIIToUTF16("49"), + ASCIIToUTF16("024"), + ASCIIToUTF16("2345678901"), + "DE", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("+49 2423/45678901")); + EXPECT_TRUE(ConstructPhoneNumber(string16(), + ASCIIToUTF16("024"), + ASCIIToUTF16("2345678901"), + "DE", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("02423/45678901")); +} + +TEST(PhoneNumberI18NTest, PhoneNumbersMatch) { + // Same numbers, defined country code. + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("4158889999"), + "US")); + // Same numbers, undefined country code. + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("4158889999"), + "")); + + // Numbers differ by country code only. + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("14158889999"), + ASCIIToUTF16("4158889999"), + "US")); + + // Same numbers, different formats. + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("415-888-9999"), + "US")); + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("(415)888-9999"), + "US")); + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("415 888 9999"), + "US")); + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("415 TUV WXYZ"), + "US")); + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("1(415)888-99-99"), + ASCIIToUTF16("+14158889999"), + "US")); + + // Partial matches don't count. + EXPECT_FALSE(PhoneNumbersMatch(ASCIIToUTF16("14158889999"), + ASCIIToUTF16("8889999"), + "US")); + + // Different numbers don't match. + EXPECT_FALSE(PhoneNumbersMatch(ASCIIToUTF16("14158889999"), + ASCIIToUTF16("1415888"), + "US")); +} diff --git a/components/autofill/browser/phone_number_unittest.cc b/components/autofill/browser/phone_number_unittest.cc new file mode 100644 index 0000000..6772e75 --- /dev/null +++ b/components/autofill/browser/phone_number_unittest.cc @@ -0,0 +1,203 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/string16.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_profile.h" +#include "components/autofill/browser/field_types.h" +#include "components/autofill/browser/phone_number.h" +#include "components/autofill/browser/phone_number_i18n.h" +#include "testing/gtest/include/gtest/gtest.h" + +TEST(PhoneNumberTest, Matcher) { + AutofillProfile profile; + profile.SetCountryCode("US"); + // Set phone number so country_code == 1, city_code = 650, number = 2345678. + string16 phone(ASCIIToUTF16("1 [650] 234-5678")); + PhoneNumber phone_number(&profile); + phone_number.SetInfo(PHONE_HOME_WHOLE_NUMBER, phone, "US"); + + FieldTypeSet matching_types; + phone_number.GetMatchingTypes(string16(), "US", &matching_types); + EXPECT_EQ(1U, matching_types.size()); + EXPECT_TRUE(matching_types.find(EMPTY_TYPE) != matching_types.end()); + matching_types.clear(); + phone_number.GetMatchingTypes(ASCIIToUTF16("1"), "US", &matching_types); + EXPECT_EQ(1U, matching_types.size()); + EXPECT_TRUE(matching_types.find(PHONE_HOME_COUNTRY_CODE) != + matching_types.end()); + matching_types.clear(); + phone_number.GetMatchingTypes(ASCIIToUTF16("16"), "US", &matching_types); + EXPECT_EQ(0U, matching_types.size()); + phone_number.GetMatchingTypes(ASCIIToUTF16("165"), "US", &matching_types); + EXPECT_EQ(0U, matching_types.size()); + phone_number.GetMatchingTypes(ASCIIToUTF16("1650"), "US", &matching_types); + EXPECT_EQ(0U, matching_types.size()); + phone_number.GetMatchingTypes(ASCIIToUTF16("16502"), "US", &matching_types); + EXPECT_EQ(0U, matching_types.size()); + phone_number.GetMatchingTypes(ASCIIToUTF16("165023"), "US", &matching_types); + EXPECT_EQ(0U, matching_types.size()); + phone_number.GetMatchingTypes(ASCIIToUTF16("1650234"), "US", &matching_types); + EXPECT_EQ(0U, matching_types.size()); + matching_types.clear(); + phone_number.GetMatchingTypes(ASCIIToUTF16("16502345678"), "US", + &matching_types); + EXPECT_EQ(1U, matching_types.size()); + EXPECT_TRUE(matching_types.find(PHONE_HOME_WHOLE_NUMBER) != + matching_types.end()); + matching_types.clear(); + phone_number.GetMatchingTypes(ASCIIToUTF16("650"), "US", &matching_types); + EXPECT_EQ(1U, matching_types.size()); + EXPECT_TRUE(matching_types.find(PHONE_HOME_CITY_CODE) != + matching_types.end()); + matching_types.clear(); + phone_number.GetMatchingTypes(ASCIIToUTF16("2345678"), "US", &matching_types); + EXPECT_EQ(1U, matching_types.size()); + EXPECT_TRUE(matching_types.find(PHONE_HOME_NUMBER) != matching_types.end()); + matching_types.clear(); + phone_number.GetMatchingTypes(ASCIIToUTF16("234"), "US", &matching_types); + EXPECT_EQ(1U, matching_types.size()); + EXPECT_TRUE(matching_types.find(PHONE_HOME_NUMBER) != matching_types.end()); + matching_types.clear(); + phone_number.GetMatchingTypes(ASCIIToUTF16("5678"), "US", &matching_types); + EXPECT_EQ(1U, matching_types.size()); + EXPECT_TRUE(matching_types.find(PHONE_HOME_NUMBER) != matching_types.end()); + matching_types.clear(); + phone_number.GetMatchingTypes(ASCIIToUTF16("2345"), "US", &matching_types); + EXPECT_EQ(0U, matching_types.size()); + matching_types.clear(); + phone_number.GetMatchingTypes(ASCIIToUTF16("6502345678"), "US", + &matching_types); + EXPECT_EQ(1U, matching_types.size()); + EXPECT_TRUE(matching_types.find(PHONE_HOME_CITY_AND_NUMBER) != + matching_types.end()); + matching_types.clear(); + phone_number.GetMatchingTypes(ASCIIToUTF16("(650)2345678"), "US", + &matching_types); + EXPECT_EQ(1U, matching_types.size()); + EXPECT_TRUE(matching_types.find(PHONE_HOME_CITY_AND_NUMBER) != + matching_types.end()); +} + +// Verify that PhoneNumber::SetInfo() correctly formats the incoming number. +TEST(PhoneNumberTest, SetInfo) { + AutofillProfile profile; + profile.SetCountryCode("US"); + + PhoneNumber phone(&profile); + EXPECT_EQ(string16(), phone.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); + + // Set the formatted info directly. + EXPECT_TRUE(phone.SetInfo(PHONE_HOME_WHOLE_NUMBER, + ASCIIToUTF16("(650) 234-5678"), "US")); + EXPECT_EQ(ASCIIToUTF16("(650) 234-5678"), + phone.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); + + // Unformatted numbers should be formatted. + EXPECT_TRUE(phone.SetInfo(PHONE_HOME_WHOLE_NUMBER, + ASCIIToUTF16("8887776666"), "US")); + EXPECT_EQ(ASCIIToUTF16("(888) 777-6666"), + phone.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); + + // Differently formatted numbers should be re-formatted. + EXPECT_TRUE(phone.SetInfo(PHONE_HOME_WHOLE_NUMBER, + ASCIIToUTF16("800-432-8765"), "US")); + EXPECT_EQ(ASCIIToUTF16("(800) 432-8765"), + phone.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); + + // Invalid numbers should not be stored. In the US, phone numbers cannot + // start with the digit '1'. + EXPECT_FALSE(phone.SetInfo(PHONE_HOME_WHOLE_NUMBER, + ASCIIToUTF16("650111111"), "US")); + EXPECT_EQ(string16(), phone.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); +} + +// Test that cached phone numbers are correctly invalidated and updated. +TEST(PhoneNumberTest, UpdateCachedPhoneNumber) { + AutofillProfile profile; + profile.SetCountryCode("US"); + + PhoneNumber phone(&profile); + phone.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("6502345678")); + EXPECT_EQ(ASCIIToUTF16("650"), phone.GetInfo(PHONE_HOME_CITY_CODE, "US")); + + // Update the area code. + phone.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("8322345678")); + EXPECT_EQ(ASCIIToUTF16("832"), phone.GetInfo(PHONE_HOME_CITY_CODE, "US")); + + // Change the phone number to have a UK format, but try to parse with the + // wrong locale. + phone.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("07023456789")); + EXPECT_EQ(string16(), phone.GetInfo(PHONE_HOME_CITY_CODE, "US")); + + // Now try parsing using the correct locale. Note that the profile's country + // code should override the app locale, which is still set to "US". + profile.SetCountryCode("GB"); + phone.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("07023456789")); + EXPECT_EQ(ASCIIToUTF16("70"), phone.GetInfo(PHONE_HOME_CITY_CODE, "US")); +} + +TEST(PhoneNumberTest, PhoneCombineHelper) { + AutofillProfile profile; + profile.SetCountryCode("US"); + + PhoneNumber::PhoneCombineHelper number1; + EXPECT_FALSE(number1.SetInfo(ADDRESS_BILLING_CITY, + ASCIIToUTF16("1"))); + EXPECT_TRUE(number1.SetInfo(PHONE_HOME_COUNTRY_CODE, + ASCIIToUTF16("1"))); + EXPECT_TRUE(number1.SetInfo(PHONE_HOME_CITY_CODE, + ASCIIToUTF16("650"))); + EXPECT_TRUE(number1.SetInfo(PHONE_HOME_NUMBER, + ASCIIToUTF16("2345678"))); + string16 parsed_phone; + EXPECT_TRUE(number1.ParseNumber(profile, "en-US", &parsed_phone)); + // International format as it has a country code. + EXPECT_EQ(ASCIIToUTF16("+1 650-234-5678"), parsed_phone); + + PhoneNumber::PhoneCombineHelper number3; + EXPECT_TRUE(number3.SetInfo(PHONE_HOME_CITY_CODE, + ASCIIToUTF16("650"))); + EXPECT_TRUE(number3.SetInfo(PHONE_HOME_NUMBER, + ASCIIToUTF16("2345680"))); + EXPECT_TRUE(number3.ParseNumber(profile, "en-US", &parsed_phone)); + // National format as it does not have a country code. + EXPECT_EQ(ASCIIToUTF16("(650) 234-5680"), parsed_phone); + + PhoneNumber::PhoneCombineHelper number4; + EXPECT_TRUE(number4.SetInfo(PHONE_HOME_CITY_CODE, + ASCIIToUTF16("123"))); // Incorrect city code. + EXPECT_TRUE(number4.SetInfo(PHONE_HOME_NUMBER, + ASCIIToUTF16("2345680"))); + EXPECT_FALSE(number4.ParseNumber(profile, "en-US", &parsed_phone)); + EXPECT_EQ(string16(), parsed_phone); + + PhoneNumber::PhoneCombineHelper number5; + EXPECT_TRUE(number5.SetInfo(PHONE_HOME_CITY_AND_NUMBER, + ASCIIToUTF16("6502345681"))); + EXPECT_TRUE(number5.ParseNumber(profile, "en-US", &parsed_phone)); + EXPECT_EQ(ASCIIToUTF16("(650) 234-5681"), parsed_phone); + + PhoneNumber::PhoneCombineHelper number6; + EXPECT_TRUE(number6.SetInfo(PHONE_HOME_CITY_CODE, + ASCIIToUTF16("650"))); + EXPECT_TRUE(number6.SetInfo(PHONE_HOME_NUMBER, + ASCIIToUTF16("234"))); + EXPECT_TRUE(number6.SetInfo(PHONE_HOME_NUMBER, + ASCIIToUTF16("5682"))); + EXPECT_TRUE(number6.ParseNumber(profile, "en-US", &parsed_phone)); + EXPECT_EQ(ASCIIToUTF16("(650) 234-5682"), parsed_phone); + + // Ensure parsing is possible when falling back to detecting the country code + // based on the app locale. + PhoneNumber::PhoneCombineHelper number7; + EXPECT_TRUE(number7.SetInfo(PHONE_HOME_CITY_CODE, + ASCIIToUTF16("650"))); + EXPECT_TRUE(number7.SetInfo(PHONE_HOME_NUMBER, + ASCIIToUTF16("234"))); + EXPECT_TRUE(number7.SetInfo(PHONE_HOME_NUMBER, + ASCIIToUTF16("5682"))); + EXPECT_TRUE(number7.ParseNumber(AutofillProfile(), "en-US", &parsed_phone)); + EXPECT_EQ(ASCIIToUTF16("(650) 234-5682"), parsed_phone); +} diff --git a/components/autofill/browser/risk/fingerprint.cc b/components/autofill/browser/risk/fingerprint.cc new file mode 100644 index 0000000..3e13e39 --- /dev/null +++ b/components/autofill/browser/risk/fingerprint.cc @@ -0,0 +1,422 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/risk/fingerprint.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/cpu.h" +#include "base/logging.h" +#include "base/strings/string_split.h" +#include "base/sys_info.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/risk/proto/fingerprint.pb.h" +#include "content/public/browser/content_browser_client.h" +#include "content/public/browser/font_list_async.h" +#include "content/public/browser/gpu_data_manager.h" +#include "content/public/browser/gpu_data_manager_observer.h" +#include "content/public/browser/plugin_service.h" +#include "content/public/browser/render_widget_host.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_view.h" +#include "content/public/common/content_client.h" +#include "content/public/common/gpu_info.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebRect.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebScreenInfo.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/screen.h" +#include "webkit/plugins/webplugininfo.h" + +using WebKit::WebScreenInfo; + +namespace autofill { +namespace risk { + +namespace { + +const int32 kFingerprinterVersion = 1; + +// Returns the delta between the local timezone and UTC. +base::TimeDelta GetTimezoneOffset() { + base::Time utc = base::Time::Now(); + + base::Time::Exploded local; + utc.LocalExplode(&local); + + return base::Time::FromUTCExploded(local) - utc; +} + +// Returns the concatenation of the operating system name and version, e.g. +// "Mac OS X 10.6.8". +std::string GetOperatingSystemVersion() { + return base::SysInfo::OperatingSystemName() + " " + + base::SysInfo::OperatingSystemVersion(); +} + +// Adds the list of |fonts| to the |machine|. +void AddFontsToFingerprint(const base::ListValue& fonts, + Fingerprint_MachineCharacteristics* machine) { + for (base::ListValue::const_iterator it = fonts.begin(); + it != fonts.end(); ++it) { + // Each item in the list is a two-element list such that the first element + // is the font family and the second is the font name. + const base::ListValue* font_description; + bool success = (*it)->GetAsList(&font_description); + DCHECK(success); + + std::string font_name; + success = font_description->GetString(1, &font_name); + DCHECK(success); + + machine->add_font(font_name); + } +} + +// Adds the list of |plugins| to the |machine|. +void AddPluginsToFingerprint(const std::vector<webkit::WebPluginInfo>& plugins, + Fingerprint_MachineCharacteristics* machine) { + for (std::vector<webkit::WebPluginInfo>::const_iterator it = plugins.begin(); + it != plugins.end(); ++it) { + Fingerprint_MachineCharacteristics_Plugin* plugin = + machine->add_plugin(); + plugin->set_name(UTF16ToUTF8(it->name)); + plugin->set_description(UTF16ToUTF8(it->desc)); + for (std::vector<webkit::WebPluginMimeType>::const_iterator mime_type = + it->mime_types.begin(); + mime_type != it->mime_types.end(); ++mime_type) { + plugin->add_mime_type(mime_type->mime_type); + } + plugin->set_version(UTF16ToUTF8(it->version)); + } +} + +// Adds the list of HTTP accept languages to the |machine|. +void AddAcceptLanguagesToFingerprint( + const std::string& accept_languages_str, + Fingerprint_MachineCharacteristics* machine) { + std::vector<std::string> accept_languages; + base::SplitString(accept_languages_str, ',', &accept_languages); + for (std::vector<std::string>::const_iterator it = accept_languages.begin(); + it != accept_languages.end(); ++it) { + machine->add_requested_language(*it); + } +} + +// Writes +// (a) the number of screens, +// (b) the primary display's screen size, +// (c) the screen's color depth, and +// (d) the size of the screen unavailable to web page content, +// i.e. the Taskbar size on Windows +// into the |machine|. +void AddScreenInfoToFingerprint(const WebScreenInfo& screen_info, + Fingerprint_MachineCharacteristics* machine) { + // TODO(scottmg): NativeScreen maybe wrong. http://crbug.com/133312 + machine->set_screen_count( + gfx::Screen::GetNativeScreen()->GetNumDisplays()); + + gfx::Size screen_size = + gfx::Screen::GetNativeScreen()->GetPrimaryDisplay().GetSizeInPixel(); + machine->mutable_screen_size()->set_width(screen_size.width()); + machine->mutable_screen_size()->set_height(screen_size.height()); + + machine->set_screen_color_depth(screen_info.depth); + + gfx::Rect screen_rect(screen_info.rect); + gfx::Rect available_rect(screen_info.availableRect); + gfx::Rect unavailable_rect = gfx::SubtractRects(screen_rect, available_rect); + machine->mutable_unavailable_screen_size()->set_width( + unavailable_rect.width()); + machine->mutable_unavailable_screen_size()->set_height( + unavailable_rect.height()); +} + +// Writes info about the machine's CPU into the |machine|. +void AddCpuInfoToFingerprint(Fingerprint_MachineCharacteristics* machine) { + base::CPU cpu; + machine->mutable_cpu()->set_vendor_name(cpu.vendor_name()); + machine->mutable_cpu()->set_brand(cpu.cpu_brand()); +} + +// Writes info about the machine's GPU into the |machine|. +void AddGpuInfoToFingerprint(Fingerprint_MachineCharacteristics* machine) { + const content::GPUInfo& gpu_info = + content::GpuDataManager::GetInstance()->GetGPUInfo(); + DCHECK(gpu_info.finalized); + + Fingerprint_MachineCharacteristics_Graphics* graphics = + machine->mutable_graphics_card(); + graphics->set_vendor_id(gpu_info.gpu.vendor_id); + graphics->set_device_id(gpu_info.gpu.device_id); + graphics->set_driver_version(gpu_info.driver_version); + graphics->set_driver_date(gpu_info.driver_date); + + Fingerprint_MachineCharacteristics_Graphics_PerformanceStatistics* + gpu_performance = graphics->mutable_performance_statistics(); + gpu_performance->set_graphics_score(gpu_info.performance_stats.graphics); + gpu_performance->set_gaming_score(gpu_info.performance_stats.gaming); + gpu_performance->set_overall_score(gpu_info.performance_stats.overall); +} + +// Waits for all asynchronous data required for the fingerprint to be loaded; +// then fills out the fingerprint. +class FingerprintDataLoader : public content::GpuDataManagerObserver { + public: + FingerprintDataLoader( + int64 gaia_id, + const gfx::Rect& window_bounds, + const gfx::Rect& content_bounds, + const WebScreenInfo& screen_info, + const std::string& version, + const std::string& charset, + const std::string& accept_languages, + const base::Time& install_time, + const base::Callback<void(scoped_ptr<Fingerprint>)>& callback); + + private: + virtual ~FingerprintDataLoader(); + + // content::GpuDataManagerObserver: + virtual void OnGpuInfoUpdate() OVERRIDE; + + // Callbacks for asynchronously loaded data. + void OnGotFonts(scoped_ptr<base::ListValue> fonts); + void OnGotPlugins(const std::vector<webkit::WebPluginInfo>& plugins); + + // If all of the asynchronous data has been loaded, calls |callback_| with + // the fingerprint data. + void MaybeFillFingerprint(); + + // Calls |callback_| with the fingerprint data. + void FillFingerprint(); + + // The GPU data provider. + content::GpuDataManager* const gpu_data_manager_; + + // Data that will be passed on to the next loading phase. + const int64 gaia_id_; + const gfx::Rect window_bounds_; + const gfx::Rect content_bounds_; + const WebScreenInfo screen_info_; + const std::string version_; + const std::string charset_; + const std::string accept_languages_; + const base::Time install_time_; + + // Data that will be loaded asynchronously. + scoped_ptr<base::ListValue> fonts_; + std::vector<webkit::WebPluginInfo> plugins_; + bool has_loaded_plugins_; + + // The callback that will be called once all the data is available. + base::Callback<void(scoped_ptr<Fingerprint>)> callback_; + + DISALLOW_COPY_AND_ASSIGN(FingerprintDataLoader); +}; + +FingerprintDataLoader::FingerprintDataLoader( + int64 gaia_id, + const gfx::Rect& window_bounds, + const gfx::Rect& content_bounds, + const WebScreenInfo& screen_info, + const std::string& version, + const std::string& charset, + const std::string& accept_languages, + const base::Time& install_time, + const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) + : gpu_data_manager_(content::GpuDataManager::GetInstance()), + gaia_id_(gaia_id), + window_bounds_(window_bounds), + content_bounds_(content_bounds), + screen_info_(screen_info), + version_(version), + charset_(charset), + accept_languages_(accept_languages), + install_time_(install_time), + has_loaded_plugins_(false), + callback_(callback) { + DCHECK(!install_time_.is_null()); + + // TODO(isherman): Investigating http://crbug.com/174296 + LOG(WARNING) << "Loading fingerprint data."; + + // Load GPU data if needed. + if (!gpu_data_manager_->IsCompleteGpuInfoAvailable()) { + // TODO(isherman): Investigating http://crbug.com/174296 + LOG(WARNING) << "Loading GPU data."; + + gpu_data_manager_->AddObserver(this); + gpu_data_manager_->RequestCompleteGpuInfoIfNeeded(); + } else { + // TODO(isherman): Investigating http://crbug.com/174296 + LOG(WARNING) << "GPU data already loaded."; + } + + // Load plugin data. + content::PluginService::GetInstance()->GetPlugins( + base::Bind(&FingerprintDataLoader::OnGotPlugins, base::Unretained(this))); + + // Load font data. + content::GetFontListAsync( + base::Bind(&FingerprintDataLoader::OnGotFonts, base::Unretained(this))); +} + +FingerprintDataLoader::~FingerprintDataLoader() { +} + +void FingerprintDataLoader::OnGpuInfoUpdate() { + if (!gpu_data_manager_->IsCompleteGpuInfoAvailable()) { + // TODO(isherman): Investigating http://crbug.com/174296 + LOG(WARNING) << "OnGpuInfoUpdate() called without complete GPU info."; + return; + } + + // TODO(isherman): Investigating http://crbug.com/174296 + LOG(WARNING) << "Loaded GPU data."; + + gpu_data_manager_->RemoveObserver(this); + MaybeFillFingerprint(); +} + +void FingerprintDataLoader::OnGotFonts(scoped_ptr<base::ListValue> fonts) { + // TODO(isherman): Investigating http://crbug.com/174296 + LOG(WARNING) << "Loaded fonts."; + + DCHECK(!fonts_); + fonts_.reset(fonts.release()); + MaybeFillFingerprint(); +} + +void FingerprintDataLoader::OnGotPlugins( + const std::vector<webkit::WebPluginInfo>& plugins) { + // TODO(isherman): Investigating http://crbug.com/174296 + LOG(WARNING) << "Loaded plugins."; + + DCHECK(!has_loaded_plugins_); + has_loaded_plugins_ = true; + plugins_ = plugins; + MaybeFillFingerprint(); +} + +void FingerprintDataLoader::MaybeFillFingerprint() { + // TODO(isherman): Investigating http://crbug.com/174296 + LOG(WARNING) << "GPU data: " + << (gpu_data_manager_->IsCompleteGpuInfoAvailable() ? + "loaded" : + "waiting"); + LOG(WARNING) << "Fonts: " + << (fonts_ ? "loaded" : "waiting"); + LOG(WARNING) << "Plugins: " + << (has_loaded_plugins_ ? "loaded" : "waiting"); + + // If all of the data has been loaded, fill the fingerprint and clean up. + if (gpu_data_manager_->IsCompleteGpuInfoAvailable() && + fonts_ && + has_loaded_plugins_) { + FillFingerprint(); + delete this; + } +} + +void FingerprintDataLoader::FillFingerprint() { + scoped_ptr<Fingerprint> fingerprint(new Fingerprint); + Fingerprint_MachineCharacteristics* machine = + fingerprint->mutable_machine_characteristics(); + + machine->set_operating_system_build(GetOperatingSystemVersion()); + // We use the delta between the install time and the Unix epoch, in hours. + machine->set_browser_install_time_hours( + (install_time_ - base::Time::UnixEpoch()).InHours()); + machine->set_utc_offset_ms(GetTimezoneOffset().InMilliseconds()); + machine->set_browser_language( + content::GetContentClient()->browser()->GetApplicationLocale()); + machine->set_charset(charset_); + machine->set_user_agent(content::GetContentClient()->GetUserAgent()); + machine->set_ram(base::SysInfo::AmountOfPhysicalMemory()); + machine->set_browser_build(version_); + AddFontsToFingerprint(*fonts_, machine); + AddPluginsToFingerprint(plugins_, machine); + AddAcceptLanguagesToFingerprint(accept_languages_, machine); + AddScreenInfoToFingerprint(screen_info_, machine); + AddCpuInfoToFingerprint(machine); + AddGpuInfoToFingerprint(machine); + + // TODO(isherman): Store the partition size of the hard drives? + + Fingerprint_TransientState* transient_state = + fingerprint->mutable_transient_state(); + Fingerprint_Dimension* inner_window_size = + transient_state->mutable_inner_window_size(); + inner_window_size->set_width(content_bounds_.width()); + inner_window_size->set_height(content_bounds_.height()); + Fingerprint_Dimension* outer_window_size = + transient_state->mutable_outer_window_size(); + outer_window_size->set_width(window_bounds_.width()); + outer_window_size->set_height(window_bounds_.height()); + + // TODO(isherman): Record network performance data, which is theoretically + // available to JS. + + // TODO(isherman): Record user behavior data. + + Fingerprint_Metadata* metadata = fingerprint->mutable_metadata(); + metadata->set_timestamp_ms( + (base::Time::Now() - base::Time::UnixEpoch()).InMilliseconds()); + metadata->set_gaia_id(gaia_id_); + metadata->set_fingerprinter_version(kFingerprinterVersion); + + callback_.Run(fingerprint.Pass()); +} + +} // namespace + +void GetFingerprint( + int64 gaia_id, + const gfx::Rect& window_bounds, + const content::WebContents& web_contents, + const std::string& version, + const std::string& charset, + const std::string& accept_languages, + const base::Time& install_time, + const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) { + gfx::Rect content_bounds; + web_contents.GetView()->GetContainerBounds(&content_bounds); + + WebKit::WebScreenInfo screen_info; + content::RenderWidgetHostView* host_view = + web_contents.GetRenderWidgetHostView(); + if (host_view) + host_view->GetRenderWidgetHost()->GetWebScreenInfo(&screen_info); + + internal::GetFingerprintInternal( + gaia_id, window_bounds, content_bounds, screen_info, version, charset, + accept_languages, install_time, callback); +} + +namespace internal { + +void GetFingerprintInternal( + int64 gaia_id, + const gfx::Rect& window_bounds, + const gfx::Rect& content_bounds, + const WebKit::WebScreenInfo& screen_info, + const std::string& version, + const std::string& charset, + const std::string& accept_languages, + const base::Time& install_time, + const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) { + // Begin loading all of the data that we need to load asynchronously. + // This class is responsible for freeing its own memory. + new FingerprintDataLoader(gaia_id, window_bounds, content_bounds, screen_info, + version, charset, accept_languages, install_time, + callback); +} + +} // namespace internal + +} // namespace risk +} // namespace autofill diff --git a/components/autofill/browser/risk/fingerprint.h b/components/autofill/browser/risk/fingerprint.h new file mode 100644 index 0000000..75c34d4 --- /dev/null +++ b/components/autofill/browser/risk/fingerprint.h @@ -0,0 +1,75 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_RISK_FINGERPRINT_H_ +#define COMPONENTS_AUTOFILL_BROWSER_RISK_FINGERPRINT_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" + +class PrefService; + +namespace base { +class Time; +} + +namespace content { +class WebContents; +} + +namespace gfx { +class Rect; +} + +namespace WebKit { +struct WebScreenInfo; +} + +namespace autofill { +namespace risk { + +class Fingerprint; + +// Asynchronously calls |callback| with statistics that, collectively, provide a +// unique fingerprint for this (machine, user) pair, used for fraud prevention. +// |gaia_id| should be the user id for Google's authentication system. +// |window_bounds| should be the bounds of the containing Chrome window. +// |web_contents| should be the host for the page the user is interacting with. +// |version| is the version number of the application. +// |charset| is the default character set. +// |accept_languages| is the Accept-Languages setting. +// |install_time| is the absolute time of installation. +void GetFingerprint( + int64 gaia_id, + const gfx::Rect& window_bounds, + const content::WebContents& web_contents, + const std::string& version, + const std::string& charset, + const std::string& accept_languages, + const base::Time& install_time, + const base::Callback<void(scoped_ptr<Fingerprint>)>& callback); + +// Exposed for testing: +namespace internal { + +void GetFingerprintInternal( + int64 gaia_id, + const gfx::Rect& window_bounds, + const gfx::Rect& content_bounds, + const WebKit::WebScreenInfo& screen_info, + const std::string& version, + const std::string& charset, + const std::string& accept_languages, + const base::Time& install_time, + const base::Callback<void(scoped_ptr<Fingerprint>)>& callback); + +} // namespace internal + +} // namespace risk +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_RISK_FINGERPRINT_H_ diff --git a/components/autofill/browser/risk/proto/fingerprint.proto b/components/autofill/browser/risk/proto/fingerprint.proto new file mode 100644 index 0000000..bd23575 --- /dev/null +++ b/components/autofill/browser/risk/proto/fingerprint.proto @@ -0,0 +1,218 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// This file contains the definition of protocol buffers for native browser +// fingerprinting. + +syntax = "proto2"; + +option optimize_for = LITE_RUNTIME; + +package autofill.risk; + +message Fingerprint { + // A simple protocol message to represent objects with width and height. + message Dimension { + optional int32 width = 1; + optional int32 height = 2; + } + + // Characteristics of the user's machine that are relatively durable, + // i.e. that are expected to change relatively infrequently. + message MachineCharacteristics { + // A simple protocol message that represents a plugin. + // e.g. flash, shockwave, acrobat reader, gears, picasa + message Plugin { + optional string name = 1; + optional string description = 2; + repeated string mime_type = 3; + optional string version = 4; + } + + // Information on the CPU. + message Cpu { + // e.g. "GenuineIntel" + optional string vendor_name = 1; + // e.g. "Intel(R) Xeon(R) CPU X5650 @ 2.67GHz\000" + optional string brand = 2; + } + + // Information on the GPU. + message Graphics { + // The GPU manufacturer's vendor id. + optional uint32 vendor_id = 1; + + // The GPU manufacturer's device id for the chip set. + optional uint32 device_id = 2; + + // The driver version on the GPU. + optional string driver_version = 3; + + // The driver date on the GPU. + optional string driver_date = 4; + + // The GPU performance statistics. + message PerformanceStatistics { + optional float graphics_score = 1; + optional float gaming_score = 2; + optional float overall_score = 3; + } + optional PerformanceStatistics performance_statistics = 5; + } + + // Username currently logged into computer / device. + // TODO(isherman): This seems like TMI. + optional string user_name = 1; + + // Build version string for the current operating system. + optional string operating_system_build = 2; + + // e.g. User-assigned computer name. + // TODO(isherman): This seems like TMI. + optional string device_name = 3; + + // Browser install time (hours since epoch). + optional int64 browser_install_time_hours = 4; + + // Fonts installed on the machine. + repeated string font = 5; + + // Plug-ins installed on the machine. + repeated Plugin plugin = 6; + + // Delta in ms of the device's time zone from UTC. + optional int64 utc_offset_ms = 7; + + // IETF-formatted language tag. e.g. "en", "en-US", "es-419", etc. + // http://en.wikipedia.org/wiki/IETF_language_tag + optional string browser_language = 8; + + // User-requested language code of viewed sites. Languages in + // accept-languages. + repeated string requested_language = 9; + + // Default charset of the browser. (e.g. ISO-8859-1, obtained from + // document.defaultCharset) + optional string charset = 10; + + // The number of physical screens. + optional int32 screen_count = 11; + + // Information about the user's monitor's physical screen size. + // (e.g. 1024 x 768) + optional Dimension screen_size = 12; + + // The color depth of the user's screen (obtained from screen.colorDepth + // or screen.pixelDepth) + optional int32 screen_color_depth = 13; + + // Information about the size of the portion of the screen that is unusable + // to a program (i.e. on Windows, the portion of the screen that is taken + // up by the taskbar) + optional Dimension unavailable_screen_size = 14; + + optional string user_agent = 15; + + // Total size of each hard drive partition. + repeated int32 partition_size = 16; + + optional Cpu cpu = 17; + + // Total RAM in bytes. + optional int64 ram = 18; + + // Graphics card being used. + optional Graphics graphics_card = 19; + + // Build version string for browser. + optional string browser_build = 20; + + } + + // Contains properties relating to more transient computer / browser state. + message TransientState { + // Corresponds to window.innerWidth / innertHeight + optional Dimension inner_window_size = 1; + + // Corresponds to window.outerWidth / outerHeight + optional Dimension outer_window_size = 2; + } + + // Measures computer / network performance. + message Performance { + // Bandwidth in MB/s. network.connection.bandwidth + optional float bandwidth = 1; + // Whether bandwidth cost is metered. network.connection.metered + optional bool metered = 2; + // Whether it's wifi, 3g, 2g, etc. network.connection.type + optional string network_type = 3; + } + + // Properties describing the user -- especially the user's state in the + // physical world. + message UserCharacteristics { + message Vector { + optional int32 x = 1; + optional int32 y = 2; + optional int32 z = 3; + } + + message Location { + // Meters above sea level. + optional double altitude = 1; + // Latitude in degrees. + optional double latitude = 2; + // Longitude in degrees. + optional double longitude = 3; + // Accuracy in meters. 95% probability of being in this radius of + // lat / long. + optional float accuracy = 4; + // Milliseconds since epoch since measurement. + optional double time_in_ms = 5; + } + + // Average force by finger presses. TouchEvent.force + optional float force = 1; + // Average finger width. TouchEvent.radiusX + optional float touch_width = 2; + // Average finger height. TouchEvent.radiusY + optional float touch_height = 3; + // TouchEvent.rotationAngle + optional int32 touch_rotation = 4; + // Orientation while user is navigating flow and the device is roughly + // stable. x for alpha, y for beta, z for gamma + // TODO(isherman): Orientation data is only available asynchronously in + // Chrome. + optional Vector device_orientation = 5; + // Acceleration while measuring orientation. + // TODO(isherman): Acceleration data is not available in Chrome. + optional Vector device_acceleration = 6; + optional Location location = 7; + } + + // Metadata associated with data collection or the user that doesn't actually + // fingerprint the device. + message Metadata { + // When this data was collected / received, in milliseconds since the epoch. + optional int64 timestamp_ms = 1; + // Gaia id associated with transaction. + optional int64 gaia_id = 2; + // Version of the native library generating this proto. + // This may be manually bumped when the code populating the proto has + // significantly changed. + optional int32 fingerprinter_version = 3; + } + + // Computer / browser fingerprint. + optional MachineCharacteristics machine_characteristics = 1; + + optional Performance performance = 2; + + optional UserCharacteristics user_characteristics = 3; + + optional TransientState transient_state = 4; + + // Metadata associated with data collection. + optional Metadata metadata = 5; +}
\ No newline at end of file diff --git a/components/autofill/browser/test_autofill_external_delegate.cc b/components/autofill/browser/test_autofill_external_delegate.cc new file mode 100644 index 0000000..31bb02b --- /dev/null +++ b/components/autofill/browser/test_autofill_external_delegate.cc @@ -0,0 +1,29 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/test_autofill_external_delegate.h" + +#include "ui/gfx/rect.h" + +namespace autofill { + +void GenerateTestAutofillPopup( + AutofillExternalDelegate* autofill_external_delegate) { + int query_id = 1; + FormData form; + FormFieldData field; + field.is_focusable = true; + field.should_autocomplete = true; + gfx::RectF bounds(100.f, 100.f); + autofill_external_delegate->OnQuery(query_id, form, field, bounds, false); + + std::vector<string16> autofill_item; + autofill_item.push_back(string16()); + std::vector<int> autofill_id; + autofill_id.push_back(0); + autofill_external_delegate->OnSuggestionsReturned( + query_id, autofill_item, autofill_item, autofill_item, autofill_id); +} + +} // namespace autofill diff --git a/components/autofill/browser/test_autofill_external_delegate.h b/components/autofill/browser/test_autofill_external_delegate.h new file mode 100644 index 0000000..81268d7 --- /dev/null +++ b/components/autofill/browser/test_autofill_external_delegate.h @@ -0,0 +1,21 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_TEST_AUTOFILL_EXTERNAL_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_BROWSER_TEST_AUTOFILL_EXTERNAL_DELEGATE_H_ + +#include "components/autofill/browser/autofill_external_delegate.h" + +class AutofillManager; + +namespace autofill { + +// Calls the required functions on the given external delegate to cause the +// delegate to display a popup. +void GenerateTestAutofillPopup( + AutofillExternalDelegate* autofill_external_delegate); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_TEST_AUTOFILL_EXTERNAL_DELEGATE_H_ diff --git a/components/autofill/browser/test_autofill_manager_delegate.cc b/components/autofill/browser/test_autofill_manager_delegate.cc new file mode 100644 index 0000000..7c8d834 --- /dev/null +++ b/components/autofill/browser/test_autofill_manager_delegate.cc @@ -0,0 +1,74 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/test_autofill_manager_delegate.h" + +namespace autofill { + +TestAutofillManagerDelegate::TestAutofillManagerDelegate() {} +TestAutofillManagerDelegate::~TestAutofillManagerDelegate() {} + +PersonalDataManager* TestAutofillManagerDelegate::GetPersonalDataManager() { + return NULL; +} + +PrefService* TestAutofillManagerDelegate::GetPrefs() { + return NULL; +} + +void TestAutofillManagerDelegate::HideRequestAutocompleteDialog() {} + +bool TestAutofillManagerDelegate::IsSavingPasswordsEnabled() const { + return false; +} + +bool TestAutofillManagerDelegate::IsPasswordSyncEnabled() const { + return false; +} + +void TestAutofillManagerDelegate::SetSyncStateChangedCallback( + const base::Closure& callback) { } + +void TestAutofillManagerDelegate::OnAutocheckoutError() {} + +void TestAutofillManagerDelegate::ShowAutofillSettings() {} + +void TestAutofillManagerDelegate::ConfirmSaveCreditCard( + const AutofillMetrics& metric_logger, + const CreditCard& credit_card, + const base::Closure& save_card_callback) {} + +void TestAutofillManagerDelegate::ShowPasswordGenerationBubble( + const gfx::Rect& bounds, + const content::PasswordForm& form, + autofill::PasswordGenerator* generator) {} + +void TestAutofillManagerDelegate::ShowAutocheckoutBubble( + const gfx::RectF& bounding_box, + const gfx::NativeView& native_view, + const base::Closure& callback) {} + +void TestAutofillManagerDelegate::ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + const content::SSLStatus& ssl_status, + const AutofillMetrics& metric_logger, + DialogType dialog_type, + const base::Callback<void(const FormStructure*)>& callback) {} + +void TestAutofillManagerDelegate::RequestAutocompleteDialogClosed() {} + +void TestAutofillManagerDelegate::ShowAutofillPopup( + const gfx::RectF& element_bounds, + const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& identifiers, + AutofillPopupDelegate* delegate) {} + +void TestAutofillManagerDelegate::HideAutofillPopup() {} + +void TestAutofillManagerDelegate::UpdateProgressBar(double value) {} + +} // namespace autofill diff --git a/components/autofill/browser/test_autofill_manager_delegate.h b/components/autofill/browser/test_autofill_manager_delegate.h new file mode 100644 index 0000000..64873c02 --- /dev/null +++ b/components/autofill/browser/test_autofill_manager_delegate.h @@ -0,0 +1,65 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_TEST_AUTOFILL_MANAGER_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_BROWSER_TEST_AUTOFILL_MANAGER_DELEGATE_H_ + +#include "base/compiler_specific.h" +#include "components/autofill/browser/autofill_manager_delegate.h" + +namespace autofill { + +// This class is only for easier writing of testings. All pure virtual functions +// have been giving empty methods. +class TestAutofillManagerDelegate : public AutofillManagerDelegate { + public: + TestAutofillManagerDelegate(); + virtual ~TestAutofillManagerDelegate(); + + // AutofillManagerDelegate implementation. + virtual PersonalDataManager* GetPersonalDataManager() OVERRIDE; + virtual PrefService* GetPrefs() OVERRIDE; + virtual void HideRequestAutocompleteDialog() OVERRIDE; + virtual bool IsSavingPasswordsEnabled() const OVERRIDE; + virtual bool IsPasswordSyncEnabled() const OVERRIDE; + virtual void SetSyncStateChangedCallback( + const base::Closure& callback) OVERRIDE; + virtual void OnAutocheckoutError() OVERRIDE; + virtual void ShowAutofillSettings() OVERRIDE; + virtual void ConfirmSaveCreditCard( + const AutofillMetrics& metric_logger, + const CreditCard& credit_card, + const base::Closure& save_card_callback) OVERRIDE; + virtual void ShowPasswordGenerationBubble( + const gfx::Rect& bounds, + const content::PasswordForm& form, + autofill::PasswordGenerator* generator) OVERRIDE; + virtual void ShowAutocheckoutBubble( + const gfx::RectF& bounding_box, + const gfx::NativeView& native_view, + const base::Closure& callback) OVERRIDE; + virtual void ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + const content::SSLStatus& ssl_status, + const AutofillMetrics& metric_logger, + DialogType dialog_type, + const base::Callback<void(const FormStructure*)>& callback) OVERRIDE; + virtual void RequestAutocompleteDialogClosed() OVERRIDE; + virtual void ShowAutofillPopup(const gfx::RectF& element_bounds, + const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& identifiers, + AutofillPopupDelegate* delegate) OVERRIDE; + virtual void HideAutofillPopup() OVERRIDE; + virtual void UpdateProgressBar(double value) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(TestAutofillManagerDelegate); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_TEST_AUTOFILL_MANAGER_DELEGATE_H_ diff --git a/components/autofill/browser/validation.cc b/components/autofill/browser/validation.cc new file mode 100644 index 0000000..1053845 --- /dev/null +++ b/components/autofill/browser/validation.cc @@ -0,0 +1,76 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/validation.h" + +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/autofill_regexes.h" +#include "components/autofill/browser/credit_card.h" + +namespace autofill { + +bool IsValidCreditCardNumber(const string16& text) { + string16 number = CreditCard::StripSeparators(text); + + // Credit card numbers are at most 19 digits in length [1]. 12 digits seems to + // be a fairly safe lower-bound [2]. + // [1] http://www.merriampark.com/anatomycc.htm + // [2] http://en.wikipedia.org/wiki/Bank_card_number + const size_t kMinCreditCardDigits = 12; + const size_t kMaxCreditCardDigits = 19; + if (number.size() < kMinCreditCardDigits || + number.size() > kMaxCreditCardDigits) + return false; + + // Use the Luhn formula [3] to validate the number. + // [3] http://en.wikipedia.org/wiki/Luhn_algorithm + int sum = 0; + bool odd = false; + for (string16::reverse_iterator iter = number.rbegin(); + iter != number.rend(); + ++iter) { + if (!IsAsciiDigit(*iter)) + return false; + + int digit = *iter - '0'; + if (odd) { + digit *= 2; + sum += digit / 10 + digit % 10; + } else { + sum += digit; + } + odd = !odd; + } + + return (sum % 10) == 0; +} + +bool IsValidCreditCardSecurityCode(const string16& text) { + if (text.size() < 3U || text.size() > 4U) + return false; + + for (string16::const_iterator iter = text.begin(); + iter != text.end(); + ++iter) { + if (!IsAsciiDigit(*iter)) + return false; + } + return true; +} + +bool IsValidEmailAddress(const string16& text) { + // E-Mail pattern as defined by the WhatWG. (4.10.7.1.5 E-Mail state) + const string16 kEmailPattern = ASCIIToUTF16( + "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@" + "[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$"); + return MatchesPattern(text, kEmailPattern); +} + +bool IsValidZip(const string16& value) { + const string16 kZipPattern = ASCIIToUTF16("^\\d{5}(-\\d{4})?$"); + return MatchesPattern(value, kZipPattern); +} + +} // namespace autofill diff --git a/components/autofill/browser/validation.h b/components/autofill/browser/validation.h new file mode 100644 index 0000000..3f6ce9a --- /dev/null +++ b/components/autofill/browser/validation.h @@ -0,0 +1,28 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_VALIDATION_H_ +#define COMPONENTS_AUTOFILL_BROWSER_VALIDATION_H_ + +#include "base/string16.h" + +namespace autofill { + +// Returns true if |text| looks like a valid credit card number. +// Uses the Luhn formula to validate the number. +bool IsValidCreditCardNumber(const string16& text); + +// Returns true if |text| looks like a valid credit card security code. +bool IsValidCreditCardSecurityCode(const string16& text); + +// Returns true if |text| looks like a valid e-mail address. +bool IsValidEmailAddress(const string16& text); + +// Returns true if |text| looks like a valid zip code. +// Valid for US zip codes only. +bool IsValidZip(const string16& text); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_VALIDATION_H_ diff --git a/components/autofill/browser/validation_unittest.cc b/components/autofill/browser/validation_unittest.cc new file mode 100644 index 0000000..3c1475b --- /dev/null +++ b/components/autofill/browser/validation_unittest.cc @@ -0,0 +1,101 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/validation.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +// From https://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm +const char* const kValidNumbers[] = { + "378282246310005", + "3714 4963 5398 431", + "3787-3449-3671-000", + "5610591081018250", + "3056 9309 0259 04", + "3852-0000-0232-37", + "6011111111111117", + "6011 0009 9013 9424", + "3530-1113-3330-0000", + "3566002020360505", + "5555 5555 5555 4444", + "5105-1051-0510-5100", + "4111111111111111", + "4012 8888 8888 1881", + "4222-2222-2222-2", + "5019717010103742", + "6331101999990016", +}; +const char* const kInvalidNumbers[] = { + "4111 1111 112", /* too short */ + "41111111111111111115", /* too long */ + "4111-1111-1111-1110", /* wrong Luhn checksum */ + "3056 9309 0259 04aa", /* non-digit characters */ +}; +const char* const kValidCreditCardSecurityCode[] = { + "323", // 3-digit CSC. + "3234", // 4-digit CSC. +}; +const char* const kInvalidCreditCardSecurityCode[] = { + "32", // CSC too short. + "12345", // CSC too long. + "asd", // non-numeric CSC. +}; +const char* const kValidEmailAddress[] = { + "user@example", + "user@example.com", + "user@subdomain.example.com", + "user+postfix@example.com", +}; +const char* const kInvalidEmailAddress[] = { + "user", + "foo.com", + "user@", + "user@=example.com" +}; +} // namespace + +TEST(AutofillValidation, IsValidCreditCardNumber) { + for (size_t i = 0; i < arraysize(kValidNumbers); ++i) { + SCOPED_TRACE(kValidNumbers[i]); + EXPECT_TRUE( + autofill::IsValidCreditCardNumber(ASCIIToUTF16(kValidNumbers[i]))); + } + for (size_t i = 0; i < arraysize(kInvalidNumbers); ++i) { + SCOPED_TRACE(kInvalidNumbers[i]); + EXPECT_FALSE( + autofill::IsValidCreditCardNumber(ASCIIToUTF16(kInvalidNumbers[i]))); + } +} + +TEST(AutofillValidation, IsValidCreditCardSecurityCode) { + for (size_t i = 0; i < arraysize(kValidCreditCardSecurityCode); ++i) { + SCOPED_TRACE(kValidCreditCardSecurityCode[i]); + EXPECT_TRUE( + autofill::IsValidCreditCardSecurityCode( + ASCIIToUTF16(kValidCreditCardSecurityCode[i]))); + } + for (size_t i = 0; i < arraysize(kInvalidCreditCardSecurityCode); ++i) { + SCOPED_TRACE(kInvalidCreditCardSecurityCode[i]); + EXPECT_FALSE( + autofill::IsValidCreditCardSecurityCode( + ASCIIToUTF16(kInvalidCreditCardSecurityCode[i]))); + } +} + +TEST(AutofillValidation, IsValidEmailAddress) { + for (size_t i = 0; i < arraysize(kValidEmailAddress); ++i) { + SCOPED_TRACE(kValidEmailAddress[i]); + EXPECT_TRUE( + autofill::IsValidEmailAddress(ASCIIToUTF16(kValidEmailAddress[i]))); + } + for (size_t i = 0; i < arraysize(kInvalidEmailAddress); ++i) { + SCOPED_TRACE(kInvalidEmailAddress[i]); + EXPECT_FALSE( + autofill::IsValidEmailAddress(ASCIIToUTF16(kInvalidEmailAddress[i]))); + } +} + + diff --git a/components/autofill/browser/wallet/cart.cc b/components/autofill/browser/wallet/cart.cc new file mode 100644 index 0000000..b6f41a4 --- /dev/null +++ b/components/autofill/browser/wallet/cart.cc @@ -0,0 +1,25 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/cart.h" + +#include "base/values.h" + +namespace autofill { +namespace wallet { + +Cart::Cart(const std::string& total_price, const std::string& currency_code) + : total_price_(total_price), currency_code_(currency_code) {} + +Cart::~Cart() {} + +scoped_ptr<base::DictionaryValue> Cart::ToDictionary() const { + base::DictionaryValue* dict = new base::DictionaryValue(); + dict->SetString("total_price", total_price_); + dict->SetString("currency_code", currency_code_); + return scoped_ptr<base::DictionaryValue>(dict); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/cart.h b/components/autofill/browser/wallet/cart.h new file mode 100644 index 0000000..fc106c1 --- /dev/null +++ b/components/autofill/browser/wallet/cart.h @@ -0,0 +1,50 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_CART_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_CART_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { +namespace wallet { + +// Container object for purchase data provided by the browser. The enclosed data +// is required to request a FullWallet from Online Wallet in order to set +// spending limits on the generated proxy card. If the actual amount is not +// available, the maximum allowable value should be used: $1850 USD. Online +// Wallet is designed to accept price information in addition to information +// about the items being purchased hence the name Cart. +class Cart { + public: + Cart(const std::string& total_price, const std::string& currency_code); + ~Cart(); + + scoped_ptr<base::DictionaryValue> ToDictionary() const; + + const std::string& total_price() const { return total_price_; } + const std::string& currency_code() const { return currency_code_; } + + private: + // |total_price_| must be a formatted as a double with no more than two + // decimals, e.g. 100.99 + std::string total_price_; + + // |currency_code_| must be one of the ISO 4217 currency codes, e.g. USD. + std::string currency_code_; + + DISALLOW_ASSIGN(Cart); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_CART_H_ diff --git a/components/autofill/browser/wallet/cart_unittest.cc b/components/autofill/browser/wallet/cart_unittest.cc new file mode 100644 index 0000000..ae84c1b --- /dev/null +++ b/components/autofill/browser/wallet/cart_unittest.cc @@ -0,0 +1,21 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/values.h" +#include "components/autofill/browser/wallet/cart.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { +namespace wallet { + +TEST(Cart, ToDictionary) { + base::DictionaryValue expected; + expected.SetString("total_price", "total_price"); + expected.SetString("currency_code", "currency_code"); + Cart cart("total_price", "currency_code"); + ASSERT_TRUE(expected.Equals(cart.ToDictionary().get())); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/encryption_escrow_client.cc b/components/autofill/browser/wallet/encryption_escrow_client.cc new file mode 100644 index 0000000..b75e792 --- /dev/null +++ b/components/autofill/browser/wallet/encryption_escrow_client.cc @@ -0,0 +1,178 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/encryption_escrow_client.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_number_conversions.h" +#include "base/stringprintf.h" +#include "base/strings/string_split.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/wallet/encryption_escrow_client_observer.h" +#include "components/autofill/browser/wallet/instrument.h" +#include "components/autofill/browser/wallet/wallet_service_url.h" +#include "googleurl/src/gurl.h" +#include "net/base/escape.h" +#include "net/http/http_status_code.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_request_context_getter.h" + +namespace { + +const char kEncryptOtpBodyFormat[] = "cvv=%s:%s"; +const char kEscrowInstrumentInformationFormat[] = "gid=%s&cardNumber=%s&cvv=%s"; +const char kEscrowCardVerficationNumberFormat[] = "gid=%s&cvv=%s"; +const char kApplicationMimeType[] = "application/x-www-form-urlencoded"; + +// The maximum number of bits in the one time pad that the server is willing to +// accept. +const size_t kMaxBits = 56; + +// The minimum number of bits in the one time pad that the server is willing to +// accept. +const size_t kMinBits = 40; + +} // anonymous namespace + +namespace autofill { +namespace wallet { + +EncryptionEscrowClient::EncryptionEscrowClient( + net::URLRequestContextGetter* context_getter, + EncryptionEscrowClientObserver* observer) + : context_getter_(context_getter), + observer_(observer), + request_type_(NO_PENDING_REQUEST) { + DCHECK(context_getter_); + DCHECK(observer_); +} + +EncryptionEscrowClient::~EncryptionEscrowClient() {} + +void EncryptionEscrowClient::EncryptOneTimePad( + const std::vector<uint8>& one_time_pad) { + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + size_t num_bits = one_time_pad.size() * 8; + DCHECK_LE(num_bits, kMaxBits); + DCHECK_GE(num_bits, kMinBits); + + request_type_ = ENCRYPT_ONE_TIME_PAD; + + std::string post_body = StringPrintf( + kEncryptOtpBodyFormat, + base::HexEncode(&num_bits, 1).c_str(), + base::HexEncode(&(one_time_pad[0]), one_time_pad.size()).c_str()); + + MakeRequest(GetEncryptionUrl(), post_body); +} + +void EncryptionEscrowClient::EscrowInstrumentInformation( + const Instrument& new_instrument, + const std::string& obfuscated_gaia_id) { + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = ESCROW_INSTRUMENT_INFORMATION; + + const std::string& primary_account_number = + net::EscapeUrlEncodedData( + UTF16ToUTF8(new_instrument.primary_account_number()), true); + const std::string& card_verification_number = + net::EscapeUrlEncodedData( + UTF16ToUTF8(new_instrument.card_verification_number()), true); + + std::string post_body = StringPrintf( + kEscrowInstrumentInformationFormat, + obfuscated_gaia_id.c_str(), + primary_account_number.c_str(), + card_verification_number.c_str()); + + MakeRequest(GetEscrowUrl(), post_body); +} + +void EncryptionEscrowClient::EscrowCardVerificationNumber( + const std::string& card_verification_number, + const std::string& obfuscated_gaia_id) { + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = ESCROW_CARD_VERIFICATION_NUMBER; + + std::string post_body = StringPrintf( + kEscrowCardVerficationNumberFormat, + obfuscated_gaia_id.c_str(), + card_verification_number.c_str()); + + MakeRequest(GetEscrowUrl(), post_body); +} + +void EncryptionEscrowClient::MakeRequest(const GURL& url, + const std::string& post_body) { + DCHECK(!request_.get()); + + request_.reset(net::URLFetcher::Create( + 1, url, net::URLFetcher::POST, this)); + request_->SetRequestContext(context_getter_); + DVLOG(1) << "url=" << url << ", post_body=" << post_body; + request_->SetUploadData(kApplicationMimeType, post_body); + request_->Start(); +} + +// TODO(ahutter): Add manual retry logic if it's necessary. +void EncryptionEscrowClient::OnURLFetchComplete( + const net::URLFetcher* source) { + DCHECK(observer_); + scoped_ptr<net::URLFetcher> old_request = request_.Pass(); + DCHECK_EQ(source, old_request.get()); + + DVLOG(1) << "Got response from " << source->GetOriginalURL(); + + RequestType type = request_type_; + request_type_ = NO_PENDING_REQUEST; + + std::string data; + source->GetResponseAsString(&data); + DVLOG(1) << "Response body: " << data; + + if (source->GetResponseCode() != net::HTTP_OK) { + observer_->OnNetworkError(source->GetResponseCode()); + return; + } + + if (data.empty()) { + HandleMalformedResponse(old_request.get()); + return; + } + + switch (type) { + case ENCRYPT_ONE_TIME_PAD: { + std::vector<std::string> splits; + // The response from the server should be formatted as + // "<session material>|<encrypted one time pad>". + base::SplitString(data, '|', &splits); + if (splits.size() == 2) + observer_->OnDidEncryptOneTimePad(splits[1], splits[0]); + else + HandleMalformedResponse(old_request.get()); + break; + } + + case ESCROW_INSTRUMENT_INFORMATION: + observer_->OnDidEscrowInstrumentInformation(data); + break; + + case ESCROW_CARD_VERIFICATION_NUMBER: + observer_->OnDidEscrowCardVerificationNumber(data); + break; + + case NO_PENDING_REQUEST: + NOTREACHED(); + } +} + +void EncryptionEscrowClient::HandleMalformedResponse(net::URLFetcher* request) { + // Called to inform exponential backoff logic of the error. + request->ReceivedContentWasMalformed(); + observer_->OnMalformedResponse(); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/encryption_escrow_client.h b/components/autofill/browser/wallet/encryption_escrow_client.h new file mode 100644 index 0000000..12d6078 --- /dev/null +++ b/components/autofill/browser/wallet/encryption_escrow_client.h @@ -0,0 +1,91 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_H_ + +#include <string> +#include <vector> + +#include "base/memory/ref_counted.h" +#include "net/url_request/url_fetcher_delegate.h" + +class GURL; + +namespace net { +class URLFetcher; +class URLRequestContextGetter; +} + +namespace autofill { +namespace wallet { + +class EncryptionEscrowClientObserver; +class Instrument; + +// EncrytionEscrowClient is responsible for making calls to the Online Wallet +// encryption and escrow backend. +class EncryptionEscrowClient : public net::URLFetcherDelegate { + public: + // |observer| must outlive |this|. + EncryptionEscrowClient(net::URLRequestContextGetter* context_getter, + EncryptionEscrowClientObserver* observer); + virtual ~EncryptionEscrowClient(); + + // Sends |one_time_pad|, a vector of cryptographically secure random bytes, to + // Online Wallet to be encrypted. These bytes must be generated using + // crypto/random.h. + void EncryptOneTimePad(const std::vector<uint8>& one_time_pad); + + // Escrows the card verfication number of an existing instrument with Online + // Wallet. The escrow is keyed off of |obfuscated_gaia_id|. + void EscrowCardVerificationNumber(const std::string& card_verification_number, + const std::string& obfuscated_gaia_id); + + // Escrows the primary account number and card verfication number of + // |new_instrument| with Online Wallet. The escrow is keyed off of + // |obfuscated_gaia_id|. + void EscrowInstrumentInformation(const Instrument& new_instrument, + const std::string& obfuscated_gaia_id); + + private: + enum RequestType { + NO_PENDING_REQUEST, + ENCRYPT_ONE_TIME_PAD, + ESCROW_INSTRUMENT_INFORMATION, + ESCROW_CARD_VERIFICATION_NUMBER, + }; + + // Posts |post_body| to |url|. When the request is complete, |observer_| is + // notified of the result. + void MakeRequest(const GURL& url, const std::string& post_body); + + // Performs bookkeeping tasks for any invalid requests. + void HandleMalformedResponse(net::URLFetcher* request); + + // net::URLFetcherDelegate: + virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; + + // The context for the request. Ensures the gdToken cookie is set as a header + // in the requests to Online Wallet if it is present. + scoped_refptr<net::URLRequestContextGetter> context_getter_; + + // Observer class that has its various On* methods called based on the results + // of a request to Online Wallet. + EncryptionEscrowClientObserver* const observer_; + + // The current request object. + scoped_ptr<net::URLFetcher> request_; + + // The type of the current request. Must be NO_PENDING_REQUEST for a request + // to be initiated as only one request may be running at a given time. + RequestType request_type_; + + DISALLOW_COPY_AND_ASSIGN(EncryptionEscrowClient); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_H_ diff --git a/components/autofill/browser/wallet/encryption_escrow_client_observer.h b/components/autofill/browser/wallet/encryption_escrow_client_observer.h new file mode 100644 index 0000000..0e05926 --- /dev/null +++ b/components/autofill/browser/wallet/encryption_escrow_client_observer.h @@ -0,0 +1,50 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_OBSERVER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_OBSERVER_H_ + +#include <string> + +namespace autofill { +namespace wallet { + +// EncryptionEscrowClientObserver is to be implemented by any classes making +// calls with EncryptionEscrowClient. The appropriate callback method will be +// called on EncryptionEscrowClientObserver with the response from the Online +// Wallet encryption and escrow backend. +class EncryptionEscrowClientObserver { + public: + // Called when an EncryptOneTimePad request finishes successfully. + // |encrypted_one_time_pad| and |session_material| must be used when getting a + // FullWallet. + virtual void OnDidEncryptOneTimePad(const std::string& encrypted_one_time_pad, + const std::string& session_material) = 0; + + // Called when an EscrowCardVerificationNumber request finishes + // successfully. |escrow_handle| must be used when authenticating an + // instrument. + virtual void OnDidEscrowCardVerificationNumber( + const std::string& escrow_handle) = 0; + + // Called when an EscrowInstrumentInformation request finishes successfully. + // |escrow_handle| must be used when saving a new instrument. + virtual void OnDidEscrowInstrumentInformation( + const std::string& escrow_handle) = 0; + + // Called when a request fails due to a network error or if the response was + // invalid. + virtual void OnNetworkError(int response_code) = 0; + + // Called when a request fails due to a malformed response. + virtual void OnMalformedResponse() = 0; + + protected: + virtual ~EncryptionEscrowClientObserver() {} +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_ENCRYPTION_ESCROW_CLIENT_OBSERVER_H_ diff --git a/components/autofill/browser/wallet/encryption_escrow_client_unittest.cc b/components/autofill/browser/wallet/encryption_escrow_client_unittest.cc new file mode 100644 index 0000000..13c2b16 --- /dev/null +++ b/components/autofill/browser/wallet/encryption_escrow_client_unittest.cc @@ -0,0 +1,209 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/wallet/encryption_escrow_client.h" +#include "components/autofill/browser/wallet/encryption_escrow_client_observer.h" +#include "components/autofill/browser/wallet/instrument.h" +#include "components/autofill/browser/wallet/wallet_test_util.h" +#include "content/public/test/test_browser_thread.h" +#include "googleurl/src/gurl.h" +#include "net/base/net_errors.h" +#include "net/http/http_status_code.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_status.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kEncryptOtpRequest[] = "cvv=30:000102030405"; +const char kEncryptOtpResponse[] = "session_material|encrypted_one_time_pad"; +const char kEscrowInstrumentInformationRequest[] = + "gid=obfuscated_gaia_id&cardNumber=4444444444444448&cvv=123"; +const char kEscrowCardVerificationNumberRequest[] = + "gid=obfuscated_gaia_id&cvv=123"; + +} // namespace + +namespace autofill { +namespace wallet { + +class MockEncryptionEscrowClientObserver : + public EncryptionEscrowClientObserver, + public base::SupportsWeakPtr<MockEncryptionEscrowClientObserver> { + public: + MockEncryptionEscrowClientObserver() {} + ~MockEncryptionEscrowClientObserver() {} + + MOCK_METHOD2(OnDidEncryptOneTimePad, + void(const std::string& encrypted_one_time_pad, + const std::string& session_material)); + MOCK_METHOD1(OnDidEscrowCardVerificationNumber, + void(const std::string& escrow_handle)); + MOCK_METHOD1(OnDidEscrowInstrumentInformation, + void(const std::string& escrow_handle)); + MOCK_METHOD0(OnMalformedResponse, void()); + MOCK_METHOD1(OnNetworkError, void(int response_code)); +}; + +class EncryptionEscrowClientTest : public testing::Test { + public: + EncryptionEscrowClientTest() : io_thread_(content::BrowserThread::IO) {} + + virtual void SetUp() { + io_thread_.StartIOThread(); + profile_.CreateRequestContext(); + } + + std::vector<uint8> MakeOneTimePad() { + std::vector<uint8> one_time_pad; + one_time_pad.push_back(0); + one_time_pad.push_back(1); + one_time_pad.push_back(2); + one_time_pad.push_back(3); + one_time_pad.push_back(4); + one_time_pad.push_back(5); + return one_time_pad; + } + + virtual void TearDown() { + profile_.ResetRequestContext(); + io_thread_.Stop(); + } + + void VerifyAndFinishRequest(const net::TestURLFetcherFactory& fetcher_factory, + net::HttpStatusCode response_code, + const std::string& request_body, + const std::string& response_body) { + net::TestURLFetcher* fetcher = fetcher_factory.GetFetcherByID(1); + ASSERT_TRUE(fetcher); + EXPECT_EQ(request_body, fetcher->upload_data()); + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response_body); + fetcher->delegate()->OnURLFetchComplete(fetcher); + } + + protected: + TestingProfile profile_; + + private: + // The profile's request context must be released on the IO thread. + content::TestBrowserThread io_thread_; +}; + +TEST_F(EncryptionEscrowClientTest, NetworkError) { + MockEncryptionEscrowClientObserver observer; + EXPECT_CALL(observer, OnNetworkError(net::HTTP_UNAUTHORIZED)).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + EncryptionEscrowClient encryption_escrow_client(profile_.GetRequestContext(), + &observer); + encryption_escrow_client.EscrowInstrumentInformation(*instrument, + "obfuscated_gaia_id"); + VerifyAndFinishRequest(factory, + net::HTTP_UNAUTHORIZED, + kEscrowInstrumentInformationRequest, + std::string()); +} + +TEST_F(EncryptionEscrowClientTest, EscrowInstrumentInformationSuccess) { + MockEncryptionEscrowClientObserver observer; + EXPECT_CALL(observer, OnDidEscrowInstrumentInformation("abc")).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + EncryptionEscrowClient encryption_escrow_client(profile_.GetRequestContext(), + &observer); + encryption_escrow_client.EscrowInstrumentInformation(*instrument, + "obfuscated_gaia_id"); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kEscrowInstrumentInformationRequest, + "abc"); +} + +TEST_F(EncryptionEscrowClientTest, EscrowInstrumentInformationFailure) { + MockEncryptionEscrowClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + scoped_ptr<Instrument> instrument = GetTestInstrument(); + EncryptionEscrowClient encryption_escrow_client(profile_.GetRequestContext(), + &observer); + encryption_escrow_client.EscrowInstrumentInformation(*instrument, + "obfuscated_gaia_id"); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kEscrowInstrumentInformationRequest, + std::string()); +} + +TEST_F(EncryptionEscrowClientTest, EscrowCardVerificationNumberSuccess) { + MockEncryptionEscrowClientObserver observer; + EXPECT_CALL(observer, OnDidEscrowCardVerificationNumber("abc")).Times(1); + + net::TestURLFetcherFactory factory; + + EncryptionEscrowClient encryption_escrow_client(profile_.GetRequestContext(), + &observer); + encryption_escrow_client.EscrowCardVerificationNumber("123", + "obfuscated_gaia_id"); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kEscrowCardVerificationNumberRequest, + "abc"); +} + +TEST_F(EncryptionEscrowClientTest, EscrowCardVerificationNumberFailure) { + MockEncryptionEscrowClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + EncryptionEscrowClient encryption_escrow_client(profile_.GetRequestContext(), + &observer); + encryption_escrow_client.EscrowCardVerificationNumber("123", + "obfuscated_gaia_id"); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kEscrowCardVerificationNumberRequest, + std::string()); +} + +TEST_F(EncryptionEscrowClientTest, EncryptOneTimePadSuccess) { + MockEncryptionEscrowClientObserver observer; + EXPECT_CALL(observer, + OnDidEncryptOneTimePad("encrypted_one_time_pad", + "session_material")).Times(1); + + net::TestURLFetcherFactory factory; + EncryptionEscrowClient encryption_escrow_client(profile_.GetRequestContext(), + &observer); + encryption_escrow_client.EncryptOneTimePad(MakeOneTimePad()); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kEncryptOtpRequest, + kEncryptOtpResponse); +} + +TEST_F(EncryptionEscrowClientTest, EncryptOneTimePadFailure) { + MockEncryptionEscrowClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + EncryptionEscrowClient encryption_escrow_client(profile_.GetRequestContext(), + &observer); + encryption_escrow_client.EncryptOneTimePad(MakeOneTimePad()); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kEncryptOtpRequest, + std::string()); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/full_wallet.cc b/components/autofill/browser/wallet/full_wallet.cc new file mode 100644 index 0000000..875ce6c --- /dev/null +++ b/components/autofill/browser/wallet/full_wallet.cc @@ -0,0 +1,224 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/full_wallet.h" + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/values.h" + +namespace { + +const size_t kPanSize = 16; +const size_t kBinSize = 6; +const size_t kCvnSize = 3; + +} // anonymous namespace + +namespace autofill { +namespace wallet { + +FullWallet::FullWallet(int expiration_month, + int expiration_year, + const std::string& iin, + const std::string& encrypted_rest, + scoped_ptr<Address> billing_address, + scoped_ptr<Address> shipping_address, + const std::vector<RequiredAction>& required_actions) + : expiration_month_(expiration_month), + expiration_year_(expiration_year), + iin_(iin), + encrypted_rest_(encrypted_rest), + billing_address_(billing_address.Pass()), + shipping_address_(shipping_address.Pass()), + required_actions_(required_actions) { + DCHECK(required_actions_.size() > 0 || billing_address_.get()); +} + +FullWallet::~FullWallet() {} + +scoped_ptr<FullWallet> + FullWallet::CreateFullWallet(const DictionaryValue& dictionary) { + const ListValue* required_actions_list; + std::vector<RequiredAction> required_actions; + if (dictionary.GetList("required_action", &required_actions_list)) { + for (size_t i = 0; i < required_actions_list->GetSize(); ++i) { + std::string action_string; + if (required_actions_list->GetString(i, &action_string)) { + RequiredAction action = ParseRequiredActionFromString(action_string); + if (!ActionAppliesToFullWallet(action)) { + DLOG(ERROR) << "Response from Google wallet with bad required action:" + " \"" << action_string << "\""; + return scoped_ptr<FullWallet>(); + } + required_actions.push_back(action); + } + } + if (required_actions.size() > 0) { + return scoped_ptr<FullWallet>(new FullWallet(-1, + -1, + std::string(), + std::string(), + scoped_ptr<Address>(), + scoped_ptr<Address>(), + required_actions)); + } + } else { + DVLOG(1) << "Response from Google wallet missing required actions"; + } + + int expiration_month; + if (!dictionary.GetInteger("expiration_month", &expiration_month)) { + DLOG(ERROR) << "Response from Google wallet missing expiration month"; + return scoped_ptr<FullWallet>(); + } + + int expiration_year; + if (!dictionary.GetInteger("expiration_year", &expiration_year)) { + DLOG(ERROR) << "Response from Google wallet missing expiration year"; + return scoped_ptr<FullWallet>(); + } + + std::string iin; + if (!dictionary.GetString("iin", &iin)) { + DLOG(ERROR) << "Response from Google wallet missing iin"; + return scoped_ptr<FullWallet>(); + } + + std::string encrypted_rest; + if (!dictionary.GetString("rest", &encrypted_rest)) { + DLOG(ERROR) << "Response from Google wallet missing rest"; + return scoped_ptr<FullWallet>(); + } + + const DictionaryValue* billing_address_dict; + if (!dictionary.GetDictionary("billing_address", &billing_address_dict)) { + DLOG(ERROR) << "Response from Google wallet missing billing address"; + return scoped_ptr<FullWallet>(); + } + + scoped_ptr<Address> billing_address = + Address::CreateAddress(*billing_address_dict); + if (!billing_address.get()) { + DLOG(ERROR) << "Response from Google wallet has malformed billing address"; + return scoped_ptr<FullWallet>(); + } + + const DictionaryValue* shipping_address_dict; + scoped_ptr<Address> shipping_address; + if (dictionary.GetDictionary("shipping_address", &shipping_address_dict)) { + shipping_address = + Address::CreateAddressWithID(*shipping_address_dict); + } else { + DVLOG(1) << "Response from Google wallet missing shipping address"; + } + + return scoped_ptr<FullWallet>(new FullWallet(expiration_month, + expiration_year, + iin, + encrypted_rest, + billing_address.Pass(), + shipping_address.Pass(), + required_actions)); +} + +bool FullWallet::HasRequiredAction(RequiredAction action) const { + DCHECK(ActionAppliesToFullWallet(action)); + return std::find(required_actions_.begin(), + required_actions_.end(), + action) != required_actions_.end(); +} + +bool FullWallet::operator==(const FullWallet& other) const { + if (expiration_month_ != other.expiration_month_) + return false; + + if (expiration_year_ != other.expiration_year_) + return false; + + if (iin_ != other.iin_) + return false; + + if (encrypted_rest_ != other.encrypted_rest_) + return false; + + if (billing_address_.get() && other.billing_address_.get()) { + if (*billing_address_.get() != *other.billing_address_.get()) + return false; + } else if (billing_address_.get() || other.billing_address_.get()) { + return false; + } + + if (shipping_address_.get() && other.shipping_address_.get()) { + if (*shipping_address_.get() != *other.shipping_address_.get()) + return false; + } else if (shipping_address_.get() || other.shipping_address_.get()) { + return false; + } + + if (required_actions_ != other.required_actions_) + return false; + + return true; +} + +bool FullWallet::operator!=(const FullWallet& other) const { + return !(*this == other); +} + +const std::string& FullWallet::GetPan() { + if (pan_.empty()) + DecryptCardInfo(); + return pan_; +} + +const std::string& FullWallet::GetCvn() { + if (cvn_.empty()) + DecryptCardInfo(); + return cvn_; +} + +void FullWallet::DecryptCardInfo() { + std::vector<uint8> operating_data; + // Convert |encrypted_rest_| to bytes so we can decrypt it with |otp|. + if (!base::HexStringToBytes(encrypted_rest_, &operating_data)) { + DLOG(ERROR) << "Failed to parse encrypted rest"; + return; + } + + // Ensure |one_time_pad_| and |encrypted_rest_| are of the same length + // otherwise something has gone wrong and we can't decrypt the data. + DCHECK_EQ(one_time_pad_.size(), operating_data.size()); + + std::vector<uint8> results; + // XOR |otp| with the encrypted data to decrypt. + for (size_t i = 0; i < one_time_pad_.size(); ++i) + results.push_back(one_time_pad_[i] ^ operating_data[i]); + + // There is no uint8* to int64 so convert the decrypted data to hex and then + // parse the hex to an int64 before getting the int64 as a string. + std::string hex_decrypted = base::HexEncode(&(results[0]), results.size()); + + int64 decrypted; + if (!base::HexStringToInt64(hex_decrypted, &decrypted)) { + DLOG(ERROR) << "Failed to parse decrypted data in hex to int64"; + return; + } + std::string card_info = base::Int64ToString(decrypted); + + size_t padded_length = kPanSize - kBinSize + kCvnSize; + // |card_info| is PAN without the IIN concatenated with the CVN, i.e. + // PANPANPANPCVN. If what was decrypted is not of that size the front needs + // to be padded with 0's until it is. + if (card_info.size() != padded_length) + card_info.insert(card_info.begin(), padded_length - card_info.size(), '0'); + + // Separate out the PAN from the CVN. + size_t split = kPanSize - kBinSize; + cvn_ = card_info.substr(split); + pan_ = iin_ + card_info.substr(0, split); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/full_wallet.h b/components/autofill/browser/wallet/full_wallet.h new file mode 100644 index 0000000..c6f2ab5 --- /dev/null +++ b/components/autofill/browser/wallet/full_wallet.h @@ -0,0 +1,121 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_FULL_WALLET_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_FULL_WALLET_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" +#include "components/autofill/browser/wallet/required_action.h" +#include "components/autofill/browser/wallet/wallet_address.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { +namespace wallet { + +class FullWalletTest; + +// FullWallet contains all the information a merchant requires from a user for +// that user to make a purchase. This includes: +// - billing information +// - shipping information +// - a proxy card for the backing card selected from a user's wallet items +class FullWallet { + public: + ~FullWallet(); + + // Returns an empty scoped_ptr if the input invalid, an empty wallet with + // required actions if there are any, or a valid wallet. + static scoped_ptr<FullWallet> + CreateFullWallet(const base::DictionaryValue& dictionary); + + // Decrypts and returns the primary account number (PAN) using the generated + // one time pad, |one_time_pad_|. + const std::string& GetPan(); + + // Decrypts and returns the card verification number (CVN) using the generated + // one time pad, |one_time_pad_|. + const std::string& GetCvn(); + + // Whether or not |action| is in |required_actions_|. + bool HasRequiredAction(RequiredAction action) const; + + bool operator==(const FullWallet& other) const; + bool operator!=(const FullWallet& other) const; + + // If there are required actions |billing_address_| might contain NULL. + const Address* billing_address() const { return billing_address_.get(); } + + // If there are required actions or shipping address is not required + // |shipping_address_| might contain NULL. + const Address* shipping_address() const { return shipping_address_.get(); } + + const std::vector<RequiredAction>& required_actions() const { + return required_actions_; + } + int expiration_month() const { return expiration_month_; } + int expiration_year() const { return expiration_year_; } + + void set_one_time_pad(const std::vector<uint8>& one_time_pad) { + one_time_pad_ = one_time_pad; + } + + private: + friend class FullWalletTest; + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, CreateFullWallet); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, CreateFullWalletWithRequiredActions); + FullWallet(int expiration_month, + int expiration_year, + const std::string& iin, + const std::string& encrypted_rest, + scoped_ptr<Address> billing_address, + scoped_ptr<Address> shipping_address, + const std::vector<RequiredAction>& required_actions); + void DecryptCardInfo(); + + // The expiration month of the proxy card. It should be 1-12. + int expiration_month_; + + // The expiration year of the proxy card. It should be a 4-digit year. + int expiration_year_; + + // Primary account number (PAN). Its format is \d{16}. + std::string pan_; + + // Card verification number (CVN). Its format is \d{3}. + std::string cvn_; + + // Issuer identification number (IIN). Its format is \d{6}. + std::string iin_; + + // Encrypted concatentation of CVN and PAN without IIN + std::string encrypted_rest_; + + // The billing address of the backing instrument. + scoped_ptr<Address> billing_address_; + + // The shipping address for the transaction. + scoped_ptr<Address> shipping_address_; + + // Actions that must be completed by the user before a FullWallet can be + // issued to them by the Online Wallet service. + std::vector<RequiredAction> required_actions_; + + // The one time pad used for FullWallet encryption. + std::vector<uint8> one_time_pad_; + + DISALLOW_COPY_AND_ASSIGN(FullWallet); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_FULL_WALLET_H_ diff --git a/components/autofill/browser/wallet/full_wallet_unittest.cc b/components/autofill/browser/wallet/full_wallet_unittest.cc new file mode 100644 index 0000000..346b31c --- /dev/null +++ b/components/autofill/browser/wallet/full_wallet_unittest.cc @@ -0,0 +1,465 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/wallet/full_wallet.h" +#include "components/autofill/browser/wallet/required_action.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kFullWalletValidResponse[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"iin\":\"iin\"," + " \"rest\":\"rest\"," + " \"billing_address\":" + " {" + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"country_name_code\"" + " }" + " }," + " \"shipping_address\":" + " {" + " \"id\":\"ship_id\"," + " \"phone_number\":\"ship_phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"ship_recipient_name\"," + " \"address_line\":" + " [" + " \"ship_address_line_1\"," + " \"ship_address_line_2\"" + " ]," + " \"locality_name\":\"ship_locality_name\"," + " \"administrative_area_name\":\"ship_admin_area_name\"," + " \"postal_code_number\":\"ship_postal_code_number\"," + " \"country_name_code\":\"ship_country_name_code\"" + " }" + " }," + " \"required_action\":" + " [" + " ]" + "}"; + +const char kFullWalletMissingExpirationMonth[] = + "{" + " \"expiration_year\":2012," + " \"iin\":\"iin\"," + " \"rest\":\"rest\"," + " \"billing_address\":" + " {" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"country_name_code\"" + " }" + " }," + " \"shipping_address\":" + " {" + " \"id\":\"ship_id\"," + " \"phone_number\":\"ship_phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"ship_recipient_name\"," + " \"address_line\":" + " [" + " \"ship_address_line_1\"," + " \"ship_address_line_2\"" + " ]," + " \"locality_name\":\"ship_locality_name\"," + " \"administrative_area_name\":\"ship_admin_area_name\"," + " \"postal_code_number\":\"ship_postal_code_number\"," + " \"country_name_code\":\"ship_country_name_code\"" + " }" + " }," + " \"required_action\":" + " [" + " ]" + "}"; + +const char kFullWalletMissingExpirationYear[] = + "{" + " \"expiration_month\":12," + " \"iin\":\"iin\"," + " \"rest\":\"rest\"," + " \"billing_address\":" + " {" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"country_name_code\"" + " }" + " }," + " \"shipping_address\":" + " {" + " \"id\":\"ship_id\"," + " \"phone_number\":\"ship_phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"ship_recipient_name\"," + " \"address_line\":" + " [" + " \"ship_address_line_1\"," + " \"ship_address_line_2\"" + " ]," + " \"locality_name\":\"ship_locality_name\"," + " \"administrative_area_name\":\"ship_admin_area_name\"," + " \"postal_code_number\":\"ship_postal_code_number\"," + " \"country_name_code\":\"ship_country_name_code\"" + " }" + " }," + " \"required_action\":" + " [" + " ]" + "}"; + +const char kFullWalletMissingIin[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"rest\":\"rest\"," + " \"billing_address\":" + " {" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"country_name_code\"" + " }" + " }," + " \"shipping_address\":" + " {" + " \"id\":\"ship_id\"," + " \"phone_number\":\"ship_phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"ship_recipient_name\"," + " \"address_line\":" + " [" + " \"ship_address_line_1\"," + " \"ship_address_line_2\"" + " ]," + " \"locality_name\":\"ship_locality_name\"," + " \"administrative_area_name\":\"ship_admin_area_name\"," + " \"postal_code_number\":\"ship_postal_code_number\"," + " \"country_name_code\":\"ship_country_name_code\"" + " }" + " }," + " \"required_action\":" + " [" + " ]" + "}"; + +const char kFullWalletMissingRest[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"iin\":\"iin\"," + " \"billing_address\":" + " {" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"country_name_code\"" + " }" + " }," + " \"shipping_address\":" + " {" + " \"id\":\"ship_id\"," + " \"phone_number\":\"ship_phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"ship_recipient_name\"," + " \"address_line\":" + " [" + " \"ship_address_line_1\"," + " \"ship_address_line_2\"" + " ]," + " \"locality_name\":\"ship_locality_name\"," + " \"administrative_area_name\":\"ship_admin_area_name\"," + " \"postal_code_number\":\"ship_postal_code_number\"," + " \"country_name_code\":\"ship_country_name_code\"" + " }" + " }," + " \"required_action\":" + " [" + " ]" + "}"; + +const char kFullWalletMissingBillingAddress[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"iin\":\"iin\"," + " \"rest\":\"rest\"," + " \"shipping_address\":" + " {" + " \"id\":\"ship_id\"," + " \"phone_number\":\"ship_phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"ship_recipient_name\"," + " \"address_line\":" + " [" + " \"ship_address_line_1\"," + " \"ship_address_line_2\"" + " ]," + " \"locality_name\":\"ship_locality_name\"," + " \"administrative_area_name\":\"ship_admin_area_name\"," + " \"postal_code_number\":\"ship_postal_code_number\"," + " \"country_name_code\":\"ship_country_name_code\"" + " }" + " }," + " \"required_action\":" + " [" + " ]" + "}"; + +const char kFullWalletWithRequiredActions[] = + "{" + " \"required_action\":" + " [" + " \"CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS\"," + " \"update_EXPIRATION_date\"," + " \"verify_CVV\"," + " \" REQuIrE_PHONE_NumBER\t\n\r \"" + " ]" + "}"; + +const char kFullWalletWithInvalidRequiredActions[] = + "{" + " \"required_action\":" + " [" + " \" setup_wallet\"," + " \"AcCePt_ToS \"," + " \"UPGRADE_MIN_ADDRESS\"," + " \"INVALID_form_field\"," + " \" \\tGAIA_auth \\n\\r\"," + " \"PASSIVE_GAIA_AUTH\"," + " \" 忍者の正体 \"" + " ]" + "}"; + +const char kFullWalletMalformedBillingAddress[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"iin\":\"iin\"," + " \"rest\":\"rest\"," + " \"billing_address\":" + " {" + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"" + " }" + " }," + " \"shipping_address\":" + " {" + " \"id\":\"ship_id\"," + " \"phone_number\":\"ship_phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"ship_recipient_name\"," + " \"address_line\":" + " [" + " \"ship_address_line_1\"," + " \"ship_address_line_2\"" + " ]," + " \"locality_name\":\"ship_locality_name\"," + " \"administrative_area_name\":\"ship_admin_area_name\"," + " \"postal_code_number\":\"ship_postal_code_number\"," + " \"country_name_code\":\"ship_country_name_code\"" + " }" + " }," + " \"required_action\":" + " [" + " ]" + "}"; + +} // anonymous namespace + +namespace autofill { +namespace wallet { + +class FullWalletTest : public testing::Test { + public: + FullWalletTest() {} + protected: + void SetUpDictionary(const std::string& json) { + scoped_ptr<Value> value(base::JSONReader::Read(json)); + ASSERT_TRUE(value.get()); + ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY)); + dict.reset(static_cast<DictionaryValue*>(value.release())); + } + scoped_ptr<DictionaryValue> dict; +}; + +TEST_F(FullWalletTest, CreateFullWalletMissingExpirationMonth) { + SetUpDictionary(kFullWalletMissingExpirationMonth); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletMissingExpirationYear) { + SetUpDictionary(kFullWalletMissingExpirationYear); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletMissingIin) { + SetUpDictionary(kFullWalletMissingIin); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletMissingRest) { + SetUpDictionary(kFullWalletMissingRest); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletMissingBillingAddress) { + SetUpDictionary(kFullWalletMissingBillingAddress); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletMalformedBillingAddress) { + SetUpDictionary(kFullWalletMalformedBillingAddress); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWalletWithRequiredActions) { + SetUpDictionary(kFullWalletWithRequiredActions); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS); + required_actions.push_back(UPDATE_EXPIRATION_DATE); + required_actions.push_back(VERIFY_CVV); + required_actions.push_back(REQUIRE_PHONE_NUMBER); + + FullWallet full_wallet(-1, + -1, + "", + "", + scoped_ptr<Address>(), + scoped_ptr<Address>(), + required_actions); + EXPECT_EQ(full_wallet, *FullWallet::CreateFullWallet(*dict)); + + ASSERT_FALSE(required_actions.empty()); + required_actions.pop_back(); + FullWallet different_required_actions( + -1, + -1, + "", + "", + scoped_ptr<Address>(), + scoped_ptr<Address>(), + required_actions); + EXPECT_NE(full_wallet, different_required_actions); +} + +TEST_F(FullWalletTest, CreateFullWalletWithInvalidRequiredActions) { + SetUpDictionary(kFullWalletWithInvalidRequiredActions); + EXPECT_EQ(NULL, FullWallet::CreateFullWallet(*dict).get()); +} + +TEST_F(FullWalletTest, CreateFullWallet) { + SetUpDictionary(kFullWalletValidResponse); + // NOTE: FullWallet billing address doesn't require an ID. + scoped_ptr<Address> billing_address(new Address( + "country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "")); + scoped_ptr<Address> shipping_address(new Address( + "ship_country_name_code", + ASCIIToUTF16("ship_recipient_name"), + ASCIIToUTF16("ship_address_line_1"), + ASCIIToUTF16("ship_address_line_2"), + ASCIIToUTF16("ship_locality_name"), + ASCIIToUTF16("ship_admin_area_name"), + ASCIIToUTF16("ship_postal_code_number"), + ASCIIToUTF16("ship_phone_number"), + "ship_id")); + std::vector<RequiredAction> required_actions; + FullWallet full_wallet(12, + 2012, + "iin", + "rest", + billing_address.Pass(), + shipping_address.Pass(), + required_actions); + EXPECT_EQ(full_wallet, *FullWallet::CreateFullWallet(*dict)); +} + +// TODO(ahutter): Add tests for GetPan and GetCvn. + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/instrument.cc b/components/autofill/browser/wallet/instrument.cc new file mode 100644 index 0000000..44c9d6748 --- /dev/null +++ b/components/autofill/browser/wallet/instrument.cc @@ -0,0 +1,110 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/instrument.h" + +#include "base/logging.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/validation.h" +#include "components/autofill/browser/wallet/wallet_address.h" + +namespace autofill { +namespace wallet { + +namespace { + +std::string FormOfPaymentToString(Instrument::FormOfPayment form_of_payment) { + switch (form_of_payment) { + case Instrument::UNKNOWN: + return "UNKNOWN"; + case Instrument::VISA: + return "VISA"; + case Instrument::MASTER_CARD: + return "MASTER_CARD"; + case Instrument::AMEX: + return "AMEX"; + case Instrument::DISCOVER: + return "DISCOVER"; + case Instrument::JCB: + return "JCB"; + } + NOTREACHED(); + return "NOT_POSSIBLE"; +} + +} // namespace + +Instrument::Instrument(const string16& primary_account_number, + const string16& card_verification_number, + int expiration_month, + int expiration_year, + FormOfPayment form_of_payment, + scoped_ptr<Address> address) + : primary_account_number_(primary_account_number), + card_verification_number_(card_verification_number), + expiration_month_(expiration_month), + expiration_year_(expiration_year), + form_of_payment_(form_of_payment), + address_(address.Pass()) { + DCHECK(address_); + Init(); +} + +Instrument::Instrument(const Instrument& instrument) + : primary_account_number_(instrument.primary_account_number()), + card_verification_number_(instrument.card_verification_number()), + expiration_month_(instrument.expiration_month()), + expiration_year_(instrument.expiration_year()), + form_of_payment_(instrument.form_of_payment()), + address_(new Address(instrument.address())) { + Init(); +} + +Instrument::~Instrument() {} + +scoped_ptr<base::DictionaryValue> Instrument::ToDictionary() const { + // |primary_account_number_| and |card_verification_number_| can never be + // sent the server in way that would require putting them into a dictionary. + // Never add them to this function. + + scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + dict->SetString("type", "CREDIT_CARD"); + dict->SetInteger("credit_card.exp_month", expiration_month_); + dict->SetInteger("credit_card.exp_year", expiration_year_); + dict->SetString("credit_card.fop_type", + FormOfPaymentToString(form_of_payment_)); + dict->SetString("credit_card.last_4_digits", last_four_digits_); + dict->Set("credit_card.address", + address_.get()->ToDictionaryWithoutID().release()); + + return dict.Pass(); +} + +bool Instrument::IsValid() const { + if (!IsStringASCII(primary_account_number_)) + return false; + bool primary_account_number_valid = + autofill::IsValidCreditCardNumber(primary_account_number_); + bool card_verification_number_valid = card_verification_number_.size() == 3 || + card_verification_number_.size() == 4; + bool exp_month_valid = expiration_month_ >= 1 && expiration_month_ <= 12; + bool exp_year_valid = expiration_year_ >= 2013 && expiration_year_ <= 2100; + + return primary_account_number_valid && + card_verification_number_valid && + exp_month_valid && + exp_year_valid; +} + +void Instrument::Init() { + if (primary_account_number_.size() >= 4) { + last_four_digits_ = + primary_account_number_.substr(primary_account_number_.size() - 4); + } +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/instrument.h b/components/autofill/browser/wallet/instrument.h new file mode 100644 index 0000000..779ac75 --- /dev/null +++ b/components/autofill/browser/wallet/instrument.h @@ -0,0 +1,93 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_INSTRUMENT_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_INSTRUMENT_H_ + +#include <string> +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { +namespace wallet { + +class Address; + +// This class contains all the data necessary to save a new instrument to a +// user's Google Wallet using WalletClient::SaveInstrument or +// WalletClient::SaveInstrumentAndAddress. +class Instrument { + public: + enum FormOfPayment { + UNKNOWN, + VISA, + MASTER_CARD, + AMEX, + DISCOVER, + JCB, + }; + + Instrument(const string16& primary_account_number, + const string16& card_verification_number, + int expiration_month, + int expiration_year, + FormOfPayment form_of_payment, + scoped_ptr<Address> address); + Instrument(const Instrument& instrument); + ~Instrument(); + + scoped_ptr<base::DictionaryValue> ToDictionary() const; + + // Users of this class should call IsValid to check that the inputs provided + // in the constructor were valid for use with Google Wallet. + bool IsValid() const; + + const string16& primary_account_number() const { + return primary_account_number_; + } + const string16& card_verification_number() const { + return card_verification_number_; + } + int expiration_month() const { return expiration_month_; } + int expiration_year() const { return expiration_year_; } + const Address& address() const { return *address_; } + FormOfPayment form_of_payment() const { return form_of_payment_; } + const string16& last_four_digits() { return last_four_digits_; } + + private: + void Init(); + + // |primary_account_number_| is expected to be \d{12-19}. + string16 primary_account_number_; + + // |card_verification_number_| is expected to be \d{3-4}. + string16 card_verification_number_; + + // |expiration month_| should be 1-12. + int expiration_month_; + + // |expiration_year_| should be a 4-digit year. + int expiration_year_; + + // The payment network of the instrument, e.g. Visa. + FormOfPayment form_of_payment_; + + // The billing address of the instrument. + scoped_ptr<Address> address_; + + // The last four digits of |primary_account_number_|. + string16 last_four_digits_; + + DISALLOW_ASSIGN(Instrument); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_INSTRUMENT_H_ diff --git a/components/autofill/browser/wallet/instrument_unittest.cc b/components/autofill/browser/wallet/instrument_unittest.cc new file mode 100644 index 0000000..e2b945b --- /dev/null +++ b/components/autofill/browser/wallet/instrument_unittest.cc @@ -0,0 +1,189 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/wallet/instrument.h" +#include "components/autofill/browser/wallet/wallet_address.h" +#include "components/autofill/browser/wallet/wallet_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kPrimaryAccountNumber[] = "4444444444444448"; +const char kCardVerificationNumber[] = "123"; +const char kLastFourDigits[] = "4448"; + +} + +namespace autofill { +namespace wallet { + +TEST(Instrument, LastFourDigits) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_EQ(ASCIIToUTF16(kLastFourDigits), instrument.last_four_digits()); + EXPECT_TRUE(instrument.IsValid()); +} + +TEST(Instrument, NoPrimaryAccountNumberIsInvalid) { + Instrument instrument(string16(), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooShortPrimaryAccountNumberIsInvalid) { + Instrument instrument(ASCIIToUTF16("44447"), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooLongPrimaryAccountNumberIsInvalid) { + Instrument instrument(ASCIIToUTF16("44444444444444444448"), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, PrimaryAccountNumberNotPassingLuhnIsInvalid) { + Instrument instrument(ASCIIToUTF16("4444444444444444"), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, NoCardVerificationNumberIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + string16(), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooShortCardVerificationNumberIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16("12"), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooLongCardVerificationNumberIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16("12345"), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, ZeroAsExpirationMonthIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 0, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooLargeExpirationMonthIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 13, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooSmallExpirationYearIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 999, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, TooLargeExpirationYearIsInvalid) { + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 10000, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_FALSE(instrument.IsValid()); +} + +TEST(Instrument, ToDictionary) { + base::DictionaryValue expected; + expected.SetString("type", "CREDIT_CARD"); + expected.SetInteger("credit_card.exp_month", 12); + expected.SetInteger("credit_card.exp_year", 2015); + expected.SetString("credit_card.last_4_digits", kLastFourDigits); + expected.SetString("credit_card.fop_type", "VISA"); + expected.SetString("credit_card.address.country_name_code", + "ship_country_name_code"); + expected.SetString("credit_card.address.recipient_name", + "ship_recipient_name"); + expected.SetString("credit_card.address.locality_name", + "ship_locality_name"); + expected.SetString("credit_card.address.administrative_area_name", + "ship_admin_area_name"); + expected.SetString("credit_card.address.postal_code_number", + "ship_postal_code_number"); + base::ListValue* address_lines = new base::ListValue(); + address_lines->AppendString("ship_address_line_1"); + address_lines->AppendString("ship_address_line_2"); + expected.Set("credit_card.address.address_line", address_lines); + + Instrument instrument(ASCIIToUTF16(kPrimaryAccountNumber), + ASCIIToUTF16(kCardVerificationNumber), + 12, + 2015, + Instrument::VISA, + GetTestShippingAddress().Pass()); + + EXPECT_TRUE(expected.Equals(instrument.ToDictionary().get())); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/required_action.cc b/components/autofill/browser/wallet/required_action.cc new file mode 100644 index 0000000..029f5f1 --- /dev/null +++ b/components/autofill/browser/wallet/required_action.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/required_action.h" + +#include "base/logging.h" +#include "base/string_util.h" + +namespace autofill { +namespace wallet { + +bool ActionAppliesToFullWallet(RequiredAction action) { + return action == UPDATE_EXPIRATION_DATE || + action == VERIFY_CVV || + action == CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS || + action == REQUIRE_PHONE_NUMBER; +} + +bool ActionAppliesToSaveToWallet(RequiredAction action) { + return action == INVALID_FORM_FIELD || + action == REQUIRE_PHONE_NUMBER; +} + +bool ActionAppliesToWalletItems(RequiredAction action) { + return action == SETUP_WALLET || + action == ACCEPT_TOS || + action == GAIA_AUTH || + action == REQUIRE_PHONE_NUMBER || + action == UPDATE_EXPIRATION_DATE || + action == UPGRADE_MIN_ADDRESS || + action == PASSIVE_GAIA_AUTH; +} + +RequiredAction ParseRequiredActionFromString(const std::string& str) { + std::string str_lower; + TrimWhitespaceASCII(StringToLowerASCII(str), TRIM_ALL, &str_lower); + + if (str_lower == "setup_wallet") + return SETUP_WALLET; + else if (str_lower == "accept_tos") + return ACCEPT_TOS; + else if (str_lower == "gaia_auth") + return GAIA_AUTH; + else if (str_lower == "update_expiration_date") + return UPDATE_EXPIRATION_DATE; + else if (str_lower == "upgrade_min_address") + return UPGRADE_MIN_ADDRESS; + else if (str_lower == "invalid_form_field") + return INVALID_FORM_FIELD; + else if (str_lower == "verify_cvv") + return VERIFY_CVV; + else if (str_lower == "passive_gaia_auth") + return PASSIVE_GAIA_AUTH; + else if (str_lower == "require_phone_number") + return REQUIRE_PHONE_NUMBER; + else if (str_lower == "choose_another_instrument_or_address") + return CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS; + + DLOG(ERROR) << "Failed to parse: \"" << str << "\" as a required action"; + return UNKNOWN_TYPE; +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/required_action.h b/components/autofill/browser/wallet/required_action.h new file mode 100644 index 0000000..d62efef --- /dev/null +++ b/components/autofill/browser/wallet/required_action.h @@ -0,0 +1,44 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_REQUIRED_ACTION_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_REQUIRED_ACTION_H_ + +#include <string> + +namespace autofill { +namespace wallet { + +// Required actions are steps that must be taken before the current transaction +// can proceed. Examples of this is include accepting the Terms of Service to +// use Google Wallet (happens on first use or when the ToS are updated) or +// typing a CVC when it's necessary verify the current user has access to the +// backing card. +enum RequiredAction { + UNKNOWN_TYPE = 0, // Catch all type. + CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS, + SETUP_WALLET, + ACCEPT_TOS, + GAIA_AUTH, + UPDATE_EXPIRATION_DATE, + UPGRADE_MIN_ADDRESS, + INVALID_FORM_FIELD, + VERIFY_CVV, + PASSIVE_GAIA_AUTH, + REQUIRE_PHONE_NUMBER, +}; + +// Static helper functions to determine if an RequiredAction applies to a +// FullWallet, WalletItems, or SaveToWallet response. +bool ActionAppliesToFullWallet(RequiredAction action); +bool ActionAppliesToSaveToWallet(RequiredAction action); +bool ActionAppliesToWalletItems(RequiredAction action); + +// Turn a string value of the parsed JSON response into an RequiredAction. +RequiredAction ParseRequiredActionFromString(const std::string& str); + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_REQUIRED_ACTION_H_ diff --git a/components/autofill/browser/wallet/wallet_address.cc b/components/autofill/browser/wallet/wallet_address.cc new file mode 100644 index 0000000..7269ab9 --- /dev/null +++ b/components/autofill/browser/wallet/wallet_address.cc @@ -0,0 +1,263 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/wallet_address.h" + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/autofill_country.h" + +namespace autofill { +namespace wallet { + +namespace { + +Address* CreateAddressInternal(const base::DictionaryValue& dictionary, + const std::string& object_id) { + std::string country_name_code; + if (!dictionary.GetString("postal_address.country_name_code", + &country_name_code)) { + DLOG(ERROR) << "Response from Google Wallet missing country name"; + return NULL; + } + + string16 recipient_name; + if (!dictionary.GetString("postal_address.recipient_name", + &recipient_name)) { + DLOG(ERROR) << "Response from Google Wallet recipient name"; + return NULL; + } + + string16 postal_code_number; + if (!dictionary.GetString("postal_address.postal_code_number", + &postal_code_number)) { + DLOG(ERROR) << "Response from Google Wallet missing postal code number"; + return NULL; + } + + string16 phone_number; + if (!dictionary.GetString("phone_number", &phone_number)) + DVLOG(1) << "Response from Google Wallet missing phone number"; + + string16 address_line_1; + string16 address_line_2; + const ListValue* address_line_list; + if (dictionary.GetList("postal_address.address_line", &address_line_list)) { + if (!address_line_list->GetString(0, &address_line_1)) + DVLOG(1) << "Response from Google Wallet missing address line 1"; + if (!address_line_list->GetString(1, &address_line_2)) + DVLOG(1) << "Response from Google Wallet missing address line 2"; + } else { + DVLOG(1) << "Response from Google Wallet missing address lines"; + } + + string16 locality_name; + if (!dictionary.GetString("postal_address.locality_name", + &locality_name)) { + DVLOG(1) << "Response from Google Wallet missing locality name"; + } + + string16 administrative_area_name; + if (!dictionary.GetString("postal_address.administrative_area_name", + &administrative_area_name)) { + DVLOG(1) << "Response from Google Wallet missing administrative area name"; + } + + return new Address(country_name_code, + recipient_name , + address_line_1, + address_line_2, + locality_name, + administrative_area_name, + postal_code_number, + phone_number, + object_id); +} + +} // namespace + +Address::Address() {} + +Address::Address(const std::string& country_name_code, + const string16& recipient_name, + const string16& address_line_1, + const string16& address_line_2, + const string16& locality_name, + const string16& administrative_area_name, + const string16& postal_code_number, + const string16& phone_number, + const std::string& object_id) + : country_name_code_(country_name_code), + recipient_name_(recipient_name), + address_line_1_(address_line_1), + address_line_2_(address_line_2), + locality_name_(locality_name), + administrative_area_name_(administrative_area_name), + postal_code_number_(postal_code_number), + phone_number_(phone_number), + object_id_(object_id) {} + +Address::~Address() {} + +// static +scoped_ptr<Address> Address::CreateAddressWithID( + const base::DictionaryValue& dictionary) { + std::string object_id; + if (!dictionary.GetString("id", &object_id)) { + DLOG(ERROR) << "Response from Google Wallet missing object id"; + return scoped_ptr<Address>(); + } + return scoped_ptr<Address>(CreateAddressInternal(dictionary, object_id)); +} + +// static +scoped_ptr<Address> Address::CreateAddress( + const base::DictionaryValue& dictionary) { + std::string object_id; + dictionary.GetString("id", &object_id); + return scoped_ptr<Address>(CreateAddressInternal(dictionary, object_id)); +} + +// static +scoped_ptr<Address> Address::CreateDisplayAddress( + const base::DictionaryValue& dictionary) { + std::string country_code; + if (!dictionary.GetString("country_code", &country_code)) { + DLOG(ERROR) << "Reponse from Google Wallet missing country code"; + return scoped_ptr<Address>(); + } + + string16 name; + if (!dictionary.GetString("name", &name)) { + DLOG(ERROR) << "Reponse from Google Wallet missing name"; + return scoped_ptr<Address>(); + } + + string16 postal_code; + if (!dictionary.GetString("postal_code", &postal_code)) { + DLOG(ERROR) << "Reponse from Google Wallet missing postal code"; + return scoped_ptr<Address>(); + } + + string16 address1; + if (!dictionary.GetString("address1", &address1)) + DVLOG(1) << "Reponse from Google Wallet missing address1"; + + string16 address2; + if (!dictionary.GetString("address2", &address2)) + DVLOG(1) << "Reponse from Google Wallet missing address2"; + + string16 city; + if (!dictionary.GetString("city", &city)) + DVLOG(1) << "Reponse from Google Wallet missing city"; + + string16 state; + if (!dictionary.GetString("state", &state)) + DVLOG(1) << "Reponse from Google Wallet missing state"; + + string16 phone_number; + if (!dictionary.GetString("phone_number", &phone_number)) + DVLOG(1) << "Reponse from Google Wallet missing phone number"; + + return scoped_ptr<Address>(new Address(country_code, + name, + address1, + address2, + city, + state, + postal_code, + phone_number, + std::string())); +} + +scoped_ptr<base::DictionaryValue> Address::ToDictionaryWithID() const { + scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + + if (!object_id_.empty()) + dict->SetString("id", object_id_); + dict->SetString("phone_number", phone_number_); + dict->Set("postal_address", ToDictionaryWithoutID().release()); + + return dict.Pass(); +} + +scoped_ptr<base::DictionaryValue> Address::ToDictionaryWithoutID() const { + scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + + scoped_ptr<base::ListValue> address_lines(new base::ListValue()); + address_lines->AppendString(address_line_1_); + if (!address_line_2_.empty()) + address_lines->AppendString(address_line_2_); + dict->Set("address_line", address_lines.release()); + + dict->SetString("country_name_code", country_name_code_); + dict->SetString("recipient_name", recipient_name_); + dict->SetString("locality_name", locality_name_); + dict->SetString("administrative_area_name", + administrative_area_name_); + dict->SetString("postal_code_number", postal_code_number_); + + return dict.Pass(); +} + +string16 Address::DisplayName() const { + // TODO(estade): improve this stub implementation. + return recipient_name() + ASCIIToUTF16(", ") + address_line_1(); +} + +string16 Address::GetInfo(AutofillFieldType type) const { + switch (type) { + case NAME_FULL: + return recipient_name(); + + case ADDRESS_HOME_LINE1: + return address_line_1(); + + case ADDRESS_HOME_LINE2: + return address_line_2(); + + case ADDRESS_HOME_CITY: + return locality_name(); + + case ADDRESS_HOME_STATE: + return administrative_area_name(); + + case ADDRESS_HOME_ZIP: + return postal_code_number(); + + case ADDRESS_HOME_COUNTRY: { + AutofillCountry country(country_name_code(), + AutofillCountry::ApplicationLocale()); + return country.name(); + } + + case PHONE_HOME_WHOLE_NUMBER: + return phone_number(); + + // TODO(estade): implement more. + default: + NOTREACHED(); + return string16(); + } +} + +bool Address::operator==(const Address& other) const { + return country_name_code_ == other.country_name_code_ && + recipient_name_ == other.recipient_name_ && + address_line_1_ == other.address_line_1_ && + address_line_2_ == other.address_line_2_ && + locality_name_ == other.locality_name_ && + administrative_area_name_ == other.administrative_area_name_ && + postal_code_number_ == other.postal_code_number_ && + phone_number_ == other.phone_number_ && + object_id_ == other.object_id_; +} + +bool Address::operator!=(const Address& other) const { + return !(*this == other); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/wallet_address.h b/components/autofill/browser/wallet/wallet_address.h new file mode 100644 index 0000000..9aa8314 --- /dev/null +++ b/components/autofill/browser/wallet/wallet_address.h @@ -0,0 +1,167 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_ADDRESS_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_ADDRESS_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/string16.h" +#include "components/autofill/browser/field_types.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { +namespace wallet { + +// TODO(ahutter): This address is a lot like +// components/autofill/browser/address.h. There should be a super +// class that both extend from to clean up duplicated code. See +// http://crbug.com/164463. + +// Address contains various address fields that have been populated from the +// user's Online Wallet. It is loosely modeled as a subet of the OASIS +// "extensible Address Language" (xAL); see +// http://www.oasis-open.org/committees/ciq/download.shtml. +class Address { + public: + Address(); + // TODO(ahutter): Use additional fields (descriptive_name, is_post_box, + // is_minimal_address, is_valid, is_default) when SaveToWallet is implemented. + // See http://crbug.com/164284. + Address(const std::string& country_name_code, + const string16& recipient_name, + const string16& address_line_1, + const string16& address_line_2, + const string16& locality_name, + const string16& administrative_area_name, + const string16& postal_code_number, + const string16& phone_number, + const std::string& object_id); + ~Address(); + + // Returns an empty scoped_ptr if input is invalid or a valid address that is + // selectable for Google Wallet use. Does not require "id" in |dictionary|. + // IDs are not required for billing addresses. + static scoped_ptr<Address> CreateAddress( + const base::DictionaryValue& dictionary); + + // Builds an Address from |dictionary|, which must have an "id" field. This + // function is designed for use with shipping addresses. The function may fail + // and return an empty pointer if its input is invalid. + static scoped_ptr<Address> CreateAddressWithID( + const base::DictionaryValue& dictionary); + + // Returns an empty scoped_ptr if input in invalid or a valid address that + // can only be used for displaying to the user. + static scoped_ptr<Address> CreateDisplayAddress( + const base::DictionaryValue& dictionary); + + // If an address is being upgraded, it will be sent to the server in a + // different format and with a few additional fields set, most importantly + // |object_id_|. + scoped_ptr<base::DictionaryValue> ToDictionaryWithID() const; + + // Newly created addresses will not have an associated |object_id_| and are + // sent to the server in a slightly different format. + scoped_ptr<base::DictionaryValue> ToDictionaryWithoutID() const; + + // Returns a string that summarizes this address, suitable for display to + // the user. + string16 DisplayName() const; + + // Returns data appropriate for |type|. + string16 GetInfo(AutofillFieldType type) const; + + const std::string& country_name_code() const { return country_name_code_; } + const string16& recipient_name() const { return recipient_name_; } + const string16& address_line_1() const { return address_line_1_; } + const string16& address_line_2() const { return address_line_2_; } + const string16& locality_name() const { return locality_name_; } + const string16& administrative_area_name() const { + return administrative_area_name_; + } + const string16& postal_code_number() const { return postal_code_number_; } + const string16& phone_number() const { return phone_number_; } + const std::string& object_id() const { return object_id_; } + + void set_country_name_code(const std::string& country_name_code) { + country_name_code_ = country_name_code; + } + void set_recipient_name(const string16& recipient_name) { + recipient_name_ = recipient_name; + } + void set_address_line_1(const string16& address_line_1) { + address_line_1_ = address_line_1; + } + void set_address_line_2(const string16& address_line_2) { + address_line_2_ = address_line_2; + } + void set_locality_name(const string16& locality_name) { + locality_name_ = locality_name; + } + void set_administrative_area_name(const string16& administrative_area_name) { + administrative_area_name_ = administrative_area_name; + } + void set_postal_code_number(const string16& postal_code_number) { + postal_code_number_ = postal_code_number; + } + void set_phone_number(const string16& phone_number) { + phone_number_ = phone_number; + } + void set_object_id(const std::string& object_id) { + object_id_ = object_id; + } + + bool operator==(const Address& other) const; + bool operator!=(const Address& other) const; + + private: + // |country_name_code_| should be an ISO 3166-1-alpha-2 (two letter codes, as + // used in DNS). For example, "GB". + std::string country_name_code_; + + // The recipient's name. For example "John Doe". + string16 recipient_name_; + + // |address_line_1| and |address_line_2| correspond to the "AddressLine" + // elements in xAL, which are used to hold unstructured text. + string16 address_line_1_; + string16 address_line_2_; + + // Locality. This is something of a fuzzy term, but it generally refers to + // the city/town portion of an address. In regions of the world where + // localities are not well defined or do not fit into this structure well + // (for example, Japan and China), leave locality_name empty and use + // |address_line_2|. + // Examples: US city, IT comune, UK post town. + string16 locality_name_; + + // Top-level administrative subdivision of this country. + // Examples: US state, IT region, UK constituent nation, JP prefecture. + string16 administrative_area_name_; + + // Despite the name, |postal_code_number_| values are frequently alphanumeric. + // Examples: "94043", "SW1W", "SW1W 9TQ". + string16 postal_code_number_; + + // A valid international phone number. If |phone_number_| is a user provided + // value, it should have been validated using libphonenumber by clients of + // this class before being set; see http://code.google.com/p/libphonenumber/. + string16 phone_number_; + + // Externalized Online Wallet id for this address. + std::string object_id_; + + DISALLOW_ASSIGN(Address); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_ADDRESS_H_ diff --git a/components/autofill/browser/wallet/wallet_address_unittest.cc b/components/autofill/browser/wallet/wallet_address_unittest.cc new file mode 100644 index 0000000..0949c6a --- /dev/null +++ b/components/autofill/browser/wallet/wallet_address_unittest.cc @@ -0,0 +1,313 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/wallet/wallet_address.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kAddressMissingObjectId[] = + "{" + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"country_name_code\"" + " }" + "}"; + +const char kAddressMissingCountryNameCode[] = + "{" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"" + " }" + "}"; + +const char kAddressMissingRecipientName[] = + "{" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"country_name_code\"" + " }" + "}"; + +const char kAddressMissingPostalCodeNumber[] = + "{" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"country_name_code\":\"country_name_code\"" + " }" + "}"; + +const char kValidAddress[] = + "{" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"country_name_code\":\"country_name_code\"," + " \"postal_code_number\":\"postal_code_number\"" + " }" + "}"; + +const char kClientAddressMissingCountryCode[] = + "{" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"" + "}"; + +const char kClientAddressMissingPostalCode[] = + "{" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + "}"; + +const char kClientAddressMissingName[] = + "{" + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + "}"; + +const char kClientValidAddress[] = + "{" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + "}"; + +} // anonymous namespace + +namespace autofill { +namespace wallet { + +class WalletAddressTest : public testing::Test { + public: + WalletAddressTest() {} + protected: + void SetUpDictionary(const std::string& json) { + scoped_ptr<Value> value(base::JSONReader::Read(json)); + DCHECK(value.get()); + DCHECK(value->IsType(Value::TYPE_DICTIONARY)); + dict_.reset(static_cast<DictionaryValue*>(value.release())); + } + scoped_ptr<const DictionaryValue> dict_; +}; + +TEST_F(WalletAddressTest, CreateAddressMissingObjectId) { + SetUpDictionary(kAddressMissingObjectId); + Address address("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + ""); + ASSERT_EQ(address, *Address::CreateAddress(*dict_)); +} + +TEST_F(WalletAddressTest, CreateAddressWithIDMissingObjectId) { + SetUpDictionary(kAddressMissingObjectId); + ASSERT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressMissingCountryNameCode) { + SetUpDictionary(kAddressMissingCountryNameCode); + ASSERT_EQ(NULL, Address::CreateAddress(*dict_).get()); + ASSERT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressMissingRecipientName) { + SetUpDictionary(kAddressMissingRecipientName); + ASSERT_EQ(NULL, Address::CreateAddress(*dict_).get()); + ASSERT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressMissingPostalCodeNumber) { + SetUpDictionary(kAddressMissingPostalCodeNumber); + ASSERT_EQ(NULL, Address::CreateAddress(*dict_).get()); + ASSERT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressWithID) { + SetUpDictionary(kValidAddress); + Address address("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id"); + ASSERT_EQ(address, *Address::CreateAddress(*dict_)); + ASSERT_EQ(address, *Address::CreateAddressWithID(*dict_)); +} + +TEST_F(WalletAddressTest, CreateDisplayAddressMissingCountryNameCode) { + SetUpDictionary(kClientAddressMissingCountryCode); + ASSERT_EQ(NULL, Address::CreateDisplayAddress(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateDisplayAddressMissingName) { + SetUpDictionary(kClientAddressMissingName); + ASSERT_EQ(NULL, Address::CreateDisplayAddress(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateDisplayAddressMissingPostalCode) { + SetUpDictionary(kClientAddressMissingPostalCode); + ASSERT_EQ(NULL, Address::CreateDisplayAddress(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateDisplayAddress) { + SetUpDictionary(kClientValidAddress); + Address address("country_code", + ASCIIToUTF16("name"), + ASCIIToUTF16("address1"), + ASCIIToUTF16("address2"), + ASCIIToUTF16("city"), + ASCIIToUTF16("state"), + ASCIIToUTF16("postal_code"), + ASCIIToUTF16("phone_number"), + ""); + ASSERT_EQ(address, *Address::CreateDisplayAddress(*dict_)); +} + +TEST_F(WalletAddressTest, ToDictionaryWithoutID) { + base::DictionaryValue expected; + expected.SetString("country_name_code", + "country_name_code"); + expected.SetString("recipient_name", + "recipient_name"); + expected.SetString("locality_name", + "locality_name"); + expected.SetString("administrative_area_name", + "administrative_area_name"); + expected.SetString("postal_code_number", + "postal_code_number"); + base::ListValue* address_lines = new base::ListValue(); + address_lines->AppendString("address_line_1"); + address_lines->AppendString("address_line_2"); + expected.Set("address_line", address_lines); + + Address address("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + ""); + + EXPECT_TRUE(expected.Equals(address.ToDictionaryWithoutID().get())); +} + +TEST_F(WalletAddressTest, ToDictionaryWithID) { + base::DictionaryValue expected; + expected.SetString("id", "id"); + expected.SetString("phone_number", "phone_number"); + expected.SetString("postal_address.country_name_code", + "country_name_code"); + expected.SetString("postal_address.recipient_name", + "recipient_name"); + expected.SetString("postal_address.locality_name", + "locality_name"); + expected.SetString("postal_address.administrative_area_name", + "administrative_area_name"); + expected.SetString("postal_address.postal_code_number", + "postal_code_number"); + base::ListValue* address_lines = new base::ListValue(); + address_lines->AppendString("address_line_1"); + address_lines->AppendString("address_line_2"); + expected.Set("postal_address.address_line", address_lines); + + Address address("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id"); + + EXPECT_TRUE(expected.Equals(address.ToDictionaryWithID().get())); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/wallet_client.cc b/components/autofill/browser/wallet/wallet_client.cc new file mode 100644 index 0000000..fbf17af --- /dev/null +++ b/components/autofill/browser/wallet/wallet_client.cc @@ -0,0 +1,664 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/wallet_client.h" + +#include "base/bind.h" +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "components/autofill/browser/wallet/cart.h" +#include "components/autofill/browser/wallet/instrument.h" +#include "components/autofill/browser/wallet/wallet_address.h" +#include "components/autofill/browser/wallet/wallet_client_observer.h" +#include "components/autofill/browser/wallet/wallet_items.h" +#include "components/autofill/browser/wallet/wallet_service_url.h" +#include "crypto/random.h" +#include "google_apis/google_api_keys.h" +#include "googleurl/src/gurl.h" +#include "net/http/http_status_code.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_request_context_getter.h" + +namespace autofill { +namespace wallet { + +namespace { + +const char kJsonMimeType[] = "application/json"; +const size_t kOneTimePadLength = 6; + +std::string AutocheckoutStatusToString(AutocheckoutStatus status) { + switch (status) { + case MISSING_FIELDMAPPING: + return "MISSING_FIELDMAPPING"; + case MISSING_ADVANCE: + return "MISSING_ADVANCE"; + case CANNOT_PROCEED: + return "CANNOT_PROCEED"; + case SUCCESS: + // SUCCESS cannot be sent to the server as it will result in a failure. + NOTREACHED(); + return "ERROR"; + } + NOTREACHED(); + return "NOT_POSSIBLE"; +} + +std::string DialogTypeToFeatureString(autofill::DialogType dialog_type) { + switch (dialog_type) { + case DIALOG_TYPE_REQUEST_AUTOCOMPLETE: + return "REQUEST_AUTOCOMPLETE"; + case DIALOG_TYPE_AUTOCHECKOUT: + return "AUTOCHECKOUT"; + } + NOTREACHED(); + return "NOT_POSSIBLE"; +} + +// Gets and parses required actions from a SaveToWallet response. Returns +// false if any unknown required actions are seen and true otherwise. +void GetRequiredActionsForSaveToWallet( + const base::DictionaryValue& dict, + std::vector<RequiredAction>* required_actions) { + const ListValue* required_action_list; + if (!dict.GetList("required_action", &required_action_list)) + return; + + for (size_t i = 0; i < required_action_list->GetSize(); ++i) { + std::string action_string; + if (required_action_list->GetString(i, &action_string)) { + RequiredAction action = ParseRequiredActionFromString(action_string); + if (!ActionAppliesToSaveToWallet(action)) { + DLOG(ERROR) << "Response from Google wallet with bad required action:" + " \"" << action_string << "\""; + required_actions->clear(); + return; + } + required_actions->push_back(action); + } + } +} + +// Keys for JSON communication with the Online Wallet server. +const char kAcceptedLegalDocumentKey[] = "accepted_legal_document"; +const char kApiKeyKey[] = "api_key"; +const char kAuthResultKey[] = "auth_result"; +const char kCartKey[] = "cart"; +const char kEncryptedOtpKey[] = "encrypted_otp"; +const char kFeatureKey[] = "feature"; +const char kGoogleTransactionIdKey[] = "google_transaction_id"; +const char kInstrumentIdKey[] = "instrument_id"; +const char kInstrumentKey[] = "instrument"; +const char kInstrumentEscrowHandleKey[] = "instrument_escrow_handle"; +const char kInstrumentPhoneNumberKey[] = "instrument_phone_number"; +const char kMerchantDomainKey[] = "merchant_domain"; +const char kReasonKey[] = "reason"; +const char kRiskParamsKey[] = "risk_params"; +const char kSelectedAddressIdKey[] = "selected_address_id"; +const char kSelectedInstrumentIdKey[] = "selected_instrument_id"; +const char kSessionMaterialKey[] = "session_material"; +const char kShippingAddressIdKey[] = "shipping_address_id"; +const char kShippingAddressKey[] = "shipping_address"; +const char kSuccessKey[] = "success"; +const char kUpgradedBillingAddressKey[] = "upgraded_billing_address"; +const char kUpgradedInstrumentIdKey[] = "upgraded_instrument_id"; + +} // namespace + + +WalletClient::WalletClient(net::URLRequestContextGetter* context_getter, + WalletClientObserver* observer) + : context_getter_(context_getter), + observer_(observer), + request_type_(NO_PENDING_REQUEST), + one_time_pad_(kOneTimePadLength), + ALLOW_THIS_IN_INITIALIZER_LIST( + encryption_escrow_client_(context_getter, this)) { + DCHECK(context_getter_); + DCHECK(observer_); +} + +WalletClient::~WalletClient() {} + +void WalletClient::AcceptLegalDocuments( + const std::vector<std::string>& document_ids, + const std::string& google_transaction_id, + const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::AcceptLegalDocuments, + base::Unretained(this), + document_ids, + google_transaction_id, + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = ACCEPT_LEGAL_DOCUMENTS; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kGoogleTransactionIdKey, google_transaction_id); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + ListValue* docs_list = new ListValue(); + for (std::vector<std::string>::const_iterator it = document_ids.begin(); + it != document_ids.end(); + ++it) { + docs_list->AppendString(*it); + } + request_dict.Set(kAcceptedLegalDocumentKey, docs_list); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetAcceptLegalDocumentsUrl(), post_body); +} + +void WalletClient::AuthenticateInstrument( + const std::string& instrument_id, + const std::string& card_verification_number, + const std::string& obfuscated_gaia_id) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::AuthenticateInstrument, + base::Unretained(this), + instrument_id, + card_verification_number, + obfuscated_gaia_id)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + DCHECK(pending_request_body_.empty()); + request_type_ = AUTHENTICATE_INSTRUMENT; + + pending_request_body_.SetString(kApiKeyKey, google_apis::GetAPIKey()); + pending_request_body_.SetString(kRiskParamsKey, GetRiskParams()); + pending_request_body_.SetString(kInstrumentIdKey, instrument_id); + + encryption_escrow_client_.EscrowCardVerificationNumber( + card_verification_number, obfuscated_gaia_id); +} + +void WalletClient::GetFullWallet(const std::string& instrument_id, + const std::string& address_id, + const GURL& source_url, + const Cart& cart, + const std::string& google_transaction_id, + autofill::DialogType dialog_type) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::GetFullWallet, + base::Unretained(this), + instrument_id, + address_id, + source_url, + cart, + google_transaction_id, + dialog_type)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + DCHECK(pending_request_body_.empty()); + request_type_ = GET_FULL_WALLET; + + pending_request_body_.SetString(kApiKeyKey, google_apis::GetAPIKey()); + pending_request_body_.SetString(kRiskParamsKey, GetRiskParams()); + pending_request_body_.SetString(kSelectedInstrumentIdKey, instrument_id); + pending_request_body_.SetString(kSelectedAddressIdKey, address_id); + pending_request_body_.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + pending_request_body_.SetString(kGoogleTransactionIdKey, + google_transaction_id); + pending_request_body_.Set(kCartKey, cart.ToDictionary().release()); + pending_request_body_.SetString(kFeatureKey, + DialogTypeToFeatureString(dialog_type)); + + crypto::RandBytes(&(one_time_pad_[0]), one_time_pad_.size()); + encryption_escrow_client_.EncryptOneTimePad(one_time_pad_); +} + +void WalletClient::GetWalletItems(const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::GetWalletItems, + base::Unretained(this), + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = GET_WALLET_ITEMS; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kRiskParamsKey, GetRiskParams()); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetGetWalletItemsUrl(), post_body); +} + +void WalletClient::SaveAddress(const Address& shipping_address, + const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::SaveAddress, + base::Unretained(this), + shipping_address, + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = SAVE_ADDRESS; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kRiskParamsKey, GetRiskParams()); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + + request_dict.Set(kShippingAddressKey, + shipping_address.ToDictionaryWithID().release()); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetSaveToWalletUrl(), post_body); +} + +void WalletClient::SaveInstrument( + const Instrument& instrument, + const std::string& obfuscated_gaia_id, + const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::SaveInstrument, + base::Unretained(this), + instrument, + obfuscated_gaia_id, + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + DCHECK(pending_request_body_.empty()); + request_type_ = SAVE_INSTRUMENT; + + pending_request_body_.SetString(kApiKeyKey, google_apis::GetAPIKey()); + pending_request_body_.SetString(kRiskParamsKey, GetRiskParams()); + pending_request_body_.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + + pending_request_body_.Set(kInstrumentKey, + instrument.ToDictionary().release()); + pending_request_body_.SetString(kInstrumentPhoneNumberKey, + instrument.address().phone_number()); + + encryption_escrow_client_.EscrowInstrumentInformation(instrument, + obfuscated_gaia_id); +} + +void WalletClient::SaveInstrumentAndAddress( + const Instrument& instrument, + const Address& address, + const std::string& obfuscated_gaia_id, + const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::SaveInstrumentAndAddress, + base::Unretained(this), + instrument, + address, + obfuscated_gaia_id, + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + DCHECK(pending_request_body_.empty()); + request_type_ = SAVE_INSTRUMENT_AND_ADDRESS; + + pending_request_body_.SetString(kApiKeyKey, google_apis::GetAPIKey()); + pending_request_body_.SetString(kRiskParamsKey, GetRiskParams()); + pending_request_body_.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + + pending_request_body_.Set(kInstrumentKey, + instrument.ToDictionary().release()); + pending_request_body_.SetString(kInstrumentPhoneNumberKey, + instrument.address().phone_number()); + + pending_request_body_.Set(kShippingAddressKey, + address.ToDictionaryWithID().release()); + + encryption_escrow_client_.EscrowInstrumentInformation(instrument, + obfuscated_gaia_id); +} + +void WalletClient::SendAutocheckoutStatus( + AutocheckoutStatus status, + const GURL& source_url, + const std::string& google_transaction_id) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::SendAutocheckoutStatus, + base::Unretained(this), + status, + source_url, + google_transaction_id)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = SEND_STATUS; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + bool success = status == SUCCESS; + request_dict.SetBoolean(kSuccessKey, success); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + if (!success) + request_dict.SetString(kReasonKey, AutocheckoutStatusToString(status)); + request_dict.SetString(kGoogleTransactionIdKey, google_transaction_id); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetSendStatusUrl(), post_body); +} + +void WalletClient::UpdateInstrument( + const std::string& instrument_id, + const Address& billing_address, + const GURL& source_url) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::UpdateInstrument, + base::Unretained(this), + instrument_id, + billing_address, + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = UPDATE_INSTRUMENT; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kRiskParamsKey, GetRiskParams()); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + + request_dict.SetString(kUpgradedInstrumentIdKey, instrument_id); + request_dict.SetString(kInstrumentPhoneNumberKey, + billing_address.phone_number()); + request_dict.Set(kUpgradedBillingAddressKey, + billing_address.ToDictionaryWithoutID().release()); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetSaveToWalletUrl(), post_body); +} + +bool WalletClient::HasRequestInProgress() const { + return request_.get() != NULL; +} + +void WalletClient::CancelPendingRequests() { + while (!pending_requests_.empty()) { + pending_requests_.pop(); + } +} + +void WalletClient::MakeWalletRequest(const GURL& url, + const std::string& post_body) { + DCHECK(!HasRequestInProgress()); + + request_.reset(net::URLFetcher::Create( + 0, url, net::URLFetcher::POST, this)); + request_->SetRequestContext(context_getter_); + DVLOG(1) << "url=" << url << ", post_body=" << post_body; + request_->SetUploadData(kJsonMimeType, post_body); + request_->Start(); +} + +// TODO(ahutter): Add manual retry logic if it's necessary. +void WalletClient::OnURLFetchComplete( + const net::URLFetcher* source) { + DCHECK_EQ(source, request_.get()); + DVLOG(1) << "Got response from " << source->GetOriginalURL(); + + std::string data; + source->GetResponseAsString(&data); + DVLOG(1) << "Response body: " << data; + + scoped_ptr<base::DictionaryValue> response_dict; + + int response_code = source->GetResponseCode(); + switch (response_code) { + // HTTP_BAD_REQUEST means the arguments are invalid. No point retrying. + case net::HTTP_BAD_REQUEST: { + request_type_ = NO_PENDING_REQUEST; + observer_->OnWalletError(); + return; + } + // HTTP_OK holds a valid response and HTTP_INTERNAL_SERVER_ERROR holds an + // error code and message for the user. + case net::HTTP_OK: + case net::HTTP_INTERNAL_SERVER_ERROR: { + scoped_ptr<Value> message_value(base::JSONReader::Read(data)); + if (message_value.get() && + message_value->IsType(Value::TYPE_DICTIONARY)) { + response_dict.reset( + static_cast<base::DictionaryValue*>(message_value.release())); + } + if (response_code == net::HTTP_INTERNAL_SERVER_ERROR) { + request_type_ = NO_PENDING_REQUEST; + // TODO(ahutter): Do something with the response. See + // http://crbug.com/164410. + observer_->OnWalletError(); + return; + } + break; + } + // Anything else is an error. + default: { + request_type_ = NO_PENDING_REQUEST; + observer_->OnNetworkError(response_code); + return; + } + } + + RequestType type = request_type_; + request_type_ = NO_PENDING_REQUEST; + + if (!(type == ACCEPT_LEGAL_DOCUMENTS || type == SEND_STATUS) && + !response_dict) { + HandleMalformedResponse(); + return; + } + + switch (type) { + case ACCEPT_LEGAL_DOCUMENTS: + observer_->OnDidAcceptLegalDocuments(); + break; + + case AUTHENTICATE_INSTRUMENT: { + std::string auth_result; + if (response_dict->GetString(kAuthResultKey, &auth_result)) { + std::string trimmed; + TrimWhitespaceASCII(auth_result, + TRIM_ALL, + &trimmed); + observer_->OnDidAuthenticateInstrument( + LowerCaseEqualsASCII(trimmed, "success")); + } else { + HandleMalformedResponse(); + } + break; + } + + case SEND_STATUS: + observer_->OnDidSendAutocheckoutStatus(); + break; + + case GET_FULL_WALLET: { + scoped_ptr<FullWallet> full_wallet( + FullWallet::CreateFullWallet(*response_dict)); + if (full_wallet) { + full_wallet->set_one_time_pad(one_time_pad_); + observer_->OnDidGetFullWallet(full_wallet.Pass()); + } else { + HandleMalformedResponse(); + } + break; + } + + case GET_WALLET_ITEMS: { + scoped_ptr<WalletItems> wallet_items( + WalletItems::CreateWalletItems(*response_dict)); + if (wallet_items) + observer_->OnDidGetWalletItems(wallet_items.Pass()); + else + HandleMalformedResponse(); + break; + } + + case SAVE_ADDRESS: { + std::string shipping_address_id; + std::vector<RequiredAction> required_actions; + GetRequiredActionsForSaveToWallet(*response_dict, &required_actions); + if (response_dict->GetString(kShippingAddressIdKey, + &shipping_address_id) || + !required_actions.empty()) { + observer_->OnDidSaveAddress(shipping_address_id, required_actions); + } else { + HandleMalformedResponse(); + } + break; + } + + case SAVE_INSTRUMENT: { + std::string instrument_id; + std::vector<RequiredAction> required_actions; + GetRequiredActionsForSaveToWallet(*response_dict, &required_actions); + if (response_dict->GetString(kInstrumentIdKey, &instrument_id) || + !required_actions.empty()) { + observer_->OnDidSaveInstrument(instrument_id, required_actions); + } else { + HandleMalformedResponse(); + } + break; + } + + case SAVE_INSTRUMENT_AND_ADDRESS: { + std::string instrument_id; + response_dict->GetString(kInstrumentIdKey, &instrument_id); + std::string shipping_address_id; + response_dict->GetString(kShippingAddressIdKey, + &shipping_address_id); + std::vector<RequiredAction> required_actions; + GetRequiredActionsForSaveToWallet(*response_dict, &required_actions); + if ((!instrument_id.empty() && !shipping_address_id.empty()) || + !required_actions.empty()) { + observer_->OnDidSaveInstrumentAndAddress(instrument_id, + shipping_address_id, + required_actions); + } else { + HandleMalformedResponse(); + } + break; + } + + case UPDATE_INSTRUMENT: { + std::string instrument_id; + std::vector<RequiredAction> required_actions; + GetRequiredActionsForSaveToWallet(*response_dict, &required_actions); + if (response_dict->GetString(kInstrumentIdKey, &instrument_id) || + !required_actions.empty()) { + observer_->OnDidUpdateInstrument(instrument_id, required_actions); + } else { + HandleMalformedResponse(); + } + break; + } + + case NO_PENDING_REQUEST: + NOTREACHED(); + } + + request_.reset(); + StartNextPendingRequest(); +} + +void WalletClient::StartNextPendingRequest() { + if (pending_requests_.empty()) + return; + + base::Closure next_request = pending_requests_.front(); + pending_requests_.pop(); + next_request.Run(); +} + +void WalletClient::HandleMalformedResponse() { + // Called to inform exponential backoff logic of the error. + request_->ReceivedContentWasMalformed(); + observer_->OnMalformedResponse(); +} + +void WalletClient::OnDidEncryptOneTimePad( + const std::string& encrypted_one_time_pad, + const std::string& session_material) { + DCHECK_EQ(GET_FULL_WALLET, request_type_); + pending_request_body_.SetString(kEncryptedOtpKey, encrypted_one_time_pad); + pending_request_body_.SetString(kSessionMaterialKey, session_material); + + std::string post_body; + base::JSONWriter::Write(&pending_request_body_, &post_body); + pending_request_body_.Clear(); + + MakeWalletRequest(GetGetFullWalletUrl(), post_body); +} + +void WalletClient::OnDidEscrowInstrumentInformation( + const std::string& escrow_handle) { + DCHECK(request_type_ == SAVE_INSTRUMENT || + request_type_ == SAVE_INSTRUMENT_AND_ADDRESS); + + pending_request_body_.SetString(kInstrumentEscrowHandleKey, escrow_handle); + + std::string post_body; + base::JSONWriter::Write(&pending_request_body_, &post_body); + pending_request_body_.Clear(); + + MakeWalletRequest(GetSaveToWalletUrl(), post_body); +} + +void WalletClient::OnDidEscrowCardVerificationNumber( + const std::string& escrow_handle) { + DCHECK_EQ(AUTHENTICATE_INSTRUMENT, request_type_); + pending_request_body_.SetString(kInstrumentEscrowHandleKey, escrow_handle); + + std::string post_body; + base::JSONWriter::Write(&pending_request_body_, &post_body); + pending_request_body_.Clear(); + + MakeWalletRequest(GetAuthenticateInstrumentUrl(), post_body); +} + +void WalletClient::OnNetworkError(int response_code) { + observer_->OnNetworkError(response_code); +} + +void WalletClient::OnMalformedResponse() { + observer_->OnMalformedResponse(); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/wallet_client.h b/components/autofill/browser/wallet/wallet_client.h new file mode 100644 index 0000000..009dc90 --- /dev/null +++ b/components/autofill/browser/wallet/wallet_client.h @@ -0,0 +1,229 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_H_ + +#include <queue> +#include <string> +#include <vector> + +#include "base/callback.h" // For base::Closure. +#include "base/memory/ref_counted.h" +#include "base/values.h" +#include "components/autofill/browser/autofill_manager_delegate.h" +#include "components/autofill/browser/wallet/encryption_escrow_client.h" +#include "components/autofill/browser/wallet/encryption_escrow_client_observer.h" +#include "components/autofill/browser/wallet/full_wallet.h" +#include "components/autofill/common/autocheckout_status.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +class GURL; + +namespace net { +class URLFetcher; +class URLRequestContextGetter; +} + +namespace autofill { +namespace wallet { + +class Address; +class Cart; +class FullWallet; +class Instrument; +class WalletClientObserver; +class WalletItems; + +// WalletClient is responsible for making calls to the Online Wallet backend on +// the user's behalf. The normal flow for using this class is as follows: +// 1) GetWalletItems should be called to retrieve the user's Wallet. +// a) If the user does not have a Wallet, they must AcceptLegalDocuments and +// SaveInstrumentAndAddress before continuing. +// b) If the user has not acccepte the most recent legal documents for +// Wallet, they must AcceptLegalDocuments. +// 2) The user then chooses what instrument and shipping address to use for the +// current transaction. +// a) If they choose an instrument with a zip code only address, the billing +// address will need to be updated using UpdateInstrument. +// b) The user may also choose to add a new instrument or address using +// SaveAddress, SaveInstrument, or SaveInstrumentAndAddress. +// 3) Once the user has selected the backing instrument and shipping address +// for this transaction, a FullWallet with the fronting card is generated +// using GetFullWallet. +// a) GetFullWallet may return a Risk challenge for the user. In that case, +// the user will need to verify who they are by authenticating their +// chosen backing instrument through AuthenticateInstrument +// 4) If the user initiated Autocheckout, SendAutocheckoutStatus to notify +// Online Wallet of the status flow to record various metrics. +// +// WalletClient is designed so only one request to Online Wallet can be outgoing +// at any one time. If |HasRequestInProgress()| is true while calling e.g. +// GetWalletItems(), the request will be queued and started later. Queued +// requests start in the order they were received. + +class WalletClient + : public net::URLFetcherDelegate, + public EncryptionEscrowClientObserver { + public: + // |context_getter| is reference counted so it has no lifetime or ownership + // requirements. |observer| must outlive |this|. + WalletClient(net::URLRequestContextGetter* context_getter, + WalletClientObserver* observer); + + virtual ~WalletClient(); + + // GetWalletItems retrieves the user's online wallet. The WalletItems + // returned may require additional action such as presenting legal documents + // to the user to be accepted. + void GetWalletItems(const GURL& source_url); + + // The GetWalletItems call to the Online Wallet backend may require the user + // to accept various legal documents before a FullWallet can be generated. + // The |document_ids| and |google_transaction_id| are provided in the response + // to the GetWalletItems call. + void AcceptLegalDocuments(const std::vector<std::string>& document_ids, + const std::string& google_transaction_id, + const GURL& source_url); + + // Authenticates that |card_verification_number| is for the backing instrument + // with |instrument_id|. |obfuscated_gaia_id| is used as a key when escrowing + // |card_verification_number|. |observer| is notified when the request is + // complete. Used to respond to Risk challenges. + void AuthenticateInstrument(const std::string& instrument_id, + const std::string& card_verification_number, + const std::string& obfuscated_gaia_id); + + // GetFullWallet retrieves the a FullWallet for the user. |instrument_id| and + // |adddress_id| should have been selected by the user in some UI, + // |merchant_domain| should come from the BrowserContext, the |cart| + // information will have been provided by the browser, |dialog_type| indicates + // which dialog requests the full wallet, RequestAutocomplete or Autocheckout, + // and |google_transaction_id| is the same one that GetWalletItems returns. + void GetFullWallet(const std::string& instrument_id, + const std::string& address_id, + const GURL& source_url, + const Cart& cart, + const std::string& google_transaction_id, + autofill::DialogType dialog_type); + + // SaveAddress saves a new shipping address. + void SaveAddress(const Address& address, const GURL& source_url); + + // SaveInstrument saves a new instrument. + void SaveInstrument(const Instrument& instrument, + const std::string& obfuscated_gaia_id, + const GURL& source_url); + + // SaveInstrumentAndAddress saves a new instrument and address. + void SaveInstrumentAndAddress(const Instrument& instrument, + const Address& shipping_address, + const std::string& obfuscated_gaia_id, + const GURL& source_url); + + // SendAutocheckoutStatus is used for tracking the success of Autocheckout + // flows. |status| is the result of the flow, |merchant_domain| is the domain + // where the purchase occured, and |google_transaction_id| is the same as the + // one provided by GetWalletItems. + void SendAutocheckoutStatus(autofill::AutocheckoutStatus status, + const GURL& source_url, + const std::string& google_transaction_id); + + // UpdateInstrument changes the instrument with id |instrument_id| with the + // information in |billing_address|. Its primary use is for upgrading ZIP code + // only addresses or those missing phone numbers. DO NOT change the name on + // |billing_address| from the one returned by Online Wallet or this call will + // fail. + void UpdateInstrument(const std::string& instrument_id, + const Address& billing_address, + const GURL& source_url); + + // Whether there is a currently running request (i.e. |request_| != NULL). + bool HasRequestInProgress() const; + + // Cancels and clears all |pending_requests_|. + void CancelPendingRequests(); + + private: + FRIEND_TEST_ALL_PREFIXES(WalletClientTest, PendingRequest); + FRIEND_TEST_ALL_PREFIXES(WalletClientTest, CancelPendingRequests); + + // TODO(ahutter): Implement this. + std::string GetRiskParams() { return std::string(); } + + enum RequestType { + NO_PENDING_REQUEST, + ACCEPT_LEGAL_DOCUMENTS, + AUTHENTICATE_INSTRUMENT, + GET_FULL_WALLET, + GET_WALLET_ITEMS, + SAVE_ADDRESS, + SAVE_INSTRUMENT, + SAVE_INSTRUMENT_AND_ADDRESS, + SEND_STATUS, + UPDATE_INSTRUMENT, + }; + + // Posts |post_body| to |url| and notifies |observer| when the request is + // complete. + void MakeWalletRequest(const GURL& url, const std::string& post_body); + + // Performs bookkeeping tasks for any invalid requests. + void HandleMalformedResponse(); + + // Start the next pending request (if any). + void StartNextPendingRequest(); + + // net::URLFetcherDelegate: + virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; + + // EncryptionEscrowClientObserver: + virtual void OnDidEncryptOneTimePad( + const std::string& encrypted_one_time_pad, + const std::string& session_material) OVERRIDE; + virtual void OnDidEscrowInstrumentInformation( + const std::string& escrow_handle) OVERRIDE; + virtual void OnDidEscrowCardVerificationNumber( + const std::string& escrow_handle) OVERRIDE; + virtual void OnNetworkError(int response_code) OVERRIDE; + virtual void OnMalformedResponse() OVERRIDE; + + // The context for the request. Ensures the gdToken cookie is set as a header + // in the requests to Online Wallet if it is present. + scoped_refptr<net::URLRequestContextGetter> context_getter_; + + // Observer class that has its various On* methods called based on the results + // of a request to Online Wallet. + WalletClientObserver* const observer_; // must outlive |this|. + + // The current request object. + scoped_ptr<net::URLFetcher> request_; + + // The type of the current request. Must be NO_PENDING_REQUEST for a request + // to be initiated as only one request may be running at a given time. + RequestType request_type_; + + // The one time pad used for GetFullWallet encryption. + std::vector<uint8> one_time_pad_; + + // GetFullWallet requests and requests that alter instruments rely on requests + // made through the |encryption_escrow_client_| finishing first. The request + // body is saved here while that those requests are in flight. + base::DictionaryValue pending_request_body_; + + // Requests that are waiting to be run. + std::queue<base::Closure> pending_requests_; + + // This client is repsonsible for making encryption and escrow calls to Online + // Wallet. + EncryptionEscrowClient encryption_escrow_client_; + + DISALLOW_COPY_AND_ASSIGN(WalletClient); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_H_ diff --git a/components/autofill/browser/wallet/wallet_client_observer.h b/components/autofill/browser/wallet/wallet_client_observer.h new file mode 100644 index 0000000..bca839b --- /dev/null +++ b/components/autofill/browser/wallet/wallet_client_observer.h @@ -0,0 +1,88 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_OBSERVER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_OBSERVER_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" + +namespace autofill { +namespace wallet { + +class FullWallet; +class WalletItems; + +// WalletClientObserver is to be implemented any classes making calls with +// WalletClient. The appropriate callback method will be called on +// WalletClientObserver with the response from the Online Wallet backend. +class WalletClientObserver { + public: + // Called when an AcceptLegalDocuments request finishes successfully. + virtual void OnDidAcceptLegalDocuments() = 0; + + // Called when an AuthenticateInstrument request finishes successfully. + virtual void OnDidAuthenticateInstrument(bool success) = 0; + + // Called when a GetFullWallet request finishes successfully. Ownership is + // transferred to implementer of this interface. + virtual void OnDidGetFullWallet(scoped_ptr<FullWallet> full_wallet) = 0; + + // Called when a GetWalletItems request finishes successfully. Ownership is + // transferred to implementer of this interface. + virtual void OnDidGetWalletItems(scoped_ptr<WalletItems> wallet_items) = 0; + + // Called when a SaveAddress request finishes successfully. |address_id| can + // be used in subsequent GetFullWallet calls. |required_actions| is populated + // if there was a validation error with the data being saved. + virtual void OnDidSaveAddress( + const std::string& address_id, + const std::vector<RequiredAction>& required_actions) = 0; + + // Called when a SaveInstrument request finishes sucessfully. |instrument_id| + // can be used in subsequent GetFullWallet calls. |required_actions| is + // populated if there was a validation error with the data being saved. + virtual void OnDidSaveInstrument( + const std::string& instrument_id, + const std::vector<RequiredAction>& required_actions) = 0; + + // Called when a SaveInstrumentAndAddress request finishes succesfully. + // |instrument_id| and |address_id| can be used in subsequent + // GetFullWallet calls. |required_actions| is populated if there was a + // validation error with the data being saved. + virtual void OnDidSaveInstrumentAndAddress( + const std::string& instrument_id, + const std::string& address_id, + const std::vector<RequiredAction>& required_actions) = 0; + + // Called when a SendAutocheckoutStatus request finishes successfully. + virtual void OnDidSendAutocheckoutStatus() = 0; + + // Called when an UpdateInstrument request finishes successfully. + // |required_actions| is populated if there was a validation error with the + // data being saved. + virtual void OnDidUpdateInstrument( + const std::string& instrument_id, + const std::vector<RequiredAction>& required_actions) = 0; + + // TODO(ahutter): This is going to need more arguments, probably an error + // code and a message for the user. + // Called when a request fails due to an Online Wallet error. + virtual void OnWalletError() = 0; + + // Called when a request fails due to a malformed response. + virtual void OnMalformedResponse() = 0; + + // Called when a request fails due to a network error. + virtual void OnNetworkError(int response_code) = 0; + + protected: + virtual ~WalletClientObserver() {} +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_OBSERVER_H_ diff --git a/components/autofill/browser/wallet/wallet_client_unittest.cc b/components/autofill/browser/wallet/wallet_client_unittest.cc new file mode 100644 index 0000000..d074bf9 --- /dev/null +++ b/components/autofill/browser/wallet/wallet_client_unittest.cc @@ -0,0 +1,1441 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/string_util.h" +#include "base/strings/string_number_conversions.h" +#include "base/values.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/browser/wallet/cart.h" +#include "components/autofill/browser/wallet/full_wallet.h" +#include "components/autofill/browser/wallet/instrument.h" +#include "components/autofill/browser/wallet/wallet_client.h" +#include "components/autofill/browser/wallet/wallet_client_observer.h" +#include "components/autofill/browser/wallet/wallet_items.h" +#include "components/autofill/browser/wallet/wallet_test_util.h" +#include "components/autofill/common/autocheckout_status.h" +#include "content/public/test/test_browser_thread.h" +#include "googleurl/src/gurl.h" +#include "net/base/net_errors.h" +#include "net/http/http_status_code.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "net/url_request/url_request_status.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kGoogleTransactionId[] = "google-transaction-id"; +const char kMerchantUrl[] = "https://example.com/path?key=value"; + +const char kGetFullWalletValidResponse[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"iin\":\"iin\"," + " \"rest\":\"rest\"," + " \"billing_address\":" + " {" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line\":" + " [" + " \"address_line_1\"," + " \"address_line_2\"" + " ]," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"country_name_code\"" + " }" + " }," + " \"shipping_address\":" + " {" + " \"id\":\"ship_id\"," + " \"phone_number\":\"ship_phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"ship_recipient_name\"," + " \"address_line\":" + " [" + " \"ship_address_line_1\"," + " \"ship_address_line_2\"" + " ]," + " \"locality_name\":\"ship_locality_name\"," + " \"administrative_area_name\":\"ship_administrative_area_name\"," + " \"postal_code_number\":\"ship_postal_code_number\"," + " \"country_name_code\":\"ship_country_name_code\"" + " }" + " }," + " \"required_action\":" + " [" + " ]" + "}"; + +const char kGetFullWalletInvalidResponse[] = + "{" + " \"garbage\":123" + "}"; + +const char kGetWalletItemsValidResponse[] = + "{" + " \"required_action\":" + " [" + " ]," + " \"google_transaction_id\":\"google_transaction_id\"," + " \"instrument\":" + " [" + " {" + " \"descriptive_name\":\"descriptive_name\"," + " \"type\":\"VISA\"," + " \"supported_currency\":\"currency_code\"," + " \"last_four_digits\":\"last_four_digits\"," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"brand\":\"monkeys\"," + " \"billing_address\":" + " {" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + " }," + " \"status\":\"VALID\"," + " \"object_id\":\"default_instrument_id\"" + " }" + " ]," + " \"default_instrument_id\":\"default_instrument_id\"," + " \"obfuscated_gaia_id\":\"obfuscated_gaia_id\"," + " \"address\":" + " [" + " ]," + " \"default_address_id\":\"default_address_id\"," + " \"required_legal_document\":" + " [" + " ]" + "}"; + +const char kSaveAddressValidResponse[] = + "{" + " \"shipping_address_id\":\"shipping_address_id\"" + "}"; + +const char kSaveAddressWithRequiredActionsValidResponse[] = + "{" + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kSaveWithInvalidRequiredActionsResponse[] = + "{" + " \"required_action\":" + " [" + " \" setup_wallet\"," + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kSaveInvalidResponse[] = + "{" + " \"garbage\":123" + "}"; + +const char kSaveInstrumentValidResponse[] = + "{" + " \"instrument_id\":\"instrument_id\"" + "}"; + +const char kSaveInstrumentWithRequiredActionsValidResponse[] = + "{" + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kSaveInstrumentAndAddressValidResponse[] = + "{" + " \"shipping_address_id\":\"shipping_address_id\"," + " \"instrument_id\":\"instrument_id\"" + "}"; + +const char kSaveInstrumentAndAddressWithRequiredActionsValidResponse[] = + "{" + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kSaveInstrumentAndAddressMissingAddressResponse[] = + "{" + " \"instrument_id\":\"instrument_id\"" + "}"; + +const char kSaveInstrumentAndAddressMissingInstrumentResponse[] = + "{" + " \"shipping_address_id\":\"shipping_address_id\"" + "}"; + +const char kUpdateInstrumentValidResponse[] = + "{" + " \"instrument_id\":\"instrument_id\"" + "}"; + +const char kUpdateInstrumentWithRequiredActionsValidResponse[] = + "{" + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kUpdateInstrumentMalformedResponse[] = + "{" + " \"cheese\":\"monkeys\"" + "}"; + +const char kAuthenticateInstrumentFailureResponse[] = + "{" + " \"auth_result\":\"anything else\"" + "}"; + +const char kAuthenticateInstrumentSuccessResponse[] = + "{" + " \"auth_result\":\"SUCCESS\"" + "}"; + +// The JSON below is used to test against the request payload being sent to +// Online Wallet. It's indented differently since JSONWriter creates compact +// JSON from DictionaryValues. + +const char kAcceptLegalDocumentsValidRequest[] = + "{" + "\"accepted_legal_document\":" + "[" + "\"doc_1\"," + "\"doc_2\"" + "]," + "\"google_transaction_id\":\"google-transaction-id\"," + "\"merchant_domain\":\"https://example.com/\"" + "}"; + +const char kAuthenticateInstrumentValidRequest[] = + "{" + "\"instrument_escrow_handle\":\"escrow_handle\"," + "\"instrument_id\":\"instrument_id\"," + "\"risk_params\":\"\"" + "}"; + +const char kGetFullWalletValidRequest[] = + "{" + "\"cart\":" + "{" + "\"currency_code\":\"currency_code\"," + "\"total_price\":\"currency_code\"" + "}," + "\"encrypted_otp\":\"encrypted_one_time_pad\"," + "\"feature\":\"REQUEST_AUTOCOMPLETE\"," + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"\"," + "\"selected_address_id\":\"shipping_address_id\"," + "\"selected_instrument_id\":\"instrument_id\"," + "\"session_material\":\"session_material\"" + "}"; + +const char kGetWalletItemsValidRequest[] = + "{" + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"\"" + "}"; + +const char kSaveAddressValidRequest[] = + "{" + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"\"," + "\"shipping_address\":" + "{" + "\"phone_number\":\"ship_phone_number\"," + "\"postal_address\":" + "{" + "\"address_line\":" + "[" + "\"ship_address_line_1\"," + "\"ship_address_line_2\"" + "]," + "\"administrative_area_name\":\"ship_admin_area_name\"," + "\"country_name_code\":\"ship_country_name_code\"," + "\"locality_name\":\"ship_locality_name\"," + "\"postal_code_number\":\"ship_postal_code_number\"," + "\"recipient_name\":\"ship_recipient_name\"" + "}" + "}" + "}"; + +const char kSaveInstrumentValidRequest[] = + "{" + "\"instrument\":" + "{" + "\"credit_card\":" + "{" + "\"address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"country_name_code\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"exp_month\":12," + "\"exp_year\":2012," + "\"fop_type\":\"VISA\"," + "\"last_4_digits\":\"4448\"" + "}," + "\"type\":\"CREDIT_CARD\"" + "}," + "\"instrument_escrow_handle\":\"escrow_handle\"," + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"\"" + "}"; + +const char kSaveInstrumentAndAddressValidRequest[] = + "{" + "\"instrument\":" + "{" + "\"credit_card\":" + "{" + "\"address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"country_name_code\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"exp_month\":12," + "\"exp_year\":2012," + "\"fop_type\":\"VISA\"," + "\"last_4_digits\":\"4448\"" + "}," + "\"type\":\"CREDIT_CARD\"" + "}," + "\"instrument_escrow_handle\":\"escrow_handle\"," + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"\"," + "\"shipping_address\":" + "{" + "\"phone_number\":\"ship_phone_number\"," + "\"postal_address\":" + "{" + "\"address_line\":" + "[" + "\"ship_address_line_1\"," + "\"ship_address_line_2\"" + "]," + "\"administrative_area_name\":\"ship_admin_area_name\"," + "\"country_name_code\":\"ship_country_name_code\"," + "\"locality_name\":\"ship_locality_name\"," + "\"postal_code_number\":\"ship_postal_code_number\"," + "\"recipient_name\":\"ship_recipient_name\"" + "}" + "}" + "}"; + +const char kSendAutocheckoutStatusOfSuccessValidRequest[] = + "{" + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"success\":true" + "}"; + +const char kSendAutocheckoutStatusOfFailureValidRequest[] = + "{" + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"reason\":\"CANNOT_PROCEED\"," + "\"success\":false" + "}"; + +const char kUpdateInstrumentValidRequest[] = + "{" + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"risk_params\":\"\"," + "\"upgraded_billing_address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"country_name_code\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"upgraded_instrument_id\":\"instrument_id\"" + "}"; + +} // namespace + +namespace autofill { +namespace wallet { + +class WalletClientTest : public testing::Test { + public: + WalletClientTest() : io_thread_(content::BrowserThread::IO) {} + + virtual void SetUp() { + io_thread_.StartIOThread(); + profile_.CreateRequestContext(); + } + + virtual void TearDown() { + profile_.ResetRequestContext(); + io_thread_.Stop(); + } + + std::string GetData(net::TestURLFetcher* fetcher) { + std::string data = fetcher->upload_data(); + scoped_ptr<Value> root(base::JSONReader::Read(data)); + + // If this is not a JSON dictionary, return plain text. + if (root.get() == NULL || !root->IsType(Value::TYPE_DICTIONARY)) + return data; + + // Remove api_key entry (to prevent accidental leak), return JSON as text. + DictionaryValue* dict = static_cast<DictionaryValue*>(root.get()); + dict->Remove("api_key", NULL); + base::JSONWriter::Write(dict, &data); + return data; + } + + void VerifyAndFinishRequest(const net::TestURLFetcherFactory& fetcher_factory, + net::HttpStatusCode response_code, + const std::string& request_body, + const std::string& response_body) { + net::TestURLFetcher* fetcher = fetcher_factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + EXPECT_EQ(request_body, GetData(fetcher)); + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response_body); + fetcher->delegate()->OnURLFetchComplete(fetcher); + } + protected: + TestingProfile profile_; + + private: + // The profile's request context must be released on the IO thread. + content::TestBrowserThread io_thread_; +}; + +class MockWalletClientObserver : public WalletClientObserver { + public: + MockWalletClientObserver() + : full_wallets_received_(0), wallet_items_received_(0) {} + ~MockWalletClientObserver() {} + + MOCK_METHOD0(OnDidAcceptLegalDocuments, void()); + MOCK_METHOD1(OnDidAuthenticateInstrument, void(bool success)); + MOCK_METHOD2(OnDidSaveAddress, + void(const std::string& address_id, + const std::vector<RequiredAction>& required_actions)); + MOCK_METHOD2(OnDidSaveInstrument, + void(const std::string& instrument_id, + const std::vector<RequiredAction>& required_actions)); + MOCK_METHOD3(OnDidSaveInstrumentAndAddress, + void(const std::string& instrument_id, + const std::string& shipping_address_id, + const std::vector<RequiredAction>& required_actions)); + MOCK_METHOD0(OnDidSendAutocheckoutStatus, void()); + MOCK_METHOD2(OnDidUpdateInstrument, + void(const std::string& instrument_id, + const std::vector<RequiredAction>& required_actions)); + MOCK_METHOD0(OnWalletError, void()); + MOCK_METHOD0(OnMalformedResponse, void()); + MOCK_METHOD1(OnNetworkError, void(int response_code)); + + virtual void OnDidGetFullWallet(scoped_ptr<FullWallet> full_wallet) OVERRIDE { + EXPECT_TRUE(full_wallet); + ++full_wallets_received_; + } + virtual void OnDidGetWalletItems(scoped_ptr<WalletItems> wallet_items) + OVERRIDE { + EXPECT_TRUE(wallet_items); + ++wallet_items_received_; + } + size_t full_wallets_received() const { return full_wallets_received_; } + size_t wallet_items_received() const { return wallet_items_received_; } + + private: + size_t full_wallets_received_; + size_t wallet_items_received_; +}; + +// TODO(ahutter): Implement API compatibility tests. See +// http://crbug.com/164465. + +// TODO(ahutter): Improve this when the error body is captured. See +// http://crbug.com/164410. +TEST_F(WalletClientTest, WalletErrorOnExpectedVoidResponse) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnWalletError()).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + ""); + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_INTERNAL_SERVER_ERROR); + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +// TODO(ahutter): Improve this when the error body is captured. See +// http://crbug.com/164410. +TEST_F(WalletClientTest, WalletErrorOnExpectedResponse) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnWalletError()).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.GetWalletItems(GURL(kMerchantUrl)); + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_INTERNAL_SERVER_ERROR); + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +TEST_F(WalletClientTest, NetworkFailureOnExpectedVoidResponse) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnNetworkError(net::HTTP_UNAUTHORIZED)).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + ""); + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_UNAUTHORIZED); + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +TEST_F(WalletClientTest, NetworkFailureOnExpectedResponse) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnNetworkError(net::HTTP_UNAUTHORIZED)).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.GetWalletItems(GURL(kMerchantUrl)); + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_UNAUTHORIZED); + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +TEST_F(WalletClientTest, RequestError) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnWalletError()).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + ""); + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + fetcher->set_response_code(net::HTTP_BAD_REQUEST); + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +TEST_F(WalletClientTest, GetFullWalletSuccess) { + MockWalletClientObserver observer; + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + Cart cart("currency_code", "currency_code"); + wallet_client.GetFullWallet("instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + cart, + "google_transaction_id", + DIALOG_TYPE_REQUEST_AUTOCOMPLETE); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString( + "session_material|encrypted_one_time_pad"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kGetFullWalletValidRequest, + kGetFullWalletValidResponse); + EXPECT_EQ(1U, observer.full_wallets_received()); +} + +TEST_F(WalletClientTest, GetFullWalletEncryptionDown) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, + OnNetworkError(net::HTTP_INTERNAL_SERVER_ERROR)).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + Cart cart("currency_code", "currency_code"); + wallet_client.GetFullWallet("instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + cart, + "google_transaction_id", + DIALOG_TYPE_REQUEST_AUTOCOMPLETE); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_INTERNAL_SERVER_ERROR); + encryption_fetcher->SetResponseString(std::string()); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + EXPECT_EQ(0U, observer.full_wallets_received()); +} + +TEST_F(WalletClientTest, GetFullWalletEncryptionMalformed) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + Cart cart("currency_code", "currency_code"); + wallet_client.GetFullWallet("instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + cart, + "google_transaction_id", + DIALOG_TYPE_REQUEST_AUTOCOMPLETE); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString( + "session_material:encrypted_one_time_pad"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + EXPECT_EQ(0U, observer.full_wallets_received()); +} + +TEST_F(WalletClientTest, GetFullWalletMalformedResponse) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + Cart cart("currency_code", "currency_code"); + wallet_client.GetFullWallet("instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + cart, + "google_transaction_id", + DIALOG_TYPE_REQUEST_AUTOCOMPLETE); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString( + "session_material|encrypted_one_time_pad"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kGetFullWalletValidRequest, + kGetFullWalletInvalidResponse); + EXPECT_EQ(0U, observer.full_wallets_received()); +} + +TEST_F(WalletClientTest, AcceptLegalDocuments) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnDidAcceptLegalDocuments()).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + std::vector<std::string> doc_ids; + doc_ids.push_back("doc_1"); + doc_ids.push_back("doc_2"); + wallet_client.AcceptLegalDocuments(doc_ids, + kGoogleTransactionId, + GURL(kMerchantUrl)); + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + EXPECT_EQ(kAcceptLegalDocumentsValidRequest, GetData(fetcher)); + fetcher->SetResponseString(")]}'"); // Invalid JSON. Should be ignored. + fetcher->set_response_code(net::HTTP_OK); + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentSucceeded) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnDidAuthenticateInstrument(true)).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.AuthenticateInstrument("instrument_id", + "cvv", + "obfuscated_gaia_id"); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kAuthenticateInstrumentValidRequest, + kAuthenticateInstrumentSuccessResponse); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentFailed) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnDidAuthenticateInstrument(false)).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.AuthenticateInstrument("instrument_id", + "cvv", + "obfuscated_gaia_id"); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kAuthenticateInstrumentValidRequest, + kAuthenticateInstrumentFailureResponse); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentEscrowDown) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, + OnNetworkError(net::HTTP_INTERNAL_SERVER_ERROR)).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.AuthenticateInstrument("instrument_id", + "cvv", + "obfuscated_gaia_id"); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_INTERNAL_SERVER_ERROR); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentEscrowMalformed) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.AuthenticateInstrument("instrument_id", + "cvv", + "obfuscated_gaia_id"); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentFailedMalformedResponse) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.AuthenticateInstrument("instrument_id", + "cvv", + "obfuscated_gaia_id"); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kAuthenticateInstrumentValidRequest, + kSaveInvalidResponse); +} + +// TODO(ahutter): Add failure tests for GetWalletItems. + +TEST_F(WalletClientTest, GetWalletItems) { + MockWalletClientObserver observer; + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.GetWalletItems(GURL(kMerchantUrl)); + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + EXPECT_EQ(kGetWalletItemsValidRequest, GetData(fetcher)); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(kGetWalletItemsValidResponse); + fetcher->delegate()->OnURLFetchComplete(fetcher); + + EXPECT_EQ(1U, observer.wallet_items_received()); +} + +TEST_F(WalletClientTest, SaveAddressSucceeded) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, + OnDidSaveAddress("shipping_address_id", + std::vector<RequiredAction>())).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveAddressValidRequest, + kSaveAddressValidResponse); +} + +TEST_F(WalletClientTest, SaveAddressWithRequiredActionsSucceeded) { + MockWalletClientObserver observer; + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + EXPECT_CALL(observer, + OnDidSaveAddress(std::string(), + required_actions)).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveAddressValidRequest, + kSaveAddressWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, SaveAddressFailedInvalidRequiredAction) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveAddressValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, SaveAddressFailedMalformedResponse) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveAddress(*address, GURL(kMerchantUrl)); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveAddressValidRequest, + kSaveInvalidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentSucceeded) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, + OnDidSaveInstrument("instrument_id", + std::vector<RequiredAction>())).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveInstrumentValidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentWithRequiredActionsSucceeded) { + MockWalletClientObserver observer; + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + EXPECT_CALL(observer, + OnDidSaveInstrument(std::string(), + required_actions)).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveInstrumentWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentFailedInvalidRequiredActions) { + MockWalletClientObserver observer; + + EXPECT_CALL(observer, OnMalformedResponse()); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentEscrowDown) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, + OnNetworkError(net::HTTP_INTERNAL_SERVER_ERROR)).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_INTERNAL_SERVER_ERROR); + encryption_fetcher->SetResponseString(std::string()); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); +} + +TEST_F(WalletClientTest, SaveInstrumentEscrowMalformed) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString(std::string()); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); +} + +TEST_F(WalletClientTest, SaveInstrumentFailedMalformedResponse) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrument(*instrument, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveInvalidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressSucceeded) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, + OnDidSaveInstrumentAndAddress( + "instrument_id", + "shipping_address_id", + std::vector<RequiredAction>())).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveInstrumentAndAddressValidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressWithRequiredActionsSucceeded) { + MockWalletClientObserver observer; + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + EXPECT_CALL(observer, + OnDidSaveInstrumentAndAddress( + std::string(), + std::string(), + required_actions)).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + VerifyAndFinishRequest( + factory, + net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveInstrumentAndAddressWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressFailedInvalidRequiredAction) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressEscrowDown) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, + OnNetworkError(net::HTTP_INTERNAL_SERVER_ERROR)).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_INTERNAL_SERVER_ERROR); + encryption_fetcher->SetResponseString(std::string()); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressEscrowMalformed) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString(std::string()); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressFailedAddressMissing) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveInstrumentAndAddressMissingAddressResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressFailedInstrumentMissing) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + + scoped_ptr<Address> address = GetTestShippingAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SaveInstrumentAndAddress(*instrument, + *address, + "obfuscated_gaia_id", + GURL(kMerchantUrl)); + + net::TestURLFetcher* encryption_fetcher = factory.GetFetcherByID(1); + ASSERT_TRUE(encryption_fetcher); + encryption_fetcher->set_response_code(net::HTTP_OK); + encryption_fetcher->SetResponseString("escrow_handle"); + encryption_fetcher->delegate()->OnURLFetchComplete(encryption_fetcher); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveInstrumentAndAddressMissingInstrumentResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentSucceeded) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, + OnDidUpdateInstrument("instrument_id", + std::vector<RequiredAction>())).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Address> address = GetTestAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.UpdateInstrument("instrument_id", + *address, + GURL(kMerchantUrl)); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kUpdateInstrumentValidRequest, + kUpdateInstrumentValidResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentWithRequiredActionsSucceeded) { + MockWalletClientObserver observer; + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + EXPECT_CALL(observer, + OnDidUpdateInstrument(std::string(), + required_actions)).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Address> address = GetTestAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.UpdateInstrument("instrument_id", + *address, + GURL(kMerchantUrl)); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kUpdateInstrumentValidRequest, + kUpdateInstrumentWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentFailedInvalidRequiredAction) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Address> address = GetTestAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.UpdateInstrument("instrument_id", + *address, + GURL(kMerchantUrl)); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kUpdateInstrumentValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentMalformedResponse) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnMalformedResponse()).Times(1); + + net::TestURLFetcherFactory factory; + + scoped_ptr<Address> address = GetTestAddress(); + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.UpdateInstrument("instrument_id", + *address, + GURL(kMerchantUrl)); + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kUpdateInstrumentValidRequest, + kUpdateInstrumentMalformedResponse); +} + +TEST_F(WalletClientTest, SendAutocheckoutOfStatusSuccess) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnDidSendAutocheckoutStatus()).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + "google_transaction_id"); + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + EXPECT_EQ(kSendAutocheckoutStatusOfSuccessValidRequest, GetData(fetcher)); + fetcher->SetResponseString(")]}'"); // Invalid JSON. Should be ignored. + fetcher->set_response_code(net::HTTP_OK); + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +TEST_F(WalletClientTest, SendAutocheckoutStatusOfFailure) { + MockWalletClientObserver observer; + EXPECT_CALL(observer, OnDidSendAutocheckoutStatus()).Times(1); + + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + wallet_client.SendAutocheckoutStatus(autofill::CANNOT_PROCEED, + GURL(kMerchantUrl), + "google_transaction_id"); + net::TestURLFetcher* fetcher = factory.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + EXPECT_EQ(kSendAutocheckoutStatusOfFailureValidRequest, GetData(fetcher)); + fetcher->set_response_code(net::HTTP_OK); + fetcher->SetResponseString(")]}'"); // Invalid JSON. Should be ignored. + fetcher->delegate()->OnURLFetchComplete(fetcher); +} + +TEST_F(WalletClientTest, HasRequestInProgress) { + MockWalletClientObserver observer; + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + EXPECT_FALSE(wallet_client.HasRequestInProgress()); + + wallet_client.GetWalletItems(GURL(kMerchantUrl)); + EXPECT_TRUE(wallet_client.HasRequestInProgress()); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kGetWalletItemsValidRequest, + kGetWalletItemsValidResponse); + EXPECT_FALSE(wallet_client.HasRequestInProgress()); +} + +TEST_F(WalletClientTest, PendingRequest) { + MockWalletClientObserver observer; + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + ASSERT_EQ(0U, wallet_client.pending_requests_.size()); + + // Shouldn't queue the first request. + wallet_client.GetWalletItems(GURL(kMerchantUrl)); + EXPECT_EQ(0U, wallet_client.pending_requests_.size()); + + wallet_client.GetWalletItems(GURL(kMerchantUrl)); + EXPECT_EQ(1U, wallet_client.pending_requests_.size()); + + VerifyAndFinishRequest(factory, + net::HTTP_OK, + kGetWalletItemsValidRequest, + kGetWalletItemsValidResponse); + EXPECT_EQ(0U, wallet_client.pending_requests_.size()); + + EXPECT_CALL(observer, OnWalletError()).Times(1); + VerifyAndFinishRequest(factory, + net::HTTP_INTERNAL_SERVER_ERROR, + kGetWalletItemsValidRequest, + std::string()); +} + +TEST_F(WalletClientTest, CancelPendingRequests) { + MockWalletClientObserver observer; + net::TestURLFetcherFactory factory; + + WalletClient wallet_client(profile_.GetRequestContext(), &observer); + ASSERT_EQ(0U, wallet_client.pending_requests_.size()); + + wallet_client.GetWalletItems(GURL(kMerchantUrl)); + wallet_client.GetWalletItems(GURL(kMerchantUrl)); + wallet_client.GetWalletItems(GURL(kMerchantUrl)); + EXPECT_EQ(2U, wallet_client.pending_requests_.size()); + + wallet_client.CancelPendingRequests(); + EXPECT_EQ(0U, wallet_client.pending_requests_.size()); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/wallet_items.cc b/components/autofill/browser/wallet/wallet_items.cc new file mode 100644 index 0000000..6a779ac --- /dev/null +++ b/components/autofill/browser/wallet/wallet_items.cc @@ -0,0 +1,411 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/wallet_items.h" + +#include "base/logging.h" +#include "base/values.h" +#include "googleurl/src/gurl.h" +#include "grit/webkit_resources.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/image/image.h" + +namespace autofill { +namespace wallet { + +namespace { + +const char kLegalDocumentUrl[] = + "https://wallet.google.com/customer/gadget/legaldocument.html?docId="; + +WalletItems::MaskedInstrument::Type + TypeFromString(const std::string& type_string) { + if (type_string == "VISA") + return WalletItems::MaskedInstrument::VISA; + if (type_string == "MASTER_CARD") + return WalletItems::MaskedInstrument::MASTER_CARD; + if (type_string == "AMEX") + return WalletItems::MaskedInstrument::AMEX; + if (type_string == "DISCOVER") + return WalletItems::MaskedInstrument::DISCOVER; + if (type_string == "SOLO") + return WalletItems::MaskedInstrument::SOLO; + if (type_string == "MAESTRO") + return WalletItems::MaskedInstrument::MAESTRO; + if (type_string == "SWITCH") + return WalletItems::MaskedInstrument::SWITCH; + return WalletItems::MaskedInstrument::UNKNOWN; +} + +WalletItems::MaskedInstrument::Status + StatusFromString(const std::string& status_string) { + if (status_string == "PENDING") + return WalletItems::MaskedInstrument::PENDING; + if (status_string == "VALID") + return WalletItems::MaskedInstrument::VALID; + if (status_string == "DECLINED") + return WalletItems::MaskedInstrument::DECLINED; + if (status_string == "DISABLED_FOR_THIS_MERCHANT") + return WalletItems::MaskedInstrument::DISABLED_FOR_THIS_MERCHANT; + if (status_string == "UNSUPPORTED_COUNTRY") + return WalletItems::MaskedInstrument::UNSUPPORTED_COUNTRY; + if (status_string == "EXPIRED") + return WalletItems::MaskedInstrument::EXPIRED; + if (status_string == "BILLING_INCOMPLETE") + return WalletItems::MaskedInstrument::BILLING_INCOMPLETE; + return WalletItems::MaskedInstrument::INAPPLICABLE; +} + +} // anonymous namespace + +WalletItems::MaskedInstrument::MaskedInstrument( + const string16& descriptive_name, + const WalletItems::MaskedInstrument::Type& type, + const std::vector<string16>& supported_currencies, + const string16& last_four_digits, + int expiration_month, + int expiration_year, + scoped_ptr<Address> address, + const WalletItems::MaskedInstrument::Status& status, + const std::string& object_id) + : descriptive_name_(descriptive_name), + type_(type), + supported_currencies_(supported_currencies), + last_four_digits_(last_four_digits), + expiration_month_(expiration_month), + expiration_year_(expiration_year), + address_(address.Pass()), + status_(status), + object_id_(object_id) { + DCHECK(address_.get()); +} + +WalletItems::MaskedInstrument::~MaskedInstrument() {} + +scoped_ptr<WalletItems::MaskedInstrument> + WalletItems::MaskedInstrument::CreateMaskedInstrument( + const base::DictionaryValue& dictionary) { + std::string type_string; + Type type; + if (dictionary.GetString("type", &type_string)) { + type = TypeFromString(type_string); + } else { + DLOG(ERROR) << "Response from Google Wallet missing card type"; + return scoped_ptr<MaskedInstrument>(); + } + + string16 last_four_digits; + if (!dictionary.GetString("last_four_digits", &last_four_digits)) { + DLOG(ERROR) << "Response from Google Wallet missing last four digits"; + return scoped_ptr<MaskedInstrument>(); + } + + std::string status_string; + Status status; + if (dictionary.GetString("status", &status_string)) { + status = StatusFromString(status_string); + } else { + DLOG(ERROR) << "Response from Google Wallet missing status"; + return scoped_ptr<MaskedInstrument>(); + } + + std::string object_id; + if (!dictionary.GetString("object_id", &object_id)) { + DLOG(ERROR) << "Response from Google Wallet missing object id"; + return scoped_ptr<MaskedInstrument>(); + } + + const DictionaryValue* address_dict; + if (!dictionary.GetDictionary("billing_address", &address_dict)) { + DLOG(ERROR) << "Response from Google wallet missing address"; + return scoped_ptr<MaskedInstrument>(); + } + scoped_ptr<Address> address = Address::CreateDisplayAddress(*address_dict); + + if (!address.get()) { + DLOG(ERROR) << "Response from Google wallet contained malformed address"; + return scoped_ptr<MaskedInstrument>(); + } + + std::vector<string16> supported_currencies; + const ListValue* supported_currency_list; + if (dictionary.GetList("supported_currency", &supported_currency_list)) { + for (size_t i = 0; i < supported_currency_list->GetSize(); ++i) { + string16 currency; + if (supported_currency_list->GetString(i, ¤cy)) + supported_currencies.push_back(currency); + } + } else { + DVLOG(1) << "Response from Google Wallet missing supported currency"; + } + + int expiration_month; + if (!dictionary.GetInteger("expiration_month", &expiration_month)) + DVLOG(1) << "Response from Google Wallet missing expiration month"; + + int expiration_year; + if (!dictionary.GetInteger("expiration_year", &expiration_year)) + DVLOG(1) << "Response from Google Wallet missing expiration year"; + + string16 descriptive_name; + if (!dictionary.GetString("descriptive_name", &descriptive_name)) + DVLOG(1) << "Response from Google Wallet missing descriptive name"; + + return scoped_ptr<MaskedInstrument>(new MaskedInstrument(descriptive_name, + type, + supported_currencies, + last_four_digits, + expiration_month, + expiration_year, + address.Pass(), + status, + object_id)); +} + +bool WalletItems::MaskedInstrument::operator==( + const WalletItems::MaskedInstrument& other) const { + if (descriptive_name_ != other.descriptive_name_) + return false; + if (type_ != other.type_) + return false; + if (supported_currencies_ != other.supported_currencies_) + return false; + if (last_four_digits_ != other.last_four_digits_) + return false; + if (expiration_month_ != other.expiration_month_) + return false; + if (expiration_year_ != other.expiration_year_) + return false; + if (address_.get()) { + if (other.address_.get()) { + if (*address_.get() != *other.address_.get()) + return false; + } else { + return false; + } + } else if (other.address_.get()) { + return false; + } + if (status_ != other.status_) + return false; + if (object_id_ != other.object_id_) + return false; + return true; +} + +bool WalletItems::MaskedInstrument::operator!=( + const WalletItems::MaskedInstrument& other) const { + return !(*this == other); +} + +bool WalletItems::HasRequiredAction(RequiredAction action) const { + DCHECK(ActionAppliesToWalletItems(action)); + return std::find(required_actions_.begin(), + required_actions_.end(), + action) != required_actions_.end(); +} + +const gfx::Image& WalletItems::MaskedInstrument::CardIcon() const { + int idr = 0; + switch (type_) { + case AMEX: + idr = IDR_AUTOFILL_CC_AMEX; + break; + + case DISCOVER: + idr = IDR_AUTOFILL_CC_DISCOVER; + break; + + case MASTER_CARD: + idr = IDR_AUTOFILL_CC_MASTERCARD; + break; + + case SOLO: + idr = IDR_AUTOFILL_CC_SOLO; + break; + + case VISA: + idr = IDR_AUTOFILL_CC_VISA; + break; + + case MAESTRO: + case SWITCH: + case UNKNOWN: + idr = IDR_AUTOFILL_CC_GENERIC; + break; + } + + return ResourceBundle::GetSharedInstance().GetImageNamed(idr); +} + +WalletItems::LegalDocument::LegalDocument(const std::string& document_id, + const std::string& display_name) + : document_id_(document_id), + display_name_(display_name) {} + +WalletItems::LegalDocument::~LegalDocument() {} + +scoped_ptr<WalletItems::LegalDocument> + WalletItems::LegalDocument::CreateLegalDocument( + const base::DictionaryValue& dictionary) { + std::string document_id; + if (!dictionary.GetString("legal_document_id", &document_id)) { + DLOG(ERROR) << "Response from Google Wallet missing legal document id"; + return scoped_ptr<LegalDocument>(); + } + + std::string display_name; + if (!dictionary.GetString("display_name", &display_name)) { + DLOG(ERROR) << "Response from Google Wallet missing display name"; + return scoped_ptr<LegalDocument>(); + } + + return scoped_ptr<LegalDocument>(new LegalDocument(document_id, + display_name)); +} + +GURL WalletItems::LegalDocument::GetUrl() { + return GURL(kLegalDocumentUrl + document_id_); +} + +bool WalletItems::LegalDocument::operator==(const LegalDocument& other) const { + return document_id_ == other.document_id_ && + display_name_ == other.display_name_; +} + +bool WalletItems::LegalDocument::operator!=(const LegalDocument& other) const { + return !(*this == other); +} + +WalletItems::WalletItems(const std::vector<RequiredAction>& required_actions, + const std::string& google_transaction_id, + const std::string& default_instrument_id, + const std::string& default_address_id, + const std::string& obfuscated_gaia_id) + : required_actions_(required_actions), + google_transaction_id_(google_transaction_id), + default_instrument_id_(default_instrument_id), + default_address_id_(default_address_id), + obfuscated_gaia_id_(obfuscated_gaia_id) {} + +WalletItems::~WalletItems() {} + +scoped_ptr<WalletItems> + WalletItems::CreateWalletItems(const base::DictionaryValue& dictionary) { + std::vector<RequiredAction> required_action; + const ListValue* required_action_list; + if (dictionary.GetList("required_action", &required_action_list)) { + for (size_t i = 0; i < required_action_list->GetSize(); ++i) { + std::string action_string; + if (required_action_list->GetString(i, &action_string)) { + RequiredAction action = ParseRequiredActionFromString(action_string); + if (!ActionAppliesToWalletItems(action)) { + DLOG(ERROR) << "Response from Google wallet with bad required action:" + " \"" << action_string << "\""; + return scoped_ptr<WalletItems>(); + } + required_action.push_back(action); + } + } + } else { + DVLOG(1) << "Response from Google wallet missing required actions"; + } + + std::string google_transaction_id; + if (!dictionary.GetString("google_transaction_id", &google_transaction_id) && + required_action.empty()) { + DLOG(ERROR) << "Response from Google wallet missing google transaction id"; + return scoped_ptr<WalletItems>(); + } + + std::string default_instrument_id; + if (!dictionary.GetString("default_instrument_id", &default_instrument_id)) + DVLOG(1) << "Response from Google wallet missing default instrument id"; + + std::string default_address_id; + if (!dictionary.GetString("default_address_id", &default_address_id)) + DVLOG(1) << "Response from Google wallet missing default_address_id"; + + std::string obfuscated_gaia_id; + if (!dictionary.GetString("obfuscated_gaia_id", &obfuscated_gaia_id)) + DVLOG(1) << "Response from Google wallet missing obfuscated gaia id"; + + scoped_ptr<WalletItems> wallet_items(new WalletItems(required_action, + google_transaction_id, + default_instrument_id, + default_address_id, + obfuscated_gaia_id)); + + const ListValue* legal_docs; + if (dictionary.GetList("required_legal_document", &legal_docs)) { + for (size_t i = 0; i < legal_docs->GetSize(); ++i) { + const DictionaryValue* legal_doc_dict; + if (legal_docs->GetDictionary(i, &legal_doc_dict)) { + scoped_ptr<LegalDocument> legal_doc( + LegalDocument::CreateLegalDocument(*legal_doc_dict)); + if (legal_doc.get()) { + wallet_items->AddLegalDocument(legal_doc.Pass()); + } else { + DLOG(ERROR) << "Malformed legal document in response from " + "Google wallet"; + return scoped_ptr<WalletItems>(); + } + } + } + } else { + DVLOG(1) << "Response from Google wallet missing legal docs"; + } + + const ListValue* instruments; + if (dictionary.GetList("instrument", &instruments)) { + for (size_t i = 0; i < instruments->GetSize(); ++i) { + const DictionaryValue* instrument_dict; + if (instruments->GetDictionary(i, &instrument_dict)) { + scoped_ptr<MaskedInstrument> instrument( + MaskedInstrument::CreateMaskedInstrument(*instrument_dict)); + if (instrument.get()) + wallet_items->AddInstrument(instrument.Pass()); + else + DLOG(ERROR) << "Malformed instrument in response from Google Wallet"; + } + } + } else { + DVLOG(1) << "Response from Google wallet missing instruments"; + } + + const ListValue* addresses; + if (dictionary.GetList("address", &addresses)) { + for (size_t i = 0; i < addresses->GetSize(); ++i) { + const DictionaryValue* address_dict; + if (addresses->GetDictionary(i, &address_dict)) { + scoped_ptr<Address> address( + Address::CreateAddressWithID(*address_dict)); + if (address.get()) + wallet_items->AddAddress(address.Pass()); + else + DLOG(ERROR) << "Malformed address in response from Google Wallet"; + } + } + } else { + DVLOG(1) << "Response from Google wallet missing addresses"; + } + + return wallet_items.Pass(); +} + +bool WalletItems::operator==(const WalletItems& other) const { + // TODO(ahutter): Check scoped vector equality. + return google_transaction_id_ == other.google_transaction_id_ && + default_instrument_id_ == other.default_instrument_id_ && + default_address_id_ == other.default_address_id_ && + required_actions_ == other.required_actions_ && + obfuscated_gaia_id_ == other.obfuscated_gaia_id_; +} + +bool WalletItems::operator!=(const WalletItems& other) const { + return !(*this == other); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/wallet_items.h b/components/autofill/browser/wallet/wallet_items.h new file mode 100644 index 0000000..f507b88 --- /dev/null +++ b/components/autofill/browser/wallet/wallet_items.h @@ -0,0 +1,262 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_ITEMS_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_ITEMS_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/string16.h" +#include "components/autofill/browser/wallet/required_action.h" +#include "components/autofill/browser/wallet/wallet_address.h" + +class GURL; + +namespace base { +class DictionaryValue; +} + +namespace gfx { +class Image; +} + +namespace autofill { +namespace wallet { + +class WalletItemsTest; + +// WalletItems is a collection of cards and addresses that a user picks from to +// construct a full wallet. However, it also provides a transaction ID which +// must be used throughout all API calls being made using this data. +// Additionally, user actions may be required before a purchase can be completed +// using Online Wallet and those actions are present in the object as well. +class WalletItems { + public: + // Container for all information about a credit card except for it's card + // verfication number (CVN) and it's complete primary account number (PAN). + class MaskedInstrument { + public: + enum Type { + AMEX, + DISCOVER, + MAESTRO, + MASTER_CARD, + SOLO, + SWITCH, + UNKNOWN, // Catch all type. + VISA, + }; + enum Status { + BILLING_INCOMPLETE, + DECLINED, + DISABLED_FOR_THIS_MERCHANT, + EXPIRED, + INAPPLICABLE, // Catch all status. + PENDING, + UNSUPPORTED_COUNTRY, + VALID, + }; + + ~MaskedInstrument(); + + // Returns an empty scoped_ptr if input is invalid or a valid masked + // instrument. + static scoped_ptr<MaskedInstrument> + CreateMaskedInstrument(const base::DictionaryValue& dictionary); + + bool operator==(const MaskedInstrument& other) const; + bool operator!=(const MaskedInstrument& other) const; + + // Gets an image to display for this instrument. + const gfx::Image& CardIcon() const; + + const string16& descriptive_name() const { return descriptive_name_; } + const Type& type() const { return type_; } + const std::vector<string16>& supported_currencies() const { + return supported_currencies_; + } + const string16& last_four_digits() const { return last_four_digits_; } + int expiration_month() const { return expiration_month_; } + int expiration_year() const { return expiration_year_; } + const Address& address() const { return *address_; } + const Status& status() const { return status_; } + const std::string& object_id() const { return object_id_; } + + private: + friend class WalletItemsTest; + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateMaskedInstrument); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems); + MaskedInstrument(const string16& descriptve_name, + const Type& type, + const std::vector<string16>& supported_currencies, + const string16& last_four_digits, + int expiration_month, + int expiration_year, + scoped_ptr<Address> address, + const Status& status, + const std::string& object_id); + + // A user-provided description of the instrument. For example, "Google Visa + // Card". + string16 descriptive_name_; + + // The payment network of the instrument. For example, Visa. + Type type_; + + // |supported_currencies_| are ISO 4217 currency codes, e.g. USD. + std::vector<string16> supported_currencies_; + + // The last four digits of the primary account number of the instrument. + string16 last_four_digits_; + + // |expiration month_| should be 1-12. + int expiration_month_; + + // |expiration_year_| should be a 4-digit year. + int expiration_year_; + + // The billing address for the instrument. + scoped_ptr<Address> address_; + + // The current status of the instrument. For example, expired or declined. + Status status_; + + // Externalized Online Wallet id for this instrument. + std::string object_id_; + + DISALLOW_COPY_AND_ASSIGN(MaskedInstrument); + }; + + // Class representing a legal document that the user must accept before they + // can use Online Wallet. + class LegalDocument { + public: + ~LegalDocument(); + + // Returns null if input is invalid or a valid legal document. Caller owns + // returned pointer. + static scoped_ptr<LegalDocument> + CreateLegalDocument(const base::DictionaryValue& dictionary); + + // Get the url where this legal document is hosted. + GURL GetUrl(); + + bool operator==(const LegalDocument& other) const; + bool operator!=(const LegalDocument& other) const; + + const std::string& document_id() const { return document_id_; } + const std::string& display_name() const { return display_name_; } + + private: + friend class WalletItemsTest; + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateLegalDocument); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, LegalDocumentGetUrl); + LegalDocument(const std::string& document_id, + const std::string& display_name); + + // Externalized Online Wallet id for the document. + std::string document_id_; + + // User displayable name for the document. + std::string display_name_; + DISALLOW_COPY_AND_ASSIGN(LegalDocument); + }; + + ~WalletItems(); + + // Returns null on invalid input, an empty wallet items with required + // actions if any are present, and a populated wallet items otherwise. Caller + // owns returned pointer. + static scoped_ptr<WalletItems> + CreateWalletItems(const base::DictionaryValue& dictionary); + + bool operator==(const WalletItems& other) const; + bool operator!=(const WalletItems& other) const; + + void AddInstrument(scoped_ptr<MaskedInstrument> instrument) { + DCHECK(instrument.get()); + instruments_.push_back(instrument.release()); + } + void AddAddress(scoped_ptr<Address> address) { + DCHECK(address.get()); + addresses_.push_back(address.release()); + } + void AddLegalDocument(scoped_ptr<LegalDocument> legal_document) { + DCHECK(legal_document.get()); + legal_documents_.push_back(legal_document.release()); + } + + // Whether or not |action| is in |required_actions_|. + bool HasRequiredAction(RequiredAction action) const; + + const std::vector<RequiredAction>& required_actions() const { + return required_actions_; + } + const std::string& google_transaction_id() const { + return google_transaction_id_; + } + const std::vector<MaskedInstrument*>& instruments() const { + return instruments_.get(); + } + const std::string& default_instrument_id() const { + return default_instrument_id_; + } + const std::vector<Address*>& addresses() const { return addresses_.get(); } + const std::string& default_address_id() const { return default_address_id_; } + const std::string& obfuscated_gaia_id() const { return obfuscated_gaia_id_; } + const std::vector<LegalDocument*>& legal_documents() const { + return legal_documents_.get(); + } + + private: + friend class WalletItemsTest; + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, + CreateWalletItemsWithRequiredActions); + + WalletItems(const std::vector<RequiredAction>& required_actions, + const std::string& google_transaction_id, + const std::string& default_instrument_id, + const std::string& default_address_id, + const std::string& obfuscated_gaia_id); + + // Actions that must be completed by the user before a FullWallet can be + // issued to them by the Online Wallet service. + std::vector<RequiredAction> required_actions_; + + // The id for this transaction issued by Google. + std::string google_transaction_id_; + + // The id of the user's default instrument. + std::string default_instrument_id_; + + // The id of the user's default address. + std::string default_address_id_; + + // The externalized Gaia id of the user. + std::string obfuscated_gaia_id_; + + // The user's backing instruments. + ScopedVector<MaskedInstrument> instruments_; + + // The user's shipping addresses. + ScopedVector<Address> addresses_; + + // Legal documents the user must accept before using Online Wallet. + ScopedVector<LegalDocument> legal_documents_; + + DISALLOW_COPY_AND_ASSIGN(WalletItems); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_ITEMS_H_ diff --git a/components/autofill/browser/wallet/wallet_items_unittest.cc b/components/autofill/browser/wallet/wallet_items_unittest.cc new file mode 100644 index 0000000..185db7f --- /dev/null +++ b/components/autofill/browser/wallet/wallet_items_unittest.cc @@ -0,0 +1,543 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/browser/wallet/required_action.h" +#include "components/autofill/browser/wallet/wallet_items.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kMaskedInstrument[] = + "{" + " \"descriptive_name\":\"descriptive_name\"," + " \"type\":\"VISA\"," + " \"supported_currency\":" + " [" + " \"currency\"" + " ]," + " \"last_four_digits\":\"last_four_digits\"," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"billing_address\":" + " {" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + " }," + " \"status\":\"VALID\"," + " \"object_id\":\"object_id\"" + "}"; + +const char kMaskedInstrumentMissingStatus[] = + "{" + " \"descriptive_name\":\"descriptive_name\"," + " \"type\":\"VISA\"," + " \"supported_currency\":" + " [" + " \"currency\"" + " ]," + " \"last_four_digits\":\"last_four_digits\"," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"billing_address\":" + " {" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + " }," + " \"object_id\":\"object_id\"" + "}"; + +const char kMaskedInstrumentMissingType[] = + "{" + " \"descriptive_name\":\"descriptive_name\"," + " \"supported_currency\":" + " [" + " \"currency\"" + " ]," + " \"last_four_digits\":\"last_four_digits\"," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"billing_address\":" + " {" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + " }," + " \"status\":\"VALID\"," + " \"object_id\":\"object_id\"" + "}"; + +const char kMaskedInstrumentMissingLastFourDigits[] = + "{" + " \"descriptive_name\":\"descriptive_name\"," + " \"type\":\"VISA\"," + " \"supported_currency\":" + " [" + " \"currency\"" + " ]," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"billing_address\":" + " {" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + " }," + " \"status\":\"VALID\"," + " \"object_id\":\"object_id\"" + "}"; + +const char kMaskedInstrumentMissingAddress[] = + "{" + " \"descriptive_name\":\"descriptive_name\"," + " \"type\":\"VISA\"," + " \"supported_currency\":" + " [" + " \"currency\"" + " ]," + " \"last_four_digits\":\"last_four_digits\"," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"status\":\"VALID\"," + " \"object_id\":\"object_id\"" + "}"; + +const char kMaskedInstrumentMalformedAddress[] = + "{" + " \"descriptive_name\":\"descriptive_name\"," + " \"type\":\"VISA\"," + " \"supported_currency\":" + " [" + " \"currency\"" + " ]," + " \"last_four_digits\":\"last_four_digits\"," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"billing_address\":" + " {" + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + " }," + " \"status\":\"VALID\"," + " \"object_id\":\"object_id\"" + "}"; + +const char kMaskedInstrumentMissingObjectId[] = + "{" + " \"descriptive_name\":\"descriptive_name\"," + " \"type\":\"VISA\"," + " \"supported_currency\":" + " [" + " \"currency\"" + " ]," + " \"last_four_digits\":\"last_four_digits\"," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"billing_address\":" + " {" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + " }," + " \"status\":\"VALID\"" + "}"; + +const char kLegalDocument[] = + "{" + " \"legal_document_id\":\"doc_id\"," + " \"display_name\":\"display_name\"" + "}"; + +const char kLegalDocumentMissingDocumentId[] = + "{" + " \"display_name\":\"display_name\"" + "}"; + +const char kLegalDocumentMissingDisplayName[] = + "{" + " \"legal_document_id\":\"doc_id\"" + "}"; + +const char kWalletItemsWithRequiredActions[] = + "{" + " \"obfuscated_gaia_id\":\"\"," + " \"required_action\":" + " [" + " \" setup_wallet\"," + " \"AcCePt_ToS \"," + " \" \\tGAIA_auth \\n\\r\"," + " \"UPDATE_expiration_date\"," + " \"UPGRADE_min_ADDRESS \"," + " \" pAsSiVe_GAIA_auth \"," + " \" REQUIRE_PHONE_NUMBER\\t \"" + " ]" + "}"; + +const char kWalletItemsWithInvalidRequiredActions[] = + "{" + " \"obfuscated_gaia_id\":\"\"," + " \"required_action\":" + " [" + " \"verify_CVV\"," + " \"invalid_FORM_FIELD\"," + " \" 忍者の正体 \"" + " ]" + "}"; + +const char kWalletItemsMissingGoogleTransactionId[] = + "{" + " \"required_action\":" + " [" + " ]," + " \"instrument\":" + " [" + " {" + " \"descriptive_name\":\"descriptive_name\"," + " \"type\":\"VISA\"," + " \"supported_currency\":" + " [" + " \"currency\"" + " ]," + " \"last_four_digits\":\"last_four_digits\"," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"billing_address\":" + " {" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + " }," + " \"status\":\"VALID\"," + " \"object_id\":\"object_id\"" + " }" + " ]," + " \"default_instrument_id\":\"default_instrument_id\"," + " \"address\":" + " [" + " {" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line_1\":\"address_line_1\"," + " \"address_line_2\":\"address_line_2\"," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"country_name_code\"" + " }" + " }" + " ]," + " \"default_address_id\":\"default_address_id\"," + " \"obfuscated_gaia_id\":\"obfuscated_gaia_id\"," + " \"required_legal_document\":" + " [" + " {" + " \"legal_document_id\":\"doc_id\"," + " \"display_name\":\"display_name\"" + " }" + " ]" + "}"; + +const char kWalletItems[] = + "{" + " \"required_action\":" + " [" + " ]," + " \"google_transaction_id\":\"google_transaction_id\"," + " \"instrument\":" + " [" + " {" + " \"descriptive_name\":\"descriptive_name\"," + " \"type\":\"VISA\"," + " \"supported_currency\":" + " [" + " \"currency\"" + " ]," + " \"last_four_digits\":\"last_four_digits\"," + " \"expiration_month\":12," + " \"expiration_year\":2012," + " \"billing_address\":" + " {" + " \"name\":\"name\"," + " \"address1\":\"address1\"," + " \"address2\":\"address2\"," + " \"city\":\"city\"," + " \"state\":\"state\"," + " \"postal_code\":\"postal_code\"," + " \"phone_number\":\"phone_number\"," + " \"country_code\":\"country_code\"" + " }," + " \"status\":\"VALID\"," + " \"object_id\":\"object_id\"" + " }" + " ]," + " \"default_instrument_id\":\"default_instrument_id\"," + " \"address\":" + " [" + " {" + " \"id\":\"id\"," + " \"phone_number\":\"phone_number\"," + " \"postal_address\":" + " {" + " \"recipient_name\":\"recipient_name\"," + " \"address_line_1\":\"address_line_1\"," + " \"address_line_2\":\"address_line_2\"," + " \"locality_name\":\"locality_name\"," + " \"administrative_area_name\":\"administrative_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"country_name_code\"" + " }" + " }" + " ]," + " \"default_address_id\":\"default_address_id\"," + " \"obfuscated_gaia_id\":\"obfuscated_gaia_id\"," + " \"required_legal_document\":" + " [" + " {" + " \"legal_document_id\":\"doc_id\"," + " \"display_name\":\"display_name\"" + " }" + " ]" + "}"; + +} // anonymous namespace + +namespace autofill { +namespace wallet { + +class WalletItemsTest : public testing::Test { + public: + WalletItemsTest() {} + protected: + void SetUpDictionary(const std::string& json) { + scoped_ptr<Value> value(base::JSONReader::Read(json)); + ASSERT_TRUE(value.get()); + ASSERT_TRUE(value->IsType(Value::TYPE_DICTIONARY)); + dict.reset(static_cast<DictionaryValue*>(value.release())); + } + scoped_ptr<DictionaryValue> dict; +}; + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingStatus) { + SetUpDictionary(kMaskedInstrumentMissingStatus); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingType) { + SetUpDictionary(kMaskedInstrumentMissingType); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingLastFourDigits) { + SetUpDictionary(kMaskedInstrumentMissingLastFourDigits); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingAddress) { + SetUpDictionary(kMaskedInstrumentMissingAddress); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMalformedAddress) { + SetUpDictionary(kMaskedInstrumentMalformedAddress); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrumentMissingObjectId) { + SetUpDictionary(kMaskedInstrumentMissingObjectId); + EXPECT_EQ(NULL, + WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateMaskedInstrument) { + SetUpDictionary(kMaskedInstrument); + scoped_ptr<Address> address(new Address("country_code", + ASCIIToUTF16("name"), + ASCIIToUTF16("address1"), + ASCIIToUTF16("address2"), + ASCIIToUTF16("city"), + ASCIIToUTF16("state"), + ASCIIToUTF16("postal_code"), + ASCIIToUTF16("phone_number"), + "")); + std::vector<string16> supported_currencies; + supported_currencies.push_back(ASCIIToUTF16("currency")); + WalletItems::MaskedInstrument masked_instrument( + ASCIIToUTF16("descriptive_name"), + WalletItems::MaskedInstrument::VISA, + supported_currencies, + ASCIIToUTF16("last_four_digits"), + 12, + 2012, + address.Pass(), + WalletItems::MaskedInstrument::VALID, + "object_id"); + EXPECT_EQ(masked_instrument, + *WalletItems::MaskedInstrument::CreateMaskedInstrument(*dict)); +} + +TEST_F(WalletItemsTest, CreateLegalDocumentMissingDocId) { + SetUpDictionary(kLegalDocumentMissingDocumentId); + EXPECT_EQ(NULL, WalletItems::LegalDocument::CreateLegalDocument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateLegalDocumentMissingDisplayName) { + SetUpDictionary(kLegalDocumentMissingDisplayName); + EXPECT_EQ(NULL, WalletItems::LegalDocument::CreateLegalDocument(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateLegalDocument) { + SetUpDictionary(kLegalDocument); + WalletItems::LegalDocument expected("doc_id", "display_name"); + EXPECT_EQ(expected, + *WalletItems::LegalDocument::CreateLegalDocument(*dict)); +} + +TEST_F(WalletItemsTest, LegalDocumentGetUrl) { + WalletItems::LegalDocument legal_doc("doc_id", "display_name"); + EXPECT_EQ("https://wallet.google.com/customer/gadget/legaldocument.html?" + "docId=doc_id", + legal_doc.GetUrl().spec()); +} + +TEST_F(WalletItemsTest, CreateWalletItemsWithRequiredActions) { + SetUpDictionary(kWalletItemsWithRequiredActions); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(SETUP_WALLET); + required_actions.push_back(ACCEPT_TOS); + required_actions.push_back(GAIA_AUTH); + required_actions.push_back(UPDATE_EXPIRATION_DATE); + required_actions.push_back(UPGRADE_MIN_ADDRESS); + required_actions.push_back(PASSIVE_GAIA_AUTH); + required_actions.push_back(REQUIRE_PHONE_NUMBER); + + WalletItems expected(required_actions, + std::string(), + std::string(), + std::string(), + std::string()); + EXPECT_EQ(expected, *WalletItems::CreateWalletItems(*dict)); + + ASSERT_FALSE(required_actions.empty()); + required_actions.pop_back(); + WalletItems different_required_actions(required_actions, + std::string(), + std::string(), + std::string(), + std::string()); + EXPECT_NE(expected, different_required_actions); +} + +TEST_F(WalletItemsTest, CreateWalletItemsWithInvalidRequiredActions) { + SetUpDictionary(kWalletItemsWithInvalidRequiredActions); + EXPECT_EQ(NULL, WalletItems::CreateWalletItems(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateWalletItemsMissingGoogleTransactionId) { + SetUpDictionary(kWalletItemsMissingGoogleTransactionId); + EXPECT_EQ(NULL, WalletItems::CreateWalletItems(*dict).get()); +} + +TEST_F(WalletItemsTest, CreateWalletItems) { + SetUpDictionary(kWalletItems); + std::vector<RequiredAction> required_actions; + WalletItems expected(required_actions, + "google_transaction_id", + "default_instrument_id", + "default_address_id", + "obfuscated_gaia_id"); + + scoped_ptr<Address> billing_address(new Address("country_code", + ASCIIToUTF16("name"), + ASCIIToUTF16("address1"), + ASCIIToUTF16("address2"), + ASCIIToUTF16("city"), + ASCIIToUTF16("state"), + ASCIIToUTF16("postal_code"), + ASCIIToUTF16("phone_number"), + "")); + std::vector<string16> supported_currencies; + supported_currencies.push_back(ASCIIToUTF16("currency")); + scoped_ptr<WalletItems::MaskedInstrument> masked_instrument( + new WalletItems::MaskedInstrument(ASCIIToUTF16("descriptive_name"), + WalletItems::MaskedInstrument::VISA, + supported_currencies, + ASCIIToUTF16("last_four_digits"), + 12, + 2012, + billing_address.Pass(), + WalletItems::MaskedInstrument::VALID, + "object_id")); + expected.AddInstrument(masked_instrument.Pass()); + + scoped_ptr<Address> shipping_address(new Address("country_code", + ASCIIToUTF16("name"), + ASCIIToUTF16("address1"), + ASCIIToUTF16("address2"), + ASCIIToUTF16("city"), + ASCIIToUTF16("state"), + ASCIIToUTF16("postal_code"), + ASCIIToUTF16("phone_number"), + "id")); + expected.AddAddress(shipping_address.Pass()); + + scoped_ptr<WalletItems::LegalDocument> legal_document( + new WalletItems::LegalDocument("doc_id", + "display_name")); + expected.AddLegalDocument(legal_document.Pass()); + + EXPECT_EQ(expected, *WalletItems::CreateWalletItems(*dict)); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/wallet_service_url.cc b/components/autofill/browser/wallet/wallet_service_url.cc new file mode 100644 index 0000000..47bc105 --- /dev/null +++ b/components/autofill/browser/wallet/wallet_service_url.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/wallet_service_url.h" + +#include <string> + +#include "base/command_line.h" +#include "components/autofill/common/autofill_switches.h" +#include "google_apis/gaia/gaia_urls.h" +#include "googleurl/src/gurl.h" +#include "net/base/url_util.h" + +namespace { + +const char kDefaultWalletServiceUrl[] = "https://wallet.google.com/"; + +GURL GetWalletHostUrl() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + std::string wallet_service_hostname = + command_line.GetSwitchValueASCII(switches::kWalletServiceUrl); + return !wallet_service_hostname.empty() ? GURL(wallet_service_hostname) : + GURL(kDefaultWalletServiceUrl); +} + +GURL GetBaseWalletUrl() { + return GetWalletHostUrl().Resolve("online/v2/"); +} + +GURL GetBaseAutocheckoutUrl() { + return GetBaseWalletUrl().Resolve("wallet/autocheckout/v1/"); +} + +GURL GetBaseSecureUrl() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + std::string wallet_secure_url = + command_line.GetSwitchValueASCII(switches::kWalletSecureServiceUrl); + return !wallet_secure_url.empty() ? GURL(wallet_secure_url) : + GURL(kDefaultWalletServiceUrl); +} + +} // anonymous namespace + +namespace autofill { +namespace wallet { + +GURL GetGetWalletItemsUrl() { + return GetBaseAutocheckoutUrl().Resolve("getWalletItemsJwtless"); +} + +GURL GetGetFullWalletUrl() { + return GetBaseAutocheckoutUrl().Resolve("getFullWalletJwtless"); +} + +GURL GetAcceptLegalDocumentsUrl() { + return GetBaseAutocheckoutUrl().Resolve("acceptLegalDocument"); +} + +GURL GetAuthenticateInstrumentUrl() { + return GetBaseAutocheckoutUrl().Resolve("authenticateInstrument"); +} + +GURL GetSendStatusUrl() { + return GetBaseAutocheckoutUrl().Resolve("reportStatus"); +} + +GURL GetSaveToWalletUrl() { + return GetBaseAutocheckoutUrl().Resolve("saveToWallet"); +} + +GURL GetPassiveAuthUrl() { + return GetBaseWalletUrl().Resolve("passiveauth"); +} + +GURL GetEncryptionUrl() { + return GetWalletHostUrl().Resolve("online-secure/temporarydata/cvv?s7e=cvv"); +} + +GURL GetEscrowUrl() { + return GetBaseSecureUrl().Resolve("dehEfe?s7e=cardNumber%3Bcvv"); +} + +GURL GetSignInUrl() { + GURL url(GaiaUrls::GetInstance()->service_login_url()); + url = net::AppendQueryParameter(url, "service", "sierra"); + url = net::AppendQueryParameter(url, "btmpl", "popup"); + url = net::AppendQueryParameter(url, + "continue", + GetSignInContinueUrl().spec()); + return url; +} + +// The continue url portion of the sign-in URL. +GURL GetSignInContinueUrl() { + return GetPassiveAuthUrl(); +} + +bool IsSignInContinueUrl(const GURL& url) { + GURL final_url = wallet::GetSignInContinueUrl(); + return url.SchemeIsSecure() && + url.host() == final_url.host() && + url.path() == final_url.path(); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/wallet_service_url.h b/components/autofill/browser/wallet/wallet_service_url.h new file mode 100644 index 0000000..3caef2e --- /dev/null +++ b/components/autofill/browser/wallet/wallet_service_url.h @@ -0,0 +1,37 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_SERVICE_URL_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_SERVICE_URL_H_ + +class GURL; + +namespace autofill { +namespace wallet { + +GURL GetGetWalletItemsUrl(); +GURL GetGetFullWalletUrl(); +GURL GetAcceptLegalDocumentsUrl(); +GURL GetAuthenticateInstrumentUrl(); +GURL GetEncryptionUrl(); +GURL GetEscrowUrl(); +GURL GetSendStatusUrl(); +GURL GetSaveToWalletUrl(); +GURL GetPassiveAuthUrl(); + +// URL to visit for presenting the user with a sign-in dialog. +GURL GetSignInUrl(); + +// The the URL to use as a continue parameter in the sign-in URL. +// A redirect to this URL will occur once sign-in is complete. +GURL GetSignInContinueUrl(); + +// Returns true if |url| is an acceptable variant of the sign-in continue +// url. Can be used for detection of navigation to the continue url. +bool IsSignInContinueUrl(const GURL& url); + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_SERVICE_URL_H_ diff --git a/components/autofill/browser/wallet/wallet_service_url_unittest.cc b/components/autofill/browser/wallet/wallet_service_url_unittest.cc new file mode 100644 index 0000000..c303be4 --- /dev/null +++ b/components/autofill/browser/wallet/wallet_service_url_unittest.cc @@ -0,0 +1,40 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/wallet_service_url.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { +namespace wallet { + +TEST(WalletServiceUrl, CheckDefaultUrls) { + ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/v1/" + "getWalletItemsJwtless", + GetGetWalletItemsUrl().spec()); + ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/v1/" + "getFullWalletJwtless", + GetGetFullWalletUrl().spec()); + ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/v1/" + "acceptLegalDocument", + GetAcceptLegalDocumentsUrl().spec()); + ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/v1/" + "authenticateInstrument", + GetAuthenticateInstrumentUrl().spec()); + ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/v1/" + "reportStatus", + GetSendStatusUrl().spec()); + ASSERT_EQ("https://wallet.google.com/online/v2/wallet/autocheckout/v1/" + "saveToWallet", + GetSaveToWalletUrl().spec()); + ASSERT_EQ("https://wallet.google.com/online/v2/passiveauth", + GetPassiveAuthUrl().spec()); + ASSERT_EQ("https://wallet.google.com/online-secure/temporarydata/cvv?s7e=cvv", + GetEncryptionUrl().spec()); + ASSERT_EQ("https://wallet.google.com/dehEfe?s7e=cardNumber%3Bcvv", + GetEscrowUrl().spec()); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/wallet_test_util.cc b/components/autofill/browser/wallet/wallet_test_util.cc new file mode 100644 index 0000000..eb4bd6c --- /dev/null +++ b/components/autofill/browser/wallet/wallet_test_util.cc @@ -0,0 +1,49 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/browser/wallet/wallet_test_util.h" + +#include "base/utf_string_conversions.h" +#include "components/autofill/browser/wallet/instrument.h" +#include "components/autofill/browser/wallet/wallet_address.h" + +namespace autofill { +namespace wallet { + +scoped_ptr<Instrument> GetTestInstrument() { + return scoped_ptr<Instrument>(new Instrument(ASCIIToUTF16("4444444444444448"), + ASCIIToUTF16("123"), + 12, + 2012, + Instrument::VISA, + GetTestAddress().Pass())); +} + +scoped_ptr<Address> GetTestShippingAddress() { + return scoped_ptr<Address>(new Address( + "ship_country_name_code", + ASCIIToUTF16("ship_recipient_name"), + ASCIIToUTF16("ship_address_line_1"), + ASCIIToUTF16("ship_address_line_2"), + ASCIIToUTF16("ship_locality_name"), + ASCIIToUTF16("ship_admin_area_name"), + ASCIIToUTF16("ship_postal_code_number"), + ASCIIToUTF16("ship_phone_number"), + std::string())); +} + +scoped_ptr<Address> GetTestAddress() { + return scoped_ptr<Address>(new Address("country_name_code", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("admin_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + std::string())); +} + +} // namespace wallet +} // namespace autofill diff --git a/components/autofill/browser/wallet/wallet_test_util.h b/components/autofill/browser/wallet/wallet_test_util.h new file mode 100644 index 0000000..cb899c8 --- /dev/null +++ b/components/autofill/browser/wallet/wallet_test_util.h @@ -0,0 +1,23 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_TEST_UTIL_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_TEST_UTIL_H_ + +#include "base/memory/scoped_ptr.h" + +namespace autofill { +namespace wallet { + +class Instrument; +class Address; + +scoped_ptr<Instrument> GetTestInstrument(); +scoped_ptr<Address> GetTestShippingAddress(); +scoped_ptr<Address> GetTestAddress(); + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_TEST_UTIL_H_ diff --git a/components/autofill/renderer/DEPS b/components/autofill/renderer/DEPS new file mode 100644 index 0000000..3279b26 --- /dev/null +++ b/components/autofill/renderer/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + # TODO(joi): Get rid of this dependency. + "!chrome/common/password_generation_util.h", + + "+content/public/common", + "+content/public/renderer", +] diff --git a/components/autofill/renderer/autofill_agent.cc b/components/autofill/renderer/autofill_agent.cc new file mode 100644 index 0000000..fb3cf4a --- /dev/null +++ b/components/autofill/renderer/autofill_agent.cc @@ -0,0 +1,858 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/renderer/autofill_agent.h" + +#include "base/bind.h" +#include "base/message_loop.h" +#include "base/string_util.h" +#include "base/strings/string_split.h" +#include "base/time.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/common/autocheckout_status.h" +#include "components/autofill/common/autofill_messages.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_data_predictions.h" +#include "components/autofill/common/form_field_data.h" +#include "components/autofill/common/web_element_descriptor.h" +#include "components/autofill/renderer/form_autofill_util.h" +#include "components/autofill/renderer/password_autofill_manager.h" +#include "content/public/common/password_form.h" +#include "content/public/common/ssl_status.h" +#include "content/public/renderer/render_view.h" +#include "grit/chromium_strings.h" +#include "grit/generated_resources.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebRect.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebURLRequest.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDataSource.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormControlElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeCollection.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebOptionElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "ui/base/keycodes/keyboard_codes.h" +#include "ui/base/l10n/l10n_util.h" + +using WebKit::WebAutofillClient; +using WebKit::WebFormControlElement; +using WebKit::WebFormElement; +using WebKit::WebFrame; +using WebKit::WebInputElement; +using WebKit::WebKeyboardEvent; +using WebKit::WebNode; +using WebKit::WebNodeCollection; +using WebKit::WebOptionElement; +using WebKit::WebString; + +namespace { + +// The size above which we stop triggering autofill for an input text field +// (so to avoid sending long strings through IPC). +const size_t kMaximumTextSizeForAutofill = 1000; + +// The maximum number of data list elements to send to the browser process +// via IPC (to prevent long IPC messages). +const size_t kMaximumDataListSizeForAutofill = 30; + +const int kAutocheckoutClickTimeout = 3; + +void AppendDataListSuggestions(const WebKit::WebInputElement& element, + std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<int>* item_ids) { + WebNodeCollection options = element.dataListOptions(); + if (options.isNull()) + return; + + string16 prefix = element.editingValue(); + if (element.isMultiple() && + element.formControlType() == WebString::fromUTF8("email")) { + std::vector<string16> parts; + base::SplitStringDontTrim(prefix, ',', &parts); + if (parts.size() > 0) + TrimWhitespace(parts[parts.size() - 1], TRIM_LEADING, &prefix); + } + for (WebOptionElement option = options.firstItem().to<WebOptionElement>(); + !option.isNull(); option = options.nextItem().to<WebOptionElement>()) { + if (!StartsWith(option.value(), prefix, false) || + option.value() == prefix || + !element.isValidValue(option.value())) + continue; + + values->push_back(option.value()); + if (option.value() != option.label()) + labels->push_back(option.label()); + else + labels->push_back(string16()); + icons->push_back(string16()); + item_ids->push_back(WebAutofillClient::MenuItemIDDataListEntry); + } +} + +// Trim the vectors before sending them to the browser process to ensure we +// don't send too much data through the IPC. +void TrimDataListsForIPC(std::vector<string16>* values, + std::vector<string16>* labels, + std::vector<string16>* icons, + std::vector<int>* unique_ids) { + // Limit the size of the vectors. + if (values->size() > kMaximumDataListSizeForAutofill) { + values->resize(kMaximumDataListSizeForAutofill); + labels->resize(kMaximumDataListSizeForAutofill); + icons->resize(kMaximumDataListSizeForAutofill); + unique_ids->resize(kMaximumDataListSizeForAutofill); + } + + // Limit the size of the strings in the vectors + for (size_t i = 0; i < values->size(); ++i) { + if ((*values)[i].length() > kMaximumTextSizeForAutofill) + (*values)[i].resize(kMaximumTextSizeForAutofill); + + if ((*labels)[i].length() > kMaximumTextSizeForAutofill) + (*labels)[i].resize(kMaximumTextSizeForAutofill); + + if ((*icons)[i].length() > kMaximumTextSizeForAutofill) + (*icons)[i].resize(kMaximumTextSizeForAutofill); + } +} + +} // namespace + +namespace autofill { + +AutofillAgent::AutofillAgent( + content::RenderView* render_view, + PasswordAutofillManager* password_autofill_manager) + : content::RenderViewObserver(render_view), + password_autofill_manager_(password_autofill_manager), + autofill_query_id_(0), + autofill_action_(AUTOFILL_NONE), + topmost_frame_(NULL), + web_view_(render_view->GetWebView()), + display_warning_if_disabled_(false), + was_query_node_autofilled_(false), + has_shown_autofill_popup_for_current_edit_(false), + did_set_node_text_(false), + autocheckout_click_in_progress_(false), + ignore_text_changes_(false), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { + render_view->GetWebView()->setAutofillClient(this); +} + +AutofillAgent::~AutofillAgent() {} + +bool AutofillAgent::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AutofillAgent, message) + IPC_MESSAGE_HANDLER(AutofillMsg_SuggestionsReturned, OnSuggestionsReturned) + IPC_MESSAGE_HANDLER(AutofillMsg_FormDataFilled, OnFormDataFilled) + IPC_MESSAGE_HANDLER(AutofillMsg_FieldTypePredictionsAvailable, + OnFieldTypePredictionsAvailable) + IPC_MESSAGE_HANDLER(AutofillMsg_SetAutofillActionFill, + OnSetAutofillActionFill) + IPC_MESSAGE_HANDLER(AutofillMsg_ClearForm, + OnClearForm) + IPC_MESSAGE_HANDLER(AutofillMsg_SetAutofillActionPreview, + OnSetAutofillActionPreview) + IPC_MESSAGE_HANDLER(AutofillMsg_ClearPreviewedForm, + OnClearPreviewedForm) + IPC_MESSAGE_HANDLER(AutofillMsg_SetNodeText, + OnSetNodeText) + IPC_MESSAGE_HANDLER(AutofillMsg_AcceptDataListSuggestion, + OnAcceptDataListSuggestion) + IPC_MESSAGE_HANDLER(AutofillMsg_AcceptPasswordAutofillSuggestion, + OnAcceptPasswordAutofillSuggestion) + IPC_MESSAGE_HANDLER(AutofillMsg_RequestAutocompleteResult, + OnRequestAutocompleteResult) + IPC_MESSAGE_HANDLER(AutofillMsg_FillFormsAndClick, + OnFillFormsAndClick) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void AutofillAgent::DidFinishDocumentLoad(WebFrame* frame) { + // The document has now been fully loaded. Scan for forms to be sent up to + // the browser. + std::vector<FormData> forms; + + if (!frame->parent()) { + topmost_frame_ = frame; + form_elements_.clear(); + form_cache_.ExtractFormsAndFormElements(*frame, &forms, &form_elements_); + } else { + form_cache_.ExtractForms(*frame, &forms); + } + + if (!forms.empty()) { + Send(new AutofillHostMsg_FormsSeen(routing_id(), forms, + base::TimeTicks::Now())); + } +} + +void AutofillAgent::DidStartProvisionalLoad(WebFrame* frame) { + if (!frame->parent()) { + topmost_frame_ = NULL; + WebKit::WebURL provisional_url = + frame->provisionalDataSource()->request().url(); + WebKit::WebURL current_url = frame->dataSource()->request().url(); + // If the URL of the topmost frame is changing and the current page is part + // of an Autocheckout flow, the click was successful as long as the + // provisional load is committed. + if (provisional_url != current_url && click_timer_.IsRunning()) { + click_timer_.Stop(); + autocheckout_click_in_progress_ = true; + } + } +} + +void AutofillAgent::DidFailProvisionalLoad(WebFrame* frame, + const WebKit::WebURLError& error) { + if (autocheckout_click_in_progress_) { + autocheckout_click_in_progress_ = false; + ClickFailed(); + } +} + +void AutofillAgent::DidCommitProvisionalLoad(WebFrame* frame, + bool is_new_navigation) { + autocheckout_click_in_progress_ = false; + in_flight_request_form_.reset(); +} + +void AutofillAgent::FrameDetached(WebFrame* frame) { + form_cache_.ResetFrame(*frame); + if (!frame->parent()) { + // |frame| is about to be destroyed so we need to clear |top_most_frame_|. + topmost_frame_ = NULL; + click_timer_.Stop(); + } +} + +void AutofillAgent::WillSubmitForm(WebFrame* frame, + const WebFormElement& form) { + FormData form_data; + if (WebFormElementToFormData(form, + WebFormControlElement(), + REQUIRE_AUTOCOMPLETE, + static_cast<ExtractMask>( + EXTRACT_VALUE | EXTRACT_OPTION_TEXT), + &form_data, + NULL)) { + Send(new AutofillHostMsg_FormSubmitted(routing_id(), form_data, + base::TimeTicks::Now())); + } +} + +void AutofillAgent::ZoomLevelChanged() { + // Any time the zoom level changes, the page's content moves, so any Autofill + // popups should be hidden. This is only needed for the new Autofill UI + // because WebKit already knows to hide the old UI when this occurs. + HideHostPopups(); +} + +void AutofillAgent::DidChangeScrollOffset(WebKit::WebFrame*) { + HidePopups(); +} + +void AutofillAgent::didRequestAutocomplete(WebKit::WebFrame* frame, + const WebFormElement& form) { + FormData form_data; + if (!in_flight_request_form_.isNull() || + !WebFormElementToFormData(form, + WebFormControlElement(), + REQUIRE_AUTOCOMPLETE, + EXTRACT_OPTIONS, + &form_data, + NULL)) { + WebFormElement(form).finishRequestAutocomplete( + WebFormElement::AutocompleteResultErrorDisabled); + return; + } + + // Cancel any pending Autofill requests and hide any currently showing popups. + ++autofill_query_id_; + HidePopups(); + + in_flight_request_form_ = form; + // TODO(ramankk): Include SSLStatus within form_data and update the IPC. + Send(new AutofillHostMsg_RequestAutocomplete( + routing_id(), + form_data, + frame->document().url(), + render_view()->GetSSLStatusOfFrame(frame))); +} + +void AutofillAgent::setIgnoreTextChanges(bool ignore) { + ignore_text_changes_ = ignore; +} + +bool AutofillAgent::InputElementClicked(const WebInputElement& element, + bool was_focused, + bool is_focused) { + if (was_focused) + ShowSuggestions(element, true, false, true); + + return false; +} + +bool AutofillAgent::InputElementLostFocus() { + HideHostPopups(); + + return false; +} + +void AutofillAgent::didAcceptAutofillSuggestion(const WebNode& node, + const WebString& value, + const WebString& label, + int item_id, + unsigned index) { + if (password_autofill_manager_->DidAcceptAutofillSuggestion(node, value)) + return; + + DCHECK(node == element_); + + switch (item_id) { + case WebAutofillClient::MenuItemIDWarningMessage: + case WebAutofillClient::MenuItemIDSeparator: + NOTREACHED(); + break; + case WebAutofillClient::MenuItemIDAutofillOptions: + // User selected 'Autofill Options'. + Send(new AutofillHostMsg_ShowAutofillDialog(routing_id())); + break; + case WebAutofillClient::MenuItemIDClearForm: + // User selected 'Clear form'. + form_cache_.ClearFormWithElement(element_); + break; + case WebAutofillClient::MenuItemIDAutocompleteEntry: + case WebAutofillClient::MenuItemIDPasswordEntry: + // User selected an Autocomplete or password entry, so we fill directly. + SetNodeText(value, &element_); + break; + case WebAutofillClient::MenuItemIDDataListEntry: + AcceptDataListSuggestion(value); + break; + default: + // A positive item_id is a unique id for an autofill (vs. autocomplete) + // suggestion. + DCHECK_GT(item_id, 0); + // Fill the values for the whole form. + FillAutofillFormData(node, item_id, AUTOFILL_FILL); + } +} + +void AutofillAgent::didSelectAutofillSuggestion(const WebNode& node, + const WebString& value, + const WebString& label, + int item_id) { + if (password_autofill_manager_->DidSelectAutofillSuggestion(node)) + return; + + didClearAutofillSelection(node); + + if (item_id > 0) + FillAutofillFormData(node, item_id, AUTOFILL_PREVIEW); +} + +void AutofillAgent::didClearAutofillSelection(const WebNode& node) { + if (password_autofill_manager_->DidClearAutofillSelection(node)) + return; + + if (!element_.isNull() && node == element_) { + ClearPreviewedFormWithElement(element_, was_query_node_autofilled_); + } else { + // TODO(isherman): There seem to be rare cases where this code *is* + // reachable: see [ http://crbug.com/96321#c6 ]. Ideally we would + // understand those cases and fix the code to avoid them. However, so far I + // have been unable to reproduce such a case locally. If you hit this + // NOTREACHED(), please file a bug against me. + NOTREACHED(); + } +} + +void AutofillAgent::removeAutocompleteSuggestion(const WebString& name, + const WebString& value) { + Send(new AutofillHostMsg_RemoveAutocompleteEntry(routing_id(), name, value)); +} + +void AutofillAgent::textFieldDidEndEditing(const WebInputElement& element) { + password_autofill_manager_->TextFieldDidEndEditing(element); + has_shown_autofill_popup_for_current_edit_ = false; + Send(new AutofillHostMsg_DidEndTextFieldEditing(routing_id())); +} + +void AutofillAgent::textFieldDidChange(const WebInputElement& element) { + if (ignore_text_changes_) + return; + + if (did_set_node_text_) { + did_set_node_text_ = false; + return; + } + + // We post a task for doing the Autofill as the caret position is not set + // properly at this point (http://bugs.webkit.org/show_bug.cgi?id=16976) and + // it is needed to trigger autofill. + weak_ptr_factory_.InvalidateWeakPtrs(); + MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&AutofillAgent::TextFieldDidChangeImpl, + weak_ptr_factory_.GetWeakPtr(), element)); +} + +void AutofillAgent::TextFieldDidChangeImpl(const WebInputElement& element) { + // If the element isn't focused then the changes don't matter. This check is + // required to properly handle IME interactions. + if (!element.focused()) + return; + + if (password_autofill_manager_->TextDidChangeInTextField(element)) { + element_ = element; + return; + } + + ShowSuggestions(element, false, true, false); + + FormData form; + FormFieldData field; + if (FindFormAndFieldForInputElement(element, &form, &field, REQUIRE_NONE)) { + Send(new AutofillHostMsg_TextFieldDidChange(routing_id(), form, field, + base::TimeTicks::Now())); + } +} + +void AutofillAgent::textFieldDidReceiveKeyDown(const WebInputElement& element, + const WebKeyboardEvent& event) { + if (password_autofill_manager_->TextFieldHandlingKeyDown(element, event)) { + element_ = element; + return; + } + + if (event.windowsKeyCode == ui::VKEY_DOWN || + event.windowsKeyCode == ui::VKEY_UP) + ShowSuggestions(element, true, true, true); +} + +void AutofillAgent::OnSuggestionsReturned(int query_id, + const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& unique_ids) { + if (query_id != autofill_query_id_) + return; + + if (element_.isNull() || !element_.isFocusable()) + return; + + std::vector<string16> v(values); + std::vector<string16> l(labels); + std::vector<string16> i(icons); + std::vector<int> ids(unique_ids); + + if (!element_.autoComplete() && !v.empty()) { + // If autofill is disabled and we had suggestions, show a warning instead. + v.assign(1, l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_FORM_DISABLED)); + l.assign(1, string16()); + i.assign(1, string16()); + ids.assign(1, WebAutofillClient::MenuItemIDWarningMessage); + } else if (ids.size() > 1 && + ids[0] == WebAutofillClient::MenuItemIDWarningMessage) { + // If we received an autofill warning plus some autocomplete suggestions, + // remove the autofill warning. + v.erase(v.begin()); + l.erase(l.begin()); + i.erase(i.begin()); + ids.erase(ids.begin()); + } + + // If we were about to show a warning and we shouldn't, don't. + if (!display_warning_if_disabled_ && !v.empty() && + ids[0] == WebAutofillClient::MenuItemIDWarningMessage) { + v.clear(); + l.clear(); + i.clear(); + ids.clear(); + } + + // Only include "Autofill Options" special menu item if we have Autofill + // items, identified by |unique_ids| having at least one valid value. + bool has_autofill_item = false; + for (size_t i = 0; i < ids.size(); ++i) { + if (ids[i] > 0) { + has_autofill_item = true; + break; + } + } + + if (has_autofill_item) { + v.push_back(string16()); + l.push_back(string16()); + i.push_back(string16()); + ids.push_back(WebAutofillClient::MenuItemIDSeparator); + + if (FormWithElementIsAutofilled(element_)) { + // The form has been auto-filled, so give the user the chance to clear the + // form. Append the 'Clear form' menu item. + v.push_back(l10n_util::GetStringUTF16(IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM)); + l.push_back(string16()); + i.push_back(string16()); + ids.push_back(WebAutofillClient::MenuItemIDClearForm); + } + + // Append the 'Chrome Autofill options' menu item; + v.push_back(l10n_util::GetStringUTF16(IDS_AUTOFILL_OPTIONS_POPUP)); + l.push_back(string16()); + i.push_back(string16()); + ids.push_back(WebAutofillClient::MenuItemIDAutofillOptions); + } + + CombineDataListEntriesAndShow(element_, v, l, i, ids, has_autofill_item); +} + +void AutofillAgent::CombineDataListEntriesAndShow( + const WebKit::WebInputElement& element, + const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& item_ids, + bool has_autofill_item) { + std::vector<string16> v; + std::vector<string16> l; + std::vector<string16> i; + std::vector<int> ids; + + AppendDataListSuggestions(element, &v, &l, &i, &ids); + + // If there are both <datalist> items and Autofill suggestions, add a + // separator between them. + if (!v.empty() && !values.empty()) { + v.push_back(string16()); + l.push_back(string16()); + i.push_back(string16()); + ids.push_back(WebAutofillClient::MenuItemIDSeparator); + } + + // Append the Autofill suggestions. + v.insert(v.end(), values.begin(), values.end()); + l.insert(l.end(), labels.begin(), labels.end()); + i.insert(i.end(), icons.begin(), icons.end()); + ids.insert(ids.end(), item_ids.begin(), item_ids.end()); + + if (v.empty()) { + // No suggestions, any popup currently showing is obsolete. + HidePopups(); + return; + } + + WebKit::WebView* web_view = render_view()->GetWebView(); + if (!web_view) + return; + + // Send to WebKit for display. + web_view->applyAutofillSuggestions(element, v, l, i, ids); + + Send(new AutofillHostMsg_DidShowAutofillSuggestions( + routing_id(), + has_autofill_item && !has_shown_autofill_popup_for_current_edit_)); + has_shown_autofill_popup_for_current_edit_ |= has_autofill_item; +} + +void AutofillAgent::AcceptDataListSuggestion(const string16& suggested_value) { + string16 new_value = suggested_value; + // If this element takes multiple values then replace the last part with + // the suggestion. + if (element_.isMultiple() && + element_.formControlType() == WebString::fromUTF8("email")) { + std::vector<string16> parts; + + base::SplitStringDontTrim(element_.editingValue(), ',', &parts); + if (parts.size() == 0) + parts.push_back(string16()); + + string16 last_part = parts.back(); + // We want to keep just the leading whitespace. + for (size_t i = 0; i < last_part.size(); ++i) { + if (!IsWhitespace(last_part[i])) { + last_part = last_part.substr(0, i); + break; + } + } + last_part.append(suggested_value); + parts[parts.size() - 1] = last_part; + + new_value = JoinString(parts, ','); + } + SetNodeText(new_value, &element_); +} + +void AutofillAgent::OnFormDataFilled(int query_id, + const FormData& form) { + if (!render_view()->GetWebView() || query_id != autofill_query_id_) + return; + + was_query_node_autofilled_ = element_.isAutofilled(); + + switch (autofill_action_) { + case AUTOFILL_FILL: + FillForm(form, element_); + Send(new AutofillHostMsg_DidFillAutofillFormData(routing_id(), + base::TimeTicks::Now())); + break; + case AUTOFILL_PREVIEW: + PreviewForm(form, element_); + Send(new AutofillHostMsg_DidPreviewAutofillFormData(routing_id())); + break; + default: + NOTREACHED(); + } + autofill_action_ = AUTOFILL_NONE; +} + +void AutofillAgent::OnFieldTypePredictionsAvailable( + const std::vector<FormDataPredictions>& forms) { + for (size_t i = 0; i < forms.size(); ++i) { + form_cache_.ShowPredictions(forms[i]); + } +} + +void AutofillAgent::OnSetAutofillActionFill() { + autofill_action_ = AUTOFILL_FILL; +} + +void AutofillAgent::OnClearForm() { + form_cache_.ClearFormWithElement(element_); +} + +void AutofillAgent::OnSetAutofillActionPreview() { + autofill_action_ = AUTOFILL_PREVIEW; +} + +void AutofillAgent::OnClearPreviewedForm() { + didClearAutofillSelection(element_); +} + +void AutofillAgent::OnSetNodeText(const string16& value) { + SetNodeText(value, &element_); +} + +void AutofillAgent::OnAcceptDataListSuggestion(const string16& value) { + AcceptDataListSuggestion(value); +} + +void AutofillAgent::OnAcceptPasswordAutofillSuggestion(const string16& value) { + // We need to make sure this is handled here because the browser process + // skipped it handling because it believed it would be handled here. If it + // isn't handled here then the browser logic needs to be updated. + bool handled = password_autofill_manager_->DidAcceptAutofillSuggestion( + element_, + value); + DCHECK(handled); +} + +void AutofillAgent::OnRequestAutocompleteResult( + WebFormElement::AutocompleteResult result, const FormData& form_data) { + if (in_flight_request_form_.isNull()) + return; + + if (result == WebFormElement::AutocompleteResultSuccess) + FillFormIncludingNonFocusableElements(form_data, in_flight_request_form_); + + in_flight_request_form_.finishRequestAutocomplete(result); + in_flight_request_form_.reset(); +} + +void AutofillAgent::OnFillFormsAndClick( + const std::vector<FormData>& forms, + const WebElementDescriptor& click_element_descriptor) { + DCHECK_EQ(forms.size(), form_elements_.size()); + + // Fill the form. + for (size_t i = 0; i < forms.size(); ++i) + FillFormIncludingNonFocusableElements(forms[i], form_elements_[i]); + + // It's possible that clicking the element to proceed in an Autocheckout + // flow will not actually proceed to the next step in the flow, e.g. there + // is a new required field that Autocheckout does not know how to fill. In + // order to capture this case and present the user with an error a timer is + // set that informs the browser of the error. |click_timer_| has to be started + // before clicking so it can start before DidStartProvisionalLoad started. + click_timer_.Start(FROM_HERE, + base::TimeDelta::FromSeconds(kAutocheckoutClickTimeout), + this, + &AutofillAgent::ClickFailed); + if (!ClickElement(topmost_frame_->document(), + click_element_descriptor)) { + click_timer_.Stop(); + Send(new AutofillHostMsg_ClickFailed(routing_id(), + MISSING_ADVANCE)); + } +} + +void AutofillAgent::ClickFailed() { + Send(new AutofillHostMsg_ClickFailed(routing_id(), + CANNOT_PROCEED)); +} + +void AutofillAgent::ShowSuggestions(const WebInputElement& element, + bool autofill_on_empty_values, + bool requires_caret_at_end, + bool display_warning_if_disabled) { + if (!element.isEnabled() || element.isReadOnly() || !element.isTextField() || + element.isPasswordField() || !element.suggestedValue().isEmpty()) + return; + + // Don't attempt to autofill with values that are too large or if filling + // criteria are not met. + WebString value = element.editingValue(); + if (value.length() > kMaximumTextSizeForAutofill || + (!autofill_on_empty_values && value.isEmpty()) || + (requires_caret_at_end && + (element.selectionStart() != element.selectionEnd() || + element.selectionEnd() != static_cast<int>(value.length())))) { + // Any popup currently showing is obsolete. + HidePopups(); + return; + } + + element_ = element; + + // If autocomplete is disabled at the form level, then we might want to show a + // warning in place of suggestions. However, if autocomplete is disabled + // specifically for this field, we never want to show a warning. Otherwise, + // we might interfere with custom popups (e.g. search suggestions) used by the + // website. Note that we cannot use the WebKit method element.autoComplete() + // as it does not allow us to distinguish the case where autocomplete is + // disabled for *both* the element and for the form. + // Also, if the field has no name, then we won't have values. + const string16 autocomplete_attribute = element.getAttribute("autocomplete"); + if (LowerCaseEqualsASCII(autocomplete_attribute, "off") || + element.nameForAutofill().isEmpty()) { + CombineDataListEntriesAndShow(element, std::vector<string16>(), + std::vector<string16>(), + std::vector<string16>(), + std::vector<int>(), false); + return; + } + + QueryAutofillSuggestions(element, display_warning_if_disabled); +} + +void AutofillAgent::QueryAutofillSuggestions(const WebInputElement& element, + bool display_warning_if_disabled) { + if (!element.document().frame()) + return; + + static int query_counter = 0; + autofill_query_id_ = query_counter++; + display_warning_if_disabled_ = display_warning_if_disabled; + + // If autocomplete is disabled at the form level, we want to see if there + // would have been any suggestions were it enabled, so that we can show a + // warning. Otherwise, we want to ignore fields that disable autocomplete, so + // that the suggestions list does not include suggestions for these form + // fields -- see comment 1 on http://crbug.com/69914 + // Rather than testing the form's autocomplete enabled state, we test the + // element's state. The DCHECK below ensures that this is equivalent. + DCHECK(element.autoComplete() || !element.form().autoComplete()); + const RequirementsMask requirements = + element.autoComplete() ? REQUIRE_AUTOCOMPLETE : REQUIRE_NONE; + + FormData form; + FormFieldData field; + if (!FindFormAndFieldForInputElement(element, &form, &field, requirements)) { + // If we didn't find the cached form, at least let autocomplete have a shot + // at providing suggestions. + WebFormControlElementToFormField(element, EXTRACT_VALUE, &field); + } + + gfx::Rect bounding_box(element_.boundsInViewportSpace()); + + float scale = web_view_->pageScaleFactor(); + gfx::RectF bounding_box_scaled(bounding_box.x() * scale, + bounding_box.y() * scale, + bounding_box.width() * scale, + bounding_box.height() * scale); + + // Find the datalist values and send them to the browser process. + std::vector<string16> data_list_values; + std::vector<string16> data_list_labels; + std::vector<string16> data_list_icons; + std::vector<int> data_list_unique_ids; + AppendDataListSuggestions(element_, + &data_list_values, + &data_list_labels, + &data_list_icons, + &data_list_unique_ids); + + TrimDataListsForIPC(&data_list_values, + &data_list_labels, + &data_list_icons, + &data_list_unique_ids); + + Send(new AutofillHostMsg_SetDataList(routing_id(), + data_list_values, + data_list_labels, + data_list_icons, + data_list_unique_ids)); + + // Add SSL Status in the formdata to let browser process alert user + // appropriately using browser UI. + form.ssl_status = render_view()->GetSSLStatusOfFrame( + element.document().frame()); + Send(new AutofillHostMsg_QueryFormFieldAutofill(routing_id(), + autofill_query_id_, + form, + field, + bounding_box_scaled, + display_warning_if_disabled)); +} + +void AutofillAgent::FillAutofillFormData(const WebNode& node, + int unique_id, + AutofillAction action) { + DCHECK_GT(unique_id, 0); + + static int query_counter = 0; + autofill_query_id_ = query_counter++; + + FormData form; + FormFieldData field; + if (!FindFormAndFieldForInputElement(node.toConst<WebInputElement>(), &form, + &field, REQUIRE_AUTOCOMPLETE)) { + return; + } + + autofill_action_ = action; + Send(new AutofillHostMsg_FillAutofillFormData( + routing_id(), autofill_query_id_, form, field, unique_id)); +} + +void AutofillAgent::SetNodeText(const string16& value, + WebKit::WebInputElement* node) { + did_set_node_text_ = true; + string16 substring = value; + substring = substring.substr(0, node->maxLength()); + + node->setEditingValue(substring); +} + +void AutofillAgent::HidePopups() { + WebKit::WebView* web_view = render_view()->GetWebView(); + if (web_view) + web_view->hidePopups(); + + HideHostPopups(); +} + +void AutofillAgent::HideHostPopups() { + Send(new AutofillHostMsg_HideAutofillPopup(routing_id())); +} + +} // namespace autofill diff --git a/components/autofill/renderer/autofill_agent.h b/components/autofill/renderer/autofill_agent.h new file mode 100644 index 0000000..5b24013 --- /dev/null +++ b/components/autofill/renderer/autofill_agent.h @@ -0,0 +1,270 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_RENDERER_AUTOFILL_AGENT_H_ +#define COMPONENTS_AUTOFILL_RENDERER_AUTOFILL_AGENT_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/weak_ptr.h" +#include "base/timer.h" +#include "components/autofill/renderer/form_cache.h" +#include "components/autofill/renderer/page_click_listener.h" +#include "content/public/renderer/render_view_observer.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" + +struct FormFieldData; + +namespace WebKit { +class WebNode; +class WebView; +} + +namespace autofill { + +struct WebElementDescriptor; +class PasswordAutofillManager; + +// AutofillAgent deals with Autofill related communications between WebKit and +// the browser. There is one AutofillAgent per RenderView. +// This code was originally part of RenderView. +// Note that Autofill encompasses: +// - single text field suggestions, that we usually refer to as Autocomplete, +// - password form fill, refered to as Password Autofill, and +// - entire form fill based on one field entry, referred to as Form Autofill. + +class AutofillAgent : public content::RenderViewObserver, + public PageClickListener, + public WebKit::WebAutofillClient { + public: + // PasswordAutofillManager is guaranteed to outlive AutofillAgent. + AutofillAgent(content::RenderView* render_view, + PasswordAutofillManager* password_autofill_manager); + virtual ~AutofillAgent(); + + private: + enum AutofillAction { + AUTOFILL_NONE, // No state set. + AUTOFILL_FILL, // Fill the Autofill form data. + AUTOFILL_PREVIEW, // Preview the Autofill form data. + }; + + // RenderView::Observer: + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void DidFinishDocumentLoad(WebKit::WebFrame* frame) OVERRIDE; + virtual void DidStartProvisionalLoad(WebKit::WebFrame* frame) OVERRIDE; + virtual void DidFailProvisionalLoad( + WebKit::WebFrame* frame, + const WebKit::WebURLError& error) OVERRIDE; + virtual void DidCommitProvisionalLoad(WebKit::WebFrame* frame, + bool is_new_navigation) OVERRIDE; + virtual void FrameDetached(WebKit::WebFrame* frame) OVERRIDE; + virtual void WillSubmitForm(WebKit::WebFrame* frame, + const WebKit::WebFormElement& form) OVERRIDE; + virtual void ZoomLevelChanged() OVERRIDE; + virtual void DidChangeScrollOffset(WebKit::WebFrame* frame) OVERRIDE; + + // PageClickListener: + virtual bool InputElementClicked(const WebKit::WebInputElement& element, + bool was_focused, + bool is_focused) OVERRIDE; + virtual bool InputElementLostFocus() OVERRIDE; + + // WebKit::WebAutofillClient: + virtual void didAcceptAutofillSuggestion(const WebKit::WebNode& node, + const WebKit::WebString& value, + const WebKit::WebString& label, + int item_id, + unsigned index) OVERRIDE; + virtual void didSelectAutofillSuggestion(const WebKit::WebNode& node, + const WebKit::WebString& value, + const WebKit::WebString& label, + int item_id) OVERRIDE; + virtual void didClearAutofillSelection(const WebKit::WebNode& node) OVERRIDE; + virtual void removeAutocompleteSuggestion( + const WebKit::WebString& name, + const WebKit::WebString& value) OVERRIDE; + virtual void textFieldDidEndEditing( + const WebKit::WebInputElement& element) OVERRIDE; + virtual void textFieldDidChange( + const WebKit::WebInputElement& element) OVERRIDE; + virtual void textFieldDidReceiveKeyDown( + const WebKit::WebInputElement& element, + const WebKit::WebKeyboardEvent& event) OVERRIDE; + virtual void didRequestAutocomplete( + WebKit::WebFrame* frame, + const WebKit::WebFormElement& form) OVERRIDE; + virtual void setIgnoreTextChanges(bool ignore) OVERRIDE; + + void OnSuggestionsReturned(int query_id, + const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& unique_ids); + void OnFormDataFilled(int query_id, const FormData& form); + void OnFieldTypePredictionsAvailable( + const std::vector<FormDataPredictions>& forms); + + // For external Autofill selection. + void OnSetAutofillActionFill(); + void OnClearForm(); + void OnSetAutofillActionPreview(); + void OnClearPreviewedForm(); + void OnSetNodeText(const string16& value); + void OnAcceptDataListSuggestion(const string16& value); + void OnAcceptPasswordAutofillSuggestion(const string16& value); + + // Called when interactive autocomplete finishes. + void OnRequestAutocompleteResult( + WebKit::WebFormElement::AutocompleteResult result, + const FormData& form_data); + + // Called when an autocomplete request succeeds or fails with the |result|. + void FinishAutocompleteRequest( + WebKit::WebFormElement::AutocompleteResult result); + + // Called when the Autofill server hints that this page should be filled using + // Autocheckout. All the relevant form fields in |form_data| will be filled + // and then element specified by |element_descriptor| will be clicked to + // proceed to the next step of the form. + void OnFillFormsAndClick(const std::vector<FormData>& form_data, + const WebElementDescriptor& element_descriptor); + + // Called when clicking an Autocheckout proceed element fails to do anything. + void ClickFailed(); + + // Called in a posted task by textFieldDidChange() to work-around a WebKit bug + // http://bugs.webkit.org/show_bug.cgi?id=16976 + void TextFieldDidChangeImpl(const WebKit::WebInputElement& element); + + // Shows the autofill suggestions for |element|. + // This call is asynchronous and may or may not lead to the showing of a + // suggestion popup (no popup is shown if there are no available suggestions). + // |autofill_on_empty_values| specifies whether suggestions should be shown + // when |element| contains no text. + // |requires_caret_at_end| specifies whether suggestions should be shown when + // the caret is not after the last character in |element|. + // |display_warning_if_disabled| specifies whether a warning should be + // displayed to the user if Autofill has suggestions available, but cannot + // fill them because it is disabled (e.g. when trying to fill a credit card + // form on a non-secure website). + void ShowSuggestions(const WebKit::WebInputElement& element, + bool autofill_on_empty_values, + bool requires_caret_at_end, + bool display_warning_if_disabled); + + // Queries the browser for Autocomplete and Autofill suggestions for the given + // |element|. + void QueryAutofillSuggestions(const WebKit::WebInputElement& element, + bool display_warning_if_disabled); + + // Combines DataList suggestion entries with the autofill ones and show them + // to the user. + void CombineDataListEntriesAndShow(const WebKit::WebInputElement& element, + const std::vector<string16>& values, + const std::vector<string16>& labels, + const std::vector<string16>& icons, + const std::vector<int>& item_ids, + bool has_autofill_item); + + // Sets the element value to reflect the selected |suggested_value|. + void AcceptDataListSuggestion(const string16& suggested_value); + + // Queries the AutofillManager for form data for the form containing |node|. + // |value| is the current text in the field, and |unique_id| is the selected + // profile's unique ID. |action| specifies whether to Fill or Preview the + // values returned from the AutofillManager. + void FillAutofillFormData(const WebKit::WebNode& node, + int unique_id, + AutofillAction action); + + // Fills |form| and |field| with the FormData and FormField corresponding to + // |node|. Returns true if the data was found; and false otherwise. + bool FindFormAndFieldForNode( + const WebKit::WebNode& node, + FormData* form, + FormFieldData* field) WARN_UNUSED_RESULT; + + // Set |node| to display the given |value|. + void SetNodeText(const string16& value, WebKit::WebInputElement* node); + + // Hides any currently showing Autofill popups in the renderer or browser. + void HidePopups(); + + // Hides any currently showing Autofill popups in the browser only. + void HideHostPopups(); + + FormCache form_cache_; + + PasswordAutofillManager* password_autofill_manager_; // WEAK reference. + + // The ID of the last request sent for form field Autofill. Used to ignore + // out of date responses. + int autofill_query_id_; + + // The element corresponding to the last request sent for form field Autofill. + WebKit::WebInputElement element_; + + // The form element currently requesting an interactive autocomplete. + WebKit::WebFormElement in_flight_request_form_; + + // All the form elements seen in the top frame. + std::vector<WebKit::WebFormElement> form_elements_; + + // The action to take when receiving Autofill data from the AutofillManager. + AutofillAction autofill_action_; + + // Pointer to the current topmost frame. Used in autocheckout flows so + // elements can be clicked. + WebKit::WebFrame* topmost_frame_; + + // Pointer to the WebView. Used to access page scale factor. + WebKit::WebView* web_view_; + + // Should we display a warning if autofill is disabled? + bool display_warning_if_disabled_; + + // Was the query node autofilled prior to previewing the form? + bool was_query_node_autofilled_; + + // Have we already shown Autofill suggestions for the field the user is + // currently editing? Used to keep track of state for metrics logging. + bool has_shown_autofill_popup_for_current_edit_; + + // If true we just set the node text so we shouldn't show the popup. + bool did_set_node_text_; + + // Watchdog timer for clicking in Autocheckout flows. + base::OneShotTimer<AutofillAgent> click_timer_; + + // Used to signal that we need to watch for loading failures in an + // Autocheckout flow. + bool autocheckout_click_in_progress_; + + // Whether or not to ignore text changes. Useful for when we're committing + // a composition when we are defocusing the WebView and we don't want to + // trigger an autofill popup to show. + bool ignore_text_changes_; + + base::WeakPtrFactory<AutofillAgent> weak_ptr_factory_; + + friend class PasswordAutofillManagerTest; + FRIEND_TEST_ALL_PREFIXES(ChromeRenderViewTest, FillFormElement); + FRIEND_TEST_ALL_PREFIXES(ChromeRenderViewTest, SendForms); + FRIEND_TEST_ALL_PREFIXES(ChromeRenderViewTest, ShowAutofillWarning); + FRIEND_TEST_ALL_PREFIXES(PasswordAutofillManagerTest, WaitUsername); + FRIEND_TEST_ALL_PREFIXES(PasswordAutofillManagerTest, SuggestionAccept); + FRIEND_TEST_ALL_PREFIXES(PasswordAutofillManagerTest, SuggestionSelect); + + DISALLOW_COPY_AND_ASSIGN(AutofillAgent); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_RENDERER_AUTOFILL_AGENT_H_ diff --git a/components/autofill/renderer/form_autofill_util.cc b/components/autofill/renderer/form_autofill_util.cc new file mode 100644 index 0000000..8b0e6fb --- /dev/null +++ b/components/autofill/renderer/form_autofill_util.cc @@ -0,0 +1,1032 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/renderer/form_autofill_util.h" + +#include <map> + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/common/autofill_switches.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_field_data.h" +#include "components/autofill/common/web_element_descriptor.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebString.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebVector.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebExceptionCode.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormControlElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebLabelElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeList.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebOptionElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSelectElement.h" + +using WebKit::WebDocument; +using WebKit::WebElement; +using WebKit::WebExceptionCode; +using WebKit::WebFormControlElement; +using WebKit::WebFormElement; +using WebKit::WebFrame; +using WebKit::WebInputElement; +using WebKit::WebLabelElement; +using WebKit::WebNode; +using WebKit::WebNodeList; +using WebKit::WebOptionElement; +using WebKit::WebSelectElement; +using WebKit::WebString; +using WebKit::WebVector; + +namespace { + +using autofill::ExtractAutofillableElements; +using autofill::IsAutofillableInputElement; +using autofill::IsCheckableElement; +using autofill::IsSelectElement; +using autofill::IsTextInput; + +// The maximum length allowed for form data. +const size_t kMaxDataLength = 1024; + +bool IsOptionElement(const WebElement& element) { + CR_DEFINE_STATIC_LOCAL(WebString, kOption, ("option")); + return element.hasTagName(kOption); +} + +bool IsScriptElement(const WebElement& element) { + CR_DEFINE_STATIC_LOCAL(WebString, kScript, ("script")); + return element.hasTagName(kScript); +} + +bool IsNoScriptElement(const WebElement& element) { + CR_DEFINE_STATIC_LOCAL(WebString, kNoScript, ("noscript")); + return element.hasTagName(kNoScript); +} + +bool HasTagName(const WebNode& node, const WebKit::WebString& tag) { + return node.isElementNode() && node.toConst<WebElement>().hasTagName(tag); +} + +bool IsAutofillableElement(const WebFormControlElement& element) { + const WebInputElement* input_element = toWebInputElement(&element); + return IsAutofillableInputElement(input_element) || IsSelectElement(element); +} + +// Check whether the given field satisfies the REQUIRE_AUTOCOMPLETE requirement. +// When Autocheckout is enabled, this requirement is enforced in the browser +// process rather than in the renderer process, and hence all fields are +// considered to satisfy this requirement. +bool SatisfiesRequireAutocomplete(const WebInputElement& input_element) { + return input_element.autoComplete() || + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableExperimentalFormFilling); +} + +// Appends |suffix| to |prefix| so that any intermediary whitespace is collapsed +// to a single space. If |force_whitespace| is true, then the resulting string +// is guaranteed to have a space between |prefix| and |suffix|. Otherwise, the +// result includes a space only if |prefix| has trailing whitespace or |suffix| +// has leading whitespace. +// A few examples: +// * CombineAndCollapseWhitespace("foo", "bar", false) -> "foobar" +// * CombineAndCollapseWhitespace("foo", "bar", true) -> "foo bar" +// * CombineAndCollapseWhitespace("foo ", "bar", false) -> "foo bar" +// * CombineAndCollapseWhitespace("foo", " bar", false) -> "foo bar" +// * CombineAndCollapseWhitespace("foo", " bar", true) -> "foo bar" +// * CombineAndCollapseWhitespace("foo ", " bar", false) -> "foo bar" +// * CombineAndCollapseWhitespace(" foo", "bar ", false) -> " foobar " +// * CombineAndCollapseWhitespace(" foo", "bar ", true) -> " foo bar " +const string16 CombineAndCollapseWhitespace(const string16& prefix, + const string16& suffix, + bool force_whitespace) { + string16 prefix_trimmed; + TrimPositions prefix_trailing_whitespace = + TrimWhitespace(prefix, TRIM_TRAILING, &prefix_trimmed); + + // Recursively compute the children's text. + string16 suffix_trimmed; + TrimPositions suffix_leading_whitespace = + TrimWhitespace(suffix, TRIM_LEADING, &suffix_trimmed); + + if (prefix_trailing_whitespace || suffix_leading_whitespace || + force_whitespace) { + return prefix_trimmed + ASCIIToUTF16(" ") + suffix_trimmed; + } else { + return prefix_trimmed + suffix_trimmed; + } +} + +// This is a helper function for the FindChildText() function (see below). +// Search depth is limited with the |depth| parameter. +string16 FindChildTextInner(const WebNode& node, int depth) { + if (depth <= 0 || node.isNull()) + return string16(); + + // Skip over comments. + if (node.nodeType() == WebNode::CommentNode) + return FindChildTextInner(node.nextSibling(), depth - 1); + + if (node.nodeType() != WebNode::ElementNode && + node.nodeType() != WebNode::TextNode) + return string16(); + + // Ignore elements known not to contain inferable labels. + if (node.isElementNode()) { + const WebElement element = node.toConst<WebElement>(); + if (IsOptionElement(element) || + IsScriptElement(element) || + IsNoScriptElement(element) || + (element.isFormControlElement() && + IsAutofillableElement(element.toConst<WebFormControlElement>()))) { + return string16(); + } + } + + // Extract the text exactly at this node. + string16 node_text = node.nodeValue(); + + // Recursively compute the children's text. + // Preserve inter-element whitespace separation. + string16 child_text = FindChildTextInner(node.firstChild(), depth - 1); + bool add_space = node.nodeType() == WebNode::TextNode && node_text.empty(); + node_text = CombineAndCollapseWhitespace(node_text, child_text, add_space); + + // Recursively compute the siblings' text. + // Again, preserve inter-element whitespace separation. + string16 sibling_text = FindChildTextInner(node.nextSibling(), depth - 1); + add_space = node.nodeType() == WebNode::TextNode && node_text.empty(); + node_text = CombineAndCollapseWhitespace(node_text, sibling_text, add_space); + + return node_text; +} + +// Returns the aggregated values of the descendants of |element| that are +// non-empty text nodes. This is a faster alternative to |innerText()| for +// performance critical operations. It does a full depth-first search so can be +// used when the structure is not directly known. However, unlike with +// |innerText()|, the search depth and breadth are limited to a fixed threshold. +// Whitespace is trimmed from text accumulated at descendant nodes. +string16 FindChildText(const WebNode& node) { + if (node.isTextNode()) + return node.nodeValue(); + + WebNode child = node.firstChild(); + + const int kChildSearchDepth = 10; + string16 node_text = FindChildTextInner(child, kChildSearchDepth); + TrimWhitespace(node_text, TRIM_ALL, &node_text); + return node_text; +} + +// Helper for |InferLabelForElement()| that infers a label, if possible, from +// a previous sibling of |element|, +// e.g. Some Text <input ...> +// or Some <span>Text</span> <input ...> +// or <p>Some Text</p><input ...> +// or <label>Some Text</label> <input ...> +// or Some Text <img><input ...> +// or <b>Some Text</b><br/> <input ...>. +string16 InferLabelFromPrevious(const WebFormControlElement& element) { + string16 inferred_label; + WebNode previous = element; + while (true) { + previous = previous.previousSibling(); + if (previous.isNull()) + break; + + // Skip over comments. + WebNode::NodeType node_type = previous.nodeType(); + if (node_type == WebNode::CommentNode) + continue; + + // Otherwise, only consider normal HTML elements and their contents. + if (node_type != WebNode::TextNode && + node_type != WebNode::ElementNode) + break; + + // A label might be split across multiple "lightweight" nodes. + // Coalesce any text contained in multiple consecutive + // (a) plain text nodes or + // (b) inline HTML elements that are essentially equivalent to text nodes. + CR_DEFINE_STATIC_LOCAL(WebString, kBold, ("b")); + CR_DEFINE_STATIC_LOCAL(WebString, kStrong, ("strong")); + CR_DEFINE_STATIC_LOCAL(WebString, kSpan, ("span")); + CR_DEFINE_STATIC_LOCAL(WebString, kFont, ("font")); + if (previous.isTextNode() || + HasTagName(previous, kBold) || HasTagName(previous, kStrong) || + HasTagName(previous, kSpan) || HasTagName(previous, kFont)) { + string16 value = FindChildText(previous); + // A text node's value will be empty if it is for a line break. + bool add_space = previous.isTextNode() && value.empty(); + inferred_label = + CombineAndCollapseWhitespace(value, inferred_label, add_space); + continue; + } + + // If we have identified a partial label and have reached a non-lightweight + // element, consider the label to be complete. + string16 trimmed_label; + TrimWhitespace(inferred_label, TRIM_ALL, &trimmed_label); + if (!trimmed_label.empty()) + break; + + // <img> and <br> tags often appear between the input element and its + // label text, so skip over them. + CR_DEFINE_STATIC_LOCAL(WebString, kImage, ("img")); + CR_DEFINE_STATIC_LOCAL(WebString, kBreak, ("br")); + if (HasTagName(previous, kImage) || HasTagName(previous, kBreak)) + continue; + + // We only expect <p> and <label> tags to contain the full label text. + CR_DEFINE_STATIC_LOCAL(WebString, kPage, ("p")); + CR_DEFINE_STATIC_LOCAL(WebString, kLabel, ("label")); + if (HasTagName(previous, kPage) || HasTagName(previous, kLabel)) + inferred_label = FindChildText(previous); + + break; + } + + TrimWhitespace(inferred_label, TRIM_ALL, &inferred_label); + return inferred_label; +} + +// Helper for |InferLabelForElement()| that infers a label, if possible, from +// enclosing list item, +// e.g. <li>Some Text<input ...><input ...><input ...></tr> +string16 InferLabelFromListItem(const WebFormControlElement& element) { + WebNode parent = element.parentNode(); + CR_DEFINE_STATIC_LOCAL(WebString, kListItem, ("li")); + while (!parent.isNull() && parent.isElementNode() && + !parent.to<WebElement>().hasTagName(kListItem)) { + parent = parent.parentNode(); + } + + if (!parent.isNull() && HasTagName(parent, kListItem)) + return FindChildText(parent); + + return string16(); +} + +// Helper for |InferLabelForElement()| that infers a label, if possible, from +// surrounding table structure, +// e.g. <tr><td>Some Text</td><td><input ...></td></tr> +// or <tr><th>Some Text</th><td><input ...></td></tr> +// or <tr><td><b>Some Text</b></td><td><b><input ...></b></td></tr> +// or <tr><th><b>Some Text</b></th><td><b><input ...></b></td></tr> +string16 InferLabelFromTableColumn(const WebFormControlElement& element) { + CR_DEFINE_STATIC_LOCAL(WebString, kTableCell, ("td")); + WebNode parent = element.parentNode(); + while (!parent.isNull() && parent.isElementNode() && + !parent.to<WebElement>().hasTagName(kTableCell)) { + parent = parent.parentNode(); + } + + if (parent.isNull()) + return string16(); + + // Check all previous siblings, skipping non-element nodes, until we find a + // non-empty text block. + string16 inferred_label; + WebNode previous = parent.previousSibling(); + CR_DEFINE_STATIC_LOCAL(WebString, kTableHeader, ("th")); + while (inferred_label.empty() && !previous.isNull()) { + if (HasTagName(previous, kTableCell) || HasTagName(previous, kTableHeader)) + inferred_label = FindChildText(previous); + + previous = previous.previousSibling(); + } + + return inferred_label; +} + +// Helper for |InferLabelForElement()| that infers a label, if possible, from +// surrounding table structure, +// e.g. <tr><td>Some Text</td></tr><tr><td><input ...></td></tr> +string16 InferLabelFromTableRow(const WebFormControlElement& element) { + CR_DEFINE_STATIC_LOCAL(WebString, kTableRow, ("tr")); + WebNode parent = element.parentNode(); + while (!parent.isNull() && parent.isElementNode() && + !parent.to<WebElement>().hasTagName(kTableRow)) { + parent = parent.parentNode(); + } + + if (parent.isNull()) + return string16(); + + // Check all previous siblings, skipping non-element nodes, until we find a + // non-empty text block. + string16 inferred_label; + WebNode previous = parent.previousSibling(); + while (inferred_label.empty() && !previous.isNull()) { + if (HasTagName(previous, kTableRow)) + inferred_label = FindChildText(previous); + + previous = previous.previousSibling(); + } + + return inferred_label; +} + +// Helper for |InferLabelForElement()| that infers a label, if possible, from +// a surrounding div table, +// e.g. <div>Some Text<span><input ...></span></div> +// e.g. <div>Some Text</div><div><input ...></div> +string16 InferLabelFromDivTable(const WebFormControlElement& element) { + WebNode node = element.parentNode(); + bool looking_for_parent = true; + + // Search the sibling and parent <div>s until we find a candidate label. + string16 inferred_label; + CR_DEFINE_STATIC_LOCAL(WebString, kDiv, ("div")); + CR_DEFINE_STATIC_LOCAL(WebString, kTable, ("table")); + CR_DEFINE_STATIC_LOCAL(WebString, kFieldSet, ("fieldset")); + while (inferred_label.empty() && !node.isNull()) { + if (HasTagName(node, kDiv)) { + looking_for_parent = false; + inferred_label = FindChildText(node); + } else if (looking_for_parent && + (HasTagName(node, kTable) || HasTagName(node, kFieldSet))) { + // If the element is in a table or fieldset, its label most likely is too. + break; + } + + if (node.previousSibling().isNull()) { + // If there are no more siblings, continue walking up the tree. + looking_for_parent = true; + } + + if (looking_for_parent) + node = node.parentNode(); + else + node = node.previousSibling(); + } + + return inferred_label; +} + +// Helper for |InferLabelForElement()| that infers a label, if possible, from +// a surrounding definition list, +// e.g. <dl><dt>Some Text</dt><dd><input ...></dd></dl> +// e.g. <dl><dt><b>Some Text</b></dt><dd><b><input ...></b></dd></dl> +string16 InferLabelFromDefinitionList(const WebFormControlElement& element) { + CR_DEFINE_STATIC_LOCAL(WebString, kDefinitionData, ("dd")); + WebNode parent = element.parentNode(); + while (!parent.isNull() && parent.isElementNode() && + !parent.to<WebElement>().hasTagName(kDefinitionData)) + parent = parent.parentNode(); + + if (parent.isNull() || !HasTagName(parent, kDefinitionData)) + return string16(); + + // Skip by any intervening text nodes. + WebNode previous = parent.previousSibling(); + while (!previous.isNull() && previous.isTextNode()) + previous = previous.previousSibling(); + + CR_DEFINE_STATIC_LOCAL(WebString, kDefinitionTag, ("dt")); + if (previous.isNull() || !HasTagName(previous, kDefinitionTag)) + return string16(); + + return FindChildText(previous); +} + +// Infers corresponding label for |element| from surrounding context in the DOM, +// e.g. the contents of the preceding <p> tag or text element. +string16 InferLabelForElement(const WebFormControlElement& element) { + string16 inferred_label = InferLabelFromPrevious(element); + if (!inferred_label.empty()) + return inferred_label; + + // If we didn't find a label, check for list item case. + inferred_label = InferLabelFromListItem(element); + if (!inferred_label.empty()) + return inferred_label; + + // If we didn't find a label, check for table cell case. + inferred_label = InferLabelFromTableColumn(element); + if (!inferred_label.empty()) + return inferred_label; + + // If we didn't find a label, check for table row case. + inferred_label = InferLabelFromTableRow(element); + if (!inferred_label.empty()) + return inferred_label; + + // If we didn't find a label, check for definition list case. + inferred_label = InferLabelFromDefinitionList(element); + if (!inferred_label.empty()) + return inferred_label; + + // If we didn't find a label, check for div table case. + return InferLabelFromDivTable(element); +} + +// Fills |option_strings| with the values of the <option> elements present in +// |select_element|. +void GetOptionStringsFromElement(const WebSelectElement& select_element, + std::vector<string16>* option_values, + std::vector<string16>* option_contents) { + DCHECK(!select_element.isNull()); + + option_values->clear(); + option_contents->clear(); + WebVector<WebElement> list_items = select_element.listItems(); + option_values->reserve(list_items.size()); + option_contents->reserve(list_items.size()); + for (size_t i = 0; i < list_items.size(); ++i) { + if (IsOptionElement(list_items[i])) { + const WebOptionElement option = list_items[i].toConst<WebOptionElement>(); + option_values->push_back(option.value()); + option_contents->push_back(option.text()); + } + } +} + +// The callback type used by |ForEachMatchingFormField()|. +typedef void (*Callback)(const FormFieldData&, + bool, /* is_initiating_element */ + WebKit::WebFormControlElement*); + +// For each autofillable field in |data| that matches a field in the |form|, +// the |callback| is invoked with the corresponding |form| field data. +void ForEachMatchingFormField(const WebFormElement& form_element, + const WebElement& initiating_element, + const FormData& data, + bool only_focusable_elements, + bool force_override, + Callback callback) { + std::vector<WebFormControlElement> control_elements; + ExtractAutofillableElements(form_element, autofill::REQUIRE_AUTOCOMPLETE, + &control_elements); + + if (control_elements.size() != data.fields.size()) { + // This case should be reachable only for pathological websites and tests, + // which add or remove form fields while the user is interacting with the + // Autofill popup. + return; + } + + // It's possible that the site has injected fields into the form after the + // page has loaded, so we can't assert that the size of the cached control + // elements is equal to the size of the fields in |form|. Fortunately, the + // one case in the wild where this happens, paypal.com signup form, the fields + // are appended to the end of the form and are not visible. + for (size_t i = 0; i < control_elements.size(); ++i) { + WebFormControlElement* element = &control_elements[i]; + + if (string16(element->nameForAutofill()) != data.fields[i].name) { + // This case should be reachable only for pathological websites, which + // rename form fields while the user is interacting with the Autofill + // popup. I (isherman) am not aware of any such websites, and so am + // optimistically including a NOTREACHED(). If you ever trip this check, + // please file a bug against me. + NOTREACHED(); + continue; + } + + bool is_initiating_element = (*element == initiating_element); + + // Only autofill empty fields and the field that initiated the filling, + // i.e. the field the user is currently editing and interacting with. + const WebInputElement* input_element = toWebInputElement(element); + if (!force_override && IsTextInput(input_element) && + !is_initiating_element && !input_element->value().isEmpty()) + continue; + + if (!element->isEnabled() || element->isReadOnly() || + (only_focusable_elements && !element->isFocusable())) + continue; + + callback(data.fields[i], is_initiating_element, element); + } +} + +// Sets the |field|'s value to the value in |data|. +// Also sets the "autofilled" attribute, causing the background to be yellow. +void FillFormField(const FormFieldData& data, + bool is_initiating_node, + WebKit::WebFormControlElement* field) { + // Nothing to fill. + if (data.value.empty()) + return; + + WebInputElement* input_element = toWebInputElement(field); + if (IsTextInput(input_element)) { + // If the maxlength attribute contains a negative value, maxLength() + // returns the default maxlength value. + input_element->setValue( + data.value.substr(0, input_element->maxLength()), true); + input_element->setAutofilled(true); + if (is_initiating_node) { + int length = input_element->value().length(); + input_element->setSelectionRange(length, length); + // Clear the current IME composition (the underline), if there is one. + input_element->document().frame()->unmarkText(); + } + } else if (IsSelectElement(*field)) { + WebSelectElement select_element = field->to<WebSelectElement>(); + if (select_element.value() != data.value) { + select_element.setValue(data.value); + select_element.dispatchFormControlChangeEvent(); + } + } else { + DCHECK(IsCheckableElement(input_element)); + input_element->setChecked(data.is_checked, true); + } +} + +// Sets the |field|'s "suggested" (non JS visible) value to the value in |data|. +// Also sets the "autofilled" attribute, causing the background to be yellow. +void PreviewFormField(const FormFieldData& data, + bool is_initiating_node, + WebKit::WebFormControlElement* field) { + // Nothing to preview. + if (data.value.empty()) + return; + + // Only preview input fields. Excludes checkboxes and radio buttons, as there + // is no provision for setSuggestedCheckedValue in WebInputElement. + WebInputElement* input_element = toWebInputElement(field); + if (!IsTextInput(input_element)) + return; + + // If the maxlength attribute contains a negative value, maxLength() + // returns the default maxlength value. + input_element->setSuggestedValue( + data.value.substr(0, input_element->maxLength())); + input_element->setAutofilled(true); + if (is_initiating_node) { + // Select the part of the text that the user didn't type. + input_element->setSelectionRange(input_element->value().length(), + input_element->suggestedValue().length()); + } +} + +std::string RetrievalMethodToString( + const autofill::WebElementDescriptor::RetrievalMethod& method) { + switch (method) { + case autofill::WebElementDescriptor::CSS_SELECTOR: + return "CSS_SELECTOR"; + case autofill::WebElementDescriptor::ID: + return "ID"; + case autofill::WebElementDescriptor::NONE: + return "NONE"; + } + NOTREACHED(); + return "UNKNOWN"; +} + +} // namespace + +namespace autofill { + +const size_t kMaxParseableFields = 100; + +// In HTML5, all text fields except password are text input fields to +// autocomplete. +bool IsTextInput(const WebInputElement* element) { + if (!element) + return false; + + return element->isTextField() && !element->isPasswordField(); +} + +bool IsSelectElement(const WebFormControlElement& element) { + // Is static for improving performance. + CR_DEFINE_STATIC_LOCAL(WebString, kSelectOne, ("select-one")); + return element.formControlType() == kSelectOne; +} + +bool IsCheckableElement(const WebInputElement* element) { + if (!element) + return false; + + return element->isCheckbox() || element->isRadioButton(); +} + +bool IsAutofillableInputElement(const WebInputElement* element) { + return IsTextInput(element) || IsCheckableElement(element); +} + +const string16 GetFormIdentifier(const WebFormElement& form) { + string16 identifier = form.name(); + CR_DEFINE_STATIC_LOCAL(WebString, kId, ("id")); + if (identifier.empty()) + identifier = form.getAttribute(kId); + + return identifier; +} + +bool ClickElement(const WebDocument& document, + const WebElementDescriptor& element_descriptor) { + WebString web_descriptor = WebString::fromUTF8(element_descriptor.descriptor); + WebKit::WebElement element; + + switch (element_descriptor.retrieval_method) { + case WebElementDescriptor::CSS_SELECTOR: { + WebExceptionCode ec = 0; + element = document.querySelector(web_descriptor, ec); + if (ec) + DVLOG(1) << "Query selector failed. Error code: " << ec << "."; + break; + } + case WebElementDescriptor::ID: + element = document.getElementById(web_descriptor); + break; + case WebElementDescriptor::NONE: + return true; + } + + if (element.isNull()) { + DVLOG(1) << "Could not find " + << element_descriptor.descriptor + << " by " + << RetrievalMethodToString(element_descriptor.retrieval_method) + << "."; + return false; + } + + element.simulateClick(); + return true; +} + +// Fills |autofillable_elements| with all the auto-fillable form control +// elements in |form_element|. +void ExtractAutofillableElements( + const WebFormElement& form_element, + RequirementsMask requirements, + std::vector<WebFormControlElement>* autofillable_elements) { + WebVector<WebFormControlElement> control_elements; + form_element.getFormControlElements(control_elements); + + autofillable_elements->clear(); + for (size_t i = 0; i < control_elements.size(); ++i) { + WebFormControlElement element = control_elements[i]; + if (!IsAutofillableElement(element)) + continue; + + if (requirements & REQUIRE_AUTOCOMPLETE) { + // TODO(jhawkins): WebKit currently doesn't handle the autocomplete + // attribute for select control elements, but it probably should. + WebInputElement* input_element = toWebInputElement(&control_elements[i]); + if (IsAutofillableInputElement(input_element) && + !SatisfiesRequireAutocomplete(*input_element)) + continue; + } + + autofillable_elements->push_back(element); + } +} + +void WebFormControlElementToFormField(const WebFormControlElement& element, + ExtractMask extract_mask, + FormFieldData* field) { + DCHECK(field); + DCHECK(!element.isNull()); + CR_DEFINE_STATIC_LOCAL(WebString, kAutocomplete, ("autocomplete")); + + // The label is not officially part of a WebFormControlElement; however, the + // labels for all form control elements are scraped from the DOM and set in + // WebFormElementToFormData. + field->name = element.nameForAutofill(); + field->form_control_type = UTF16ToUTF8(element.formControlType()); + field->autocomplete_attribute = + UTF16ToUTF8(element.getAttribute(kAutocomplete)); + if (field->autocomplete_attribute.size() > kMaxDataLength) { + // Discard overly long attribute values to avoid DOS-ing the browser + // process. However, send over a default string to indicate that the + // attribute was present. + field->autocomplete_attribute = "x-max-data-length-exceeded"; + } + + if (!IsAutofillableElement(element)) + return; + + const WebInputElement* input_element = toWebInputElement(&element); + if (IsAutofillableInputElement(input_element)) { + if (IsTextInput(input_element)) + field->max_length = input_element->maxLength(); + + field->is_autofilled = input_element->isAutofilled(); + field->is_focusable = input_element->isFocusable(); + field->should_autocomplete = input_element->autoComplete(); + field->is_checkable = IsCheckableElement(input_element); + } else if (extract_mask & EXTRACT_OPTIONS) { + // Set option strings on the field if available. + DCHECK(IsSelectElement(element)); + const WebSelectElement select_element = element.toConst<WebSelectElement>(); + GetOptionStringsFromElement(select_element, + &field->option_values, + &field->option_contents); + } + + if (!(extract_mask & EXTRACT_VALUE)) + return; + + string16 value; + if (IsAutofillableInputElement(input_element)) { + value = input_element->value(); + } else { + DCHECK(IsSelectElement(element)); + const WebSelectElement select_element = element.toConst<WebSelectElement>(); + value = select_element.value(); + + // Convert the |select_element| value to text if requested. + if (extract_mask & EXTRACT_OPTION_TEXT) { + WebVector<WebElement> list_items = select_element.listItems(); + for (size_t i = 0; i < list_items.size(); ++i) { + if (IsOptionElement(list_items[i])) { + const WebOptionElement option_element = + list_items[i].toConst<WebOptionElement>(); + if (option_element.value() == value) { + value = option_element.text(); + break; + } + } + } + } + } + + // Constrain the maximum data length to prevent a malicious site from DOS'ing + // the browser: http://crbug.com/49332 + if (value.size() > kMaxDataLength) + value = value.substr(0, kMaxDataLength); + + field->value = value; +} + +bool WebFormElementToFormData( + const WebKit::WebFormElement& form_element, + const WebKit::WebFormControlElement& form_control_element, + RequirementsMask requirements, + ExtractMask extract_mask, + FormData* form, + FormFieldData* field) { + CR_DEFINE_STATIC_LOCAL(WebString, kLabel, ("label")); + CR_DEFINE_STATIC_LOCAL(WebString, kFor, ("for")); + CR_DEFINE_STATIC_LOCAL(WebString, kHidden, ("hidden")); + + const WebFrame* frame = form_element.document().frame(); + if (!frame) + return false; + + if (requirements & REQUIRE_AUTOCOMPLETE && !form_element.autoComplete()) + return false; + + form->name = GetFormIdentifier(form_element); + form->method = form_element.method(); + form->origin = frame->document().url(); + form->action = frame->document().completeURL(form_element.action()); + form->user_submitted = form_element.wasUserSubmitted(); + + // If the completed URL is not valid, just use the action we get from + // WebKit. + if (!form->action.is_valid()) + form->action = GURL(form_element.action()); + + // A map from a FormFieldData's name to the FormFieldData itself. + std::map<string16, FormFieldData*> name_map; + + // The extracted FormFields. We use pointers so we can store them in + // |name_map|. + ScopedVector<FormFieldData> form_fields; + + WebVector<WebFormControlElement> control_elements; + form_element.getFormControlElements(control_elements); + + // A vector of bools that indicate whether each field in the form meets the + // requirements and thus will be in the resulting |form|. + std::vector<bool> fields_extracted(control_elements.size(), false); + + for (size_t i = 0; i < control_elements.size(); ++i) { + const WebFormControlElement& control_element = control_elements[i]; + + if (!IsAutofillableElement(control_element)) + continue; + + const WebInputElement* input_element = toWebInputElement(&control_element); + if (requirements & REQUIRE_AUTOCOMPLETE && + IsAutofillableInputElement(input_element) && + !SatisfiesRequireAutocomplete(*input_element)) + continue; + + // Create a new FormFieldData, fill it out and map it to the field's name. + FormFieldData* form_field = new FormFieldData; + WebFormControlElementToFormField(control_element, extract_mask, form_field); + form_fields.push_back(form_field); + // TODO(jhawkins): A label element is mapped to a form control element's id. + // field->name() will contain the id only if the name does not exist. Add + // an id() method to WebFormControlElement and use that here. + name_map[form_field->name] = form_field; + fields_extracted[i] = true; + } + + // If we failed to extract any fields, give up. Also, to avoid overly + // expensive computation, we impose a maximum number of allowable fields. + if (form_fields.empty() || form_fields.size() > kMaxParseableFields) + return false; + + // Loop through the label elements inside the form element. For each label + // element, get the corresponding form control element, use the form control + // element's name as a key into the <name, FormFieldData> map to find the + // previously created FormFieldData and set the FormFieldData's label to the + // label.firstChild().nodeValue() of the label element. + WebNodeList labels = form_element.getElementsByTagName(kLabel); + for (unsigned i = 0; i < labels.length(); ++i) { + WebLabelElement label = labels.item(i).to<WebLabelElement>(); + WebFormControlElement field_element = + label.correspondingControl().to<WebFormControlElement>(); + + string16 element_name; + if (field_element.isNull()) { + // Sometimes site authors will incorrectly specify the corresponding + // field element's name rather than its id, so we compensate here. + element_name = label.getAttribute(kFor); + } else if ( + !field_element.isFormControlElement() || + field_element.formControlType() == kHidden) { + continue; + } else { + element_name = field_element.nameForAutofill(); + } + + std::map<string16, FormFieldData*>::iterator iter = + name_map.find(element_name); + if (iter != name_map.end()) { + string16 label_text = FindChildText(label); + + // Concatenate labels because some sites might have multiple label + // candidates. + if (!iter->second->label.empty() && !label_text.empty()) + iter->second->label += ASCIIToUTF16(" "); + iter->second->label += label_text; + } + } + + // Loop through the form control elements, extracting the label text from + // the DOM. We use the |fields_extracted| vector to make sure we assign the + // extracted label to the correct field, as it's possible |form_fields| will + // not contain all of the elements in |control_elements|. + for (size_t i = 0, field_idx = 0; + i < control_elements.size() && field_idx < form_fields.size(); ++i) { + // This field didn't meet the requirements, so don't try to find a label + // for it. + if (!fields_extracted[i]) + continue; + + const WebFormControlElement& control_element = control_elements[i]; + if (form_fields[field_idx]->label.empty()) + form_fields[field_idx]->label = InferLabelForElement(control_element); + + if (field && form_control_element == control_element) + *field = *form_fields[field_idx]; + + ++field_idx; + } + + // Copy the created FormFields into the resulting FormData object. + for (ScopedVector<FormFieldData>::const_iterator iter = form_fields.begin(); + iter != form_fields.end(); ++iter) { + form->fields.push_back(**iter); + } + + return true; +} + +bool FindFormAndFieldForInputElement(const WebInputElement& element, + FormData* form, + FormFieldData* field, + RequirementsMask requirements) { + if (!IsAutofillableElement(element)) + return false; + + const WebFormElement form_element = element.form(); + if (form_element.isNull()) + return false; + + ExtractMask extract_mask = + static_cast<ExtractMask>(EXTRACT_VALUE | EXTRACT_OPTIONS); + return WebFormElementToFormData(form_element, + element, + requirements, + extract_mask, + form, + field); +} + +void FillForm(const FormData& form, const WebInputElement& element) { + WebFormElement form_element = element.form(); + if (form_element.isNull()) + return; + + ForEachMatchingFormField(form_element, + element, + form, + true, /* only_focusable_elements */ + false, /* dont force override */ + &FillFormField); +} + +void FillFormIncludingNonFocusableElements(const FormData& form_data, + const WebFormElement& form_element) { + if (form_element.isNull()) + return; + + ForEachMatchingFormField(form_element, + WebInputElement(), + form_data, + false, /* only_focusable_elements */ + true, /* force override */ + &FillFormField); +} + +void PreviewForm(const FormData& form, const WebInputElement& element) { + WebFormElement form_element = element.form(); + if (form_element.isNull()) + return; + + ForEachMatchingFormField(form_element, + element, + form, + true, /* only_focusable_elements */ + false, /* dont force override */ + &PreviewFormField); +} + +bool ClearPreviewedFormWithElement(const WebInputElement& element, + bool was_autofilled) { + WebFormElement form_element = element.form(); + if (form_element.isNull()) + return false; + + std::vector<WebFormControlElement> control_elements; + ExtractAutofillableElements(form_element, REQUIRE_AUTOCOMPLETE, + &control_elements); + for (size_t i = 0; i < control_elements.size(); ++i) { + // Only text input elements can be previewed. + WebInputElement* input_element = toWebInputElement(&control_elements[i]); + if (!IsTextInput(input_element)) + continue; + + // If the input element is not auto-filled, we did not preview it, so there + // is nothing to reset. + if (!input_element->isAutofilled()) + continue; + + // There might be unrelated elements in this form which have already been + // auto-filled. For example, the user might have already filled the address + // part of a form and now be dealing with the credit card section. We only + // want to reset the auto-filled status for fields that were previewed. + if (input_element->suggestedValue().isEmpty()) + continue; + + // Clear the suggested value. For the initiating node, also restore the + // original value. + input_element->setSuggestedValue(WebString()); + bool is_initiating_node = (element == *input_element); + if (is_initiating_node) + input_element->setAutofilled(was_autofilled); + else + input_element->setAutofilled(false); + + // Clearing the suggested value in the focused node (above) can cause + // selection to be lost. We force selection range to restore the text + // cursor. + if (is_initiating_node) { + int length = input_element->value().length(); + input_element->setSelectionRange(length, length); + } + } + + return true; +} + +bool FormWithElementIsAutofilled(const WebInputElement& element) { + WebFormElement form_element = element.form(); + if (form_element.isNull()) + return false; + + std::vector<WebFormControlElement> control_elements; + ExtractAutofillableElements(form_element, REQUIRE_AUTOCOMPLETE, + &control_elements); + for (size_t i = 0; i < control_elements.size(); ++i) { + WebInputElement* input_element = toWebInputElement(&control_elements[i]); + if (!IsAutofillableInputElement(input_element)) + continue; + + if (input_element->isAutofilled()) + return true; + } + + return false; +} + +} // namespace autofill diff --git a/components/autofill/renderer/form_autofill_util.h b/components/autofill/renderer/form_autofill_util.h new file mode 100644 index 0000000..a7579ce --- /dev/null +++ b/components/autofill/renderer/form_autofill_util.h @@ -0,0 +1,139 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_RENDERER_FORM_AUTOFILL_UTIL_H_ +#define COMPONENTS_AUTOFILL_RENDERER_FORM_AUTOFILL_UTIL_H_ + +#include <vector> + +#include "base/string16.h" + +struct FormData; +struct FormFieldData; + +namespace WebKit { +class WebDocument; +class WebFormElement; +class WebFormControlElement; +class WebInputElement; +} + +namespace autofill { + +struct WebElementDescriptor; + +// A bit field mask for form or form element requirements. +enum RequirementsMask { + REQUIRE_NONE = 0, // No requirements. + REQUIRE_AUTOCOMPLETE = 1, // Require that autocomplete != off. +}; + +// A bit field mask to extract data from WebFormControlElement. +enum ExtractMask { + EXTRACT_NONE = 0, + EXTRACT_VALUE = 1 << 0, // Extract value from WebFormControlElement. + EXTRACT_OPTION_TEXT = 1 << 1, // Extract option text from + // WebFormSelectElement. Only valid when + // |EXTRACT_VALUE| is set. + // This is used for form submission where + // human readable value is captured. + EXTRACT_OPTIONS = 1 << 2, // Extract options from + // WebFormControlElement. +}; + +// The maximum number of form fields we are willing to parse, due to +// computational costs. Several examples of forms with lots of fields that are +// not relevant to Autofill: (1) the Netflix queue; (2) the Amazon wishlist; +// (3) router configuration pages; and (4) other configuration pages, e.g. for +// Google code project settings. +extern const size_t kMaxParseableFields; + +// Returns true if |element| is a text input element. +bool IsTextInput(const WebKit::WebInputElement* element); + +// Returns true if |element| is a select element. +bool IsSelectElement(const WebKit::WebFormControlElement& element); + +// Returns true if |element| is a checkbox or a radio button element. +bool IsCheckableElement(const WebKit::WebInputElement* element); + +// Returns true if |element| is one of the input element types that can be +// autofilled. {Text, Radiobutton, Checkbox}. +bool IsAutofillableInputElement(const WebKit::WebInputElement* element); + +// Returns the form's |name| attribute if non-empty; otherwise the form's |id| +// attribute. +const string16 GetFormIdentifier(const WebKit::WebFormElement& form); + +// Returns true if the element specified by |click_element| was successfully +// clicked. +bool ClickElement(const WebKit::WebDocument& document, + const WebElementDescriptor& element_descriptor); + +// Fills |autofillable_elements| with all the auto-fillable form control +// elements in |form_element|. +void ExtractAutofillableElements( + const WebKit::WebFormElement& form_element, + RequirementsMask requirements, + std::vector<WebKit::WebFormControlElement>* autofillable_elements); + +// Fills out a FormField object from a given WebFormControlElement. +// |extract_mask|: See the enum ExtractMask above for details. +void WebFormControlElementToFormField( + const WebKit::WebFormControlElement& element, + ExtractMask extract_mask, + FormFieldData* field); + +// Fills |form| with the FormData object corresponding to the |form_element|. +// If |field| is non-NULL, also fills |field| with the FormField object +// corresponding to the |form_control_element|. +// |extract_mask| controls what data is extracted. +// Returns true if |form| is filled out; it's possible that the |form_element| +// won't meet the |requirements|. Also returns false if there are no fields or +// too many fields in the |form|. +bool WebFormElementToFormData( + const WebKit::WebFormElement& form_element, + const WebKit::WebFormControlElement& form_control_element, + RequirementsMask requirements, + ExtractMask extract_mask, + FormData* form, + FormFieldData* field); + +// Finds the form that contains |element| and returns it in |form|. Fills +// |field| with the |FormField| representation for element. +// Returns false if the form is not found or cannot be serialized. +bool FindFormAndFieldForInputElement(const WebKit::WebInputElement& element, + FormData* form, + FormFieldData* field, + RequirementsMask requirements); + +// Fills the form represented by |form|. |element| is the input element that +// initiated the auto-fill process. +void FillForm(const FormData& form, + const WebKit::WebInputElement& element); + +// Fills focusable and non-focusable form control elements within |form_element| +// with field data from |form_data|. +void FillFormIncludingNonFocusableElements( + const FormData& form_data, + const WebKit::WebFormElement& form_element); + +// Previews the form represented by |form|. |element| is the input element that +// initiated the preview process. +void PreviewForm(const FormData& form, + const WebKit::WebInputElement& element); + +// Clears the placeholder values and the auto-filled background for any fields +// in the form containing |node| that have been previewed. Resets the +// autofilled state of |node| to |was_autofilled|. Returns false if the form is +// not found. +bool ClearPreviewedFormWithElement(const WebKit::WebInputElement& element, + bool was_autofilled); + +// Returns true if |form| has any auto-filled fields. +bool FormWithElementIsAutofilled(const WebKit::WebInputElement& element); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_RENDERER_FORM_AUTOFILL_UTIL_H_ diff --git a/components/autofill/renderer/form_cache.cc b/components/autofill/renderer/form_cache.cc new file mode 100644 index 0000000..ab92f46 --- /dev/null +++ b/components/autofill/renderer/form_cache.cc @@ -0,0 +1,295 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/renderer/form_cache.h" + +#include "base/logging.h" +#include "base/utf_string_conversions.h" +#include "components/autofill/common/form_data.h" +#include "components/autofill/common/form_data_predictions.h" +#include "components/autofill/common/form_field_data.h" +#include "components/autofill/common/form_field_data_predictions.h" +#include "components/autofill/renderer/form_autofill_util.h" +#include "grit/generated_resources.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebString.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebVector.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormControlElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSelectElement.h" +#include "ui/base/l10n/l10n_util.h" + +using WebKit::WebDocument; +using WebKit::WebFormControlElement; +using WebKit::WebFormElement; +using WebKit::WebFrame; +using WebKit::WebInputElement; +using WebKit::WebSelectElement; +using WebKit::WebString; +using WebKit::WebVector; + +namespace { + +// The number of fields required by Autofill. Ideally we could send the forms +// to Autofill no matter how many fields are in the forms; however, finding the +// label for each field is a costly operation and we can't spare the cycles if +// it's not necessary. +const size_t kRequiredAutofillFields = 3; + +} // namespace + +namespace autofill { + +// Helper function to discard state of various WebFormElements when they go out +// of web frame's scope. This is done to release memory that we no longer need +// to hold. +// K should inherit from WebFormControlElement as the function looks to extract +// WebFormElement for K.form(). +template <class K, class V> +void RemoveOldElements(const WebFrame& frame, std::map<const K, V>* states) { + std::vector<K> to_remove; + for (typename std::map<const K, V>::const_iterator it = states->begin(); + it != states->end(); ++it) { + WebFormElement form_element = it->first.form(); + if (form_element.isNull()) { + to_remove.push_back(it->first); + } else { + const WebFrame* element_frame = form_element.document().frame(); + if (!element_frame || element_frame == &frame) + to_remove.push_back(it->first); + } + } + + for (typename std::vector<K>::const_iterator it = to_remove.begin(); + it != to_remove.end(); ++it) { + states->erase(*it); + } +} + +FormCache::FormCache() { +} + +FormCache::~FormCache() { +} + +void FormCache::ExtractForms(const WebFrame& frame, + std::vector<FormData>* forms) { + ExtractFormsAndFormElements(frame, forms, NULL); +} + +void FormCache::ExtractFormsAndFormElements( + const WebFrame& frame, + std::vector<FormData>* forms, + std::vector<WebFormElement>* web_form_elements) { + // Reset the cache for this frame. + ResetFrame(frame); + + WebDocument document = frame.document(); + if (document.isNull()) + return; + + web_documents_.insert(document); + + WebVector<WebFormElement> web_forms; + document.forms(web_forms); + + size_t num_fields_seen = 0; + for (size_t i = 0; i < web_forms.size(); ++i) { + WebFormElement form_element = web_forms[i]; + + std::vector<WebFormControlElement> control_elements; + ExtractAutofillableElements(form_element, autofill::REQUIRE_NONE, + &control_elements); + + size_t num_editable_elements = 0; + for (size_t j = 0; j < control_elements.size(); ++j) { + WebFormControlElement element = control_elements[j]; + + // Save original values of <select> elements so we can restore them + // when |ClearFormWithNode()| is invoked. + if (IsSelectElement(element)) { + const WebSelectElement select_element = + element.toConst<WebSelectElement>(); + initial_select_values_.insert(std::make_pair(select_element, + select_element.value())); + ++num_editable_elements; + } else { + const WebInputElement input_element = + element.toConst<WebInputElement>(); + if (IsCheckableElement(&input_element)) { + initial_checked_state_.insert( + std::make_pair(input_element, input_element.isChecked())); + } else { + ++num_editable_elements; + } + } + } + + // To avoid overly expensive computation, we impose a minimum number of + // allowable fields. The corresponding maximum number of allowable fields + // is imposed by WebFormElementToFormData(). + if (num_editable_elements < kRequiredAutofillFields) + continue; + + FormData form; + ExtractMask extract_mask = + static_cast<ExtractMask>(EXTRACT_VALUE | EXTRACT_OPTIONS); + + if (!WebFormElementToFormData(form_element, WebFormControlElement(), + REQUIRE_NONE, extract_mask, &form, NULL)) { + continue; + } + + num_fields_seen += form.fields.size(); + if (num_fields_seen > kMaxParseableFields) + break; + + if (form.fields.size() >= kRequiredAutofillFields) { + forms->push_back(form); + if (web_form_elements) + web_form_elements->push_back(form_element); + } + } +} + +void FormCache::ResetFrame(const WebFrame& frame) { + std::vector<WebDocument> documents_to_delete; + for (std::set<WebDocument>::const_iterator it = web_documents_.begin(); + it != web_documents_.end(); ++it) { + const WebFrame* document_frame = it->frame(); + if (!document_frame || document_frame == &frame) + documents_to_delete.push_back(*it); + } + + for (std::vector<WebDocument>::const_iterator it = + documents_to_delete.begin(); + it != documents_to_delete.end(); ++it) { + web_documents_.erase(*it); + } + + RemoveOldElements(frame, &initial_select_values_); + RemoveOldElements(frame, &initial_checked_state_); +} + +bool FormCache::ClearFormWithElement(const WebInputElement& element) { + WebFormElement form_element = element.form(); + if (form_element.isNull()) + return false; + + std::vector<WebFormControlElement> control_elements; + ExtractAutofillableElements(form_element, autofill::REQUIRE_NONE, + &control_elements); + for (size_t i = 0; i < control_elements.size(); ++i) { + WebFormControlElement control_element = control_elements[i]; + WebInputElement* input_element = toWebInputElement(&control_element); + if (IsTextInput(input_element)) { + // We don't modify the value of disabled fields. + if (!input_element->isEnabled()) + continue; + + input_element->setValue(string16(), true); + input_element->setAutofilled(false); + + // Clearing the value in the focused node (above) can cause selection + // to be lost. We force selection range to restore the text cursor. + if (element == *input_element) { + int length = input_element->value().length(); + input_element->setSelectionRange(length, length); + } + } else if (IsSelectElement(control_element)) { + WebSelectElement select_element = control_element.to<WebSelectElement>(); + + std::map<const WebSelectElement, string16>::const_iterator + initial_value_iter = initial_select_values_.find(select_element); + if (initial_value_iter != initial_select_values_.end() && + select_element.value() != initial_value_iter->second) { + select_element.setValue(initial_value_iter->second); + select_element.dispatchFormControlChangeEvent(); + } + } else { + WebInputElement input_element = control_element.to<WebInputElement>(); + DCHECK(IsCheckableElement(&input_element)); + std::map<const WebInputElement, bool>::const_iterator it = + initial_checked_state_.find(input_element); + if (it != initial_checked_state_.end() && + input_element.isChecked() != it->second) { + input_element.setChecked(it->second, true); + } + } + } + + return true; +} + +bool FormCache::ShowPredictions(const FormDataPredictions& form) { + DCHECK_EQ(form.data.fields.size(), form.fields.size()); + + // Find the form. + bool found_form = false; + WebFormElement form_element; + for (std::set<WebDocument>::const_iterator it = web_documents_.begin(); + it != web_documents_.end() && !found_form; ++it) { + WebVector<WebFormElement> web_forms; + it->forms(web_forms); + + for (size_t i = 0; i < web_forms.size(); ++i) { + form_element = web_forms[i]; + + // Note: matching on the form name here which is not guaranteed to be + // unique for the page, nor is it guaranteed to be non-empty. Ideally, we + // would have a way to uniquely identify the form cross-process. For now, + // we'll check form name and form action for identity. + // Also note that WebString() == WebString(string16()) does not evaluate + // to |true| -- WebKit distinguishes between a "null" string (lhs) and an + // "empty" string (rhs). We don't want that distinction, so forcing to + // string16. + string16 element_name = GetFormIdentifier(form_element); + GURL action(form_element.document().completeURL(form_element.action())); + if (element_name == form.data.name && action == form.data.action) { + found_form = true; + break; + } + } + } + + if (!found_form) + return false; + + std::vector<WebFormControlElement> control_elements; + ExtractAutofillableElements(form_element, autofill::REQUIRE_NONE, + &control_elements); + if (control_elements.size() != form.fields.size()) { + // Keep things simple. Don't show predictions for forms that were modified + // between page load and the server's response to our query. + return false; + } + + for (size_t i = 0; i < control_elements.size(); ++i) { + WebFormControlElement* element = &control_elements[i]; + + if (string16(element->nameForAutofill()) != form.data.fields[i].name) { + // Keep things simple. Don't show predictions for elements whose names + // were modified between page load and the server's response to our query. + continue; + } + + std::string placeholder = form.fields[i].overall_type; + string16 title = l10n_util::GetStringFUTF16( + IDS_AUTOFILL_SHOW_PREDICTIONS_TITLE, + UTF8ToUTF16(form.fields[i].heuristic_type), + UTF8ToUTF16(form.fields[i].server_type), + UTF8ToUTF16(form.fields[i].signature), + UTF8ToUTF16(form.signature), + UTF8ToUTF16(form.experiment_id)); + if (!element->hasAttribute("placeholder")) + element->setAttribute("placeholder", WebString(UTF8ToUTF16(placeholder))); + element->setAttribute("title", WebString(title)); + } + + return true; +} + +} // namespace autofill diff --git a/components/autofill/renderer/form_cache.h b/components/autofill/renderer/form_cache.h new file mode 100644 index 0000000..eb0ea3a --- /dev/null +++ b/components/autofill/renderer/form_cache.h @@ -0,0 +1,74 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_RENDERER_FORM_CACHE_H_ +#define COMPONENTS_AUTOFILL_RENDERER_FORM_CACHE_H_ + +#include <map> +#include <set> +#include <vector> + +#include "base/string16.h" + +struct FormData; +struct FormDataPredictions; + +namespace WebKit { +class WebDocument; +class WebFormElement; +class WebFrame; +class WebInputElement; +class WebSelectElement; +} + +namespace autofill { + +// Manages the forms in a RenderView. +class FormCache { + public: + FormCache(); + ~FormCache(); + + // Scans the DOM in |frame| extracting and storing forms. + // Returns a vector of the extracted forms. + void ExtractForms(const WebKit::WebFrame& frame, + std::vector<FormData>* forms); + + // Scans the DOM in |frame| extracting and storing forms. + // Returns a vector of the extracted forms and vector of associated web + // form elements. + void ExtractFormsAndFormElements( + const WebKit::WebFrame& frame, + std::vector<FormData>* forms, + std::vector<WebKit::WebFormElement>* web_form_elements); + + // Resets the forms for the specified |frame|. + void ResetFrame(const WebKit::WebFrame& frame); + + // Clears the values of all input elements in the form that contains + // |element|. Returns false if the form is not found. + bool ClearFormWithElement(const WebKit::WebInputElement& element); + + // For each field in the |form|, sets the field's placeholder text to the + // field's overall predicted type. Also sets the title to include the field's + // heuristic type, server type, and signature; as well as the form's signature + // and the experiment id for the server predictions. + bool ShowPredictions(const FormDataPredictions& form); + + private: + // The cached web frames. + std::set<WebKit::WebDocument> web_documents_; + + // The cached initial values for <select> elements. + std::map<const WebKit::WebSelectElement, string16> initial_select_values_; + + // The cached initial values for checkable <input> elements. + std::map<const WebKit::WebInputElement, bool> initial_checked_state_; + + DISALLOW_COPY_AND_ASSIGN(FormCache); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_RENDERER_FORM_CACHE_H_ diff --git a/components/autofill/renderer/page_click_listener.h b/components/autofill/renderer/page_click_listener.h new file mode 100644 index 0000000..83c41d4 --- /dev/null +++ b/components/autofill/renderer/page_click_listener.h @@ -0,0 +1,36 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_RENDERER_PAGE_CLICK_LISTENER_H_ +#define COMPONENTS_AUTOFILL_RENDERER_PAGE_CLICK_LISTENER_H_ + +namespace WebKit { +class WebInputElement; +} + +// Interface that should be implemented by classes interested in getting +// notifications for clicks on a page. +// Register on the PageListenerTracker object. +class PageClickListener { + public: + // Notification that |element| was clicked. + // |was_focused| is true if |element| had focus BEFORE the click. + // |is_focused| is true if |element| has focus AFTER the click was processed. + // If this method returns true, the notification will not be propagated to + // other listeners. + virtual bool InputElementClicked(const WebKit::WebInputElement& element, + bool was_focused, + bool is_focused) = 0; + + // If the previously focused element was an input field, listeners are + // informed that the text field has lost its focus. + // If this method returns true, the notification will not be propagated to + // other listeners. + virtual bool InputElementLostFocus() = 0; + + protected: + virtual ~PageClickListener() {} +}; + +#endif // COMPONENTS_AUTOFILL_RENDERER_PAGE_CLICK_LISTENER_H_ diff --git a/components/autofill/renderer/page_click_tracker.cc b/components/autofill/renderer/page_click_tracker.cc new file mode 100644 index 0000000..21e28a2 --- /dev/null +++ b/components/autofill/renderer/page_click_tracker.cc @@ -0,0 +1,160 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/renderer/page_click_tracker.h" + +#include "components/autofill/renderer/form_autofill_util.h" +#include "components/autofill/renderer/page_click_listener.h" +#include "content/public/renderer/render_view.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebString.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDOMMouseEvent.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" + +using WebKit::WebDOMEvent; +using WebKit::WebDOMMouseEvent; +using WebKit::WebElement; +using WebKit::WebFormControlElement; +using WebKit::WebFrame; +using WebKit::WebInputElement; +using WebKit::WebInputEvent; +using WebKit::WebMouseEvent; +using WebKit::WebNode; +using WebKit::WebString; +using WebKit::WebView; + +namespace { + +// Casts |node| to a WebInputElement. +// Returns an empty (isNull()) WebInputElement if |node| is not a text field. +const WebInputElement GetTextWebInputElement(const WebNode& node) { + if (!node.isElementNode()) + return WebInputElement(); + const WebElement element = node.toConst<WebElement>(); + if (!element.hasTagName("input")) + return WebInputElement(); + const WebInputElement* input = WebKit::toWebInputElement(&element); + if (!autofill::IsTextInput(input)) + return WebInputElement(); + return *input; +} + +// Checks to see if a text field was the previously selected node and is now +// losing its focus. +bool DidSelectedTextFieldLoseFocus(const WebNode& newly_clicked_node) { + WebKit::WebNode focused_node = newly_clicked_node.document().focusedNode(); + + if (focused_node.isNull() || GetTextWebInputElement(focused_node).isNull()) + return false; + + return focused_node != newly_clicked_node; +} + +} // namespace + +PageClickTracker::PageClickTracker(content::RenderView* render_view) + : content::RenderViewObserver(render_view), + was_focused_(false) { +} + +PageClickTracker::~PageClickTracker() { + // Note that even though RenderView calls FrameDetached when notified that + // a frame was closed, it might not always get that notification from WebKit + // for all frames. + // By the time we get here, the frame could have been destroyed so we cannot + // unregister listeners in frames remaining in tracked_frames_ as they might + // be invalid. +} + +void PageClickTracker::DidHandleMouseEvent(const WebMouseEvent& event) { + if (event.type != WebInputEvent::MouseDown || + last_node_clicked_.isNull()) { + return; + } + + // We are only interested in text field clicks. + const WebInputElement input_element = + GetTextWebInputElement(last_node_clicked_); + if (input_element.isNull()) + return; + + bool is_focused = (last_node_clicked_ == render_view()->GetFocusedNode()); + ObserverListBase<PageClickListener>::Iterator it(listeners_); + PageClickListener* listener; + while ((listener = it.GetNext()) != NULL) { + if (listener->InputElementClicked(input_element, was_focused_, is_focused)) + break; + } +} + +void PageClickTracker::AddListener(PageClickListener* listener) { + listeners_.AddObserver(listener); +} + +void PageClickTracker::RemoveListener(PageClickListener* listener) { + listeners_.RemoveObserver(listener); +} + +void PageClickTracker::DidFinishDocumentLoad(WebKit::WebFrame* frame) { + tracked_frames_.push_back(frame); + frame->document().addEventListener("mousedown", this, false); +} + +void PageClickTracker::FrameDetached(WebKit::WebFrame* frame) { + FrameList::iterator iter = + std::find(tracked_frames_.begin(), tracked_frames_.end(), frame); + if (iter == tracked_frames_.end()) { + // Some frames might never load contents so we may not have a listener on + // them. Calling removeEventListener() on them would trigger an assert, so + // we need to keep track of which frames we are listening to. + return; + } + tracked_frames_.erase(iter); +} + +void PageClickTracker::handleEvent(const WebDOMEvent& event) { + last_node_clicked_.reset(); + + if (!event.isMouseEvent()) + return; + + const WebDOMMouseEvent mouse_event = event.toConst<WebDOMMouseEvent>(); + DCHECK(mouse_event.buttonDown()); + if (mouse_event.button() != 0) + return; // We are only interested in left clicks. + + // Remember which node has focus before the click is processed. + // We'll get a notification once the mouse event has been processed + // (DidHandleMouseEvent), we'll notify the listener at that point. + WebNode node = mouse_event.target(); + if (node.isNull()) + // Node may be null if the target was an SVG instance element from a <use> + // tree and the tree has been rebuilt due to an earlier event. + return; + + HandleTextFieldMaybeLosingFocus(node); + + // We are only interested in text field clicks. + if (GetTextWebInputElement(node).isNull()) + return; + + last_node_clicked_ = node; + was_focused_ = (node.document().focusedNode() == last_node_clicked_); +} + +void PageClickTracker::HandleTextFieldMaybeLosingFocus( + const WebNode& newly_clicked_node) { + if (!DidSelectedTextFieldLoseFocus(newly_clicked_node)) + return; + + ObserverListBase<PageClickListener>::Iterator it(listeners_); + PageClickListener* listener; + while ((listener = it.GetNext()) != NULL) { + if (listener->InputElementLostFocus()) + break; + } +} diff --git a/components/autofill/renderer/page_click_tracker.h b/components/autofill/renderer/page_click_tracker.h new file mode 100644 index 0000000..e4aedb2 --- /dev/null +++ b/components/autofill/renderer/page_click_tracker.h @@ -0,0 +1,74 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_RENDERER_PAGE_CLICK_TRACKER_H_ +#define COMPONENTS_AUTOFILL_RENDERER_PAGE_CLICK_TRACKER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/observer_list.h" +#include "content/public/renderer/render_view_observer.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDOMEventListener.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" + +class PageClickListener; + + +// This class is responsible for tracking clicks on elements in web pages and +// notifiying the associated listener when a node is clicked. +// Compared to a simple WebDOMEventListener, it offers the added capability of +// notifying the listeners of whether the clicked node was already focused +// before it was clicked. +// +// This is useful for password/form autofill where we want to trigger a +// suggestion popup when a text input is clicked. +// It only notifies of WebInputElement that are text inputs being clicked, but +// could easily be changed to report click on any type of WebNode. +// +// There is one PageClickTracker per RenderView. +class PageClickTracker : public content::RenderViewObserver, + public WebKit::WebDOMEventListener { + public: + explicit PageClickTracker(content::RenderView* render_view); + virtual ~PageClickTracker(); + + // Adds/removes a listener for getting notification when an element is + // clicked. Note that the order of insertion is important as a listener when + // notified can decide to stop the propagation of the event (so that listeners + // inserted after don't get the notification). + void AddListener(PageClickListener* listener); + void RemoveListener(PageClickListener* listener); + + private: + // RenderView::Observer implementation. + virtual void DidFinishDocumentLoad(WebKit::WebFrame* frame) OVERRIDE; + virtual void FrameDetached(WebKit::WebFrame* frame) OVERRIDE; + virtual void DidHandleMouseEvent(const WebKit::WebMouseEvent& event) OVERRIDE; + + // WebKit::WebDOMEventListener implementation. + virtual void handleEvent(const WebKit::WebDOMEvent& event); + + // Checks to see if a text field is losing focus and inform listeners if + // it is. + void HandleTextFieldMaybeLosingFocus( + const WebKit::WebNode& newly_clicked_node); + + // The last node that was clicked and had focus. + WebKit::WebNode last_node_clicked_; + + // Whether the last clicked node had focused before it was clicked. + bool was_focused_; + + // The frames we are listening to for mouse events. + typedef std::vector<WebKit::WebFrame*> FrameList; + FrameList tracked_frames_; + + // The listener getting the actual notifications. + ObserverList<PageClickListener> listeners_; + + DISALLOW_COPY_AND_ASSIGN(PageClickTracker); +}; + +#endif // COMPONENTS_AUTOFILL_RENDERER_PAGE_CLICK_TRACKER_H_ diff --git a/components/autofill/renderer/password_autofill_manager.cc b/components/autofill/renderer/password_autofill_manager.cc new file mode 100644 index 0000000..1ab3d59 --- /dev/null +++ b/components/autofill/renderer/password_autofill_manager.cc @@ -0,0 +1,649 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/renderer/password_autofill_manager.h" + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "components/autofill/common/autofill_messages.h" +#include "components/autofill/common/form_field_data.h" +#include "components/autofill/common/password_form_fill_data.h" +#include "components/autofill/renderer/form_autofill_util.h" +#include "content/public/common/password_form.h" +#include "content/public/renderer/password_form_conversion_utils.h" +#include "content/public/renderer/render_view.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebVector.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputEvent.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "ui/base/keycodes/keyboard_codes.h" + +namespace { + +// The size above which we stop triggering autocomplete. +static const size_t kMaximumTextSizeForAutocomplete = 1000; + +// Maps element names to the actual elements to simplify form filling. +typedef std::map<string16, WebKit::WebInputElement> + FormInputElementMap; + +// Utility struct for form lookup and autofill. When we parse the DOM to look up +// a form, in addition to action and origin URL's we have to compare all +// necessary form elements. To avoid having to look these up again when we want +// to fill the form, the FindFormElements function stores the pointers +// in a FormElements* result, referenced to ensure they are safe to use. +struct FormElements { + WebKit::WebFormElement form_element; + FormInputElementMap input_elements; +}; + +typedef std::vector<FormElements*> FormElementsList; + +// Helper to search the given form element for the specified input elements +// in |data|, and add results to |result|. +static bool FindFormInputElements(WebKit::WebFormElement* fe, + const FormData& data, + FormElements* result) { + // Loop through the list of elements we need to find on the form in order to + // autofill it. If we don't find any one of them, abort processing this + // form; it can't be the right one. + for (size_t j = 0; j < data.fields.size(); j++) { + WebKit::WebVector<WebKit::WebNode> temp_elements; + fe->getNamedElements(data.fields[j].name, temp_elements); + + // Match the first input element, if any. + // |getNamedElements| may return non-input elements where the names match, + // so the results are filtered for input elements. + // If more than one match is made, then we have ambiguity (due to misuse + // of "name" attribute) so is it considered not found. + bool found_input = false; + for (size_t i = 0; i < temp_elements.size(); ++i) { + if (temp_elements[i].to<WebKit::WebElement>().hasTagName("input")) { + // Check for a non-unique match. + if (found_input) { + found_input = false; + break; + } + + // This element matched, add it to our temporary result. It's possible + // there are multiple matches, but for purposes of identifying the form + // one suffices and if some function needs to deal with multiple + // matching elements it can get at them through the FormElement*. + // Note: This assignment adds a reference to the InputElement. + result->input_elements[data.fields[j].name] = + temp_elements[i].to<WebKit::WebInputElement>(); + found_input = true; + } + } + + // A required element was not found. This is not the right form. + // Make sure no input elements from a partially matched form in this + // iteration remain in the result set. + // Note: clear will remove a reference from each InputElement. + if (!found_input) { + result->input_elements.clear(); + return false; + } + } + return true; +} + +// Helper to locate form elements identified by |data|. +void FindFormElements(WebKit::WebView* view, + const FormData& data, + FormElementsList* results) { + DCHECK(view); + DCHECK(results); + WebKit::WebFrame* main_frame = view->mainFrame(); + if (!main_frame) + return; + + GURL::Replacements rep; + rep.ClearQuery(); + rep.ClearRef(); + + // Loop through each frame. + for (WebKit::WebFrame* f = main_frame; f; f = f->traverseNext(false)) { + WebKit::WebDocument doc = f->document(); + if (!doc.isHTMLDocument()) + continue; + + GURL full_origin(doc.url()); + if (data.origin != full_origin.ReplaceComponents(rep)) + continue; + + WebKit::WebVector<WebKit::WebFormElement> forms; + doc.forms(forms); + + for (size_t i = 0; i < forms.size(); ++i) { + WebKit::WebFormElement fe = forms[i]; + + GURL full_action(f->document().completeURL(fe.action())); + if (full_action.is_empty()) { + // The default action URL is the form's origin. + full_action = full_origin; + } + + // Action URL must match. + if (data.action != full_action.ReplaceComponents(rep)) + continue; + + scoped_ptr<FormElements> curr_elements(new FormElements); + if (!FindFormInputElements(&fe, data, curr_elements.get())) + continue; + + // We found the right element. + // Note: this assignment adds a reference to |fe|. + curr_elements->form_element = fe; + results->push_back(curr_elements.release()); + } + } +} + +bool IsElementEditable(const WebKit::WebInputElement& element) { + return element.isEnabled() && !element.isReadOnly(); +} + +void FillForm(FormElements* fe, const FormData& data) { + if (!fe->form_element.autoComplete()) + return; + + std::map<string16, string16> data_map; + for (size_t i = 0; i < data.fields.size(); i++) + data_map[data.fields[i].name] = data.fields[i].value; + + for (FormInputElementMap::iterator it = fe->input_elements.begin(); + it != fe->input_elements.end(); ++it) { + WebKit::WebInputElement element = it->second; + // Don't fill a form that has pre-filled values distinct from the ones we + // want to fill with. + if (!element.value().isEmpty() && element.value() != data_map[it->first]) + return; + } + + for (FormInputElementMap::iterator it = fe->input_elements.begin(); + it != fe->input_elements.end(); ++it) { + WebKit::WebInputElement element = it->second; + if (!IsElementEditable(element)) + continue; // Don't fill uneditable fields. + + // TODO(tkent): Check maxlength and pattern. + element.setValue(data_map[it->first]); + element.setAutofilled(true); + element.dispatchFormControlChangeEvent(); + } +} + +void SetElementAutofilled(WebKit::WebInputElement* element, bool autofilled) { + if (element->isAutofilled() == autofilled) + return; + element->setAutofilled(autofilled); + // Notify any changeEvent listeners. + element->dispatchFormControlChangeEvent(); +} + +bool DoUsernamesMatch(const string16& username1, + const string16& username2, + bool exact_match) { + if (exact_match) + return username1 == username2; + return StartsWith(username1, username2, true); +} + +} // namespace + +namespace autofill { + +//////////////////////////////////////////////////////////////////////////////// +// PasswordAutofillManager, public: + +PasswordAutofillManager::PasswordAutofillManager( + content::RenderView* render_view) + : content::RenderViewObserver(render_view), + disable_popup_(false), + web_view_(render_view->GetWebView()), + ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { +} + +PasswordAutofillManager::~PasswordAutofillManager() { +} + +bool PasswordAutofillManager::TextFieldDidEndEditing( + const WebKit::WebInputElement& element) { + LoginToPasswordInfoMap::const_iterator iter = + login_to_password_info_.find(element); + if (iter == login_to_password_info_.end()) + return false; + + const PasswordFormFillData& fill_data = + iter->second.fill_data; + + // If wait_for_username is false, we should have filled when the text changed. + if (!fill_data.wait_for_username) + return false; + + WebKit::WebInputElement password = iter->second.password_field; + if (!IsElementEditable(password)) + return false; + + WebKit::WebInputElement username = element; // We need a non-const. + + // Do not set selection when ending an editing session, otherwise it can + // mess with focus. + FillUserNameAndPassword(&username, &password, fill_data, true, false); + return true; +} + +bool PasswordAutofillManager::TextDidChangeInTextField( + const WebKit::WebInputElement& element) { + LoginToPasswordInfoMap::const_iterator iter = + login_to_password_info_.find(element); + if (iter == login_to_password_info_.end()) + return false; + + // The input text is being changed, so any autofilled password is now + // outdated. + WebKit::WebInputElement username = element; // We need a non-const. + WebKit::WebInputElement password = iter->second.password_field; + SetElementAutofilled(&username, false); + if (password.isAutofilled()) { + password.setValue(string16()); + SetElementAutofilled(&password, false); + } + + // If wait_for_username is true we will fill when the username loses focus. + if (iter->second.fill_data.wait_for_username) + return false; + + if (!IsElementEditable(element) || + !element.isText() || + !element.autoComplete()) { + return false; + } + + // Don't inline autocomplete if the user is deleting, that would be confusing. + // But refresh the popup. Note, since this is ours, return true to signal + // no further processing is required. + if (iter->second.backspace_pressed_last) { + ShowSuggestionPopup(iter->second.fill_data, username); + return true; + } + + WebKit::WebString name = element.nameForAutofill(); + if (name.isEmpty()) + return false; // If the field has no name, then we won't have values. + + // Don't attempt to autofill with values that are too large. + if (element.value().length() > kMaximumTextSizeForAutocomplete) + return false; + + // The caret position should have already been updated. + PerformInlineAutocomplete(element, password, iter->second.fill_data); + return true; +} + +bool PasswordAutofillManager::TextFieldHandlingKeyDown( + const WebKit::WebInputElement& element, + const WebKit::WebKeyboardEvent& event) { + // If using the new Autofill UI that lives in the browser, it will handle + // keypresses before this function. This is not currently an issue but if + // the keys handled there or here change, this issue may appear. + + LoginToPasswordInfoMap::iterator iter = login_to_password_info_.find(element); + if (iter == login_to_password_info_.end()) + return false; + + int win_key_code = event.windowsKeyCode; + iter->second.backspace_pressed_last = + (win_key_code == ui::VKEY_BACK || win_key_code == ui::VKEY_DELETE); + return true; +} + +bool PasswordAutofillManager::DidAcceptAutofillSuggestion( + const WebKit::WebNode& node, + const WebKit::WebString& value) { + WebKit::WebInputElement input; + PasswordInfo password; + if (!FindLoginInfo(node, &input, &password)) + return false; + + // Set the incoming |value| in the text field and |FillUserNameAndPassword| + // will do the rest. + input.setValue(value, true); + return FillUserNameAndPassword(&input, &password.password_field, + password.fill_data, true, true); +} + +bool PasswordAutofillManager::DidSelectAutofillSuggestion( + const WebKit::WebNode& node) { + WebKit::WebInputElement input; + PasswordInfo password; + return FindLoginInfo(node, &input, &password); +} + +bool PasswordAutofillManager::DidClearAutofillSelection( + const WebKit::WebNode& node) { + WebKit::WebInputElement input; + PasswordInfo password; + return FindLoginInfo(node, &input, &password); +} + +void PasswordAutofillManager::SendPasswordForms(WebKit::WebFrame* frame, + bool only_visible) { + // Make sure that this security origin is allowed to use password manager. + WebKit::WebSecurityOrigin origin = frame->document().securityOrigin(); + if (!origin.canAccessPasswordManager()) + return; + + WebKit::WebVector<WebKit::WebFormElement> forms; + frame->document().forms(forms); + + std::vector<content::PasswordForm> password_forms; + for (size_t i = 0; i < forms.size(); ++i) { + const WebKit::WebFormElement& form = forms[i]; + + // If requested, ignore non-rendered forms, e.g. those styled with + // display:none. + if (only_visible && !form.hasNonEmptyBoundingBox()) + continue; + + scoped_ptr<content::PasswordForm> password_form( + content::CreatePasswordForm(form)); + if (password_form.get()) + password_forms.push_back(*password_form); + } + + if (password_forms.empty() && !only_visible) { + // We need to send the PasswordFormsRendered message regardless of whether + // there are any forms visible, as this is also the code path that triggers + // showing the infobar. + return; + } + + if (only_visible) { + Send(new AutofillHostMsg_PasswordFormsRendered( + routing_id(), password_forms)); + } else { + Send(new AutofillHostMsg_PasswordFormsParsed(routing_id(), password_forms)); + } +} + +bool PasswordAutofillManager::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PasswordAutofillManager, message) + IPC_MESSAGE_HANDLER(AutofillMsg_FillPasswordForm, OnFillPasswordForm) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void PasswordAutofillManager::DidFinishDocumentLoad(WebKit::WebFrame* frame) { + // The |frame| contents have been parsed, but not yet rendered. Let the + // PasswordManager know that forms are loaded, even though we can't yet tell + // whether they're visible. + SendPasswordForms(frame, false); +} + +void PasswordAutofillManager::DidFinishLoad(WebKit::WebFrame* frame) { + // The |frame| contents have been rendered. Let the PasswordManager know + // which of the loaded frames are actually visible to the user. This also + // triggers the "Save password?" infobar if the user just submitted a password + // form. + SendPasswordForms(frame, true); +} + +void PasswordAutofillManager::FrameDetached(WebKit::WebFrame* frame) { + FrameClosing(frame); +} + +void PasswordAutofillManager::FrameWillClose(WebKit::WebFrame* frame) { + FrameClosing(frame); +} + + +//////////////////////////////////////////////////////////////////////////////// +// PageClickListener implementation: + +bool PasswordAutofillManager::InputElementClicked( + const WebKit::WebInputElement& element, + bool was_focused, + bool is_focused) { + // TODO(jcivelli): http://crbug.com/51644 Implement behavior. + return false; +} + +bool PasswordAutofillManager::InputElementLostFocus() { + return false; +} + +void PasswordAutofillManager::OnFillPasswordForm( + const PasswordFormFillData& form_data, + bool disable_popup) { + disable_popup_ = disable_popup; + + FormElementsList forms; + // We own the FormElements* in forms. + FindFormElements(render_view()->GetWebView(), form_data.basic_data, &forms); + FormElementsList::iterator iter; + for (iter = forms.begin(); iter != forms.end(); ++iter) { + scoped_ptr<FormElements> form_elements(*iter); + + // If wait_for_username is true, we don't want to initially fill the form + // until the user types in a valid username. + if (!form_data.wait_for_username) + FillForm(form_elements.get(), form_data.basic_data); + + // Attach autocomplete listener to enable selecting alternate logins. + // First, get pointers to username element. + WebKit::WebInputElement username_element = + form_elements->input_elements[form_data.basic_data.fields[0].name]; + + // Get pointer to password element. (We currently only support single + // password forms). + WebKit::WebInputElement password_element = + form_elements->input_elements[form_data.basic_data.fields[1].name]; + + // We might have already filled this form if there are two <form> elements + // with identical markup. + if (login_to_password_info_.find(username_element) != + login_to_password_info_.end()) + continue; + + PasswordInfo password_info; + password_info.fill_data = form_data; + password_info.password_field = password_element; + login_to_password_info_[username_element] = password_info; + + FormData form; + FormFieldData field; + FindFormAndFieldForInputElement( + username_element, &form, &field, REQUIRE_NONE); + Send(new AutofillHostMsg_AddPasswordFormMapping( + routing_id(), + field, + form_data)); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// PasswordAutofillManager, private: + +void PasswordAutofillManager::GetSuggestions( + const PasswordFormFillData& fill_data, + const string16& input, + std::vector<string16>* suggestions) { + if (StartsWith(fill_data.basic_data.fields[0].value, input, false)) + suggestions->push_back(fill_data.basic_data.fields[0].value); + + PasswordFormFillData::LoginCollection::const_iterator iter; + for (iter = fill_data.additional_logins.begin(); + iter != fill_data.additional_logins.end(); ++iter) { + if (StartsWith(iter->first, input, false)) + suggestions->push_back(iter->first); + } +} + +bool PasswordAutofillManager::ShowSuggestionPopup( + const PasswordFormFillData& fill_data, + const WebKit::WebInputElement& user_input) { + WebKit::WebFrame* frame = user_input.document().frame(); + if (!frame) + return false; + + WebKit::WebView* webview = frame->view(); + if (!webview) + return false; + + std::vector<string16> suggestions; + GetSuggestions(fill_data, user_input.value(), &suggestions); + + if (disable_popup_) { + FormData form; + FormFieldData field; + FindFormAndFieldForInputElement( + user_input, &form, &field, REQUIRE_NONE); + + WebKit::WebInputElement selected_element = user_input; + gfx::Rect bounding_box(selected_element.boundsInViewportSpace()); + + float scale = web_view_->pageScaleFactor(); + gfx::RectF bounding_box_scaled(bounding_box.x() * scale, + bounding_box.y() * scale, + bounding_box.width() * scale, + bounding_box.height() * scale); + Send(new AutofillHostMsg_ShowPasswordSuggestions(routing_id(), + field, + bounding_box_scaled, + suggestions)); + return !suggestions.empty(); + } + + + if (suggestions.empty()) { + webview->hidePopups(); + return false; + } + + std::vector<string16> labels(suggestions.size()); + std::vector<string16> icons(suggestions.size()); + std::vector<int> ids(suggestions.size(), + WebKit::WebAutofillClient::MenuItemIDPasswordEntry); + webview->applyAutofillSuggestions( + user_input, suggestions, labels, icons, ids); + return true; +} + +bool PasswordAutofillManager::FillUserNameAndPassword( + WebKit::WebInputElement* username_element, + WebKit::WebInputElement* password_element, + const PasswordFormFillData& fill_data, + bool exact_username_match, + bool set_selection) { + string16 current_username = username_element->value(); + // username and password will contain the match found if any. + string16 username; + string16 password; + + // Look for any suitable matches to current field text. + if (DoUsernamesMatch(fill_data.basic_data.fields[0].value, current_username, + exact_username_match)) { + username = fill_data.basic_data.fields[0].value; + password = fill_data.basic_data.fields[1].value; + } else { + // Scan additional logins for a match. + PasswordFormFillData::LoginCollection::const_iterator iter; + for (iter = fill_data.additional_logins.begin(); + iter != fill_data.additional_logins.end(); ++iter) { + if (DoUsernamesMatch(iter->first, current_username, + exact_username_match)) { + username = iter->first; + password = iter->second; + break; + } + } + } + if (password.empty()) + return false; // No match was found. + + // Input matches the username, fill in required values. + username_element->setValue(username); + + if (set_selection) { + username_element->setSelectionRange(current_username.length(), + username.length()); + } + + SetElementAutofilled(username_element, true); + if (IsElementEditable(*password_element)) + password_element->setValue(password); + SetElementAutofilled(password_element, true); + return true; +} + +void PasswordAutofillManager::PerformInlineAutocomplete( + const WebKit::WebInputElement& username_input, + const WebKit::WebInputElement& password_input, + const PasswordFormFillData& fill_data) { + DCHECK(!fill_data.wait_for_username); + + // We need non-const versions of the username and password inputs. + WebKit::WebInputElement username = username_input; + WebKit::WebInputElement password = password_input; + + // Don't inline autocomplete if the caret is not at the end. + // TODO(jcivelli): is there a better way to test the caret location? + if (username.selectionStart() != username.selectionEnd() || + username.selectionEnd() != static_cast<int>(username.value().length())) { + return; + } + + // Show the popup with the list of available usernames. + ShowSuggestionPopup(fill_data, username); + + +#if !defined(OS_ANDROID) + // Fill the user and password field with the most relevant match. Android + // only fills in the fields after the user clicks on the suggestion popup. + FillUserNameAndPassword(&username, &password, fill_data, false, true); +#endif +} + +void PasswordAutofillManager::FrameClosing(const WebKit::WebFrame* frame) { + for (LoginToPasswordInfoMap::iterator iter = login_to_password_info_.begin(); + iter != login_to_password_info_.end();) { + if (iter->first.document().frame() == frame) + login_to_password_info_.erase(iter++); + else + ++iter; + } +} + +bool PasswordAutofillManager::FindLoginInfo( + const WebKit::WebNode& node, + WebKit::WebInputElement* found_input, + PasswordInfo* found_password) { + if (!node.isElementNode()) + return false; + + WebKit::WebElement element = node.toConst<WebKit::WebElement>(); + if (!element.hasTagName("input")) + return false; + + WebKit::WebInputElement input = element.to<WebKit::WebInputElement>(); + LoginToPasswordInfoMap::iterator iter = login_to_password_info_.find(input); + if (iter == login_to_password_info_.end()) + return false; + + *found_input = input; + *found_password = iter->second; + return true; +} + +} // namespace autofill diff --git a/components/autofill/renderer/password_autofill_manager.h b/components/autofill/renderer/password_autofill_manager.h new file mode 100644 index 0000000..52b13bf --- /dev/null +++ b/components/autofill/renderer/password_autofill_manager.h @@ -0,0 +1,131 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_RENDERER_PASSWORD_AUTOFILL_MANAGER_H_ +#define COMPONENTS_AUTOFILL_RENDERER_PASSWORD_AUTOFILL_MANAGER_H_ + +#include <map> +#include <vector> + +#include "base/memory/weak_ptr.h" +#include "components/autofill/common/password_form_fill_data.h" +#include "components/autofill/renderer/page_click_listener.h" +#include "content/public/renderer/render_view_observer.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" + +namespace WebKit { +class WebInputElement; +class WebKeyboardEvent; +class WebView; +} + +namespace autofill { + +// This class is responsible for filling password forms. +// There is one PasswordAutofillManager per RenderView. +class PasswordAutofillManager : public content::RenderViewObserver, + public PageClickListener { + public: + explicit PasswordAutofillManager(content::RenderView* render_view); + virtual ~PasswordAutofillManager(); + + // WebViewClient editor related calls forwarded by the RenderView. + // If they return true, it indicates the event was consumed and should not + // be used for any other autofill activity. + bool TextFieldDidEndEditing(const WebKit::WebInputElement& element); + bool TextDidChangeInTextField(const WebKit::WebInputElement& element); + bool TextFieldHandlingKeyDown(const WebKit::WebInputElement& element, + const WebKit::WebKeyboardEvent& event); + + // Fills the password associated with user name |value|. Returns true if the + // username and password fields were filled, false otherwise. + bool DidAcceptAutofillSuggestion(const WebKit::WebNode& node, + const WebKit::WebString& value); + // A no-op. No filling happens for selection. But this method returns + // true when |node| is fillable by password Autofill. + bool DidSelectAutofillSuggestion(const WebKit::WebNode& node); + // A no-op. Password forms are not previewed, so they do not need to be + // cleared when the selection changes. However, this method returns + // true when |node| is fillable by password Autofill. + bool DidClearAutofillSelection(const WebKit::WebNode& node); + + private: + friend class PasswordAutofillManagerTest; + + struct PasswordInfo { + WebKit::WebInputElement password_field; + PasswordFormFillData fill_data; + bool backspace_pressed_last; + PasswordInfo() : backspace_pressed_last(false) {} + }; + typedef std::map<WebKit::WebElement, PasswordInfo> LoginToPasswordInfoMap; + + // RenderViewObserver: + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void DidFinishDocumentLoad(WebKit::WebFrame* frame) OVERRIDE; + virtual void DidFinishLoad(WebKit::WebFrame* frame) OVERRIDE; + virtual void FrameDetached(WebKit::WebFrame* frame) OVERRIDE; + virtual void FrameWillClose(WebKit::WebFrame* frame) OVERRIDE; + + // PageClickListener: + virtual bool InputElementClicked(const WebKit::WebInputElement& element, + bool was_focused, + bool is_focused) OVERRIDE; + virtual bool InputElementLostFocus() OVERRIDE; + + // RenderView IPC handlers: + void OnFillPasswordForm(const PasswordFormFillData& form_data, + bool disable_popup); + + // Scans the given frame for password forms and sends them up to the browser. + // If |only_visible| is true, only forms visible in the layout are sent. + void SendPasswordForms(WebKit::WebFrame* frame, bool only_visible); + + void GetSuggestions(const PasswordFormFillData& fill_data, + const string16& input, + std::vector<string16>* suggestions); + + bool ShowSuggestionPopup(const PasswordFormFillData& fill_data, + const WebKit::WebInputElement& user_input); + + bool FillUserNameAndPassword( + WebKit::WebInputElement* username_element, + WebKit::WebInputElement* password_element, + const PasswordFormFillData& fill_data, + bool exact_username_match, + bool set_selection); + + // Fills |login_input| and |password| with the most relevant suggestion from + // |fill_data| and shows a popup with other suggestions. + void PerformInlineAutocomplete( + const WebKit::WebInputElement& username, + const WebKit::WebInputElement& password, + const PasswordFormFillData& fill_data); + + // Invoked when the passed frame is closing. Gives us a chance to clear any + // reference we may have to elements in that frame. + void FrameClosing(const WebKit::WebFrame* frame); + + // Finds login information for a |node| that was previously filled. + bool FindLoginInfo(const WebKit::WebNode& node, + WebKit::WebInputElement* found_input, + PasswordInfo* found_password); + + // The logins we have filled so far with their associated info. + LoginToPasswordInfoMap login_to_password_info_; + + // Used to disable and hide the popup. + bool disable_popup_; + + // Pointer to the WebView. Used to access page scale factor. + WebKit::WebView* web_view_; + + base::WeakPtrFactory<PasswordAutofillManager> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(PasswordAutofillManager); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_RENDERER_PASSWORD_AUTOFILL_MANAGER_H_ diff --git a/components/autofill/renderer/password_generation_manager.cc b/components/autofill/renderer/password_generation_manager.cc new file mode 100644 index 0000000..3b1f000 --- /dev/null +++ b/components/autofill/renderer/password_generation_manager.cc @@ -0,0 +1,256 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/autofill/renderer/password_generation_manager.h" + +#include "base/logging.h" +#include "chrome/common/password_generation_util.h" +#include "components/autofill/common/autofill_messages.h" +#include "content/public/renderer/password_form_conversion_utils.h" +#include "content/public/renderer/render_view.h" +#include "google_apis/gaia/gaia_urls.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebCString.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebRect.h" +#include "third_party/WebKit/Source/Platform/chromium/public/WebVector.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFormElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebSecurityOrigin.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" +#include "ui/gfx/rect.h" + +namespace autofill { + +namespace { + +// Returns true if we think that this form is for account creation. |passwords| +// is filled with the password field(s) in the form. +bool GetAccountCreationPasswordFields( + const WebKit::WebFormElement& form, + std::vector<WebKit::WebInputElement>* passwords) { + // Grab all of the passwords for the form. + WebKit::WebVector<WebKit::WebFormControlElement> control_elements; + form.getFormControlElements(control_elements); + + size_t num_input_elements = 0; + for (size_t i = 0; i < control_elements.size(); i++) { + WebKit::WebInputElement* input_element = + toWebInputElement(&control_elements[i]); + // Only pay attention to visible password fields. + if (input_element && + input_element->isTextField() && + input_element->hasNonEmptyBoundingBox()) { + num_input_elements++; + if (input_element->isPasswordField()) + passwords->push_back(*input_element); + } + } + + // This may be too lenient, but we assume that any form with at least three + // input elements where at least one of them is a password is an account + // creation form. + if (!passwords->empty() && num_input_elements >= 3) { + // We trim |passwords| because occasionally there are forms where the + // security question answers are put in password fields and we don't want + // to fill those. + if (passwords->size() > 2) + passwords->resize(2); + + return true; + } + + return false; +} + +} // namespace + +PasswordGenerationManager::PasswordGenerationManager( + content::RenderView* render_view) + : content::RenderViewObserver(render_view), + render_view_(render_view), + enabled_(false) { + render_view_->GetWebView()->addTextFieldDecoratorClient(this); +} +PasswordGenerationManager::~PasswordGenerationManager() {} + +void PasswordGenerationManager::DidFinishDocumentLoad(WebKit::WebFrame* frame) { + // In every navigation, the IPC message sent by the password autofill manager + // to query whether the current form is blacklisted or not happens when the + // document load finishes, so we need to clear previous states here before we + // hear back from the browser. We only clear this state on main frame load + // as we don't want subframe loads to clear state that we have recieved from + // the main frame. Note that we assume there is only one account creation + // form, but there could be multiple password forms in each frame. + if (!frame->parent()) { + not_blacklisted_password_form_origins_.clear(); + // Initialize to an empty and invalid GURL. + account_creation_form_origin_ = GURL(); + passwords_.clear(); + } +} + +void PasswordGenerationManager::DidFinishLoad(WebKit::WebFrame* frame) { + // We don't want to generate passwords if the browser won't store or sync + // them. + if (!enabled_) + return; + + if (!ShouldAnalyzeDocument(frame->document())) + return; + + WebKit::WebVector<WebKit::WebFormElement> forms; + frame->document().forms(forms); + for (size_t i = 0; i < forms.size(); ++i) { + if (forms[i].isNull()) + continue; + + // If we can't get a valid PasswordForm, we skip this form because the + // the password won't get saved even if we generate it. + scoped_ptr<content::PasswordForm> password_form( + content::CreatePasswordForm(forms[i])); + if (!password_form.get()) { + DVLOG(2) << "Skipping form as it would not be saved"; + continue; + } + + // Do not generate password for GAIA since it is used to retrieve the + // generated paswords. + GURL realm(password_form->signon_realm); + if (realm == GURL(GaiaUrls::GetInstance()->gaia_login_form_realm())) + continue; + + std::vector<WebKit::WebInputElement> passwords; + if (GetAccountCreationPasswordFields(forms[i], &passwords)) { + DVLOG(2) << "Account creation form detected"; + password_generation::LogPasswordGenerationEvent( + password_generation::SIGN_UP_DETECTED); + passwords_ = passwords; + account_creation_form_origin_ = password_form->origin; + MaybeShowIcon(); + // We assume that there is only one account creation field per URL. + return; + } + } + password_generation::LogPasswordGenerationEvent( + password_generation::NO_SIGN_UP_DETECTED); +} + +bool PasswordGenerationManager::ShouldAnalyzeDocument( + const WebKit::WebDocument& document) const { + // Make sure that this security origin is allowed to use password manager. + // Generating a password that can't be saved is a bad idea. + WebKit::WebSecurityOrigin origin = document.securityOrigin(); + if (!origin.canAccessPasswordManager()) { + DVLOG(1) << "No PasswordManager access"; + return false; + } + + return true; +} + +bool PasswordGenerationManager::shouldAddDecorationTo( + const WebKit::WebInputElement& element) { + return element.isPasswordField(); +} + +bool PasswordGenerationManager::visibleByDefault() { + return false; +} + +WebKit::WebCString PasswordGenerationManager::imageNameForNormalState() { + return WebKit::WebCString("generatePassword"); +} + +WebKit::WebCString PasswordGenerationManager::imageNameForDisabledState() { + return imageNameForNormalState(); +} + +WebKit::WebCString PasswordGenerationManager::imageNameForReadOnlyState() { + return imageNameForNormalState(); +} + +WebKit::WebCString PasswordGenerationManager::imageNameForHoverState() { + return WebKit::WebCString("generatePasswordHover"); +} + +void PasswordGenerationManager::handleClick(WebKit::WebInputElement& element) { + gfx::Rect rect(element.decorationElementFor(this).boundsInViewportSpace()); + scoped_ptr<content::PasswordForm> password_form( + content::CreatePasswordForm(element.form())); + // We should not have shown the icon we can't create a valid PasswordForm. + DCHECK(password_form.get()); + + Send(new AutofillHostMsg_ShowPasswordGenerationPopup(routing_id(), + rect, + element.maxLength(), + *password_form)); + password_generation::LogPasswordGenerationEvent( + password_generation::BUBBLE_SHOWN); +} + +void PasswordGenerationManager::willDetach( + const WebKit::WebInputElement& element) { + // No implementation +} + +bool PasswordGenerationManager::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PasswordGenerationManager, message) + IPC_MESSAGE_HANDLER(AutofillMsg_FormNotBlacklisted, + OnFormNotBlacklisted) + IPC_MESSAGE_HANDLER(AutofillMsg_GeneratedPasswordAccepted, + OnPasswordAccepted) + IPC_MESSAGE_HANDLER(AutofillMsg_PasswordGenerationEnabled, + OnPasswordGenerationEnabled) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void PasswordGenerationManager::OnFormNotBlacklisted( + const content::PasswordForm& form) { + not_blacklisted_password_form_origins_.push_back(form.origin); + MaybeShowIcon(); +} + +void PasswordGenerationManager::OnPasswordAccepted(const string16& password) { + for (std::vector<WebKit::WebInputElement>::iterator it = passwords_.begin(); + it != passwords_.end(); ++it) { + it->setValue(password); + it->setAutofilled(true); + // Advance focus to the next input field. We assume password fields in + // an account creation form are always adjacent. + render_view_->GetWebView()->advanceFocus(false); + } +} + +void PasswordGenerationManager::OnPasswordGenerationEnabled(bool enabled) { + enabled_ = enabled; +} + +void PasswordGenerationManager::MaybeShowIcon() { + // We should show the password generation icon only when we have detected + // account creation form and we have confirmed from browser that this form + // is not blacklisted by the users. + if (!account_creation_form_origin_.is_valid() || + passwords_.empty() || + not_blacklisted_password_form_origins_.empty()) { + return; + } + + for (std::vector<GURL>::iterator it = + not_blacklisted_password_form_origins_.begin(); + it != not_blacklisted_password_form_origins_.end(); ++it) { + if (*it == account_creation_form_origin_) { + passwords_[0].decorationElementFor(this).setAttribute("style", + "display:block"); + password_generation::LogPasswordGenerationEvent( + password_generation::ICON_SHOWN); + return; + } + } +} + +} // namespace autofill diff --git a/components/autofill/renderer/password_generation_manager.h b/components/autofill/renderer/password_generation_manager.h new file mode 100644 index 0000000..9ed94ca --- /dev/null +++ b/components/autofill/renderer/password_generation_manager.h @@ -0,0 +1,90 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_AUTOFILL_RENDERER_PASSWORD_GENERATION_MANAGER_H_ +#define COMPONENTS_AUTOFILL_RENDERER_PASSWORD_GENERATION_MANAGER_H_ + +#include <map> +#include <utility> +#include <vector> + +#include "content/public/renderer/render_view_observer.h" +#include "googleurl/src/gurl.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebInputElement.h" +#include "third_party/WebKit/Source/WebKit/chromium/public/WebTextFieldDecoratorClient.h" + +namespace WebKit { +class WebCString; +class WebDocument; +} + +namespace content { +struct PasswordForm; +} + +namespace autofill { + +// This class is responsible for controlling communication for password +// generation between the browser (which shows the popup and generates +// passwords) and WebKit (shows the generation icon in the password field). +class PasswordGenerationManager : public content::RenderViewObserver, + public WebKit::WebTextFieldDecoratorClient { + public: + explicit PasswordGenerationManager(content::RenderView* render_view); + virtual ~PasswordGenerationManager(); + + protected: + // Returns true if this document is one that we should consider analyzing. + // Virtual so that it can be overriden during testing. + virtual bool ShouldAnalyzeDocument(const WebKit::WebDocument& document) const; + + // RenderViewObserver: + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + private: + // RenderViewObserver: + virtual void DidFinishDocumentLoad(WebKit::WebFrame* frame) OVERRIDE; + virtual void DidFinishLoad(WebKit::WebFrame* frame) OVERRIDE; + + // WebTextFieldDecoratorClient: + virtual bool shouldAddDecorationTo( + const WebKit::WebInputElement& element) OVERRIDE; + virtual bool visibleByDefault() OVERRIDE; + virtual WebKit::WebCString imageNameForNormalState() OVERRIDE; + virtual WebKit::WebCString imageNameForDisabledState() OVERRIDE; + virtual WebKit::WebCString imageNameForReadOnlyState() OVERRIDE; + virtual WebKit::WebCString imageNameForHoverState() OVERRIDE; + virtual void handleClick(WebKit::WebInputElement& element) OVERRIDE; + virtual void willDetach(const WebKit::WebInputElement& element) OVERRIDE; + + // Message handlers. + void OnFormNotBlacklisted(const content::PasswordForm& form); + void OnPasswordAccepted(const string16& password); + void OnPasswordGenerationEnabled(bool enabled); + + // Helper function to decide whether we should show password generation icon. + void MaybeShowIcon(); + + content::RenderView* render_view_; + + // True if password generation is enabled for the profile associated + // with this renderer. + bool enabled_; + + // Stores the origin of the account creation form we detected. + GURL account_creation_form_origin_; + + // Stores the origins of the password forms confirmed not to be blacklisted + // by the browser. A form can be blacklisted if a user chooses "never save + // passwords for this site". + std::vector<GURL> not_blacklisted_password_form_origins_; + + std::vector<WebKit::WebInputElement> passwords_; + + DISALLOW_COPY_AND_ASSIGN(PasswordGenerationManager); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_RENDERER_PASSWORD_GENERATION_MANAGER_H_ |