diff options
author | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
---|---|---|
committer | Zeno Albisser <zeno.albisser@digia.com> | 2013-08-15 21:46:11 +0200 |
commit | 679147eead574d186ebf3069647b4c23e8ccace6 (patch) | |
tree | fc247a0ac8ff119f7c8550879ebb6d3dd8d1ff69 /chromium/components | |
download | qtwebengine-chromium-679147eead574d186ebf3069647b4c23e8ccace6.tar.gz |
Initial import.
Diffstat (limited to 'chromium/components')
533 files changed, 83010 insertions, 0 deletions
diff --git a/chromium/components/DEPS b/chromium/components/DEPS new file mode 100644 index 00000000000..d6fbc7c41ed --- /dev/null +++ b/chromium/components/DEPS @@ -0,0 +1,16 @@ +include_rules = [ + # Do not add chrome/ as an allowed include. Components MUST NOT + # depend on anything under src/chrome. + "-chrome", + + # Components should only depend on the public Content API, and on + # layers below the Content Module. They must not depend on the + # implementation of the Content Module. + # + # Subdirectories of e.g. src/components/component_name should add + # the additional parts of the Content API that they need, + # e.g. components/component_name/browser/DEPS would add a + # "+content/public/browser" rule. + "-content", + "+content/public/common", +] diff --git a/chromium/components/OWNERS b/chromium/components/OWNERS new file mode 100644 index 00000000000..ff02f9e3125 --- /dev/null +++ b/chromium/components/OWNERS @@ -0,0 +1,46 @@ +joi@chromium.org + +per-file autofill*=dhollowa@chromium.org +per-file autofill*=isherman@chromium.org +per-file autofill*=kaiwang@chromium.org + +per-file breakpad.gypi=jochen@chromium.org +per-file breakpad.gypi=rsesek@chromium.org +per-file breakpad.gypi=thestig@chromium.org + +per-file json_schema.gypi=asargent@chromium.org +per-file json_schema.gypi=calamity@chromium.org +per-file json_schema.gypi=kalman@chromium.org +per-file json_schema.gypi=koz@chromium.org +per-file json_schema.gypi=mpcomplete@chromium.org + +per-file nacl*=bradchen@chromium.org +per-file nacl*=bradnelson@chromium.org +per-file nacl*=jvoung@chromium.org +per-file nacl*=mseaborn@chromium.org +per-file nacl*=noelallen@chromium.org +per-file nacl*=sehr@chromium.org + +per-file navigation_interception.gypi=joth@chromium.org +per-file navigation_interception.gypi=mkosiba@chromium.org + +per-file policy.gypi=mnissler@chromium.org +per-file policy.gypi=pastarmovj@chromium.org +per-file policy.gypi=joaodasilva@chromium.org +per-file policy.gypi=bartfab@chromium.org +per-file policy.gypi=atwilson@chromium.org +per-file policy.gypi=pneubeck@chromium.org + +per-file sessions.gypi=marja@chromium.org +per-file sessions.gypi=sky@chromium.org + +per-file tracing*=jbauman@chromium.org +per-file tracing*=nduca@chromium.org + +per-file user_prefs.gypi=battre@chromium.org +per-file user_prefs.gypi=bauerb@chromium.org +per-file user_prefs.gypi=mnissler@chromium.org +per-file user_prefs.gypi=pam@chromium.org + +per-file *.isolate=csharp@chromium.org +per-file *.isolate=maruel@chromium.org diff --git a/chromium/components/README b/chromium/components/README new file mode 100644 index 00000000000..b20ca499c36 --- /dev/null +++ b/chromium/components/README @@ -0,0 +1,35 @@ +This directory is for components that have the Content Module as the +uppermost layer they depend on. They may depend only on the Content +API (content/public) and on lower layers (e.g. base/, net/, ipc/ +etc.). + +Components that have bits of code that need to live in different +processes (e.g. some code in the browser process, some in the renderer +process, etc.) should separate the code into different subdirectories. +Hence for a component named 'foo' you might end up with a structure +like the following: + +components/foo - DEPS, OWNERS, foo.gypi +components/foo/browser - code that needs the browser process +components/foo/common - for e.g. IPC constants and such +components/foo/renderer - code that needs renderer process + +These subdirectories should have DEPS files with the relevant +restrictions in place, i.e. only components/*/browser should +be allowed to #include from content/public/browser. + +Note that there may also be an 'android' subdir, with a Java source +code structure underneath it where the package name is +org.chromium.components.foo, and with subdirs after 'foo' +to illustrate process, e.g. 'browser' or 'renderer': + +components/foo/android/OWNERS, DEPS +components/foo/android/java/src/org/chromium/components/foo/browser/ +components/foo/android/javatests/src/org/chromium/components/foo/browser/ + +Code in a component should be placed in a namespace corresponding to +the name of the component; e.g. for a component living in +//components/foo, code in that component should be in the foo:: +namespace. Note that it used to be the rule that all code under +//components should be in the components:: namespace; this is being +phased out. diff --git a/chromium/components/auto_login_parser.gypi b/chromium/components/auto_login_parser.gypi new file mode 100644 index 00000000000..1dae9360602 --- /dev/null +++ b/chromium/components/auto_login_parser.gypi @@ -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. + +{ + 'targets': [ + { + 'target_name': 'auto_login_parser', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../net/net.gyp:net', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'auto_login_parser/auto_login_parser.cc', + 'auto_login_parser/auto_login_parser.h', + ], + }, + ], +} diff --git a/chromium/components/auto_login_parser/DEPS b/chromium/components/auto_login_parser/DEPS new file mode 100644 index 00000000000..8fa9d48d882 --- /dev/null +++ b/chromium/components/auto_login_parser/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+net", +] diff --git a/chromium/components/auto_login_parser/OWNERS b/chromium/components/auto_login_parser/OWNERS new file mode 100644 index 00000000000..a7d929a2338 --- /dev/null +++ b/chromium/components/auto_login_parser/OWNERS @@ -0,0 +1 @@ +bulach@chromium.org diff --git a/chromium/components/auto_login_parser/auto_login_parser.cc b/chromium/components/auto_login_parser/auto_login_parser.cc new file mode 100644 index 00000000000..298c9b7acf3 --- /dev/null +++ b/chromium/components/auto_login_parser/auto_login_parser.cc @@ -0,0 +1,82 @@ +// 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/auto_login_parser/auto_login_parser.h" + +#include <utility> +#include <vector> + +#include "base/logging.h" +#include "base/strings/string_split.h" +#include "net/base/escape.h" +#include "net/url_request/url_request.h" + +namespace auto_login_parser { + +namespace { + +const char kHeaderName[] = "X-Auto-Login"; + +bool MatchRealm(const std::string& realm, RealmRestriction restriction) { + switch (restriction) { + case ONLY_GOOGLE_COM: + return realm == "com.google"; + case ALLOW_ANY_REALM: + return true; + default: + NOTREACHED(); + return false; + } +} + +} // namespace + +HeaderData::HeaderData() {} +HeaderData::~HeaderData() {} + +bool ParseHeader(const std::string& header, + RealmRestriction realm_restriction, + HeaderData* header_data) { + // TODO(pliard): Investigate/fix potential internationalization issue. It + // seems that "account" from the x-auto-login header might contain non-ASCII + // characters. + if (header.empty()) + return false; + + std::vector<std::pair<std::string, std::string> > pairs; + if (!base::SplitStringIntoKeyValuePairs(header, '=', '&', &pairs)) + return false; + + // Parse the information from the |header| string. + HeaderData local_params; + for (size_t i = 0; i < pairs.size(); ++i) { + const std::pair<std::string, std::string>& pair = pairs[i]; + std::string unescaped_value(net::UnescapeURLComponent( + pair.second, net::UnescapeRule::URL_SPECIAL_CHARS)); + if (pair.first == "realm") { + if (!MatchRealm(unescaped_value, realm_restriction)) + return false; + local_params.realm = unescaped_value; + } else if (pair.first == "account") { + local_params.account = unescaped_value; + } else if (pair.first == "args") { + local_params.args = unescaped_value; + } + } + if (local_params.realm.empty() || local_params.args.empty()) + return false; + + *header_data = local_params; + return true; +} + +bool ParserHeaderInResponse(net::URLRequest* request, + RealmRestriction realm_restriction, + HeaderData* header_data) { + std::string header_string; + request->GetResponseHeaderByName(kHeaderName, &header_string); + return ParseHeader(header_string, realm_restriction, header_data); +} + +} // namespace auto_login_parser diff --git a/chromium/components/auto_login_parser/auto_login_parser.h b/chromium/components/auto_login_parser/auto_login_parser.h new file mode 100644 index 00000000000..bd2f28f9996 --- /dev/null +++ b/chromium/components/auto_login_parser/auto_login_parser.h @@ -0,0 +1,50 @@ +// 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_AUTO_LOGIN_PARSER_AUTO_LOGIN_PARSER_H_ +#define COMPONENTS_AUTO_LOGIN_PARSER_AUTO_LOGIN_PARSER_H_ + +#include <string> + +namespace net { +class URLRequest; +} + +namespace auto_login_parser { + +enum RealmRestriction { + ONLY_GOOGLE_COM, + ALLOW_ANY_REALM +}; + +struct HeaderData { + HeaderData(); + ~HeaderData(); + + // "realm" string from x-auto-login (e.g. "com.google"). + std::string realm; + + // "account" string from x-auto-login. + std::string account; + + // "args" string from x-auto-login to be passed to MergeSession. This string + // should be considered opaque and not be cracked open to look inside. + std::string args; +}; + +// Returns whether parsing succeeded. Parameter |header_data| will not be +// modified if parsing fails. +bool ParseHeader(const std::string& header, + RealmRestriction realm_restriction, + HeaderData* header_data); + +// Helper function that also retrieves the header from the response of the +// given URLRequest. +bool ParserHeaderInResponse(net::URLRequest* request, + RealmRestriction realm_restriction, + HeaderData* header_data); + +} // namespace auto_login_parser + +#endif // COMPONENTS_AUTO_LOGIN_PARSER_AUTO_LOGIN_PARSER_H_ diff --git a/chromium/components/auto_login_parser/auto_login_parser_unittest.cc b/chromium/components/auto_login_parser/auto_login_parser_unittest.cc new file mode 100644 index 00000000000..442784c734f --- /dev/null +++ b/chromium/components/auto_login_parser/auto_login_parser_unittest.cc @@ -0,0 +1,92 @@ +// 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/auto_login_parser/auto_login_parser.h" + +#include <string> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace auto_login_parser { + +class AutoLoginParserTest : public testing::Test { + protected: + static bool IsHeaderDataEmpty(const HeaderData& header) { + return header.realm.empty() && header.account.empty() && + header.args.empty(); + } +}; + +TEST_F(AutoLoginParserTest, ParseHeader) { + std::string header = + "realm=com.google&" + "account=fred.example%40gmail.com&" + "args=kfdshfwoeriudslkfsdjfhdskjfhsdkr"; + + HeaderData header_data; + EXPECT_TRUE(ParseHeader(header, ONLY_GOOGLE_COM, &header_data)); + + ASSERT_EQ("com.google", header_data.realm); + ASSERT_EQ("fred.example@gmail.com", header_data.account); + ASSERT_EQ("kfdshfwoeriudslkfsdjfhdskjfhsdkr", header_data.args); +} + +TEST_F(AutoLoginParserTest, ParseHeaderOnlySupportsComGoogle) { + std::string header = + "realm=com.microsoft&" + "account=fred.example%40gmail.com&" + "args=kfdshfwoeriudslkfsdjfhdskjfhsdkr"; + + HeaderData header_data; + EXPECT_FALSE(ParseHeader(header, ONLY_GOOGLE_COM, &header_data)); + // |header| should not be touched when parsing fails. + EXPECT_TRUE(IsHeaderDataEmpty(header_data)); +} + +TEST_F(AutoLoginParserTest, ParseHeaderWithMissingRealm) { + std::string header = + "account=fred.example%40gmail.com&" + "args=kfdshfwoeriudslkfsdjfhdskjfhsdkr"; + + HeaderData header_data; + EXPECT_FALSE(ParseHeader(header, ONLY_GOOGLE_COM, &header_data)); + EXPECT_TRUE(IsHeaderDataEmpty(header_data)); +} + +TEST_F(AutoLoginParserTest, ParseHeaderWithMissingArgs) { + std::string header = + "realm=com.google&" + "account=fred.example%40gmail.com&"; + + HeaderData header_data; + EXPECT_FALSE(ParseHeader(header, ONLY_GOOGLE_COM, &header_data)); + EXPECT_TRUE(IsHeaderDataEmpty(header_data)); +} + +TEST_F(AutoLoginParserTest, ParseHeaderWithoutOptionalAccount) { + std::string header = + "realm=com.google&" + "args=kfdshfwoeriudslkfsdjfhdskjfhsdkr"; + + HeaderData header_data; + EXPECT_TRUE(ParseHeader(header, ONLY_GOOGLE_COM, &header_data)); + ASSERT_EQ("com.google", header_data.realm); + ASSERT_EQ("kfdshfwoeriudslkfsdjfhdskjfhsdkr", header_data.args); +} + +TEST_F(AutoLoginParserTest, ParseHeaderAllowsAnyRealmWithOption) { + std::string header = + "realm=com.microsoft&" + "account=fred.example%40gmail.com&" + "args=kfdshfwoeriudslkfsdjfhdskjfhsdkr"; + + HeaderData header_data; + EXPECT_TRUE(ParseHeader(header, ALLOW_ANY_REALM, &header_data)); + + ASSERT_EQ("com.microsoft", header_data.realm); + ASSERT_EQ("fred.example@gmail.com", header_data.account); + ASSERT_EQ("kfdshfwoeriudslkfsdjfhdskjfhsdkr", header_data.args); +} + +} // namespace auto_login_parser diff --git a/chromium/components/autofill.gypi b/chromium/components/autofill.gypi new file mode 100644 index 00000000000..92eccd3a1d6 --- /dev/null +++ b/chromium/components/autofill.gypi @@ -0,0 +1,408 @@ +# 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. + +{ + 'targets': [ + { + # Private target only used in components/autofill. + 'target_name': 'autofill_regexes', + 'type': 'none', + 'actions': [{ + 'action_name': 'autofill_regexes', + 'inputs': [ + '<(DEPTH)/build/escape_unicode.py', + 'autofill/core/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/core/browser/autofill_regex_constants.cc.utf8'], + }], + }, + ], + 'conditions': [ + ['OS != "ios"', { + 'targets': [ + { + 'target_name': 'autofill_core_common', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../content/content.gyp:content_common', + '../ipc/ipc.gyp:ipc', + '../third_party/WebKit/public/blink.gyp:blink_minimal', + '../ui/ui.gyp:ui', + '../url/url.gyp:url_lib', + ], + 'conditions': [ + ['OS == "android"', { + 'dependencies': [ + 'autofill_jni_headers', + ], + }], + ], + 'include_dirs': [ + '..', + '<(SHARED_INTERMEDIATE_DIR)/autofill' + ], + 'sources': [ + 'autofill/core/browser/android/auxiliary_profile_loader_android.cc', + 'autofill/core/browser/android/auxiliary_profile_loader_android.h', + 'autofill/core/browser/android/auxiliary_profiles_android.cc', + 'autofill/core/browser/android/auxiliary_profiles_android.h', + 'autofill/core/browser/android/component_jni_registrar.cc', + 'autofill/core/browser/android/component_jni_registrar.h', + 'autofill/core/browser/android/personal_data_manager_android.cc', + 'autofill/core/common/autocheckout_status.h', + 'autofill/core/common/autofill_constants.cc', + 'autofill/core/common/autofill_constants.h', + 'autofill/core/common/autofill_messages.h', + 'autofill/core/common/autofill_message_generator.cc', + 'autofill/core/common/autofill_message_generator.h', + 'autofill/core/common/autofill_pref_names.cc', + 'autofill/core/common/autofill_pref_names.h', + 'autofill/core/common/autofill_switches.cc', + 'autofill/core/common/autofill_switches.h', + 'autofill/core/common/form_data.cc', + 'autofill/core/common/form_data.h', + 'autofill/core/common/form_data_predictions.cc', + 'autofill/core/common/form_data_predictions.h', + 'autofill/core/common/form_field_data.cc', + 'autofill/core/common/form_field_data.h', + 'autofill/core/common/form_field_data_predictions.cc', + 'autofill/core/common/form_field_data_predictions.h', + 'autofill/core/common/password_form_fill_data.cc', + 'autofill/core/common/password_form_fill_data.h', + 'autofill/core/common/password_generation_util.cc', + 'autofill/core/common/password_generation_util.h', + 'autofill/core/common/web_element_descriptor.cc', + 'autofill/core/common/web_element_descriptor.h', + ], + }, + + { + 'target_name': 'autofill_core_browser', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + 'autofill_core_common', + 'autofill_regexes', + 'encryptor', + 'user_prefs', + 'webdata_common', + '../base/base.gyp:base', + '../base/base.gyp:base_i18n', + '../base/base.gyp:base_prefs', + '../content/content.gyp:content_browser', + '../content/content.gyp:content_common', + '../google_apis/google_apis.gyp:google_apis', + '../ipc/ipc.gyp:ipc', + '../skia/skia.gyp:skia', + '../sql/sql.gyp:sql', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', + '../third_party/libjingle/libjingle.gyp:libjingle', + '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber', + '../ui/ui.gyp:ui', + '../url/url.gyp:url_lib', + '../webkit/webkit_resources.gyp:webkit_resources', + + 'component_strings.gyp:component_strings', + ], + 'sources': [ + 'autofill/core/browser/address.cc', + 'autofill/core/browser/address.h', + 'autofill/core/browser/address_field.cc', + 'autofill/core/browser/address_field.h', + 'autofill/core/browser/autocomplete_history_manager.cc', + 'autofill/core/browser/autocomplete_history_manager.h', + 'autofill/core/browser/autofill-inl.h', + 'autofill/core/browser/autofill_country.cc', + 'autofill/core/browser/autofill_country.h', + 'autofill/core/browser/autofill_data_model.cc', + 'autofill/core/browser/autofill_data_model.h', + 'autofill/core/browser/autofill_download.cc', + 'autofill/core/browser/autofill_download.h', + 'autofill/core/browser/autofill_download_url.cc', + 'autofill/core/browser/autofill_download_url.h', + 'autofill/core/browser/autofill_driver.h', + 'autofill/core/browser/autofill_external_delegate.cc', + 'autofill/core/browser/autofill_external_delegate.h', + 'autofill/core/browser/autofill_field.cc', + 'autofill/core/browser/autofill_field.h', + 'autofill/core/browser/autofill_ie_toolbar_import_win.cc', + 'autofill/core/browser/autofill_ie_toolbar_import_win.h', + 'autofill/core/browser/autofill_manager.cc', + 'autofill/core/browser/autofill_manager.h', + 'autofill/core/browser/autofill_manager_delegate.h', + 'autofill/core/browser/autofill_manager_test_delegate.h', + 'autofill/core/browser/autofill_metrics.cc', + 'autofill/core/browser/autofill_metrics.h', + 'autofill/core/browser/autofill_popup_delegate.h', + 'autofill/core/browser/autofill_profile.cc', + 'autofill/core/browser/autofill_profile.h', + 'autofill/core/browser/autofill_regex_constants.cc.utf8', + 'autofill/core/browser/autofill_regex_constants.h', + 'autofill/core/browser/autofill_regexes.cc', + 'autofill/core/browser/autofill_regexes.h', + 'autofill/core/browser/autofill_scanner.cc', + 'autofill/core/browser/autofill_scanner.h', + 'autofill/core/browser/autofill_server_field_info.h', + 'autofill/core/browser/autofill_type.cc', + 'autofill/core/browser/autofill_type.h', + 'autofill/core/browser/autofill_xml_parser.cc', + 'autofill/core/browser/autofill_xml_parser.h', + 'autofill/core/browser/contact_info.cc', + 'autofill/core/browser/contact_info.h', + 'autofill/core/browser/credit_card.cc', + 'autofill/core/browser/credit_card.h', + 'autofill/core/browser/credit_card_field.cc', + 'autofill/core/browser/credit_card_field.h', + 'autofill/core/browser/email_field.cc', + 'autofill/core/browser/email_field.h', + 'autofill/core/browser/field_types.h', + 'autofill/core/browser/form_field.cc', + 'autofill/core/browser/form_field.h', + 'autofill/core/browser/form_group.cc', + 'autofill/core/browser/form_group.h', + 'autofill/core/browser/form_structure.cc', + 'autofill/core/browser/form_structure.h', + 'autofill/core/browser/name_field.cc', + 'autofill/core/browser/name_field.h', + 'autofill/core/browser/password_autofill_manager.cc', + 'autofill/core/browser/password_autofill_manager.h', + 'autofill/core/browser/password_generator.cc', + 'autofill/core/browser/password_generator.h', + 'autofill/core/browser/personal_data_manager.cc', + 'autofill/core/browser/personal_data_manager.h', + 'autofill/core/browser/personal_data_manager_mac.mm', + 'autofill/core/browser/personal_data_manager_observer.h', + 'autofill/core/browser/phone_field.cc', + 'autofill/core/browser/phone_field.h', + 'autofill/core/browser/phone_number.cc', + 'autofill/core/browser/phone_number.h', + 'autofill/core/browser/phone_number_i18n.cc', + 'autofill/core/browser/phone_number_i18n.h', + 'autofill/core/browser/state_names.cc', + 'autofill/core/browser/state_names.h', + 'autofill/core/browser/validation.cc', + 'autofill/core/browser/validation.h', + 'autofill/core/browser/webdata/autofill_change.cc', + 'autofill/core/browser/webdata/autofill_change.h', + 'autofill/core/browser/webdata/autofill_entry.cc', + 'autofill/core/browser/webdata/autofill_entry.h', + 'autofill/core/browser/webdata/autofill_table.cc', + 'autofill/core/browser/webdata/autofill_table.h', + 'autofill/core/browser/webdata/autofill_webdata.h', + 'autofill/core/browser/webdata/autofill_webdata_backend.h', + 'autofill/core/browser/webdata/autofill_webdata_backend_impl.cc', + 'autofill/core/browser/webdata/autofill_webdata_backend_impl.h', + 'autofill/core/browser/webdata/autofill_webdata_service.cc', + 'autofill/core/browser/webdata/autofill_webdata_service.h', + 'autofill/core/browser/webdata/autofill_webdata_service_observer.h', + + # This file is generated by the autofill_regexes action. + '<(SHARED_INTERMEDIATE_DIR)/autofill_regex_constants.cc', + ], + + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [4267, ], + }, + + { + 'target_name': 'autofill_core_test_support', + 'type': 'static_library', + 'dependencies': [ + 'autofill_core_common', + 'autofill_core_browser', + '../testing/gtest.gyp:gtest', + ], + 'sources': [ + 'autofill/core/browser/android/test_auxiliary_profile_loader_android.cc', + 'autofill/core/browser/android/test_auxiliary_profile_loader_android.h', + 'autofill/core/browser/autofill_common_test.cc', + 'autofill/core/browser/autofill_common_test.h', + 'autofill/core/browser/data_driven_test.cc', + 'autofill/core/browser/data_driven_test.h', + 'autofill/core/browser/test_autofill_driver.cc', + 'autofill/core/browser/test_autofill_driver.h', + 'autofill/core/browser/test_autofill_external_delegate.cc', + 'autofill/core/browser/test_autofill_external_delegate.h', + 'autofill/core/browser/test_autofill_manager_delegate.cc', + 'autofill/core/browser/test_autofill_manager_delegate.h', + 'autofill/core/browser/test_personal_data_manager.cc', + 'autofill/core/browser/test_personal_data_manager.h', + ], + }, + + { + # Protobuf compiler / generate rule for Autofill's risk integration. + 'target_name': 'autofill_content_risk_proto', + 'type': 'static_library', + 'sources': [ + 'autofill/content/browser/risk/proto/fingerprint.proto', + ], + 'variables': { + 'proto_in_dir': 'autofill/content/browser/risk/proto', + 'proto_out_dir': 'components/autofill/content/browser/risk/proto', + }, + 'includes': [ '../build/protoc.gypi' ] + }, + { + 'target_name': 'autofill_content_test_support', + 'type': 'static_library', + 'dependencies': [ + '../testing/gmock.gyp:gmock', + ], + 'sources': [ + 'autofill/content/browser/wallet/mock_wallet_client.cc', + 'autofill/content/browser/wallet/mock_wallet_client.h', + 'autofill/content/browser/wallet/wallet_test_util.cc', + 'autofill/content/browser/wallet/wallet_test_util.h', + ], + 'include_dirs': [ '..' ], + }, + { + 'target_name': 'autofill_content_browser', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + 'autofill_content_risk_proto', + 'autofill_core_browser', + 'autofill_core_common', + 'autofill_regexes', + 'encryptor', + 'user_prefs', + 'webdata_common', + '../base/base.gyp:base', + '../base/base.gyp:base_i18n', + '../base/base.gyp:base_prefs', + '../content/content.gyp:content_browser', + '../content/content.gyp:content_common', + '../google_apis/google_apis.gyp:google_apis', + '../ipc/ipc.gyp:ipc', + '../skia/skia.gyp:skia', + '../sql/sql.gyp:sql', + '../third_party/icu/icu.gyp:icui18n', + '../third_party/icu/icu.gyp:icuuc', + '../third_party/libjingle/libjingle.gyp:libjingle', + '../third_party/libphonenumber/libphonenumber.gyp:libphonenumber', + '../ui/ui.gyp:ui', + '../url/url.gyp:url_lib', + '../webkit/webkit_resources.gyp:webkit_resources', + + 'component_strings.gyp:component_strings', + ], + 'sources': [ + 'autofill/content/browser/autocheckout/whitelist_manager.cc', + 'autofill/content/browser/autocheckout/whitelist_manager.h', + 'autofill/content/browser/autocheckout_manager.cc', + 'autofill/content/browser/autocheckout_manager.h', + 'autofill/content/browser/autocheckout_page_meta_data.cc', + 'autofill/content/browser/autocheckout_page_meta_data.h', + 'autofill/content/browser/autocheckout_request_manager.cc', + 'autofill/content/browser/autocheckout_request_manager.h', + 'autofill/content/browser/autocheckout_statistic.cc', + 'autofill/content/browser/autocheckout_statistic.h', + 'autofill/content/browser/autocheckout_steps.h', + 'autofill/content/browser/autofill_driver_impl.cc', + 'autofill/content/browser/autofill_driver_impl.h', + 'autofill/content/browser/risk/fingerprint.cc', + 'autofill/content/browser/risk/fingerprint.h', + 'autofill/content/browser/wallet/form_field_error.cc', + 'autofill/content/browser/wallet/form_field_error.h', + 'autofill/content/browser/wallet/full_wallet.cc', + 'autofill/content/browser/wallet/full_wallet.h', + 'autofill/content/browser/wallet/instrument.cc', + 'autofill/content/browser/wallet/instrument.h', + 'autofill/content/browser/wallet/required_action.cc', + 'autofill/content/browser/wallet/required_action.h', + 'autofill/content/browser/wallet/wallet_address.cc', + 'autofill/content/browser/wallet/wallet_address.h', + 'autofill/content/browser/wallet/wallet_client.cc', + 'autofill/content/browser/wallet/wallet_client.h', + 'autofill/content/browser/wallet/wallet_client_delegate.h', + 'autofill/content/browser/wallet/wallet_items.cc', + 'autofill/content/browser/wallet/wallet_items.h', + 'autofill/content/browser/wallet/wallet_service_url.cc', + 'autofill/content/browser/wallet/wallet_service_url.h', + 'autofill/content/browser/wallet/wallet_signin_helper.cc', + 'autofill/content/browser/wallet/wallet_signin_helper.h', + ], + + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [4267, ], + }, + + { + 'target_name': 'autofill_content_renderer', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + 'autofill_core_common', + '../base/base.gyp:base', + '../content/content.gyp:content_renderer', + '../content/content.gyp:content_common', + '../ipc/ipc.gyp:ipc', + '../skia/skia.gyp:skia', + + 'component_strings.gyp:component_strings', + ], + 'sources': [ + 'autofill/content/renderer/autofill_agent.cc', + 'autofill/content/renderer/autofill_agent.h', + 'autofill/content/renderer/form_autofill_util.cc', + 'autofill/content/renderer/form_autofill_util.h', + 'autofill/content/renderer/form_cache.cc', + 'autofill/content/renderer/form_cache.h', + 'autofill/content/renderer/page_click_listener.h', + 'autofill/content/renderer/page_click_tracker.cc', + 'autofill/content/renderer/page_click_tracker.h', + 'autofill/content/renderer/password_autofill_agent.cc', + 'autofill/content/renderer/password_autofill_agent.h', + 'autofill/content/renderer/password_generation_manager.cc', + 'autofill/content/renderer/password_generation_manager.h', + ], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [4267, ], + }, + ], + }], + ['OS == "android"', { + 'targets': [ + { + 'target_name': 'autofill_java', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base', + '../content/content.gyp:content_java', + ], + 'variables': { + 'java_in_dir': 'autofill/core/browser/android/java', + }, + 'includes': [ '../build/java.gypi' ], + }, + { + 'target_name': 'autofill_jni_headers', + 'type': 'none', + 'sources': [ + 'autofill/core/browser/android/java/src/org/chromium/components/browser/autofill/PersonalAutofillPopulator.java', + ], + 'variables': { + 'jni_gen_package': 'autofill', + }, + 'includes': [ '../build/jni_generator.gypi' ], + }, + ], + }], + ], +} diff --git a/chromium/components/autofill/DEPS b/chromium/components/autofill/DEPS new file mode 100644 index 00000000000..e136eaf419b --- /dev/null +++ b/chromium/components/autofill/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + "+google_apis/gaia/gaia_urls.h", + "+grit", # For generated headers + "+jni", + "+net", + "+ui", + # Autofill is a layered component; subdirectories must explicitly introduce + # the ability to use the content layer as appropriate. + "-components/autofill/content", + "-content/public/common", +] diff --git a/chromium/components/autofill/OWNERS b/chromium/components/autofill/OWNERS new file mode 100644 index 00000000000..af7b445d2a8 --- /dev/null +++ b/chromium/components/autofill/OWNERS @@ -0,0 +1,8 @@ +estade@chromium.org +isherman@chromium.org + +# Owner for password autofill/generation only. +gcasto@chromium.org + +# Temporary owner, for refactoring changes only. +joi@chromium.org diff --git a/chromium/components/autofill/README b/chromium/components/autofill/README new file mode 100644 index 00000000000..44be6bfe04c --- /dev/null +++ b/chromium/components/autofill/README @@ -0,0 +1,23 @@ +Autofill is in the process of becoming a layered component +(https://sites.google.com/a/chromium.org/dev/developers/design-documents/layered-components-design) +to enable it to be shared cleanly on iOS. + +When this process is complete, this component will have the following structure: + +- core/: shared code that does not depend on src/content/ or src/ios/ + - browser/: Browser process code + - common/: Code shared by the browser and the renderer +- content/: Driver for the shared code based on the content layer. + - browser/: Browser process code. + - renderer/: Renderer process code. + - common/: Code shared by the browser and the renderer. +- ios/: Driver for the shared code based on src/ios. + +See +https://sites.google.com/a/chromium.org/dev/developers/design-documents/layered-components-technical-approach/making-autofill-into-a-layered-component +for an outline of the project. + +For pointers on how to continue getting your work done as the component moves +into its new structure, see +https://sites.google.com/a/chromium.org/dev/developers/design-documents/layered-components-technical-approach/making-autofill-into-a-layered-component#TOC-Help-How-Do-I-Get-My-Autofill-Related-Work-Done- + diff --git a/chromium/components/autofill/content/DEPS b/chromium/components/autofill/content/DEPS new file mode 100644 index 00000000000..bd7656f8b4e --- /dev/null +++ b/chromium/components/autofill/content/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+content/public/common", + # Allow inclusion of WebKit API files. + "+third_party/WebKit/public/platform", + "+third_party/WebKit/public/web", +] diff --git a/chromium/components/autofill/content/browser/DEPS b/chromium/components/autofill/content/browser/DEPS new file mode 100644 index 00000000000..fb768b60c28 --- /dev/null +++ b/chromium/components/autofill/content/browser/DEPS @@ -0,0 +1,41 @@ +include_rules = [ + "+components/webdata/common", + "+content/public/browser", + "+crypto/random.h", + "+google_apis/gaia", + "+google_apis/google_api_keys.h", + "+gpu/config/gpu_info.h", + "+net", + "+sql", + "+third_party/libjingle", + "+third_party/libphonenumber", # For phone number i18n. +] + +specific_include_rules = { + '.*_[a-z]*test\.cc': [ + "+content/public/test", + ], + + # TODO(joi): Removing these dependencies needs to wait until some + # other things (AutofillWebData::FromBrowserContext and a few other + # things) move out of being built in //chrome. If we break the + # dependency on ChromeRenderViewHostTestHarness now (by switching to + # content::RenderViewHostTestHarness) but leave the test running in + # the 'unit_tests' target, it will fail at runtime trying to cast a + # plain BrowserContext to a Profile. If on the other hand we move it + # to the 'components_unittests' target, it will at this point fail + # to build due to a few link-time dependencies. + 'autocheckout_manager_unittest.cc': [ + "!chrome/test/base/chrome_render_view_host_test_harness.h", + "!chrome/test/base/testing_profile.h", + ], + 'autofill_driver_impl_unittest.cc': [ + "!chrome/test/base/chrome_render_view_host_test_harness.h", + ], + 'wallet_client_unittest.cc': [ + "!chrome/test/base/testing_profile.h" + ], + 'wallet_signin_helper_unittest.cc': [ + "!chrome/test/base/testing_profile.h" + ], +} diff --git a/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.cc b/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.cc new file mode 100644 index 00000000000..7d5423adc8d --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.cc @@ -0,0 +1,205 @@ +// 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/content/browser/autocheckout/whitelist_manager.h" + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/metrics/field_trial.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "components/autofill/core/common/autofill_switches.h" +#include "content/public/browser/browser_context.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" +#include "url/gurl.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 net::BackoffEntry::Policy kBackoffPolicy = { + // Number of initial errors to ignore before starting to back off. + 0, + + // Initial delay in ms: 3 seconds. + 3000, + + // Factor by which the waiting time is multiplied. + 6, + + // Fuzzing percentage: no fuzzing logic. + 0, + + // Maximum delay in ms: 1 hour. + 1000 * 60 * 60, + + // When to discard an entry: 3 hours. + 1000 * 60 * 60 * 3, + + // |always_use_initial_delay|; false means that the initial delay is + // applied after the first error, and starts backing off from there. + false, +}; + +const char kDefaultWhitelistUrl[] = + "https://www.gstatic.com/commerce/autocheckout/whitelist.csv"; + +const char kWhiteListKeyName[] = "autocheckout_whitelist_manager"; + +std::string GetWhitelistUrl() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + std::string whitelist_url = command_line.GetSwitchValueASCII( + autofill::switches::kAutocheckoutWhitelistUrl); + + return whitelist_url.empty() ? kDefaultWhitelistUrl : whitelist_url; +} + +} // namespace + + +namespace autofill { +namespace autocheckout { + +WhitelistManager::WhitelistManager() + : callback_is_pending_(false), + experimental_form_filling_enabled_( + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableExperimentalFormFilling) || + base::FieldTrialList::FindFullName("Autocheckout") == "Yes"), + bypass_autocheckout_whitelist_( + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kBypassAutocheckoutWhitelist)), + retry_entry_(&kBackoffPolicy) { +} + +WhitelistManager::~WhitelistManager() {} + +void WhitelistManager::Init(net::URLRequestContextGetter* context_getter) { + DCHECK(context_getter); + context_getter_ = context_getter; + ScheduleDownload(base::TimeDelta::FromSeconds(kInitialDownloadDelaySeconds)); +} + +void WhitelistManager::ScheduleDownload(base::TimeDelta interval) { + 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); +} + +void WhitelistManager::StartDownloadTimer(base::TimeDelta interval) { + download_timer_.Start(FROM_HERE, + interval, + this, + &WhitelistManager::TriggerDownload); +} + +const AutofillMetrics& WhitelistManager::GetMetricLogger() const { + return metrics_logger_; +} + +void WhitelistManager::TriggerDownload() { + callback_is_pending_ = true; + + request_started_timestamp_ = base::Time::Now(); + + request_.reset(net::URLFetcher::Create( + 0, GURL(GetWhitelistUrl()), 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()); + + AutofillMetrics::AutocheckoutWhitelistDownloadStatus status; + base::TimeDelta duration = base::Time::Now() - request_started_timestamp_; + + // Refresh the whitelist after kDownloadIntervalSeconds (24 hours). + base::TimeDelta next_download_time = + base::TimeDelta::FromSeconds(kDownloadIntervalSeconds); + + if (source->GetResponseCode() == net::HTTP_OK) { + std::string data; + source->GetResponseAsString(&data); + BuildWhitelist(data); + status = AutofillMetrics::AUTOCHECKOUT_WHITELIST_DOWNLOAD_SUCCEEDED; + retry_entry_.Reset(); + } else { + status = AutofillMetrics::AUTOCHECKOUT_WHITELIST_DOWNLOAD_FAILED; + retry_entry_.InformOfRequest(false); + if (!retry_entry_.CanDiscard()) + next_download_time = retry_entry_.GetTimeUntilRelease(); + } + + GetMetricLogger().LogAutocheckoutWhitelistDownloadDuration(duration, status); + ScheduleDownload(next_download_time); +} + +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 bypass_autocheckout_whitelist_ ? url.spec() : 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/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.h b/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.h new file mode 100644 index 00000000000..40c16f5d697 --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout/whitelist_manager.h @@ -0,0 +1,111 @@ +// 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_CONTENT_BROWSER_AUTOCHECKOUT_WHITELIST_MANAGER_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_WHITELIST_MANAGER_H_ + +#include <string> +#include <vector> + +#include "base/timer/timer.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "net/base/backoff_entry.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: + WhitelistManager(); + virtual ~WhitelistManager(); + + // Schedule a fetch of the Autocheckout whitelist file if it's not already + // loaded. This helps ensure that the whitelist will be available by the time + // the user navigates to a form on which Autocheckout should be enabled. + void Init(net::URLRequestContextGetter* context_getter); + + // 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: + // Schedules a future call to TriggerDownload if one isn't already pending. + virtual void ScheduleDownload(base::TimeDelta interval); + + // Start the download timer. It is called by ScheduleDownload(), and exposed + // as a separate method for mocking out in tests. + virtual void StartDownloadTimer(base::TimeDelta interval); + + // Returns the |AutofillMetrics| instance that should be used for logging + // Autocheckout whitelist file downloading. + virtual const AutofillMetrics& GetMetricLogger() const; + + // 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* context_getter_; // WEAK + + // State of the kEnableExperimentalFormFilling flag. + const bool experimental_form_filling_enabled_; + + // State of the kBypassAutocheckoutWhitelist flag. + const bool bypass_autocheckout_whitelist_; + + // Exponential back-off delay to retry a failed download. + net::BackoffEntry retry_entry_; + + // Logger for UMA metrics. + AutofillMetrics metrics_logger_; + + // When the whitelist download started. Used to track download latency. + base::Time request_started_timestamp_; + + // The request object. + scoped_ptr<net::URLFetcher> request_; + + DISALLOW_COPY_AND_ASSIGN(WhitelistManager); +}; + +} // namespace autocheckout +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_WHITELIST_MANAGER_H_ + diff --git a/chromium/components/autofill/content/browser/autocheckout/whitelist_manager_unittest.cc b/chromium/components/autofill/content/browser/autocheckout/whitelist_manager_unittest.cc new file mode 100644 index 00000000000..304d8bf02b8 --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout/whitelist_manager_unittest.cc @@ -0,0 +1,309 @@ +// 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 <cmath> + +#include "base/command_line.h" +#include "base/format_macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/stringprintf.h" +#include "components/autofill/content/browser/autocheckout/whitelist_manager.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/common/autofill_switches.h" +#include "content/public/test/test_browser_thread_bundle.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" +#include "url/gurl.h" + +namespace { + +const int64 kTestDownloadInterval = 3; // 3 seconds + +// First 4 retry delays are 3, 18, 108, 648 seconds, and following retries +// capped at one hour. +const int64 kBackoffDelaysInMs[] = { + 3000, 18000, 108000, 648000, 3600000, 3600000 }; + +const char kDownloadWhitelistResponse[] = + "https://www.merchant1.com/checkout/\n" + "https://cart.merchant2.com/"; + +} // namespace + +namespace autofill { +namespace autocheckout { + +class WhitelistManagerTest; + +class MockAutofillMetrics : public AutofillMetrics { + public: + MockAutofillMetrics() {} + MOCK_CONST_METHOD2(LogAutocheckoutWhitelistDownloadDuration, + void(const base::TimeDelta& duration, + AutofillMetrics::AutocheckoutWhitelistDownloadStatus)); + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillMetrics); +}; + +class TestWhitelistManager : public WhitelistManager { + public: + TestWhitelistManager() + : WhitelistManager(), + did_start_download_timer_(false) {} + + virtual void ScheduleDownload(base::TimeDelta interval) OVERRIDE { + did_start_download_timer_ = false; + download_interval_ = interval; + return WhitelistManager::ScheduleDownload(interval); + } + + virtual void StartDownloadTimer(base::TimeDelta interval) OVERRIDE { + WhitelistManager::StartDownloadTimer(interval); + 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 base::TimeDelta& download_interval() const { + return download_interval_; + } + + const std::vector<std::string>& url_prefixes() const { + return WhitelistManager::url_prefixes(); + } + + virtual const AutofillMetrics& GetMetricLogger() const OVERRIDE { + return mock_metrics_logger_; + } + + private: + bool did_start_download_timer_; + base::TimeDelta download_interval_; + + MockAutofillMetrics mock_metrics_logger_; + + DISALLOW_COPY_AND_ASSIGN(TestWhitelistManager); +}; + +class WhitelistManagerTest : public testing::Test { + public: + WhitelistManagerTest() + : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP) {} + + protected: + void CreateWhitelistManager() { + if (!whitelist_manager_.get()) { + whitelist_manager_.reset(new TestWhitelistManager()); + } + } + + void DownloadWhitelist(int response_code, const std::string& response) { + // Create and register factory. + net::TestURLFetcherFactory factory; + + CreateWhitelistManager(); + + AutofillMetrics::AutocheckoutWhitelistDownloadStatus status; + if (response_code == net::HTTP_OK) + status = AutofillMetrics::AUTOCHECKOUT_WHITELIST_DOWNLOAD_SUCCEEDED; + else + status = AutofillMetrics::AUTOCHECKOUT_WHITELIST_DOWNLOAD_FAILED; + EXPECT_CALL( + static_cast<const MockAutofillMetrics&>( + whitelist_manager_->GetMetricLogger()), + LogAutocheckoutWhitelistDownloadDuration(testing::_, status)).Times(1); + + 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); + } + + protected: + scoped_ptr<TestWhitelistManager> whitelist_manager_; + + private: + content::TestBrowserThreadBundle thread_bundle_; +}; + +TEST_F(WhitelistManagerTest, DownloadWhitelist) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalFormFilling); + DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); + ASSERT_EQ(2U, whitelist_manager_->url_prefixes().size()); + EXPECT_EQ("https://www.merchant1.com/checkout/", + whitelist_manager_->url_prefixes()[0]); + EXPECT_EQ("https://cart.merchant2.com/", + whitelist_manager_->url_prefixes()[1]); +} + +TEST_F(WhitelistManagerTest, DoNotDownloadWhitelistWhenSwitchIsOff) { + CreateWhitelistManager(); + whitelist_manager_->ScheduleDownload( + base::TimeDelta::FromSeconds(kTestDownloadInterval)); + EXPECT_FALSE(whitelist_manager_->did_start_download_timer()); +} + +TEST_F(WhitelistManagerTest, DoNotDownloadWhitelistIfAlreadyScheduled) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalFormFilling); + CreateWhitelistManager(); + // First attempt should schedule a download. + whitelist_manager_->ScheduleDownload( + base::TimeDelta::FromSeconds(kTestDownloadInterval)); + EXPECT_TRUE(whitelist_manager_->did_start_download_timer()); + // Second attempt should NOT schedule a download while there is already one. + whitelist_manager_->ScheduleDownload( + base::TimeDelta::FromSeconds(kTestDownloadInterval)); + EXPECT_FALSE(whitelist_manager_->did_start_download_timer()); + // It should schedule a new download when not in backoff mode. + whitelist_manager_->StopDownloadTimer(); + whitelist_manager_->ScheduleDownload( + base::TimeDelta::FromSeconds(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, whitelist_manager_->url_prefixes().size()); + + whitelist_manager_->StopDownloadTimer(); + DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); + EXPECT_EQ(2U, whitelist_manager_->url_prefixes().size()); + + whitelist_manager_->StopDownloadTimer(); + DownloadWhitelist(net::HTTP_INTERNAL_SERVER_ERROR, + kDownloadWhitelistResponse); + EXPECT_EQ(2U, whitelist_manager_->url_prefixes().size()); +} + +TEST_F(WhitelistManagerTest, DownloadWhitelistRetry) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalFormFilling); + + for (size_t i = 0; i < arraysize(kBackoffDelaysInMs); ++i) { + DownloadWhitelist(net::HTTP_INTERNAL_SERVER_ERROR, + kDownloadWhitelistResponse); + SCOPED_TRACE(base::StringPrintf("Testing retry %" PRIuS + ", expecting delay: %" PRId64, + i, + kBackoffDelaysInMs[i])); + EXPECT_EQ( + kBackoffDelaysInMs[i], + whitelist_manager_->download_interval().InMillisecondsRoundedUp()); + } +} + +TEST_F(WhitelistManagerTest, GetMatchedURLPrefix) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalFormFilling); + DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); + EXPECT_EQ(2U, whitelist_manager_->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/"))); +} + +TEST_F(WhitelistManagerTest, BypassWhitelist) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kEnableExperimentalFormFilling); + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kBypassAutocheckoutWhitelist); + DownloadWhitelist(net::HTTP_OK, kDownloadWhitelistResponse); + EXPECT_EQ(2U, whitelist_manager_->url_prefixes().size()); + + // Empty url. + EXPECT_EQ(std::string(), + whitelist_manager_->GetMatchedURLPrefix(GURL(std::string()))); + // Positive tests. + EXPECT_EQ("https://www.merchant1.com/checkout/", + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://www.merchant1.com/checkout/"))); + EXPECT_EQ("https://cart.merchant2.com/", + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://cart.merchant2.com/ShippingInfo?a=b&c=d"))); + // Bypass other urls. + EXPECT_EQ(std::string("https://bypass.me/"), + whitelist_manager_->GetMatchedURLPrefix( + GURL("https://bypass.me/"))); +} + +} // namespace autocheckout +} // namespace autofill + diff --git a/chromium/components/autofill/content/browser/autocheckout_manager.cc b/chromium/components/autofill/content/browser/autocheckout_manager.cc new file mode 100644 index 00000000000..be8192362e8 --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_manager.cc @@ -0,0 +1,581 @@ +// 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/content/browser/autocheckout_manager.h" + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/content/browser/autocheckout_request_manager.h" +#include "components/autofill/content/browser/autocheckout_statistic.h" +#include "components/autofill/content/browser/autocheckout_steps.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/web_element_descriptor.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "net/cookies/cookie_options.h" +#include "net/cookies/cookie_store.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" +#include "ui/gfx/rect.h" +#include "url/gurl.h" + +using content::RenderViewHost; +using content::WebContents; + +namespace autofill { + +namespace { + +const char kGoogleAccountsUrl[] = "https://accounts.google.com/"; + +// 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 = base::string16(); + field.value = base::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("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 address-line1")); + formdata.fields.push_back(BuildField("billing address-line2")); + 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("billing tel")); + formdata.fields.push_back(BuildField("shipping name")); + formdata.fields.push_back(BuildField("shipping address-line1")); + formdata.fields.push_back(BuildField("shipping address-line2")); + 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")); + formdata.fields.push_back(BuildField("shipping tel")); + return formdata; +} + +AutofillMetrics::AutocheckoutBuyFlowMetric AutocheckoutStatusToUmaMetric( + AutocheckoutStatus status) { + switch (status) { + case SUCCESS: + return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_SUCCESS; + case MISSING_FIELDMAPPING: + return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_FIELDMAPPING; + case MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING: + return AutofillMetrics:: + AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING; + case MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING: + return AutofillMetrics:: + AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING; + case MISSING_ADVANCE: + return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_ADVANCE_ELEMENT; + case CANNOT_PROCEED: + return AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_CANNOT_PROCEED; + case AUTOCHECKOUT_STATUS_NUM_STATUS: + NOTREACHED(); + } + + NOTREACHED(); + return AutofillMetrics::NUM_AUTOCHECKOUT_BUY_FLOW_METRICS; +} + +// Callback for retrieving Google Account cookies. |callback| is passed the +// retrieved cookies and posted back to the UI thread. |cookies| is any Google +// Account cookies. +void GetGoogleCookiesCallback( + const base::Callback<void(const std::string&)>& callback, + const std::string& cookies) { + content::BrowserThread::PostTask(content::BrowserThread::UI, + FROM_HERE, + base::Bind(callback, cookies)); +} + +// Gets Google Account cookies. Must be called on the IO thread. +// |request_context_getter| is a getter for the current request context. +// |callback| is called when retrieving cookies is completed. +void GetGoogleCookies( + scoped_refptr<net::URLRequestContextGetter> request_context_getter, + const base::Callback<void(const std::string&)>& callback) { + net::URLRequestContext* url_request_context = + request_context_getter->GetURLRequestContext(); + if (!url_request_context) + return; + + net::CookieStore* cookie_store = url_request_context->cookie_store(); + + base::Callback<void(const std::string&)> cookie_callback = base::Bind( + &GetGoogleCookiesCallback, + callback); + + net::CookieOptions cookie_options; + cookie_options.set_include_httponly(); + cookie_store->GetCookiesWithOptionsAsync(GURL(kGoogleAccountsUrl), + cookie_options, + cookie_callback); +} + +bool IsBillingGroup(FieldTypeGroup group) { + return group == ADDRESS_BILLING || + group == PHONE_BILLING || + group == NAME_BILLING; +} + +const char kTransactionIdNotSet[] = "transaction id not set"; + +} // namespace + +AutocheckoutManager::AutocheckoutManager(AutofillManager* autofill_manager) + : autofill_manager_(autofill_manager), + metric_logger_(new AutofillMetrics), + should_show_bubble_(true), + is_autocheckout_bubble_showing_(false), + in_autocheckout_flow_(false), + should_preserve_dialog_(false), + google_transaction_id_(kTransactionIdNotSet), + 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) { + FormStructure* form_structure = *iter; + form_structure->set_filled_by_autocheckout(true); + 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_->click_elements_before_form_fill, + page_meta_data_->click_elements_after_form_fill, + page_meta_data_->proceed_element_descriptor)); + // Record time taken for navigating current page. + RecordTimeTaken(page_meta_data_->current_page_number); +} + +void AutocheckoutManager::OnAutocheckoutPageCompleted( + AutocheckoutStatus status) { + if (!in_autocheckout_flow_) + return; + + DVLOG(2) << "OnAutocheckoutPageCompleted, page_no: " + << page_meta_data_->current_page_number + << " status: " + << status; + + DCHECK_NE(MISSING_FIELDMAPPING, status); + + SetStepProgressForPage( + page_meta_data_->current_page_number, + (status == SUCCESS) ? AUTOCHECKOUT_STEP_COMPLETED : + AUTOCHECKOUT_STEP_FAILED); + + if (page_meta_data_->IsEndOfAutofillableFlow() || status != SUCCESS) + EndAutocheckout(status); +} + +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(); + + // If there is no click element in the last page, then it's the real last page + // of the flow, and the dialog will be closed when the page navigates. + // Otherwise, the dialog should be preserved for the page loaded by the click + // element on the last page of the flow. + // Note, |should_preserve_dialog_| has to be computed at this point because + // |in_autocheckout_flow_| may change after |OnLoadedPageMetaData| is called. + should_preserve_dialog_ = in_autocheckout_flow_ || + (old_meta_data.get() && + old_meta_data->IsEndOfAutofillableFlow() && + old_meta_data->proceed_element_descriptor.retrieval_method != + WebElementDescriptor::NONE); + + // Don't log that the bubble could be displayed if the user entered an + // Autocheckout flow and sees the first page of the flow again due to an + // error. + if (IsStartOfAutofillableFlow() && !in_autocheckout_flow_) { + metric_logger_->LogAutocheckoutBubbleMetric( + AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED); + } + + // 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; + + AutocheckoutStatus status = SUCCESS; + + // Missing Autofill server results. + if (!page_meta_data_.get()) { + status = MISSING_FIELDMAPPING; + } else if (IsStartOfAutofillableFlow()) { + // Not possible unless Autocheckout failed to proceed. + status = CANNOT_PROCEED; + } else if (!page_meta_data_->IsInAutofillableFlow()) { + // Missing Autocheckout meta data in the Autofill server results. + status = MISSING_FIELDMAPPING; + } else if (page_meta_data_->current_page_number <= + old_meta_data->current_page_number) { + // Not possible unless Autocheckout failed to proceed. + status = CANNOT_PROCEED; + } + + // Encountered an error during the Autocheckout flow, probably to + // do with a problem on the previous page. + if (status != SUCCESS) { + SetStepProgressForPage(old_meta_data->current_page_number, + AUTOCHECKOUT_STEP_FAILED); + EndAutocheckout(status); + return; + } + + SetStepProgressForPage(page_meta_data_->current_page_number, + AUTOCHECKOUT_STEP_STARTED); + + FillForms(); +} + +void AutocheckoutManager::OnFormsSeen() { + should_show_bubble_ = true; +} + +bool AutocheckoutManager::ShouldIgnoreAjax() { + return in_autocheckout_flow_ && page_meta_data_->ignore_ajax; +} + +void AutocheckoutManager::MaybeShowAutocheckoutBubble( + const GURL& frame_url, + const gfx::RectF& bounding_box) { + if (!should_show_bubble_ || + is_autocheckout_bubble_showing_ || + !IsStartOfAutofillableFlow()) + return; + + base::Callback<void(const std::string&)> callback = base::Bind( + &AutocheckoutManager::ShowAutocheckoutBubble, + weak_ptr_factory_.GetWeakPtr(), + frame_url, + bounding_box); + + content::WebContents* web_contents = autofill_manager_->GetWebContents(); + if (!web_contents) + return; + + content::BrowserContext* browser_context = web_contents->GetBrowserContext(); + if(!browser_context) + return; + + scoped_refptr<net::URLRequestContextGetter> request_context = + scoped_refptr<net::URLRequestContextGetter>( + browser_context->GetRequestContext()); + + if (!request_context.get()) + return; + + base::Closure task = base::Bind(&GetGoogleCookies, request_context, callback); + + content::BrowserThread::PostTask(content::BrowserThread::IO, + FROM_HERE, + task); +} + +void AutocheckoutManager::ReturnAutocheckoutData( + const FormStructure* result, + const std::string& google_transaction_id) { + if (!result) { + // When user cancels the dialog, |result| is NULL. + // TODO(): add AutocheckoutStatus.USER_CANCELLED, and call + // EndAutocheckout(USER_CANCELLED) instead. + in_autocheckout_flow_ = false; + return; + } + + latency_statistics_.clear(); + last_step_completion_timestamp_ = base::TimeTicks().Now(); + google_transaction_id_ = google_transaction_id; + in_autocheckout_flow_ = true; + should_preserve_dialog_ = true; + metric_logger_->LogAutocheckoutBuyFlowMetric( + AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_STARTED); + + profile_.reset(new AutofillProfile()); + credit_card_.reset(new CreditCard()); + billing_address_.reset(new AutofillProfile()); + + for (size_t i = 0; i < result->field_count(); ++i) { + const AutofillType& type = result->field(i)->Type(); + const base::string16& value = result->field(i)->value; + ServerFieldType server_type = type.GetStorableType(); + if (server_type == CREDIT_CARD_VERIFICATION_CODE) { + cvv_ = result->field(i)->value; + continue; + } + FieldTypeGroup group = type.group(); + if (group == CREDIT_CARD) { + credit_card_->SetRawInfo(server_type, value); + // TODO(dgwallinga): Find a way of cleanly deprecating CREDIT_CARD_NAME. + // code.google.com/p/chromium/issues/detail?id=263498 + if (server_type == CREDIT_CARD_NAME) + billing_address_->SetRawInfo(NAME_BILLING_FULL, value); + } else if (server_type == ADDRESS_HOME_COUNTRY) { + if (IsBillingGroup(group)) + billing_address_->SetInfo(type, value, autofill_manager_->app_locale()); + else + profile_->SetInfo(type, value, autofill_manager_->app_locale()); + } else if (IsBillingGroup(group)) { + billing_address_->SetRawInfo(server_type, value); + } else { + profile_->SetRawInfo(server_type, value); + } + } + + // Page types only available in first-page meta data, so save + // them for use later as we navigate. + page_types_ = page_meta_data_->page_types; + SetStepProgressForPage(page_meta_data_->current_page_number, + AUTOCHECKOUT_STEP_STARTED); + + FillForms(); +} + +void AutocheckoutManager::set_metric_logger( + scoped_ptr<AutofillMetrics> metric_logger) { + metric_logger_ = metric_logger.Pass(); +} + +void AutocheckoutManager::MaybeShowAutocheckoutDialog( + const GURL& frame_url, + AutocheckoutBubbleState state) { + is_autocheckout_bubble_showing_ = false; + + // User has taken action on the bubble, don't offer bubble again. + if (state != AUTOCHECKOUT_BUBBLE_IGNORED) + should_show_bubble_ = false; + + if (state != AUTOCHECKOUT_BUBBLE_ACCEPTED) + return; + + base::Callback<void(const FormStructure*, const std::string&)> callback = + base::Bind(&AutocheckoutManager::ReturnAutocheckoutData, + weak_ptr_factory_.GetWeakPtr()); + autofill_manager_->ShowRequestAutocompleteDialog(BuildAutocheckoutFormData(), + frame_url, + DIALOG_TYPE_AUTOCHECKOUT, + callback); + + for (std::map<int, std::vector<AutocheckoutStepType> >::const_iterator + it = page_meta_data_->page_types.begin(); + it != page_meta_data_->page_types.end(); ++it) { + for (size_t i = 0; i < it->second.size(); ++i) { + autofill_manager_->delegate()->AddAutocheckoutStep(it->second[i]); + } + } +} + +void AutocheckoutManager::ShowAutocheckoutBubble( + const GURL& frame_url, + const gfx::RectF& bounding_box, + const std::string& cookies) { + DCHECK(thread_checker_.CalledOnValidThread()); + + base::Callback<void(AutocheckoutBubbleState)> callback = base::Bind( + &AutocheckoutManager::MaybeShowAutocheckoutDialog, + weak_ptr_factory_.GetWeakPtr(), + frame_url); + is_autocheckout_bubble_showing_ = + autofill_manager_->delegate()->ShowAutocheckoutBubble( + bounding_box, + cookies.find("LSID") != std::string::npos, + 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::SetValue(const AutofillField& field, + FormFieldData* field_to_fill) { + // No-op if Autofill server doesn't know about the field. + if (field.server_type() == NO_SERVER_DATA) + return; + + const AutofillType& type = field.Type(); + + ServerFieldType server_type = type.GetStorableType(); + if (server_type == FIELD_WITH_DEFAULT_VALUE) { + // 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. + // + // FIELD_WITH_DEFAULT_VALUE can also be used for selects, the correspondent + // example of the radio buttons example above is: + // <SELECT name="sex"> + // <OPTION value="female">Female</OPTION> + // <OPTION value="male">Male</OPTION> + // </SELECT> + base::string16 default_value = UTF8ToUTF16(field.default_value()); + if (field.is_checkable) { + // 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); + } else if (field.form_control_type == "select-one") { + field_to_fill->value = default_value; + } else { + // FIELD_WITH_DEFAULT_VALUE should not be used for other type of fields. + NOTREACHED(); + } + return; + } + + // Handle verification code directly. + if (server_type == CREDIT_CARD_VERIFICATION_CODE) { + field_to_fill->value = cvv_; + return; + } + + if (type.group() == CREDIT_CARD) { + credit_card_->FillFormField( + field, 0, autofill_manager_->app_locale(), field_to_fill); + } else if (IsBillingGroup(type.group())) { + billing_address_->FillFormField( + field, 0, autofill_manager_->app_locale(), field_to_fill); + } else { + profile_->FillFormField( + field, 0, autofill_manager_->app_locale(), field_to_fill); + } +} + +void AutocheckoutManager::SendAutocheckoutStatus(AutocheckoutStatus status) { + // To ensure stale data isn't being sent. + DCHECK_NE(kTransactionIdNotSet, google_transaction_id_); + + AutocheckoutRequestManager::CreateForBrowserContext( + autofill_manager_->GetWebContents()->GetBrowserContext()); + AutocheckoutRequestManager* autocheckout_request_manager = + AutocheckoutRequestManager::FromBrowserContext( + autofill_manager_->GetWebContents()->GetBrowserContext()); + // It is assumed that the domain Autocheckout starts on does not change + // during the flow. If this proves to be incorrect, the |source_url| from + // AutofillDialogControllerImpl will need to be provided in its callback in + // addition to the Google transaction id. + autocheckout_request_manager->SendAutocheckoutStatus( + status, + autofill_manager_->GetWebContents()->GetURL(), + latency_statistics_, + google_transaction_id_); + + // Log the result of this Autocheckout flow to UMA. + metric_logger_->LogAutocheckoutBuyFlowMetric( + AutocheckoutStatusToUmaMetric(status)); + + google_transaction_id_ = kTransactionIdNotSet; +} + +void AutocheckoutManager::SetStepProgressForPage( + int page_number, + AutocheckoutStepStatus status) { + if (page_types_.count(page_number) == 1) { + for (size_t i = 0; i < page_types_[page_number].size(); ++i) { + autofill_manager_->delegate()->UpdateAutocheckoutStep( + page_types_[page_number][i], status); + } + } +} + +void AutocheckoutManager::RecordTimeTaken(int page_number) { + AutocheckoutStatistic statistic; + statistic.page_number = page_number; + if (page_types_.count(page_number) == 1) { + for (size_t i = 0; i < page_types_[page_number].size(); ++i) { + statistic.steps.push_back(page_types_[page_number][i]); + } + } + + statistic.time_taken = + base::TimeTicks().Now() - last_step_completion_timestamp_; + latency_statistics_.push_back(statistic); + + // Reset timestamp. + last_step_completion_timestamp_ = base::TimeTicks().Now(); +} + +void AutocheckoutManager::EndAutocheckout(AutocheckoutStatus status) { + DCHECK(in_autocheckout_flow_); + + DVLOG(2) << "EndAutocheckout at step: " + << page_meta_data_->current_page_number + << " with status: " + << status; + + SendAutocheckoutStatus(status); + if (status == SUCCESS) + autofill_manager_->delegate()->OnAutocheckoutSuccess(); + else + autofill_manager_->delegate()->OnAutocheckoutError(); + in_autocheckout_flow_ = false; +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/autocheckout_manager.h b/chromium/components/autofill/content/browser/autocheckout_manager.h new file mode 100644 index 00000000000..b265d8e2ec4 --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_manager.h @@ -0,0 +1,191 @@ +// 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_CONTENT_BROWSER_AUTOCHECKOUT_MANAGER_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_MANAGER_H_ + +#include <string> + +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "base/threading/thread_checker.h" +#include "base/time/time.h" +#include "components/autofill/content/browser/autocheckout_page_meta_data.h" +#include "components/autofill/content/browser/autocheckout_statistic.h" +#include "components/autofill/core/browser/autocheckout_bubble_state.h" +#include "components/autofill/core/common/autocheckout_status.h" + +class GURL; + +namespace gfx { +class RectF; +} + +namespace net { +class URLRequestContextGetter; +} + +namespace autofill { + +class AutofillField; +class AutofillManager; +class AutofillMetrics; +class AutofillProfile; +class CreditCard; +class FormStructure; + +struct FormData; +struct FormFieldData; + +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(); + + // Called to signal that the renderer has completed processing a page in the + // Autocheckout flow. |status| is the reason for the failure, or |SUCCESS| if + // there were no errors. + void OnAutocheckoutPageCompleted(AutocheckoutStatus status); + + // 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(); + + // Whether ajax on the current page should be ignored during + // an Autocheckout flow. + bool ShouldIgnoreAjax(); + + // 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. |bounding_box| is the bounding box of the input field in + // focus. + virtual void MaybeShowAutocheckoutBubble(const GURL& frame_url, + const gfx::RectF& bounding_box); + + // Determine whether we should keep the dialog visible. + bool should_preserve_dialog() const { return should_preserve_dialog_; } + + void set_should_show_bubble(bool should_show_bubble) { + should_show_bubble_ = should_show_bubble; + } + + bool is_autocheckout_bubble_showing() const { + return is_autocheckout_bubble_showing_; + } + + protected: + // Exposed for testing. + bool in_autocheckout_flow() const { return in_autocheckout_flow_; } + + // Exposed for testing. + bool should_show_bubble() const { return should_show_bubble_; } + + // Show the requestAutocomplete dialog if |state| is + // AUTOCHECKOUT_BUBBLE_ACCEPTED. Also, does bookkeeping for whether or not + // the bubble is showing. + virtual void MaybeShowAutocheckoutDialog(const GURL& frame_url, + AutocheckoutBubbleState state); + + // Callback called from AutofillDialogController on filling up the UI form. + void ReturnAutocheckoutData(const FormStructure* result, + const std::string& google_transaction_id); + + const AutofillMetrics& metric_logger() const { return *metric_logger_; } + void set_metric_logger(scoped_ptr<AutofillMetrics> metric_logger); + + private: + // Shows the Autocheckout bubble. Must be called on the UI thread. |frame_url| + // is the page where Autocheckout is being initiated. |bounding_box| is the + // bounding box of the input field in focus. |cookies| is any Google Account + // cookies. + void ShowAutocheckoutBubble(const GURL& frame_url, + const gfx::RectF& bounding_box, + const std::string& cookies); + + // 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; + + // Sends |status| to Online Wallet using AutocheckoutRequestManager. + void SendAutocheckoutStatus(AutocheckoutStatus status); + + // 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); + + // Sets the progress of all steps for the given page to the provided value. + void SetStepProgressForPage(int page_number, AutocheckoutStepStatus status); + + // Account time spent between now and |last_step_completion_timestamp_| + // towards |page_number|. + void RecordTimeTaken(int page_number); + + // Terminate the Autocheckout flow and send Autocheckout status to Wallet + // server. + void EndAutocheckout(AutocheckoutStatus status); + + AutofillManager* autofill_manager_; // WEAK; owns us + + // Credit card verification code. + base::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_; + + // Billing address built using data supplied by requestAutocomplete dialog. + scoped_ptr<AutofillProfile> billing_address_; + + // Autocheckout specific page meta data of current page. + scoped_ptr<AutocheckoutPageMetaData> page_meta_data_; + + scoped_ptr<AutofillMetrics> metric_logger_; + + // Whether or not the Autocheckout bubble should be shown to user. + bool should_show_bubble_; + + // Whether or not the Autocheckout bubble is being displayed to the user. + bool is_autocheckout_bubble_showing_; + + // Whether or not the user is in an Autocheckout flow. + bool in_autocheckout_flow_; + + // Whether or not the currently visible dialog, if there is one, should be + // preserved. + bool should_preserve_dialog_; + + // AutocheckoutStepTypes for the various pages of the flow. + std::map<int, std::vector<AutocheckoutStepType> > page_types_; + + // Timestamp of last step's completion. + base::TimeTicks last_step_completion_timestamp_; + + // Per page latency statistics. + std::vector<AutocheckoutStatistic> latency_statistics_; + + std::string google_transaction_id_; + + base::WeakPtrFactory<AutocheckoutManager> weak_ptr_factory_; + + base::ThreadChecker thread_checker_; + + DISALLOW_COPY_AND_ASSIGN(AutocheckoutManager); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_MANAGER_H_ diff --git a/chromium/components/autofill/content/browser/autocheckout_manager_unittest.cc b/chromium/components/autofill/content/browser/autocheckout_manager_unittest.cc new file mode 100644 index 00000000000..c8e425566ee --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_manager_unittest.cc @@ -0,0 +1,949 @@ +// 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 <map> + +#include "base/strings/utf_string_conversions.h" +#include "base/tuple.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "components/autofill/content/browser/autocheckout_manager.h" +#include "components/autofill/core/browser/autofill_common_test.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/browser/test_autofill_driver.h" +#include "components/autofill/core/browser/test_autofill_manager_delegate.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/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 "content/public/test/test_utils.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 Tuple4<std::vector<FormData>, + std::vector<WebElementDescriptor>, + std::vector<WebElementDescriptor>, + WebElementDescriptor> AutofillParam; + +enum ProceedElementPresence { + NO_PROCEED_ELEMENT, + HAS_PROCEED_ELEMENT, +}; + +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<ServerFieldType>& 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]); + // Set heuristic type to make sure that server_types are used and not + // heuritic type. + form_structure->field(i)->set_heuristic_type(CREDIT_CARD_NUMBER); + } + + return form_structure.Pass(); +} + +scoped_ptr<FormStructure> CreateTestAddressFormStructure() { + std::vector<ServerFieldType> autofill_types; + autofill_types.push_back(NAME_FULL); + autofill_types.push_back(PHONE_HOME_WHOLE_NUMBER); + autofill_types.push_back(EMAIL_ADDRESS); + 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); + autofill_types.push_back(NO_SERVER_DATA); + return CreateTestFormStructure(autofill_types); +} + +scoped_ptr<FormStructure> CreateTestCreditCardFormStructure() { + std::vector<ServerFieldType> 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_BILLING_LINE1); + autofill_types.push_back(ADDRESS_BILLING_CITY); + autofill_types.push_back(ADDRESS_BILLING_STATE); + autofill_types.push_back(ADDRESS_BILLING_COUNTRY); + autofill_types.push_back(ADDRESS_BILLING_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); + FormFieldData select = BuildField("SelectField"); + select.form_control_type = "select-one"; + form.fields.push_back(select); + + 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"); + form_structure->field(2)->set_server_type(FIELD_WITH_DEFAULT_VALUE); + form_structure->field(2)->set_default_value("Default Value"); + + return form_structure.Pass(); +} + +void PopulateClickElement(WebElementDescriptor* proceed_element, + const std::string& descriptor) { + proceed_element->descriptor = descriptor; + proceed_element->retrieval_method = WebElementDescriptor::ID; +} + +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->page_types[0].push_back(AUTOCHECKOUT_STEP_SHIPPING); + start_of_flow->page_types[1].push_back(AUTOCHECKOUT_STEP_DELIVERY); + start_of_flow->page_types[2].push_back(AUTOCHECKOUT_STEP_BILLING); + PopulateClickElement(&start_of_flow->proceed_element_descriptor, "#foo"); + 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; + PopulateClickElement(&in_flow->proceed_element_descriptor, "#foo"); + return in_flow.Pass(); +} + +scoped_ptr<AutocheckoutPageMetaData> CreateNotInFlowMetaData() { + scoped_ptr<AutocheckoutPageMetaData> not_in_flow( + new AutocheckoutPageMetaData()); + PopulateClickElement(¬_in_flow->proceed_element_descriptor, "#foo"); + return not_in_flow.Pass(); +} + +scoped_ptr<AutocheckoutPageMetaData> CreateEndOfFlowMetaData( + ProceedElementPresence proceed_element_presence) { + scoped_ptr<AutocheckoutPageMetaData> end_of_flow( + new AutocheckoutPageMetaData()); + end_of_flow->current_page_number = 2; + end_of_flow->total_pages = 3; + if (proceed_element_presence == HAS_PROCEED_ELEMENT) + PopulateClickElement(&end_of_flow->proceed_element_descriptor, "#foo"); + return end_of_flow.Pass(); +} + +scoped_ptr<AutocheckoutPageMetaData> CreateOnePageFlowMetaData( + ProceedElementPresence proceed_element_presence) { + scoped_ptr<AutocheckoutPageMetaData> one_page_flow( + new AutocheckoutPageMetaData()); + one_page_flow->current_page_number = 0; + one_page_flow->total_pages = 1; + one_page_flow->page_types[0].push_back(AUTOCHECKOUT_STEP_SHIPPING); + one_page_flow->page_types[0].push_back(AUTOCHECKOUT_STEP_DELIVERY); + one_page_flow->page_types[0].push_back(AUTOCHECKOUT_STEP_BILLING); + if (proceed_element_presence == HAS_PROCEED_ELEMENT) + PopulateClickElement(&one_page_flow->proceed_element_descriptor, "#foo"); + return one_page_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(); +} + +scoped_ptr<AutocheckoutPageMetaData> CreateMultiClickMetaData() { + scoped_ptr<AutocheckoutPageMetaData> metadata(new AutocheckoutPageMetaData()); + metadata->current_page_number = 1; + metadata->total_pages = 3; + PopulateClickElement(&metadata->proceed_element_descriptor, "#foo"); + WebElementDescriptor element; + PopulateClickElement(&element, "#before_form_fill_1"); + metadata->click_elements_before_form_fill.push_back(element); + PopulateClickElement(&element, "#before_form_fill_2"); + metadata->click_elements_before_form_fill.push_back(element); + PopulateClickElement(&element, "#after_form_fill"); + metadata->click_elements_after_form_fill.push_back(element); + return metadata.Pass(); +} + +struct TestField { + const char* const field_type; + const char* const field_value; + ServerFieldType 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}, + {"billing-street-address", "Billing Street", ADDRESS_BILLING_LINE1}, + {"billing-locality", "Billing City", ADDRESS_BILLING_CITY}, + {"billing-region", "BillingState", ADDRESS_BILLING_STATE}, + {"billing-country", "Canada", ADDRESS_BILLING_COUNTRY}, + {"billing-postal-code", "11111", ADDRESS_BILLING_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), + should_autoclick_bubble_(true) {} + + virtual ~MockAutofillManagerDelegate() {} + + virtual void HideRequestAutocompleteDialog() OVERRIDE { + request_autocomplete_dialog_open_ = false; + } + + MOCK_METHOD0(OnAutocheckoutError, void()); + MOCK_METHOD0(OnAutocheckoutSuccess, void()); + + virtual bool ShowAutocheckoutBubble( + const gfx::RectF& bounds, + bool is_google_user, + const base::Callback<void(AutocheckoutBubbleState)>& callback) OVERRIDE { + autocheckout_bubble_shown_ = true; + if (should_autoclick_bubble_) + callback.Run(AUTOCHECKOUT_BUBBLE_ACCEPTED); + return true; + } + + virtual void ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + DialogType dialog_type, + const base::Callback<void(const FormStructure*, + const std::string&)>& callback) OVERRIDE { + request_autocomplete_dialog_open_ = true; + callback.Run(user_supplied_data_.get(), "google_transaction_id"); + } + + virtual void AddAutocheckoutStep(AutocheckoutStepType step_type) OVERRIDE { + if (autocheckout_steps_.count(step_type) == 0) + autocheckout_steps_[step_type] = AUTOCHECKOUT_STEP_UNSTARTED; + } + + virtual void UpdateAutocheckoutStep( + AutocheckoutStepType step_type, + AutocheckoutStepStatus step_status) OVERRIDE { + autocheckout_steps_[step_type] = step_status; + } + + 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_; + } + + void set_should_autoclick_bubble(bool should_autoclick_bubble) { + should_autoclick_bubble_ = should_autoclick_bubble; + } + + bool AutocheckoutStepExistsWithStatus( + AutocheckoutStepType step_type, + AutocheckoutStepStatus step_status) const { + std::map<AutocheckoutStepType, AutocheckoutStepStatus>::const_iterator it = + autocheckout_steps_.find(step_type); + return it != autocheckout_steps_.end() && it->second == step_status; + } + + private: + // Whether or not ShowRequestAutocompleteDialog method has been called. + bool request_autocomplete_dialog_open_; + + // Whether or not Autocheckout bubble is displayed to user. + bool autocheckout_bubble_shown_; + + // Whether or not to accept the Autocheckout bubble when offered. + bool should_autoclick_bubble_; + + // User specified data that would be returned to AutocheckoutManager when + // dialog is accepted. + scoped_ptr<FormStructure> user_supplied_data_; + + // Step status of various Autocheckout steps in this checkout flow. + std::map<AutocheckoutStepType, AutocheckoutStepStatus> autocheckout_steps_; +}; + +class TestAutofillManager : public AutofillManager { + public: + explicit TestAutofillManager(AutofillDriver* driver, + AutofillManagerDelegate* delegate) + : AutofillManager(driver, delegate, NULL) { + } + virtual ~TestAutofillManager() {} + + void SetFormStructure(scoped_ptr<FormStructure> form_structure) { + form_structures()->clear(); + form_structures()->push_back(form_structure.release()); + } +}; + +class MockAutofillMetrics : public AutofillMetrics { + public: + MockAutofillMetrics() {} + MOCK_CONST_METHOD1(LogAutocheckoutBubbleMetric, void(BubbleMetric)); + MOCK_CONST_METHOD1(LogAutocheckoutBuyFlowMetric, + void(AutocheckoutBuyFlowMetric)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillMetrics); +}; + +class TestAutocheckoutManager: public AutocheckoutManager { + public: + explicit TestAutocheckoutManager(AutofillManager* autofill_manager) + : AutocheckoutManager(autofill_manager) { + set_metric_logger(scoped_ptr<AutofillMetrics>(new MockAutofillMetrics)); + } + + const MockAutofillMetrics& metric_logger() const { + return static_cast<const MockAutofillMetrics&>( + AutocheckoutManager::metric_logger()); + } + + virtual void MaybeShowAutocheckoutBubble( + const GURL& frame_url, + const gfx::RectF& bounding_box) OVERRIDE { + AutocheckoutManager::MaybeShowAutocheckoutBubble(frame_url, + bounding_box); + // Needed for AutocheckoutManager to post task on IO thread. + content::RunAllPendingInMessageLoop(BrowserThread::IO); + } + + using AutocheckoutManager::in_autocheckout_flow; + using AutocheckoutManager::should_show_bubble; + using AutocheckoutManager::MaybeShowAutocheckoutDialog; + using AutocheckoutManager::ReturnAutocheckoutData; +}; + +} // namespace + +class AutocheckoutManagerTest : public ChromeRenderViewHostTestHarness { + protected: + virtual void SetUp() OVERRIDE { + SetThreadBundleOptions(content::TestBrowserThreadBundle::REAL_IO_THREAD); + ChromeRenderViewHostTestHarness::SetUp(); + autofill_manager_delegate_.reset(new MockAutofillManagerDelegate()); + autofill_driver_.reset(new TestAutofillDriver(web_contents())); + autofill_manager_.reset(new TestAutofillManager( + autofill_driver_.get(), + 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(); + autofill_driver_.reset(); + ChromeRenderViewHostTestHarness::TearDown(); + } + + 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 CheckFillFormsAndClickIpc( + ProceedElementPresence proceed_element_presence) { + EXPECT_EQ(1U, process()->sink().message_count()); + uint32 kMsgID = AutofillMsg_FillFormsAndClick::ID; + const IPC::Message* message = + process()->sink().GetFirstMessageMatching(kMsgID); + EXPECT_TRUE(message); + AutofillParam autofill_param; + AutofillMsg_FillFormsAndClick::Read(message, &autofill_param); + if (proceed_element_presence == HAS_PROCEED_ELEMENT) { + EXPECT_EQ(WebElementDescriptor::ID, autofill_param.d.retrieval_method); + EXPECT_EQ("#foo", autofill_param.d.descriptor); + } else { + EXPECT_EQ(WebElementDescriptor::NONE, autofill_param.d.retrieval_method); + } + ClearIpcSink(); + } + + void ClearIpcSink() { + process()->sink().ClearMessages(); + } + + void OpenRequestAutocompleteDialog() { + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_FALSE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBubbleMetric( + AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); + // Simulate the user submitting some data via the requestAutocomplete UI. + autofill_manager_delegate_->SetUserSuppliedData( + FakeUserSubmittedFormStructure()); + GURL frame_url; + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric( + AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_STARTED)).Times(1); + autocheckout_manager_->MaybeShowAutocheckoutDialog( + frame_url, + AUTOCHECKOUT_BUBBLE_ACCEPTED); + CheckFillFormsAndClickIpc(HAS_PROCEED_ELEMENT); + EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); + EXPECT_TRUE(autofill_manager_delegate_->request_autocomplete_dialog_open()); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, + AUTOCHECKOUT_STEP_STARTED)); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, + AUTOCHECKOUT_STEP_UNSTARTED)); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_BILLING, + AUTOCHECKOUT_STEP_UNSTARTED)); + } + + void HideRequestAutocompleteDialog() { + EXPECT_TRUE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + autofill_manager_delegate_->HideRequestAutocompleteDialog(); + EXPECT_FALSE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + } + + // Test a multi-page Autocheckout flow end to end. + // |proceed_element_presence_on_last_page| indicates whether the last page + // of the flow should have a proceed element. + void TestFullAutocheckoutFlow( + ProceedElementPresence proceed_element_presence_on_last_page) { + // Test for progression through last page. + OpenRequestAutocompleteDialog(); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, + AUTOCHECKOUT_STEP_STARTED)); + // Complete the first page. + autocheckout_manager_->OnAutocheckoutPageCompleted(SUCCESS); + EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, + AUTOCHECKOUT_STEP_COMPLETED)); + + // Go to the second page. + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutSuccess()).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); + EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); + CheckFillFormsAndClickIpc(HAS_PROCEED_ELEMENT); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, + AUTOCHECKOUT_STEP_STARTED)); + autocheckout_manager_->OnAutocheckoutPageCompleted(SUCCESS); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, + AUTOCHECKOUT_STEP_COMPLETED)); + + // Go to the third page. + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric( + AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_SUCCESS)).Times(1); + autocheckout_manager_->OnLoadedPageMetaData( + CreateEndOfFlowMetaData(proceed_element_presence_on_last_page)); + CheckFillFormsAndClickIpc(proceed_element_presence_on_last_page); + EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_BILLING, + AUTOCHECKOUT_STEP_STARTED)); + autocheckout_manager_->OnAutocheckoutPageCompleted(SUCCESS); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_BILLING, + AUTOCHECKOUT_STEP_COMPLETED)); + + EXPECT_TRUE(autofill_manager_delegate_->request_autocomplete_dialog_open()); + + // Go to the page after the flow. + autocheckout_manager_->OnLoadedPageMetaData( + scoped_ptr<AutocheckoutPageMetaData>()); + EXPECT_EQ(proceed_element_presence_on_last_page == HAS_PROCEED_ELEMENT, + autocheckout_manager_->should_preserve_dialog()); + // Go to another page and we should not preserve the dialog now. + autocheckout_manager_->OnLoadedPageMetaData( + scoped_ptr<AutocheckoutPageMetaData>()); + EXPECT_FALSE(autocheckout_manager_->should_preserve_dialog()); + } + + // Test a signle-page Autocheckout flow. |proceed_element_presence| indicates + // whether the page should have a proceed element. + void TestSinglePageFlow(ProceedElementPresence proceed_element_presence) { + // Test one page flow. + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_FALSE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBubbleMetric( + AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutSuccess()).Times(1); + autocheckout_manager_->OnLoadedPageMetaData( + CreateOnePageFlowMetaData(proceed_element_presence)); + // Simulate the user submitting some data via the requestAutocomplete UI. + autofill_manager_delegate_->SetUserSuppliedData( + FakeUserSubmittedFormStructure()); + GURL frame_url; + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric( + AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_STARTED)).Times(1); + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric( + AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_SUCCESS)).Times(1); + autocheckout_manager_->MaybeShowAutocheckoutDialog( + frame_url, + AUTOCHECKOUT_BUBBLE_ACCEPTED); + autocheckout_manager_->OnAutocheckoutPageCompleted(SUCCESS); + CheckFillFormsAndClickIpc(proceed_element_presence); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_TRUE(autocheckout_manager_->should_preserve_dialog()); + EXPECT_TRUE(autofill_manager_delegate_->request_autocomplete_dialog_open()); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, + AUTOCHECKOUT_STEP_COMPLETED)); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, + AUTOCHECKOUT_STEP_COMPLETED)); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_BILLING, + AUTOCHECKOUT_STEP_COMPLETED)); + // Go to the page after the flow. + autocheckout_manager_->OnLoadedPageMetaData( + scoped_ptr<AutocheckoutPageMetaData>()); + EXPECT_EQ(proceed_element_presence == HAS_PROCEED_ELEMENT, + autocheckout_manager_->should_preserve_dialog()); + // Go to another page, and we should not preserve the dialog now. + autocheckout_manager_->OnLoadedPageMetaData( + scoped_ptr<AutocheckoutPageMetaData>()); + EXPECT_FALSE(autocheckout_manager_->should_preserve_dialog()); + } + + protected: + scoped_ptr<TestAutofillDriver> autofill_driver_; + scoped_ptr<TestAutofillManager> autofill_manager_; + scoped_ptr<TestAutocheckoutManager> autocheckout_manager_; + scoped_ptr<MockAutofillManagerDelegate> autofill_manager_delegate_; +}; + +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(9U, 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); + EXPECT_EQ(ASCIIToUTF16("Fake Street"), filled_forms[0].fields[3].value); + EXPECT_EQ(ASCIIToUTF16("Mocked City"), filled_forms[0].fields[4].value); + EXPECT_EQ(ASCIIToUTF16("California"), filled_forms[0].fields[5].value); + EXPECT_EQ(ASCIIToUTF16("United States"), filled_forms[0].fields[6].value); + EXPECT_EQ(ASCIIToUTF16("49012"), filled_forms[0].fields[7].value); + // Last field should not be filled, because there is no server mapping + // available for it. + EXPECT_EQ(ASCIIToUTF16("SomeField"), filled_forms[0].fields[8].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("Billing Street"), filled_forms[0].fields[5].value); + EXPECT_EQ(ASCIIToUTF16("Billing City"), filled_forms[0].fields[6].value); + EXPECT_EQ(ASCIIToUTF16("BillingState"), filled_forms[0].fields[7].value); + EXPECT_EQ(ASCIIToUTF16("Canada"), filled_forms[0].fields[8].value); + EXPECT_EQ(ASCIIToUTF16("11111"), 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(3U, 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); + EXPECT_EQ(ASCIIToUTF16("Default Value"), filled_forms[0].fields[2].value); +} + +TEST_F(AutocheckoutManagerTest, OnFormsSeenTest) { + GURL frame_url; + gfx::RectF bounding_box; + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBubbleMetric( + AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); + autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); + + EXPECT_FALSE(autocheckout_manager_->should_show_bubble()); + // OnFormsSeen resets whether or not the bubble was shown. + autocheckout_manager_->OnFormsSeen(); + EXPECT_TRUE(autocheckout_manager_->should_show_bubble()); +} + +TEST_F(AutocheckoutManagerTest, OnClickFailedTestMissingAdvance) { + OpenRequestAutocompleteDialog(); + + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + EXPECT_CALL( + autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric( + AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_ADVANCE_ELEMENT)) + .Times(1); + autocheckout_manager_->OnAutocheckoutPageCompleted(MISSING_ADVANCE); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_TRUE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, + AUTOCHECKOUT_STEP_FAILED)); +} + +TEST_F(AutocheckoutManagerTest, OnClickFailedTestMissingClickBeforeFilling) { + OpenRequestAutocompleteDialog(); + + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + EXPECT_CALL( + autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric(AutofillMetrics:: + AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING)) + .Times(1); + autocheckout_manager_->OnAutocheckoutPageCompleted( + MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_TRUE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); +} + +TEST_F(AutocheckoutManagerTest, OnClickFailedTestMissingClickAfterFilling) { + OpenRequestAutocompleteDialog(); + + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + EXPECT_CALL( + autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric(AutofillMetrics:: + AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING)) + .Times(1); + autocheckout_manager_->OnAutocheckoutPageCompleted( + MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_TRUE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); +} + +TEST_F(AutocheckoutManagerTest, MaybeShowAutocheckoutBubbleTest) { + GURL frame_url; + gfx::RectF bounding_box; + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBubbleMetric( + AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); + // MaybeShowAutocheckoutBubble shows bubble if it has not been shown. + autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); + EXPECT_FALSE(autocheckout_manager_->should_show_bubble()); + 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. + autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); + EXPECT_FALSE(autocheckout_manager_->should_show_bubble()); + EXPECT_FALSE(autofill_manager_delegate_->autocheckout_bubble_shown()); + EXPECT_FALSE(autofill_manager_delegate_->request_autocomplete_dialog_open()); +} + +TEST_F(AutocheckoutManagerTest, OnLoadedPageMetaDataMissingMetaData) { + // Gettting no meta data after any autocheckout page is an error. + OpenRequestAutocompleteDialog(); + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + EXPECT_CALL( + autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric( + AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_FIELDMAPPING)) + .Times(1); + autocheckout_manager_->OnLoadedPageMetaData( + scoped_ptr<AutocheckoutPageMetaData>()); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_EQ(0U, process()->sink().message_count()); + EXPECT_TRUE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); +} + +TEST_F(AutocheckoutManagerTest, OnLoadedPageMetaDataRepeatedStartPage) { + // Getting start page twice in a row is an error. + OpenRequestAutocompleteDialog(); + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + EXPECT_CALL( + autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric( + AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_CANNOT_PROCEED)).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_EQ(0U, process()->sink().message_count()); + EXPECT_TRUE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_SHIPPING, + AUTOCHECKOUT_STEP_FAILED)); +} + +TEST_F(AutocheckoutManagerTest, OnLoadedPageMetaDataRepeatedPage) { + // Repeating a page is an error. + OpenRequestAutocompleteDialog(); + // Go to second page. + autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); + EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); + CheckFillFormsAndClickIpc(HAS_PROCEED_ELEMENT); + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + EXPECT_CALL( + autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric( + AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_CANNOT_PROCEED)).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_EQ(0U, process()->sink().message_count()); + EXPECT_TRUE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, + AUTOCHECKOUT_STEP_FAILED)); +} + +TEST_F(AutocheckoutManagerTest, OnLoadedPageMetaDataNotInFlow) { + // Repeating a page is an error. + OpenRequestAutocompleteDialog(); + // Go to second page. + autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); + EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); + CheckFillFormsAndClickIpc(HAS_PROCEED_ELEMENT); + EXPECT_CALL(*autofill_manager_delegate_, OnAutocheckoutError()).Times(1); + EXPECT_CALL( + autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric( + AutofillMetrics::AUTOCHECKOUT_BUY_FLOW_MISSING_FIELDMAPPING)) + .Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateNotInFlowMetaData()); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_EQ(0U, process()->sink().message_count()); + EXPECT_TRUE( + autofill_manager_delegate_->request_autocomplete_dialog_open()); + EXPECT_TRUE(autofill_manager_delegate_ + ->AutocheckoutStepExistsWithStatus(AUTOCHECKOUT_STEP_DELIVERY, + AUTOCHECKOUT_STEP_FAILED)); +} + +TEST_F(AutocheckoutManagerTest, + OnLoadedPageMetaDataShouldNotFillFormsIfNotInFlow) { + // If not in flow, OnLoadedPageMetaData does not fill forms. + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBubbleMetric( + AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); + // Go to second page. + autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); + EXPECT_EQ(0U, process()->sink().message_count()); +} + +TEST_F(AutocheckoutManagerTest, FullAutocheckoutFlow) { + TestFullAutocheckoutFlow(HAS_PROCEED_ELEMENT); +} + +TEST_F(AutocheckoutManagerTest, FullAutocheckoutFlowNoClickOnLastPage) { + TestFullAutocheckoutFlow(NO_PROCEED_ELEMENT); +} + +TEST_F(AutocheckoutManagerTest, CancelledAutocheckoutFlow) { + // Test for progression through last page. + OpenRequestAutocompleteDialog(); + // Go to second page. + autocheckout_manager_->OnLoadedPageMetaData(CreateInFlowMetaData()); + EXPECT_TRUE(autocheckout_manager_->in_autocheckout_flow()); + CheckFillFormsAndClickIpc(HAS_PROCEED_ELEMENT); + + // Cancel the flow. + autocheckout_manager_->ReturnAutocheckoutData(NULL, std::string()); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + + // Go to third page. + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBuyFlowMetric(testing::_)).Times(0); + autocheckout_manager_->OnLoadedPageMetaData( + CreateEndOfFlowMetaData(HAS_PROCEED_ELEMENT)); + EXPECT_EQ(0U, process()->sink().message_count()); +} + +TEST_F(AutocheckoutManagerTest, SinglePageFlow) { + TestSinglePageFlow(HAS_PROCEED_ELEMENT); +} + +TEST_F(AutocheckoutManagerTest, SinglePageFlowNoClickElement) { + TestSinglePageFlow(NO_PROCEED_ELEMENT); +} + +TEST_F(AutocheckoutManagerTest, CancelAutocheckoutBubble) { + GURL frame_url; + gfx::RectF bounding_box; + autofill_manager_delegate_->set_should_autoclick_bubble(false); + EXPECT_FALSE(autocheckout_manager_->in_autocheckout_flow()); + EXPECT_FALSE(autofill_manager_delegate_->request_autocomplete_dialog_open()); + EXPECT_FALSE(autocheckout_manager_->is_autocheckout_bubble_showing()); + EXPECT_CALL(autocheckout_manager_->metric_logger(), + LogAutocheckoutBubbleMetric( + AutofillMetrics::BUBBLE_COULD_BE_DISPLAYED)).Times(1); + autocheckout_manager_->OnLoadedPageMetaData(CreateStartOfFlowMetaData()); + + autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); + EXPECT_TRUE(autocheckout_manager_->is_autocheckout_bubble_showing()); + autocheckout_manager_->MaybeShowAutocheckoutDialog( + frame_url, + AUTOCHECKOUT_BUBBLE_IGNORED); + EXPECT_FALSE(autocheckout_manager_->is_autocheckout_bubble_showing()); + EXPECT_TRUE(autocheckout_manager_->should_show_bubble()); + + autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); + EXPECT_TRUE(autocheckout_manager_->is_autocheckout_bubble_showing()); + autocheckout_manager_->MaybeShowAutocheckoutDialog( + frame_url, + AUTOCHECKOUT_BUBBLE_CANCELED); + EXPECT_FALSE(autocheckout_manager_->is_autocheckout_bubble_showing()); + EXPECT_FALSE(autocheckout_manager_->should_show_bubble()); + + autocheckout_manager_->MaybeShowAutocheckoutBubble(frame_url, bounding_box); + EXPECT_FALSE(autocheckout_manager_->is_autocheckout_bubble_showing()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/autocheckout_page_meta_data.cc b/chromium/components/autofill/content/browser/autocheckout_page_meta_data.cc new file mode 100644 index 00000000000..be03d70c592 --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_page_meta_data.cc @@ -0,0 +1,28 @@ +// 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/content/browser/autocheckout_page_meta_data.h" + +namespace autofill { + +AutocheckoutPageMetaData::AutocheckoutPageMetaData() + : current_page_number(-1), + total_pages(-1), + ignore_ajax(true) {} + +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/chromium/components/autofill/content/browser/autocheckout_page_meta_data.h b/chromium/components/autofill/content/browser/autocheckout_page_meta_data.h new file mode 100644 index 00000000000..6839d1b3ec5 --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_page_meta_data.h @@ -0,0 +1,77 @@ +// 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_CONTENT_BROWSER_AUTOCHECKOUT_PAGE_META_DATA_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_PAGE_META_DATA_H_ + +#include <map> +#include <vector> + +#include "base/basictypes.h" +#include "components/autofill/content/browser/autocheckout_steps.h" +#include "components/autofill/core/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; + + // Whether ajaxy form changes that occur on this page during an Autocheckout + // flow should be ignored. + bool ignore_ajax; + + // A list of elements to click before filling form fields. Elements have to be + // clicked in order. + std::vector<WebElementDescriptor> click_elements_before_form_fill; + + // A list of elements to click after filling form fields, and before clicking + // page_advance_button. Elements have to be clicked in order. + std::vector<WebElementDescriptor> click_elements_after_form_fill; + + // The proceed element of the multipage Autofill flow. It can be empty + // if current page is the last page of a flow or isn't a member of a flow. + // + // We do expect page navigation when click on |proceed_element_descriptor|, + // and report an error if it doesn't. Oppositely, we do not expect page + // navigation when click elements in |click_elements_before_form_fill| and + // |click_elements_after_form_fill|. Because of this behavior difference and + // |proceed_element_descriptor| is optional, we separate it from + // |click_elements_after_form_fill|. + WebElementDescriptor proceed_element_descriptor; + + // Mapping of page numbers to the types of Autocheckout actions that will be + // performed on the given page of a multipage Autofill flow. + // If this form doesn't belong to such a flow, the map will be empty. + std::map<int, std::vector<AutocheckoutStepType> > page_types; + + private: + DISALLOW_COPY_AND_ASSIGN(AutocheckoutPageMetaData); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_PAGE_META_DATA_H_ diff --git a/chromium/components/autofill/content/browser/autocheckout_page_meta_data_unittest.cc b/chromium/components/autofill/content/browser/autocheckout_page_meta_data_unittest.cc new file mode 100644 index 00000000000..0e740608319 --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_page_meta_data_unittest.cc @@ -0,0 +1,54 @@ +// 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/content/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/chromium/components/autofill/content/browser/autocheckout_request_manager.cc b/chromium/components/autofill/content/browser/autocheckout_request_manager.cc new file mode 100644 index 00000000000..e22e9c7b040 --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_request_manager.cc @@ -0,0 +1,110 @@ +// 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/content/browser/autocheckout_request_manager.h" + +#include "components/autofill/core/browser/autofill_manager_delegate.h" +#include "content/public/browser/browser_context.h" + +namespace { + +const char kAutocheckoutRequestManagerKey[] = + "browser_context_autocheckout_request_manager"; + +} // namespace + +namespace autofill { + +AutocheckoutRequestManager::~AutocheckoutRequestManager() {} + +// static +void AutocheckoutRequestManager::CreateForBrowserContext( + content::BrowserContext* browser_context) { + if (FromBrowserContext(browser_context)) + return; + + browser_context->SetUserData( + kAutocheckoutRequestManagerKey, + new AutocheckoutRequestManager(browser_context->GetRequestContext())); +} + +// static +AutocheckoutRequestManager* AutocheckoutRequestManager::FromBrowserContext( + content::BrowserContext* browser_context) { + return static_cast<AutocheckoutRequestManager*>( + browser_context->GetUserData(kAutocheckoutRequestManagerKey)); +} + +void AutocheckoutRequestManager::SendAutocheckoutStatus( + AutocheckoutStatus status, + const GURL& source_url, + const std::vector<AutocheckoutStatistic>& latency_statistics, + const std::string& google_transaction_id) { + wallet_client_.SendAutocheckoutStatus(status, + source_url, + latency_statistics, + google_transaction_id); +} + + +const AutofillMetrics& AutocheckoutRequestManager::GetMetricLogger() const { + return metric_logger_; +} + +DialogType AutocheckoutRequestManager::GetDialogType() const { + return DIALOG_TYPE_AUTOCHECKOUT; +} + +std::string AutocheckoutRequestManager::GetRiskData() const { + NOTREACHED(); + return std::string(); +} + +std::string AutocheckoutRequestManager::GetWalletCookieValue() const { + return std::string(); +} + +bool AutocheckoutRequestManager::IsShippingAddressRequired() const { + NOTREACHED(); + return true; +} + +void AutocheckoutRequestManager::OnDidAcceptLegalDocuments() { + NOTREACHED(); +} + +void AutocheckoutRequestManager::OnDidAuthenticateInstrument(bool success) { + NOTREACHED(); +} + +void AutocheckoutRequestManager::OnDidGetFullWallet( + scoped_ptr<wallet::FullWallet> full_wallet) { + NOTREACHED(); +} + +void AutocheckoutRequestManager::OnDidGetWalletItems( + scoped_ptr<wallet::WalletItems> wallet_items) { + NOTREACHED(); +} + + +void AutocheckoutRequestManager::OnDidSaveToWallet( + const std::string& instrument_id, + const std::string& address_id, + const std::vector<wallet::RequiredAction>& required_actions, + const std::vector<wallet::FormFieldError>& form_field_errors) { + NOTREACHED(); +} + +void AutocheckoutRequestManager::OnWalletError( + wallet::WalletClient::ErrorType error_type) { + // Nothing to be done. |error_type| is logged by |metric_logger_|. +} + +AutocheckoutRequestManager::AutocheckoutRequestManager( + net::URLRequestContextGetter* request_context_getter) + : wallet_client_(request_context_getter, this) { +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/autocheckout_request_manager.h b/chromium/components/autofill/content/browser/autocheckout_request_manager.h new file mode 100644 index 00000000000..4d20b5f727b --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_request_manager.h @@ -0,0 +1,90 @@ +// 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_CONTENT_BROWSER_AUTOCHECKOUT_REQUEST_MANAGER_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_REQUEST_MANAGER_H_ + +#include "base/supports_user_data.h" +#include "components/autofill/content/browser/autocheckout_statistic.h" +#include "components/autofill/content/browser/wallet/wallet_client.h" +#include "components/autofill/content/browser/wallet/wallet_client_delegate.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/common/autocheckout_status.h" +#include "url/gurl.h" + +namespace content { +class BrowserContext; +} + +namespace net { +class URLRequestContextGetter; +} + +namespace autofill { + +// AutocheckoutRequestManager's only responsiblity is to make sure any +// SendAutocheckoutStatus calls succeed regardless of any actions the user may +// make in the browser i.e. closing a tab, the requestAutocomplete dialog, etc. +// To that end, it is a piece of user data tied to the BrowserContext. +class AutocheckoutRequestManager : public base::SupportsUserData::Data, + public wallet::WalletClientDelegate { + public: + virtual ~AutocheckoutRequestManager(); + + // Creates a new AutocheckoutRequestManager and stores it as user data in + // |browser_context| if one does not already exist. + static void CreateForBrowserContext( + content::BrowserContext* browser_context); + + // Retrieves the AutocheckoutRequestManager for |browser_context| if one + // exists. + static AutocheckoutRequestManager* FromBrowserContext( + content::BrowserContext* browser_context); + + // Sends the |status| of an Autocheckout flow to Online Wallet using + // |wallet_client_|. + void SendAutocheckoutStatus( + AutocheckoutStatus status, + const GURL& source_url, + const std::vector<AutocheckoutStatistic>& latency_statistics, + const std::string& google_transaction_id); + + // wallet::WalletClientDelegate: + virtual const AutofillMetrics& GetMetricLogger() const OVERRIDE; + virtual DialogType GetDialogType() const OVERRIDE; + virtual std::string GetRiskData() const OVERRIDE; + virtual std::string GetWalletCookieValue() const OVERRIDE; + virtual bool IsShippingAddressRequired() const OVERRIDE; + virtual void OnDidAcceptLegalDocuments() OVERRIDE; + virtual void OnDidAuthenticateInstrument(bool success) OVERRIDE; + virtual void OnDidGetFullWallet( + scoped_ptr<wallet::FullWallet> full_wallet) OVERRIDE; + virtual void OnDidGetWalletItems( + scoped_ptr<wallet::WalletItems> wallet_items) OVERRIDE; + virtual void OnDidSaveToWallet( + const std::string& instrument_id, + const std::string& address_id, + const std::vector<wallet::RequiredAction>& required_actions, + const std::vector<wallet::FormFieldError>& form_field_errors) OVERRIDE; + virtual void OnWalletError( + wallet::WalletClient::ErrorType error_type) OVERRIDE; + + private: + // |request_context_getter| is passed in to construct |wallet_client_|. + AutocheckoutRequestManager( + net::URLRequestContextGetter* request_context_getter); + + // Logs various UMA metrics. + AutofillMetrics metric_logger_; + + // Makes requests to Online Wallet. The only request this class is configured + // to make is SendAutocheckoutStatus. + wallet::WalletClient wallet_client_; + + DISALLOW_COPY_AND_ASSIGN(AutocheckoutRequestManager); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_REQUEST_MANAGER_H_ diff --git a/chromium/components/autofill/content/browser/autocheckout_statistic.cc b/chromium/components/autofill/content/browser/autocheckout_statistic.cc new file mode 100644 index 00000000000..9df4f4e4a61 --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_statistic.cc @@ -0,0 +1,52 @@ +// 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/content/browser/autocheckout_statistic.h" + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/values.h" + +namespace autofill { + +namespace { + +std::string AutocheckoutStepToString(AutocheckoutStepType step) { + switch(step) { + case AUTOCHECKOUT_STEP_SHIPPING: + return "AUTOCHECKOUT_STEP_SHIPPING"; + case AUTOCHECKOUT_STEP_DELIVERY: + return "AUTOCHECKOUT_STEP_DELIVERY"; + case AUTOCHECKOUT_STEP_BILLING: + return "AUTOCHECKOUT_STEP_BILLING"; + case AUTOCHECKOUT_STEP_PROXY_CARD: + return "AUTOCHECKOUT_STEP_PROXY_CARD"; + } + NOTREACHED(); + return "NOT_POSSIBLE"; +} + +} // namespace + +AutocheckoutStatistic::AutocheckoutStatistic() : page_number(-1) { +} + +AutocheckoutStatistic::~AutocheckoutStatistic() { +} + +scoped_ptr<base::DictionaryValue> AutocheckoutStatistic::ToDictionary() const { + scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue()); + std::string description = base::IntToString(page_number); + for (size_t i = 0; i < steps.size(); ++i) { + description = description + "_" + AutocheckoutStepToString(steps[i]); + } + dict->SetString("step_description", description); + dict->SetInteger("time_taken", time_taken.InMilliseconds()); + DVLOG(1) << "Step : " << description + << ", time_taken: " << time_taken.InMilliseconds(); + return dict.Pass(); +} + +} // namespace autofill + diff --git a/chromium/components/autofill/content/browser/autocheckout_statistic.h b/chromium/components/autofill/content/browser/autocheckout_statistic.h new file mode 100644 index 00000000000..74f7df7cd9b --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_statistic.h @@ -0,0 +1,40 @@ +// 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_CONTENT_BROWSER_AUTOCHECKOUT_STATISTIC_H__ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_STATISTIC_H__ + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/time/time.h" +#include "components/autofill/content/browser/autocheckout_steps.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { + +// Per page statistics gathered in Autocheckout flow. +struct AutocheckoutStatistic { + AutocheckoutStatistic(); + ~AutocheckoutStatistic(); + + // Page number for which this statistic is being recorded. + int page_number; + + // Autocheckout steps that are part of |page_number|. + std::vector<AutocheckoutStepType> steps; + + // Time taken for performing |steps|, used for measuring latency. + base::TimeDelta time_taken; + + // Helper method to convert AutocheckoutStatistic to json representation. + scoped_ptr<base::DictionaryValue> ToDictionary() const; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOCHECKOUT_STATISTIC_H__ diff --git a/chromium/components/autofill/content/browser/autocheckout_steps.h b/chromium/components/autofill/content/browser/autocheckout_steps.h new file mode 100644 index 00000000000..d46abccc40f --- /dev/null +++ b/chromium/components/autofill/content/browser/autocheckout_steps.h @@ -0,0 +1,32 @@ +// 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_AUTOCHECKOUT_STEPS_H_ +#define COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_STEPS_H_ + +namespace autofill { + +// Stages of a buy flow that may be encountered on an Autocheckout-supported +// site, used primarily for display purposes. +// Indexed for easy conversion from int values returned by Autofill server. +enum AutocheckoutStepType { + AUTOCHECKOUT_STEP_MIN_VALUE = 1, + AUTOCHECKOUT_STEP_SHIPPING = AUTOCHECKOUT_STEP_MIN_VALUE, + AUTOCHECKOUT_STEP_DELIVERY = 2, + AUTOCHECKOUT_STEP_BILLING = 3, + AUTOCHECKOUT_STEP_PROXY_CARD = 4, + AUTOCHECKOUT_STEP_MAX_VALUE = AUTOCHECKOUT_STEP_PROXY_CARD, +}; + +// Possible statuses for the above step types, again used primarily for display. +enum AutocheckoutStepStatus { + AUTOCHECKOUT_STEP_UNSTARTED, + AUTOCHECKOUT_STEP_STARTED, + AUTOCHECKOUT_STEP_COMPLETED, + AUTOCHECKOUT_STEP_FAILED, +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_AUTOCHECKOUT_STEPS_H_ diff --git a/chromium/components/autofill/content/browser/autofill_driver_impl.cc b/chromium/components/autofill/content/browser/autofill_driver_impl.cc new file mode 100644 index 00000000000..d0cb1f62861 --- /dev/null +++ b/chromium/components/autofill/content/browser/autofill_driver_impl.cc @@ -0,0 +1,235 @@ +// 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/content/browser/autofill_driver_impl.h" + +#include "base/command_line.h" +#include "components/autofill/core/browser/autofill_external_delegate.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "components/autofill/core/browser/autofill_manager_delegate.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/common/autofill_switches.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_details.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 "content/public/common/frame_navigate_params.h" +#include "ipc/ipc_message_macros.h" + +namespace autofill { + +namespace { + +const char kAutofillDriverImplWebContentsUserDataKey[] = + "web_contents_autofill_driver_impl"; + +} // namespace + +// static +void AutofillDriverImpl::CreateForWebContentsAndDelegate( + content::WebContents* contents, + autofill::AutofillManagerDelegate* delegate, + const std::string& app_locale, + AutofillManager::AutofillDownloadManagerState enable_download_manager) { + if (FromWebContents(contents)) + return; + + contents->SetUserData(kAutofillDriverImplWebContentsUserDataKey, + new AutofillDriverImpl(contents, + delegate, + app_locale, + enable_download_manager)); + // Trigger the lazy creation of AutocheckoutWhitelistManagerService, and + // schedule a fetch of the Autocheckout whitelist file if it's not already + // loaded. This helps ensure that the whitelist will be available by the time + // the user navigates to a form on which Autocheckout should be enabled. + delegate->GetAutocheckoutWhitelistManager(); +} + +// static +AutofillDriverImpl* AutofillDriverImpl::FromWebContents( + content::WebContents* contents) { + return static_cast<AutofillDriverImpl*>( + contents->GetUserData(kAutofillDriverImplWebContentsUserDataKey)); +} + +AutofillDriverImpl::AutofillDriverImpl( + content::WebContents* web_contents, + autofill::AutofillManagerDelegate* delegate, + const std::string& app_locale, + AutofillManager::AutofillDownloadManagerState enable_download_manager) + : content::WebContentsObserver(web_contents), + autofill_manager_(new AutofillManager( + this, delegate, app_locale, enable_download_manager)) { + SetAutofillExternalDelegate(scoped_ptr<AutofillExternalDelegate>( + new AutofillExternalDelegate(web_contents, autofill_manager_.get(), + this))); + + 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()))); +} + +AutofillDriverImpl::~AutofillDriverImpl() {} + +content::WebContents* AutofillDriverImpl::GetWebContents() { + return web_contents(); +} + +bool AutofillDriverImpl::RendererIsAvailable() { + return (web_contents()->GetRenderViewHost() != NULL); +} + +void AutofillDriverImpl::SetRendererActionOnFormDataReception( + RendererFormDataAction action) { + if (!RendererIsAvailable()) + return; + + content::RenderViewHost* host = web_contents()->GetRenderViewHost(); + switch(action) { + case FORM_DATA_ACTION_PREVIEW: + host->Send(new AutofillMsg_SetAutofillActionPreview( + host->GetRoutingID())); + return; + case FORM_DATA_ACTION_FILL: + host->Send(new AutofillMsg_SetAutofillActionFill(host->GetRoutingID())); + return; + } +} + +void AutofillDriverImpl::SendFormDataToRenderer(int query_id, + const FormData& data) { + if (!RendererIsAvailable()) + return; + content::RenderViewHost* host = web_contents()->GetRenderViewHost(); + host->Send( + new AutofillMsg_FormDataFilled(host->GetRoutingID(), query_id, data)); +} + +void AutofillDriverImpl::SendAutofillTypePredictionsToRenderer( + const std::vector<FormStructure*>& forms) { + if (!CommandLine::ForCurrentProcess()->HasSwitch( + switches::kShowAutofillTypePredictions)) + return; + + content::RenderViewHost* host = GetWebContents()->GetRenderViewHost(); + if (!host) + return; + + std::vector<FormDataPredictions> type_predictions; + FormStructure::GetFieldTypePredictions(forms, &type_predictions); + host->Send( + new AutofillMsg_FieldTypePredictionsAvailable(host->GetRoutingID(), + type_predictions)); +} + +void AutofillDriverImpl::RendererShouldClearFilledForm() { + if (!RendererIsAvailable()) + return; + content::RenderViewHost* host = web_contents()->GetRenderViewHost(); + host->Send(new AutofillMsg_ClearForm(host->GetRoutingID())); +} + +void AutofillDriverImpl::RendererShouldClearPreviewedForm() { + if (!RendererIsAvailable()) + return; + content::RenderViewHost* host = web_contents()->GetRenderViewHost(); + host->Send(new AutofillMsg_ClearPreviewedForm(host->GetRoutingID())); +} + +bool AutofillDriverImpl::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AutofillDriverImpl, message) + IPC_MESSAGE_FORWARD(AutofillHostMsg_FormsSeen, autofill_manager_.get(), + AutofillManager::OnFormsSeen) + IPC_MESSAGE_FORWARD(AutofillHostMsg_FormSubmitted, autofill_manager_.get(), + AutofillManager::OnFormSubmitted) + IPC_MESSAGE_FORWARD(AutofillHostMsg_TextFieldDidChange, + autofill_manager_.get(), + AutofillManager::OnTextFieldDidChange) + IPC_MESSAGE_FORWARD(AutofillHostMsg_QueryFormFieldAutofill, + autofill_manager_.get(), + AutofillManager::OnQueryFormFieldAutofill) + IPC_MESSAGE_FORWARD(AutofillHostMsg_ShowAutofillDialog, + autofill_manager_.get(), + AutofillManager::OnShowAutofillDialog) + IPC_MESSAGE_FORWARD(AutofillHostMsg_FillAutofillFormData, + autofill_manager_.get(), + AutofillManager::OnFillAutofillFormData) + IPC_MESSAGE_FORWARD(AutofillHostMsg_DidPreviewAutofillFormData, + autofill_manager_.get(), + AutofillManager::OnDidPreviewAutofillFormData) + IPC_MESSAGE_FORWARD(AutofillHostMsg_DidFillAutofillFormData, + autofill_manager_.get(), + AutofillManager::OnDidFillAutofillFormData) + IPC_MESSAGE_FORWARD(AutofillHostMsg_DidEndTextFieldEditing, + autofill_manager_.get(), + AutofillManager::OnDidEndTextFieldEditing) + IPC_MESSAGE_FORWARD(AutofillHostMsg_HideAutofillUI, autofill_manager_.get(), + AutofillManager::OnHideAutofillUI) + IPC_MESSAGE_FORWARD(AutofillHostMsg_AddPasswordFormMapping, + autofill_manager_.get(), + AutofillManager::OnAddPasswordFormMapping) + IPC_MESSAGE_FORWARD(AutofillHostMsg_ShowPasswordSuggestions, + autofill_manager_.get(), + AutofillManager::OnShowPasswordSuggestions) + IPC_MESSAGE_FORWARD(AutofillHostMsg_SetDataList, autofill_manager_.get(), + AutofillManager::OnSetDataList) + IPC_MESSAGE_FORWARD(AutofillHostMsg_RequestAutocomplete, + autofill_manager_.get(), + AutofillManager::OnRequestAutocomplete) + IPC_MESSAGE_FORWARD(AutofillHostMsg_AutocheckoutPageCompleted, + autofill_manager_.get(), + AutofillManager::OnAutocheckoutPageCompleted) + IPC_MESSAGE_FORWARD(AutofillHostMsg_MaybeShowAutocheckoutBubble, + autofill_manager_.get(), + AutofillManager::OnMaybeShowAutocheckoutBubble) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void AutofillDriverImpl::DidNavigateMainFrame( + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params) { + if (details.is_navigation_to_different_page()) + autofill_manager_->Reset(); +} + +void AutofillDriverImpl::SetAutofillExternalDelegate( + scoped_ptr<AutofillExternalDelegate> delegate) { + autofill_external_delegate_ = delegate.Pass(); + autofill_manager_->SetExternalDelegate(autofill_external_delegate_.get()); +} + +void AutofillDriverImpl::SetAutofillManager( + scoped_ptr<AutofillManager> manager) { + autofill_manager_ = manager.Pass(); + autofill_manager_->SetExternalDelegate(autofill_external_delegate_.get()); +} + +void AutofillDriverImpl::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(); + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/autofill_driver_impl.h b/chromium/components/autofill/content/browser/autofill_driver_impl.h new file mode 100644 index 00000000000..999822eb373 --- /dev/null +++ b/chromium/components/autofill/content/browser/autofill_driver_impl.h @@ -0,0 +1,108 @@ +// 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_CONTENT_BROWSER_AUTOFILL_DRIVER_IMPL_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOFILL_DRIVER_IMPL_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/supports_user_data.h" +#include "components/autofill/core/browser/autofill_driver.h" +#include "components/autofill/core/browser/autofill_external_delegate.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/web_contents_observer.h" + +namespace content { +class WebContents; +} + +namespace IPC { +class Message; +} + +namespace autofill { + +class AutofillContext; +class AutofillManagerDelegate; + +// Class that drives autofill flow in the browser process based on +// communication from the renderer and from the external world. There is one +// instance per WebContents. +class AutofillDriverImpl : public AutofillDriver, + public content::NotificationObserver, + public content::WebContentsObserver, + public base::SupportsUserData::Data { + public: + static void CreateForWebContentsAndDelegate( + content::WebContents* contents, + autofill::AutofillManagerDelegate* delegate, + const std::string& app_locale, + AutofillManager::AutofillDownloadManagerState enable_download_manager); + static AutofillDriverImpl* FromWebContents(content::WebContents* contents); + + // AutofillDriver: + virtual content::WebContents* GetWebContents() OVERRIDE; + virtual bool RendererIsAvailable() OVERRIDE; + virtual void SetRendererActionOnFormDataReception( + RendererFormDataAction action) OVERRIDE; + virtual void SendFormDataToRenderer(int query_id, + const FormData& data) OVERRIDE; + virtual void SendAutofillTypePredictionsToRenderer( + const std::vector<FormStructure*>& forms) OVERRIDE; + virtual void RendererShouldClearFilledForm() OVERRIDE; + virtual void RendererShouldClearPreviewedForm() OVERRIDE; + + AutofillExternalDelegate* autofill_external_delegate() { + return autofill_external_delegate_.get(); + } + + // Sets the external delegate to |delegate| both within this class and in the + // shared Autofill code. Takes ownership of |delegate|. + void SetAutofillExternalDelegate( + scoped_ptr<AutofillExternalDelegate> delegate); + + AutofillManager* autofill_manager() { return autofill_manager_.get(); } + + protected: + AutofillDriverImpl( + content::WebContents* web_contents, + autofill::AutofillManagerDelegate* delegate, + const std::string& app_locale, + AutofillManager::AutofillDownloadManagerState enable_download_manager); + virtual ~AutofillDriverImpl(); + + // content::WebContentsObserver: + virtual void DidNavigateMainFrame( + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + // Sets the manager to |manager| and sets |manager|'s external delegate + // to |autofill_external_delegate_|. Takes ownership of |manager|. + void SetAutofillManager(scoped_ptr<AutofillManager> manager); + + private: + // content::NotificationObserver: + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // A scoped container for notification registries. + content::NotificationRegistrar registrar_; + + // AutofillExternalDelegate instance that this object instantiates in the + // case where the autofill native UI is enabled. + scoped_ptr<AutofillExternalDelegate> autofill_external_delegate_; + + // AutofillManager instance via which this object drives the shared Autofill + // code. + scoped_ptr<AutofillManager> autofill_manager_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_AUTOFILL_DRIVER_IMPL_H_ diff --git a/chromium/components/autofill/content/browser/autofill_driver_impl_unittest.cc b/chromium/components/autofill/content/browser/autofill_driver_impl_unittest.cc new file mode 100644 index 00000000000..875b783a565 --- /dev/null +++ b/chromium/components/autofill/content/browser/autofill_driver_impl_unittest.cc @@ -0,0 +1,224 @@ +// 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 <algorithm> +#include <vector> + +#include "base/command_line.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "components/autofill/content/browser/autofill_driver_impl.h" +#include "components/autofill/core/browser/autofill_common_test.h" +#include "components/autofill/core/browser/autofill_external_delegate.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "components/autofill/core/browser/test_autofill_manager_delegate.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/common/autofill_switches.h" +#include "components/autofill/core/common/form_data_predictions.h" +#include "content/public/browser/navigation_details.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/frame_navigate_params.h" +#include "content/public/test/mock_render_process_host.h" +#include "ipc/ipc_test_sink.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +namespace { + +const std::string kAppLocale = "en-US"; +const AutofillManager::AutofillDownloadManagerState kDownloadState = + AutofillManager::DISABLE_AUTOFILL_DOWNLOAD_MANAGER; + +} // namespace + +class MockAutofillManager : public AutofillManager { + public: + MockAutofillManager(AutofillDriver* driver, + AutofillManagerDelegate* delegate) + : AutofillManager(driver, delegate, kAppLocale, kDownloadState) { + } + virtual ~MockAutofillManager() {} + + MOCK_METHOD0(Reset, void()); +}; + +class TestAutofillDriverImpl : public AutofillDriverImpl { + public: + TestAutofillDriverImpl(content::WebContents* contents, + AutofillManagerDelegate* delegate) + : AutofillDriverImpl(contents, delegate, kAppLocale, kDownloadState) { + scoped_ptr<AutofillManager> autofill_manager( + new MockAutofillManager(this, delegate)); + SetAutofillManager(autofill_manager.Pass()); + } + virtual ~TestAutofillDriverImpl() {} + + virtual MockAutofillManager* mock_autofill_manager() { + return static_cast<MockAutofillManager*>(autofill_manager()); + } + + using AutofillDriverImpl::DidNavigateMainFrame; +}; + +class AutofillDriverImplTest : public ChromeRenderViewHostTestHarness { + public: + virtual void SetUp() OVERRIDE { + ChromeRenderViewHostTestHarness::SetUp(); + + test_manager_delegate_.reset(new TestAutofillManagerDelegate()); + driver_.reset(new TestAutofillDriverImpl(web_contents(), + test_manager_delegate_.get())); + } + + virtual void TearDown() OVERRIDE { + // Reset the driver now to cause all pref observers to be removed and avoid + // crashes that otherwise occur in the destructor. + driver_.reset(); + ChromeRenderViewHostTestHarness::TearDown(); + } + + protected: + // Searches for an |AutofillMsg_FormDataFilled| message in the queue of sent + // IPC messages. If none is present, returns false. Otherwise, extracts the + // first |AutofillMsg_FormDataFilled| message, fills the output parameters + // with the values of the message's parameters, and clears the queue of sent + // messages. + 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; + } + + // Searches for an |AutofillMsg_FieldTypePredictionsAvailable| message in the + // queue of sent IPC messages. If none is present, returns false. Otherwise, + // extracts the first |AutofillMsg_FieldTypePredictionsAvailable| message, + // fills the output parameter with the values of the message's parameter, and + // clears the queue of sent messages. + bool GetFieldTypePredictionsAvailable( + std::vector<FormDataPredictions>* predictions) { + const uint32 kMsgID = AutofillMsg_FieldTypePredictionsAvailable::ID; + const IPC::Message* message = + process()->sink().GetFirstMessageMatching(kMsgID); + if (!message) + return false; + Tuple1<std::vector<FormDataPredictions> > autofill_param; + AutofillMsg_FieldTypePredictionsAvailable::Read(message, &autofill_param); + if (predictions) + *predictions = autofill_param.a; + + process()->sink().ClearMessages(); + return true; + } + + // Searches for a message matching |messageID| in the queue of sent IPC + // messages. If none is present, returns false. Otherwise, clears the queue + // of sent messages and returns true. + bool HasMessageMatchingID(uint32 messageID) { + const IPC::Message* message = + process()->sink().GetFirstMessageMatching(messageID); + if (!message) + return false; + process()->sink().ClearMessages(); + return true; + } + + scoped_ptr<TestAutofillManagerDelegate> test_manager_delegate_; + scoped_ptr<TestAutofillDriverImpl> driver_; +}; + +TEST_F(AutofillDriverImplTest, NavigatedToDifferentPage) { + EXPECT_CALL(*driver_->mock_autofill_manager(), Reset()); + content::LoadCommittedDetails details = content::LoadCommittedDetails(); + details.is_main_frame = true; + details.is_in_page = false; + ASSERT_TRUE(details.is_navigation_to_different_page()); + content::FrameNavigateParams params = content::FrameNavigateParams(); + driver_->DidNavigateMainFrame(details, params); +} + +TEST_F(AutofillDriverImplTest, NavigatedWithinSamePage) { + EXPECT_CALL(*driver_->mock_autofill_manager(), Reset()).Times(0); + content::LoadCommittedDetails details = content::LoadCommittedDetails(); + details.is_main_frame = false; + ASSERT_TRUE(!details.is_navigation_to_different_page()); + content::FrameNavigateParams params = content::FrameNavigateParams(); + driver_->DidNavigateMainFrame(details, params); +} + +TEST_F(AutofillDriverImplTest, FormDataSentToRenderer) { + int input_page_id = 42; + FormData input_form_data; + test::CreateTestAddressFormData(&input_form_data); + driver_->SendFormDataToRenderer(input_page_id, input_form_data); + + int output_page_id = 0; + FormData output_form_data; + EXPECT_TRUE(GetAutofillFormDataFilledMessage(&output_page_id, + &output_form_data)); + EXPECT_EQ(input_page_id, output_page_id); + EXPECT_EQ(input_form_data, output_form_data); +} + +TEST_F(AutofillDriverImplTest, TypePredictionsNotSentToRendererWhenDisabled) { + FormData form; + test::CreateTestAddressFormData(&form); + FormStructure form_structure(form, std::string()); + std::vector<FormStructure*> forms(1, &form_structure); + driver_->SendAutofillTypePredictionsToRenderer(forms); + EXPECT_FALSE(GetFieldTypePredictionsAvailable(NULL)); +} + +TEST_F(AutofillDriverImplTest, TypePredictionsSentToRendererWhenEnabled) { + CommandLine::ForCurrentProcess()->AppendSwitch( + switches::kShowAutofillTypePredictions); + + FormData form; + test::CreateTestAddressFormData(&form); + FormStructure form_structure(form, std::string()); + std::vector<FormStructure*> forms(1, &form_structure); + std::vector<FormDataPredictions> expected_type_predictions; + FormStructure::GetFieldTypePredictions(forms, &expected_type_predictions); + driver_->SendAutofillTypePredictionsToRenderer(forms); + + std::vector<FormDataPredictions> output_type_predictions; + EXPECT_TRUE(GetFieldTypePredictionsAvailable(&output_type_predictions)); + EXPECT_EQ(expected_type_predictions, output_type_predictions); +} + +TEST_F(AutofillDriverImplTest, PreviewActionSentToRenderer) { + driver_->SetRendererActionOnFormDataReception( + AutofillDriver::FORM_DATA_ACTION_PREVIEW); + EXPECT_TRUE(HasMessageMatchingID(AutofillMsg_SetAutofillActionPreview::ID)); +} + +TEST_F(AutofillDriverImplTest, FillActionSentToRenderer) { + driver_->SetRendererActionOnFormDataReception( + AutofillDriver::FORM_DATA_ACTION_FILL); + EXPECT_TRUE(HasMessageMatchingID(AutofillMsg_SetAutofillActionFill::ID)); +} + +TEST_F(AutofillDriverImplTest, ClearFilledFormSentToRenderer) { + driver_->RendererShouldClearFilledForm(); + EXPECT_TRUE(HasMessageMatchingID(AutofillMsg_ClearForm::ID)); +} + +TEST_F(AutofillDriverImplTest, ClearPreviewedFormSentToRenderer) { + driver_->RendererShouldClearPreviewedForm(); + EXPECT_TRUE(HasMessageMatchingID(AutofillMsg_ClearPreviewedForm::ID)); +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/risk/fingerprint.cc b/chromium/components/autofill/content/browser/risk/fingerprint.cc new file mode 100644 index 00000000000..635224d1395 --- /dev/null +++ b/chromium/components/autofill/content/browser/risk/fingerprint.cc @@ -0,0 +1,505 @@ +// 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. +// +// Generating a fingerprint consists of two major steps: +// (1) Gather all the necessary data. +// (2) Write it into a protocol buffer. +// +// Step (2) is as simple as it sounds -- it's really just a matter of copying +// data. Step (1) requires waiting on several asynchronous callbacks, which are +// managed by the FingerprintDataLoader class. + +#include "components/autofill/content/browser/risk/fingerprint.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/cpu.h" +#include "base/logging.h" +#include "base/scoped_observer.h" +#include "base/strings/string_split.h" +#include "base/strings/utf_string_conversions.h" +#include "base/sys_info.h" +#include "base/time/time.h" +#include "base/values.h" +#include "components/autofill/content/browser/risk/proto/fingerprint.pb.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/font_list_async.h" +#include "content/public/browser/geolocation_provider.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/geoposition.h" +#include "content/public/common/webplugininfo.h" +#include "gpu/config/gpu_info.h" +#include "third_party/WebKit/public/platform/WebRect.h" +#include "third_party/WebKit/public/platform/WebScreenInfo.h" +#include "ui/gfx/rect.h" +#include "ui/gfx/screen.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() { + const 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(); +} + +Fingerprint::MachineCharacteristics::BrowserFeature + DialogTypeToBrowserFeature(DialogType dialog_type) { + switch (dialog_type) { + case DIALOG_TYPE_AUTOCHECKOUT: + return Fingerprint::MachineCharacteristics::FEATURE_AUTOCHECKOUT; + + case DIALOG_TYPE_REQUEST_AUTOCOMPLETE: + return Fingerprint::MachineCharacteristics::FEATURE_REQUEST_AUTOCOMPLETE; + } + + NOTREACHED(); + return Fingerprint::MachineCharacteristics::FEATURE_UNKNOWN; +} + +// 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 = NULL; + 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<content::WebPluginInfo>& plugins, + Fingerprint::MachineCharacteristics* machine) { + for (std::vector<content::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<content::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); + } +} + +// This function 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()); + + const 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); + + const gfx::Rect screen_rect(screen_info.rect); + const gfx::Rect available_rect(screen_info.availableRect); + const 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 gpu::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( + uint64 obfuscated_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, + DialogType dialog_type, + const std::string& app_locale, + 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<content::WebPluginInfo>& plugins); + void OnGotGeoposition(const content::Geoposition& geoposition); + + // Methods that run on the IO thread to communicate with the + // GeolocationProvider. + void LoadGeoposition(); + void OnGotGeopositionOnIOThread(const content::Geoposition& geoposition); + + // 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. + // Weak reference because the GpuDataManager class is a singleton. + content::GpuDataManager* const gpu_data_manager_; + + // Ensures that any observer registratiosn for the GPU data are cleaned up by + // the time this object is destroyed. + ScopedObserver<content::GpuDataManager, FingerprintDataLoader> gpu_observer_; + + // The callback used as an "observer" of the GeolocationProvider. Accessed + // only on the IO thread. + content::GeolocationProvider::LocationUpdateCallback geolocation_callback_; + + // Data that will be passed on to the next loading phase. See the comment for + // GetFingerprint() for a description of these variables. + const uint64 obfuscated_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_; + DialogType dialog_type_; + + // Data that will be loaded asynchronously. + scoped_ptr<base::ListValue> fonts_; + std::vector<content::WebPluginInfo> plugins_; + bool waiting_on_plugins_; + content::Geoposition geoposition_; + + // The current application locale. + std::string app_locale_; + + // 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( + uint64 obfuscated_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, + DialogType dialog_type, + const std::string& app_locale, + const base::Callback<void(scoped_ptr<Fingerprint>)>& callback) + : gpu_data_manager_(content::GpuDataManager::GetInstance()), + gpu_observer_(this), + obfuscated_gaia_id_(obfuscated_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), + dialog_type_(dialog_type), + waiting_on_plugins_(true), + callback_(callback) { + DCHECK(!install_time_.is_null()); + + // Load GPU data if needed. + if (!gpu_data_manager_->IsCompleteGpuInfoAvailable()) { + gpu_observer_.Add(gpu_data_manager_); + gpu_data_manager_->RequestCompleteGpuInfoIfNeeded(); + } + +#if defined(ENABLE_PLUGINS) + // Load plugin data. + content::PluginService::GetInstance()->GetPlugins( + base::Bind(&FingerprintDataLoader::OnGotPlugins, base::Unretained(this))); +#else + waiting_on_plugins_ = false; +#endif + + // Load font data. + content::GetFontListAsync( + base::Bind(&FingerprintDataLoader::OnGotFonts, base::Unretained(this))); + + // Load geolocation data. + content::BrowserThread::PostTask( + content::BrowserThread::IO, FROM_HERE, + base::Bind(&FingerprintDataLoader::LoadGeoposition, + base::Unretained(this))); +} + +void FingerprintDataLoader::OnGpuInfoUpdate() { + if (!gpu_data_manager_->IsCompleteGpuInfoAvailable()) + return; + + gpu_observer_.Remove(gpu_data_manager_); + MaybeFillFingerprint(); +} + +void FingerprintDataLoader::OnGotFonts(scoped_ptr<base::ListValue> fonts) { + DCHECK(!fonts_); + fonts_.reset(fonts.release()); + MaybeFillFingerprint(); +} + +void FingerprintDataLoader::OnGotPlugins( + const std::vector<content::WebPluginInfo>& plugins) { + DCHECK(waiting_on_plugins_); + waiting_on_plugins_ = false; + plugins_ = plugins; + MaybeFillFingerprint(); +} + +void FingerprintDataLoader::OnGotGeoposition( + const content::Geoposition& geoposition) { + DCHECK(!geoposition_.Validate()); + + geoposition_ = geoposition; + DCHECK(geoposition_.Validate() || + geoposition_.error_code != content::Geoposition::ERROR_CODE_NONE); + + MaybeFillFingerprint(); +} + +void FingerprintDataLoader::LoadGeoposition() { + geolocation_callback_ = + base::Bind(&FingerprintDataLoader::OnGotGeopositionOnIOThread, + base::Unretained(this)); + content::GeolocationProvider::GetInstance()->AddLocationUpdateCallback( + geolocation_callback_, false); +} + +void FingerprintDataLoader::OnGotGeopositionOnIOThread( + const content::Geoposition& geoposition) { + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&FingerprintDataLoader::OnGotGeoposition, + base::Unretained(this), geoposition)); + + // Unregister as an observer, since this class instance might be destroyed + // after this callback. Note: It's important to unregister *after* posting + // the task above. Unregistering as an observer can have the side-effect of + // modifying the value of |geoposition|. + bool removed = + content::GeolocationProvider::GetInstance()->RemoveLocationUpdateCallback( + geolocation_callback_); + DCHECK(removed); +} + +void FingerprintDataLoader::MaybeFillFingerprint() { + // If all of the data has been loaded, fill the fingerprint and clean up. + if (gpu_data_manager_->IsCompleteGpuInfoAvailable() && + fonts_ && + !waiting_on_plugins_ && + (geoposition_.Validate() || + geoposition_.error_code != content::Geoposition::ERROR_CODE_NONE)) { + 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(app_locale_); + machine->set_charset(charset_); + machine->set_user_agent(content::GetUserAgent(GURL())); + machine->set_ram(base::SysInfo::AmountOfPhysicalMemory()); + machine->set_browser_build(version_); + machine->set_browser_feature(DialogTypeToBrowserFeature(dialog_type_)); + AddFontsToFingerprint(*fonts_, machine); + AddPluginsToFingerprint(plugins_, machine); + AddAcceptLanguagesToFingerprint(accept_languages_, machine); + AddScreenInfoToFingerprint(screen_info_, machine); + AddCpuInfoToFingerprint(machine); + AddGpuInfoToFingerprint(machine); + + // TODO(isherman): Record the user_and_device_name_hash. + // TODO(isherman): Record 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 more user behavior data. + if (geoposition_.error_code == content::Geoposition::ERROR_CODE_NONE) { + Fingerprint::UserCharacteristics::Location* location = + fingerprint->mutable_user_characteristics()->mutable_location(); + location->set_altitude(geoposition_.altitude); + location->set_latitude(geoposition_.latitude); + location->set_longitude(geoposition_.longitude); + location->set_accuracy(geoposition_.accuracy); + location->set_time_in_ms( + (geoposition_.timestamp - base::Time::UnixEpoch()).InMilliseconds()); + } + + Fingerprint::Metadata* metadata = fingerprint->mutable_metadata(); + metadata->set_timestamp_ms( + (base::Time::Now() - base::Time::UnixEpoch()).InMilliseconds()); + metadata->set_obfuscated_gaia_id(obfuscated_gaia_id_); + metadata->set_fingerprinter_version(kFingerprinterVersion); + + callback_.Run(fingerprint.Pass()); +} + +} // namespace + +namespace internal { + +void GetFingerprintInternal( + uint64 obfuscated_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, + DialogType dialog_type, + const std::string& app_locale, + 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(obfuscated_gaia_id, window_bounds, content_bounds, + screen_info, version, charset, accept_languages, + install_time, dialog_type, app_locale, callback); +} + +} // namespace internal + +void GetFingerprint( + uint64 obfuscated_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, + DialogType dialog_type, + const std::string& app_locale, + 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( + obfuscated_gaia_id, window_bounds, content_bounds, screen_info, version, + charset, accept_languages, install_time, dialog_type, app_locale, + callback); +} + +} // namespace risk +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/risk/fingerprint.h b/chromium/components/autofill/content/browser/risk/fingerprint.h new file mode 100644 index 00000000000..d66bde43b86 --- /dev/null +++ b/chromium/components/autofill/content/browser/risk/fingerprint.h @@ -0,0 +1,68 @@ +// 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. +// +// Generates fingerprints appropriate for sending to the Google Wallet Risk +// engine, which is the fraud-detection engine used for purchases powered by +// Google Wallet. A fingerprint encapsulates machine and user characteristics. +// Because much of the data is privacy-sensitive, fingerprints should only be +// generated with explicit user consent, including consent to gather geolocation +// data. + +#ifndef COMPONENTS_AUTOFILL_CONTENT_BROWSER_RISK_FINGERPRINT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_RISK_FINGERPRINT_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/memory/scoped_ptr.h" +#include "components/autofill/core/browser/autofill_manager_delegate.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. +// |obfuscated_gaia_id| is an obfuscated 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( + uint64 obfuscated_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, + DialogType dialog_type, + const std::string& app_locale, + const base::Callback<void(scoped_ptr<Fingerprint>)>& callback); + +} // namespace risk +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_RISK_FINGERPRINT_H_ diff --git a/chromium/components/autofill/content/browser/risk/proto/fingerprint.proto b/chromium/components/autofill/content/browser/risk/proto/fingerprint.proto new file mode 100644 index 00000000000..5cacd76d645 --- /dev/null +++ b/chromium/components/autofill/content/browser/risk/proto/fingerprint.proto @@ -0,0 +1,224 @@ +// 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. +// +// 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; + } + + // Browser features that integrate with Risk. + enum BrowserFeature { + FEATURE_UNKNOWN = 0; // Should not be reachable. + FEATURE_AUTOCHECKOUT = 1; + FEATURE_REQUEST_AUTOCOMPLETE = 2; + } + + // A hash of the concatenatation of: + // * The username of the user currently logged into computer / device. + // * The user-assigned computer or device name. + optional fixed64 user_and_device_name_hash = 1; + + // Build version string for the current operating system. + optional string operating_system_build = 2; + + // Browser install time (hours since epoch). + optional int64 browser_install_time_hours = 3; + + // Fonts installed on the machine. + repeated string font = 4; + + // Plug-ins installed on the machine. + repeated Plugin plugin = 5; + + // Delta in ms of the device's time zone from UTC. + optional int64 utc_offset_ms = 6; + + // IETF-formatted language tag. e.g. "en", "en-US", "es-419", etc. + // http://en.wikipedia.org/wiki/IETF_language_tag + optional string browser_language = 7; + + // User-requested language code of viewed sites. Languages in + // accept-languages. + repeated string requested_language = 8; + + // Default charset of the browser. (e.g. ISO-8859-1, obtained from + // document.defaultCharset) + optional string charset = 9; + + // The number of physical screens. + optional int32 screen_count = 10; + + // Information about the user's monitor's physical screen size. + // (e.g. 1024 x 768) + optional Dimension screen_size = 11; + + // The color depth of the user's screen (obtained from screen.colorDepth + // or screen.pixelDepth) + optional int32 screen_color_depth = 12; + + // 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 = 13; + + optional string user_agent = 14; + + // Total size of each hard drive partition. + repeated int32 partition_size = 15; + + optional Cpu cpu = 16; + + // Total RAM in bytes. + optional int64 ram = 17; + + // Graphics card being used. + optional Graphics graphics_card = 18; + + // Build version string for browser. + optional string browser_build = 19; + + // The client-side feature corresponding to this request. + optional BrowserFeature browser_feature = 20; + } + + // Contains properties relating to more transient computer / browser state. + message TransientState { + // Corresponds to window.innerWidth / innerHeight + 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 double accuracy = 4; + // Milliseconds since epoch since measurement. + optional int64 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; + // Obfuscated Gaia id associated with transaction. + optional uint64 obfuscated_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; +} diff --git a/chromium/components/autofill/content/browser/wallet/OWNERS b/chromium/components/autofill/content/browser/wallet/OWNERS new file mode 100644 index 00000000000..cba869d6b8a --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/OWNERS @@ -0,0 +1 @@ +dbeam@chromium.org diff --git a/chromium/components/autofill/content/browser/wallet/form_field_error.cc b/chromium/components/autofill/content/browser/wallet/form_field_error.cc new file mode 100644 index 00000000000..826ea37df2c --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/form_field_error.cc @@ -0,0 +1,154 @@ +// 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/content/browser/wallet/form_field_error.h" + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" + +namespace autofill { +namespace wallet { + +namespace { + +FormFieldError::ErrorType ErrorTypeFromString(const std::string& error_type) { + if (LowerCaseEqualsASCII(error_type, "unknown_error")) + return FormFieldError::UNKNOWN_ERROR; + if (LowerCaseEqualsASCII(error_type, "invalid_phone_number")) + return FormFieldError::INVALID_PHONE_NUMBER; + if (LowerCaseEqualsASCII(error_type, "invalid_postal_code")) + return FormFieldError::INVALID_POSTAL_CODE; + if (LowerCaseEqualsASCII(error_type, "invalid_address")) + return FormFieldError::INVALID_ADDRESS; + if (LowerCaseEqualsASCII(error_type, "invalid_card_details")) + return FormFieldError::INVALID_CARD_DETAILS; + if (LowerCaseEqualsASCII(error_type, "invalid_city")) + return FormFieldError::INVALID_CITY; + if (LowerCaseEqualsASCII(error_type, "invalid_instrument")) + return FormFieldError::INVALID_INSTRUMENT; + if (LowerCaseEqualsASCII(error_type, "invalid_state")) + return FormFieldError::INVALID_STATE; + if (LowerCaseEqualsASCII(error_type, "required_field_not_set")) + return FormFieldError::REQUIRED_FIELD_NOT_SET; + return FormFieldError::UNKNOWN_ERROR; +} + +FormFieldError::Location LocationFromString(const std::string& location) { + if (LowerCaseEqualsASCII(location, "unknown_location")) + return FormFieldError::UNKNOWN_LOCATION; + if (LowerCaseEqualsASCII(location, "payment_instrument")) + return FormFieldError::PAYMENT_INSTRUMENT; + if (LowerCaseEqualsASCII(location, "shipping_address")) + return FormFieldError::SHIPPING_ADDRESS; + if (LowerCaseEqualsASCII(location, "legal_address")) + return FormFieldError::LEGAL_ADDRESS; + return FormFieldError::UNKNOWN_LOCATION; +} + +} // namespace + +FormFieldError::FormFieldError(ErrorType error_type, Location location) + : error_type_(error_type), + location_(location) {} + +FormFieldError::~FormFieldError() {} + +ServerFieldType FormFieldError::GetAutofillType() const { + switch (error_type_) { + case INVALID_PHONE_NUMBER: + if (location_ == LEGAL_ADDRESS || location_ == PAYMENT_INSTRUMENT) + return PHONE_BILLING_WHOLE_NUMBER; + if (location_ == SHIPPING_ADDRESS) + return PHONE_HOME_WHOLE_NUMBER; + break; + + case INVALID_POSTAL_CODE: + case INVALID_CITY: + case INVALID_STATE: + if (location_ == LEGAL_ADDRESS || location_ == PAYMENT_INSTRUMENT) + return ADDRESS_BILLING_ZIP; + if (location_ == SHIPPING_ADDRESS) + return ADDRESS_HOME_ZIP; + break; + + case INVALID_ADDRESS: + if (location_ == LEGAL_ADDRESS || location_ == PAYMENT_INSTRUMENT) + return ADDRESS_BILLING_LINE1; + if (location_ == SHIPPING_ADDRESS) + return ADDRESS_HOME_LINE1; + break; + + case INVALID_CARD_DETAILS: + return CREDIT_CARD_VERIFICATION_CODE; + + case INVALID_INSTRUMENT: + return CREDIT_CARD_NUMBER; + + case REQUIRED_FIELD_NOT_SET: + case UNKNOWN_ERROR: + return MAX_VALID_FIELD_TYPE; + } + + return MAX_VALID_FIELD_TYPE; +} + +// TODO(ahutter): L10n after UX provides strings. +base::string16 FormFieldError::GetErrorMessage() const { + switch (error_type_) { + case INVALID_PHONE_NUMBER: + return base::ASCIIToUTF16("Not a valid phone number"); + + case INVALID_POSTAL_CODE: + return base::ASCIIToUTF16("Not a valid zip code"); + + case INVALID_CITY: + return base::ASCIIToUTF16("Zip code is not valid for the entered city"); + + case INVALID_STATE: + return base::ASCIIToUTF16("Zip code is not valid for the entered state"); + + case INVALID_ADDRESS: + return base::ASCIIToUTF16("Not a valid street address"); + + case INVALID_CARD_DETAILS: + return base::ASCIIToUTF16("Not a valid CVN"); + + case INVALID_INSTRUMENT: + return base::ASCIIToUTF16("Not a valid CC#"); + + case REQUIRED_FIELD_NOT_SET: + return base::ASCIIToUTF16("Required field is missing"); + + case UNKNOWN_ERROR: + return base::ASCIIToUTF16("An unknown error occurred"); + } + + NOTREACHED(); + return base::string16(); +} + +// static +FormFieldError FormFieldError::CreateFormFieldError( + const base::DictionaryValue& dictionary) { + FormFieldError form_field_error(UNKNOWN_ERROR, UNKNOWN_LOCATION); + + std::string error_type; + if (dictionary.GetString("type", &error_type)) + form_field_error.error_type_ = ErrorTypeFromString(error_type); + + std::string location; + if (dictionary.GetString("location", &location)) + form_field_error.location_ = LocationFromString(location); + + return form_field_error; +} + +bool FormFieldError::operator==(const FormFieldError& other) const { + return error_type_ == other.error_type_ && location_ == other.location_; +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/form_field_error.h b/chromium/components/autofill/content/browser/wallet/form_field_error.h new file mode 100644 index 00000000000..60df6d94148 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/form_field_error.h @@ -0,0 +1,88 @@ +// 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_FORM_FIELD_ERROR_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_FORM_FIELD_ERROR_H_ + +#include "base/strings/string16.h" +#include "components/autofill/core/browser/field_types.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { +namespace wallet { + +// Class for representing a single Wallet server side validation error. +class FormFieldError { + public: + // The validation error returned from the server. + enum ErrorType { + UNKNOWN_ERROR, + INVALID_PHONE_NUMBER, + INVALID_POSTAL_CODE, + // Bad street address. + INVALID_ADDRESS, + // Bad CVC. + INVALID_CARD_DETAILS, + // Wallet sends this when ZIP is invalid for the given city. + INVALID_CITY, + // Catch-all for many errors. E.g., no address given, no address ID, + // invalid card number. Wallet should only send us this error for invalid + // card number. + INVALID_INSTRUMENT, + // Wallet sends this when ZIP is invalid for the given state. + INVALID_STATE, + REQUIRED_FIELD_NOT_SET, + // TODO(ahutter): Add INVALID_COUNTRY when user can select country in the + // chooser. + }; + + // The section of the "form" where the error occurred. + enum Location { + UNKNOWN_LOCATION, + PAYMENT_INSTRUMENT, + SHIPPING_ADDRESS, + // Currently Sugar uses the billing address as user's legal address. So any + // error in billing address will be accompanied by an error in legal + // address. The client side should map LEGAL_ADDRESS to the billing address. + // This will ensure compatibility in case Sugar starts having a separate + // legal address form. + LEGAL_ADDRESS, + }; + + FormFieldError(ErrorType error_type, Location location); + ~FormFieldError(); + + ErrorType error_type() const { return error_type_; } + Location location() const { return location_; } + + // Gets the appropriate field type for |location| and |error_type|. + ServerFieldType GetAutofillType() const; + + // Gets a user facing error message appropriate for |location| and + // |error_type|. + base::string16 GetErrorMessage() const; + + // Creates an instance of FormFieldError from the input dictionary. + static FormFieldError CreateFormFieldError( + const base::DictionaryValue& dictionary); + + bool operator==(const FormFieldError& other) const; + + private: + // The type of error as defined by the Wallet server. + ErrorType error_type_; + + // The location of the error as defined by the Wallet server. + Location location_; + + // This class is intentionally copyable and assignable. +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_FORM_FIELD_ERROR_H_ diff --git a/chromium/components/autofill/content/browser/wallet/full_wallet.cc b/chromium/components/autofill/content/browser/wallet/full_wallet.cc new file mode 100644 index 00000000000..f66b28a9857 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/full_wallet.cc @@ -0,0 +1,323 @@ +// 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/content/browser/wallet/full_wallet.h" + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/credit_card.h" + +namespace { + +const size_t kPanSize = 16; +const size_t kBinSize = 6; +const size_t kCvnSize = 3; +const size_t kEncryptedRestSize = 12; + +} // 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() {} + +// static +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)); +} + +// static +scoped_ptr<FullWallet> + FullWallet::CreateFullWalletFromClearText( + int expiration_month, + int expiration_year, + const std::string& pan, + const std::string& cvn, + scoped_ptr<Address> billing_address, + scoped_ptr<Address> shipping_address) { + DCHECK(billing_address); + DCHECK(!pan.empty()); + DCHECK(!cvn.empty()); + + scoped_ptr<FullWallet> wallet(new FullWallet( + expiration_month, + expiration_year, + std::string(), // no iin -- clear text pan/cvn are set below. + std::string(), // no encrypted_rest -- clear text pan/cvn are set below. + billing_address.Pass(), + shipping_address.Pass(), + std::vector<RequiredAction>())); // no required actions in clear text. + wallet->pan_ = pan; + wallet->cvn_ = cvn; + return wallet.Pass(); +} + +base::string16 FullWallet::GetInfo(const AutofillType& type) { + switch (type.GetStorableType()) { + case CREDIT_CARD_NUMBER: + return UTF8ToUTF16(GetPan()); + + case CREDIT_CARD_NAME: + return billing_address()->recipient_name(); + + case CREDIT_CARD_VERIFICATION_CODE: + return UTF8ToUTF16(GetCvn()); + + case CREDIT_CARD_EXP_MONTH: + if (expiration_month() == 0) + return base::string16(); + return base::IntToString16(expiration_month()); + + case CREDIT_CARD_EXP_4_DIGIT_YEAR: + if (expiration_year() == 0) + return base::string16(); + return base::IntToString16(expiration_year()); + + case CREDIT_CARD_EXP_2_DIGIT_YEAR: + if (expiration_year() == 0) + return base::string16(); + return base::IntToString16(expiration_year() % 100); + + case CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: + if (expiration_month() == 0 || expiration_year() == 0) + return base::string16(); + return base::IntToString16(expiration_month()) + ASCIIToUTF16("/") + + base::IntToString16(expiration_year() % 100); + + case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: + if (expiration_month() == 0 || expiration_year() == 0) + return base::string16(); + return base::IntToString16(expiration_month()) + ASCIIToUTF16("/") + + base::IntToString16(expiration_year()); + + case CREDIT_CARD_TYPE: { + std::string internal_type = + CreditCard::GetCreditCardType(UTF8ToUTF16(GetPan())); + if (internal_type == kGenericCard) + return base::string16(); + return CreditCard::TypeForDisplay(internal_type); + } + + default: + NOTREACHED(); + } + + return base::string16(); +} + +bool FullWallet::HasRequiredAction(RequiredAction action) const { + DCHECK(ActionAppliesToFullWallet(action)); + return std::find(required_actions_.begin(), + required_actions_.end(), + action) != required_actions_.end(); +} + +base::string16 FullWallet::TypeAndLastFourDigits() { + CreditCard card; + card.SetRawInfo(CREDIT_CARD_NUMBER, + GetInfo(AutofillType(CREDIT_CARD_NUMBER))); + return card.TypeAndLastFourDigits(); +} + +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); +} + +void FullWallet::DecryptCardInfo() { + // |encrypted_rest_| must be of length |kEncryptedRestSize| in order for + // decryption to succeed and the server will not pad it with zeros. + while (encrypted_rest_.size() < kEncryptedRestSize) { + encrypted_rest_ = '0' + encrypted_rest_; + } + + DCHECK_EQ(kEncryptedRestSize, encrypted_rest_.size()); + + 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); +} + +const std::string& FullWallet::GetPan() { + if (pan_.empty()) + DecryptCardInfo(); + return pan_; +} + +const std::string& FullWallet::GetCvn() { + if (cvn_.empty()) + DecryptCardInfo(); + return cvn_; +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/full_wallet.h b/chromium/components/autofill/content/browser/wallet/full_wallet.h new file mode 100644 index 00000000000..84cc82e3835 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/full_wallet.h @@ -0,0 +1,150 @@ +// 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_CONTENT_BROWSER_WALLET_FULL_WALLET_H_ +#define COMPONENTS_AUTOFILL_CONTENT_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 "base/strings/string16.h" +#include "components/autofill/content/browser/wallet/required_action.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { + +class AutofillType; + +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); + + // Returns a wallet built from the provided clear-text data. + // Data is not validated; |pan|, |cvn| and |billing_address| must be set. + static scoped_ptr<FullWallet> + CreateFullWalletFromClearText(int expiration_month, + int expiration_year, + const std::string& pan, + const std::string& cvn, + scoped_ptr<Address> billing_address, + scoped_ptr<Address> shipping_address); + + // Returns corresponding data for |type|. + base::string16 GetInfo(const AutofillType& type); + + // Whether or not |action| is in |required_actions_|. + bool HasRequiredAction(RequiredAction action) const; + + // The type of the card that this FullWallet contains and the last four digits + // like this "Visa - 4111". + base::string16 TypeAndLastFourDigits(); + + 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 scoped_ptr<FullWallet> GetTestFullWallet(); + friend scoped_ptr<FullWallet> GetTestFullWalletInstrumentOnly(); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, CreateFullWallet); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, CreateFullWalletWithRequiredActions); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, RestLengthCorrectDecryptionTest); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, RestLengthUnderDecryptionTest); + FRIEND_TEST_ALL_PREFIXES(FullWalletTest, GetCreditCardInfo); + + 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); + + // Decrypts both |pan_| and |cvn_|. + void DecryptCardInfo(); + + // 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(); + + // 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_CONTENT_BROWSER_WALLET_FULL_WALLET_H_ diff --git a/chromium/components/autofill/content/browser/wallet/full_wallet_unittest.cc b/chromium/components/autofill/content/browser/wallet/full_wallet_unittest.cc new file mode 100644 index 00000000000..6f662131ffd --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/full_wallet_unittest.cc @@ -0,0 +1,530 @@ +// 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 "base/json/json_reader.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/full_wallet.h" +#include "components/autofill/content/browser/wallet/required_action.h" +#include "components/autofill/content/browser/wallet/wallet_test_util.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/field_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kFullWalletValidResponse[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":3000," + " \"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\":\"admin_area_name\"," + " \"postal_code_number\":\"postal_code_number\"," + " \"country_name_code\":\"US\"" + " }" + " }," + " \"shipping_address\":" + " {" + " \"id\":\"address_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\":\"US\"" + " }" + " }," + " \"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\":\"address_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\":\"address_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\":\"address_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\":\"address_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\":\"address_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\":\"address_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, + std::string(), + std::string(), + 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, + std::string(), + std::string(), + 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); + std::vector<RequiredAction> required_actions; + FullWallet full_wallet(12, + 3000, + "iin", + "rest", + GetTestAddress(), + GetTestNonDefaultShippingAddress(), + required_actions); + EXPECT_EQ(full_wallet, *FullWallet::CreateFullWallet(*dict)); +} + +TEST_F(FullWalletTest, RestLengthCorrectDecryptionTest) { + std::vector<RequiredAction> required_actions; + FullWallet full_wallet(12, + 2012, + "528512", + "5ec4feecf9d6", + GetTestAddress(), + GetTestShippingAddress(), + required_actions); + std::vector<uint8> one_time_pad; + EXPECT_TRUE(base::HexStringToBytes("5F04A8704183", &one_time_pad)); + full_wallet.set_one_time_pad(one_time_pad); + EXPECT_EQ(ASCIIToUTF16("5285121925598459"), + full_wallet.GetInfo(AutofillType(CREDIT_CARD_NUMBER))); + EXPECT_EQ(ASCIIToUTF16("989"), + full_wallet.GetInfo(AutofillType(CREDIT_CARD_VERIFICATION_CODE))); +} + +TEST_F(FullWalletTest, RestLengthUnderDecryptionTest) { + std::vector<RequiredAction> required_actions; + FullWallet full_wallet(12, + 2012, + "528512", + "4c567667e6", + GetTestAddress(), + GetTestShippingAddress(), + required_actions); + std::vector<uint8> one_time_pad; + EXPECT_TRUE(base::HexStringToBytes("063AD35324BF", &one_time_pad)); + full_wallet.set_one_time_pad(one_time_pad); + EXPECT_EQ(ASCIIToUTF16("5285127106109719"), + full_wallet.GetInfo(AutofillType(CREDIT_CARD_NUMBER))); + EXPECT_EQ(ASCIIToUTF16("385"), + full_wallet.GetInfo(AutofillType(CREDIT_CARD_VERIFICATION_CODE))); +} + +TEST_F(FullWalletTest, GetCreditCardInfo) { + std::vector<RequiredAction> required_actions; + FullWallet full_wallet(12, + 2015, + "528512", + "1a068673eb0", + GetTestAddress(), + GetTestShippingAddress(), + required_actions); + + EXPECT_EQ(ASCIIToUTF16("15"), + full_wallet.GetInfo(AutofillType(CREDIT_CARD_EXP_2_DIGIT_YEAR))); + + EXPECT_EQ(ASCIIToUTF16("12/15"), + full_wallet.GetInfo( + AutofillType(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR))); + + EXPECT_EQ(ASCIIToUTF16("12/2015"), + full_wallet.GetInfo( + AutofillType(CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR))); + + std::vector<uint8> one_time_pad; + EXPECT_TRUE(base::HexStringToBytes("075DA779F98B", &one_time_pad)); + full_wallet.set_one_time_pad(one_time_pad); + EXPECT_EQ(ASCIIToUTF16("MasterCard"), + full_wallet.GetInfo(AutofillType(CREDIT_CARD_TYPE))); +} + +TEST_F(FullWalletTest, CreateFullWalletFromClearTextData) { + scoped_ptr<FullWallet> full_wallet = + FullWallet::CreateFullWalletFromClearText( + 11, 2012, + "5555555555554444", "123", + GetTestAddress(), GetTestShippingAddress()); + EXPECT_EQ(ASCIIToUTF16("5555555555554444"), + full_wallet->GetInfo(AutofillType(CREDIT_CARD_NUMBER))); + EXPECT_EQ(ASCIIToUTF16("MasterCard"), + full_wallet->GetInfo(AutofillType(CREDIT_CARD_TYPE))); + EXPECT_EQ(ASCIIToUTF16("123"), + full_wallet->GetInfo(AutofillType(CREDIT_CARD_VERIFICATION_CODE))); + EXPECT_EQ(ASCIIToUTF16("11/12"), + full_wallet->GetInfo( + AutofillType(CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR))); + EXPECT_TRUE(GetTestAddress()->EqualsIgnoreID( + *full_wallet->billing_address())); + EXPECT_TRUE(GetTestShippingAddress()->EqualsIgnoreID( + *full_wallet->shipping_address())); +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/instrument.cc b/chromium/components/autofill/content/browser/wallet/instrument.cc new file mode 100644 index 00000000000..d50bc453638 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/instrument.cc @@ -0,0 +1,125 @@ +// 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/content/browser/wallet/instrument.h" + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/validation.h" + +namespace autofill { +namespace wallet { + +namespace { + +// Converts a known Autofill card type to a Instrument::FormOfPayment. +// Used for creating new Instruments. +Instrument::FormOfPayment FormOfPaymentFromCardType(const std::string& type) { + if (type == kAmericanExpressCard) + return Instrument::AMEX; + else if (type == kDiscoverCard) + return Instrument::DISCOVER; + else if (type == kMasterCard) + return Instrument::MASTER_CARD; + else if (type == kVisaCard) + return Instrument::VISA; + + return Instrument::UNKNOWN; +} + +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 CreditCard& card, + const base::string16& card_verification_number, + const AutofillProfile& profile) + : primary_account_number_(card.GetRawInfo(CREDIT_CARD_NUMBER)), + card_verification_number_(card_verification_number), + expiration_month_(card.expiration_month()), + expiration_year_(card.expiration_year()), + form_of_payment_(FormOfPaymentFromCardType(card.type())), + address_(new Address(profile)) { + Init(); +} + +Instrument::Instrument(const base::string16& primary_account_number, + const base::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()) { + 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_(instrument.address() ? + new Address(*instrument.address()) : NULL) { + 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(); +} + +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/chromium/components/autofill/content/browser/wallet/instrument.h b/chromium/components/autofill/content/browser/wallet/instrument.h new file mode 100644 index 00000000000..b706a7e340d --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/instrument.h @@ -0,0 +1,105 @@ +// 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_CONTENT_BROWSER_WALLET_INSTRUMENT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_INSTRUMENT_H_ + +#include <string> +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { + +class AutofillProfile; +class CreditCard; + +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, + }; + + // Convert the info in |card| to an Instrument using |profile| for address. + Instrument(const CreditCard& card, + const base::string16& card_verification_number, + const AutofillProfile& profile); + + Instrument(const base::string16& primary_account_number, + const base::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; + + const base::string16& primary_account_number() const { + return primary_account_number_; + } + const base::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_.get(); } + FormOfPayment form_of_payment() const { return form_of_payment_; } + const base::string16& last_four_digits() const { return last_four_digits_; } + const std::string& object_id() const { return object_id_; } + void set_object_id(const std::string& object_id) { object_id_ = object_id; } + + private: + void Init(); + + // |primary_account_number_| is expected to be \d{12-19}. + base::string16 primary_account_number_; + + // |card_verification_number_| is expected to be \d{3-4}. + base::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_|. + base::string16 last_four_digits_; + + // Externalized Online Wallet id for this instrument. + std::string object_id_; + + DISALLOW_ASSIGN(Instrument); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_INSTRUMENT_H_ diff --git a/chromium/components/autofill/content/browser/wallet/instrument_unittest.cc b/chromium/components/autofill/content/browser/wallet/instrument_unittest.cc new file mode 100644 index 00000000000..b5f273f87fe --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/instrument_unittest.cc @@ -0,0 +1,66 @@ +// 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 "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" +#include "components/autofill/content/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()); +} + +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", "US"); + 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/chromium/components/autofill/content/browser/wallet/mock_wallet_client.cc b/chromium/components/autofill/content/browser/wallet/mock_wallet_client.cc new file mode 100644 index 00000000000..994bd3eaf41 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/mock_wallet_client.cc @@ -0,0 +1,17 @@ +// 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/content/browser/wallet/mock_wallet_client.h" + +namespace autofill { +namespace wallet { + +MockWalletClient::MockWalletClient(net::URLRequestContextGetter* context, + wallet::WalletClientDelegate* delegate) + : wallet::WalletClient(context, delegate) {} + +MockWalletClient::~MockWalletClient() {} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/mock_wallet_client.h b/chromium/components/autofill/content/browser/wallet/mock_wallet_client.h new file mode 100644 index 00000000000..c8d03b33a46 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/mock_wallet_client.h @@ -0,0 +1,67 @@ +// 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_CONTENT_BROWSER_WALLET_MOCK_WALLET_CLIENT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_MOCK_WALLET_CLIENT_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" +#include "components/autofill/content/browser/wallet/wallet_client.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace autofill { +namespace wallet { + +// A mock version of WalletClient that never issues real requests, just records +// mock calls to each entry point. +class MockWalletClient : public WalletClient { + public: + MockWalletClient(net::URLRequestContextGetter* context, + WalletClientDelegate* delegate); + virtual ~MockWalletClient(); + + MOCK_METHOD1(GetWalletItems, void(const GURL& source_url)); + + MOCK_METHOD3(AcceptLegalDocuments, + void(const std::vector<WalletItems::LegalDocument*>& documents, + const std::string& google_transaction_id, + const GURL& source_url)); + + MOCK_METHOD2(AuthenticateInstrument, + void(const std::string& instrument_id, + const std::string& card_verification_number)); + + MOCK_METHOD1(GetFullWallet, + void(const WalletClient::FullWalletRequest& request)); + + // Methods with scoped_ptrs can't be mocked but by using the implementation + // below the same effect can be achieved. + virtual void SaveToWallet(scoped_ptr<wallet::Instrument> instrument, + scoped_ptr<wallet::Address> address, + const GURL& source_url) OVERRIDE { + SaveToWalletMock(instrument.get(), address.get(), source_url); + } + + MOCK_METHOD3(SaveToWalletMock, + void(Instrument* instrument, + Address* address, + const GURL& source_url)); + + MOCK_METHOD4(SendAutocheckoutStatus, + void(autofill::AutocheckoutStatus status, + const GURL& source_url, + const std::vector<AutocheckoutStatistic>& latency_statistics, + const std::string& google_transaction_id)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockWalletClient); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_MOCK_WALLET_CLIENT_H_ diff --git a/chromium/components/autofill/content/browser/wallet/required_action.cc b/chromium/components/autofill/content/browser/wallet/required_action.cc new file mode 100644 index 00000000000..28f9b20ba2f --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/required_action.cc @@ -0,0 +1,66 @@ +// 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/content/browser/wallet/required_action.h" + +#include "base/logging.h" +#include "base/strings/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 == CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS || + 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/chromium/components/autofill/content/browser/wallet/required_action.h b/chromium/components/autofill/content/browser/wallet/required_action.h new file mode 100644 index 00000000000..eb536220953 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/required_action.h @@ -0,0 +1,44 @@ +// 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_CONTENT_BROWSER_WALLET_REQUIRED_ACTION_H_ +#define COMPONENTS_AUTOFILL_CONTENT_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_CONTENT_BROWSER_WALLET_REQUIRED_ACTION_H_ diff --git a/chromium/components/autofill/content/browser/wallet/wallet_address.cc b/chromium/components/autofill/content/browser/wallet/wallet_address.cc new file mode 100644 index 00000000000..8ab59982f2b --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_address.cc @@ -0,0 +1,332 @@ +// 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/content/browser/wallet/wallet_address.h" + +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/state_names.h" + +namespace autofill { +namespace wallet { + +// Server specified type for address with complete details. +const char kFullAddress[] = "FULL"; + +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 missing 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"; + } + + Address* address = 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); + + bool is_minimal_address = false; + if (dictionary.GetBoolean("is_minimal_address", &is_minimal_address)) + address->set_is_complete_address(!is_minimal_address); + else + DVLOG(1) << "Response from Google Wallet missing is_minimal_address bit"; + + return address; +} + +} // namespace + +Address::Address() {} + +Address::Address(const AutofillProfile& profile) + : country_name_code_( + UTF16ToASCII(profile.GetRawInfo(ADDRESS_HOME_COUNTRY))), + recipient_name_(profile.GetRawInfo(NAME_FULL)), + address_line_1_(profile.GetRawInfo(ADDRESS_HOME_LINE1)), + address_line_2_(profile.GetRawInfo(ADDRESS_HOME_LINE2)), + locality_name_(profile.GetRawInfo(ADDRESS_HOME_CITY)), + postal_code_number_(profile.GetRawInfo(ADDRESS_HOME_ZIP)), + phone_number_(profile.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)), + is_complete_address_(true) { + state_names::GetNameAndAbbreviation(profile.GetRawInfo(ADDRESS_HOME_STATE), + NULL, + &administrative_area_name_); + StringToUpperASCII(&administrative_area_name_); +} + +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), + is_complete_address_(true) { +} + +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"; + + std::string address_state; + if (!dictionary.GetString("type", &address_state)) + DVLOG(1) << "Response from Google Wallet missing type/state of address"; + + scoped_ptr<Address> address( + new Address(country_code, + name, + address1, + address2, + city, + state, + postal_code, + phone_number, + std::string())); + address->set_is_complete_address(address_state == kFullAddress); + + return address.Pass(); +} + +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 { +#if defined(OS_ANDROID) + // TODO(aruslan): improve this stub implementation. + return recipient_name(); +#else + // TODO(estade): improve this stub implementation + l10n. + return recipient_name() + ASCIIToUTF16(", ") + address_line_1(); +#endif +} + +string16 Address::DisplayNameDetail() const { +#if defined(OS_ANDROID) + // TODO(aruslan): improve this stub implementation. + return address_line_1(); +#else + return string16(); +#endif +} + +string16 Address::GetInfo(const AutofillType& type, + const std::string& app_locale) const { + if (type.html_type() == HTML_TYPE_COUNTRY_CODE) { + DCHECK(IsStringASCII(country_name_code())); + return ASCIIToUTF16(country_name_code()); + } else if (type.html_type() == HTML_TYPE_STREET_ADDRESS) { + base::string16 address = address_line_1(); + if (!address_line_2().empty()) + address += ASCIIToUTF16(", ") + address_line_2(); + return address; + } + + switch (type.GetStorableType()) { + 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(), app_locale); + return country.name(); + } + + case PHONE_HOME_WHOLE_NUMBER: + return phone_number(); + + // TODO(estade): implement more. + default: + NOTREACHED(); + return string16(); + } +} + +bool Address::EqualsIgnoreID(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_ && + is_complete_address_ == other.is_complete_address_; +} + +bool Address::operator==(const Address& other) const { + return object_id_ == other.object_id_ && EqualsIgnoreID(other); +} + +bool Address::operator!=(const Address& other) const { + return !(*this == other); +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_address.h b/chromium/components/autofill/content/browser/wallet/wallet_address.h new file mode 100644 index 00000000000..cead6cc5d4c --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_address.h @@ -0,0 +1,201 @@ +// 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_CONTENT_BROWSER_WALLET_WALLET_ADDRESS_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_ADDRESS_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" + +namespace base { +class DictionaryValue; +} + +namespace autofill { + +class AutofillProfile; +class AutofillType; + +namespace wallet { + +// TODO(ahutter): This address is a lot like +// components/autofill/core/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: + // TODO(ahutter): Use additional fields (descriptive_name, is_post_box, + // is_valid, is_default) when SaveToWallet is implemented. + // See http://crbug.com/164284. + + Address(); + + // Using the raw info in |profile|, create a wallet::Address. + explicit Address(const AutofillProfile& profile); + + Address(const std::string& country_name_code, + const base::string16& recipient_name, + const base::string16& address_line_1, + const base::string16& address_line_2, + const base::string16& locality_name, + const base::string16& administrative_area_name, + const base::string16& postal_code_number, + const base::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); + + // TODO(ahutter): Make obvious in the function name that this public method + // only works for shipping address and assumes existance of "postal_address". + // 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. + base::string16 DisplayName() const; + + // Returns a string that could be used as a sub-label, suitable for display + // to the user together with DisplayName(). + base::string16 DisplayNameDetail() const; + + // Returns data appropriate for |type|. + base::string16 GetInfo(const AutofillType& type, + const std::string& app_locale) const; + + const std::string& country_name_code() const { return country_name_code_; } + const base::string16& recipient_name() const { return recipient_name_; } + const base::string16& address_line_1() const { return address_line_1_; } + const base::string16& address_line_2() const { return address_line_2_; } + const base::string16& locality_name() const { return locality_name_; } + const base::string16& administrative_area_name() const { + return administrative_area_name_; + } + const base::string16& postal_code_number() const { + return postal_code_number_; + } + const base::string16& phone_number() const { return phone_number_; } + const std::string& object_id() const { return object_id_; } + bool is_complete_address() const { + return is_complete_address_; + } + + void set_country_name_code(const std::string& country_name_code) { + country_name_code_ = country_name_code; + } + void set_recipient_name(const base::string16& recipient_name) { + recipient_name_ = recipient_name; + } + void set_address_line_1(const base::string16& address_line_1) { + address_line_1_ = address_line_1; + } + void set_address_line_2(const base::string16& address_line_2) { + address_line_2_ = address_line_2; + } + void set_locality_name(const base::string16& locality_name) { + locality_name_ = locality_name; + } + void set_administrative_area_name( + const base::string16& administrative_area_name) { + administrative_area_name_ = administrative_area_name; + } + void set_postal_code_number(const base::string16& postal_code_number) { + postal_code_number_ = postal_code_number; + } + void set_phone_number(const base::string16& phone_number) { + phone_number_ = phone_number; + } + void set_object_id(const std::string& object_id) { + object_id_ = object_id; + } + void set_is_complete_address(bool is_complete_address) { + is_complete_address_ = is_complete_address; + } + + // Tests if this address exact matches |other|. |object_id| is ignored. + bool EqualsIgnoreID(const Address& other) const; + + // Tests if this address exact matches |other| including |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". + base::string16 recipient_name_; + + // |address_line_1| and |address_line_2| correspond to the "AddressLine" + // elements in xAL, which are used to hold unstructured text. + base::string16 address_line_1_; + base::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. + base::string16 locality_name_; + + // Top-level administrative subdivision of this country. + // Examples: US state, IT region, UK constituent nation, JP prefecture. + // Note: this must be in short form, e.g. TX rather than Texas. + base::string16 administrative_area_name_; + + // Despite the name, |postal_code_number_| values are frequently alphanumeric. + // Examples: "94043", "SW1W", "SW1W 9TQ". + base::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/. + base::string16 phone_number_; + + // Externalized Online Wallet id for this address. + std::string object_id_; + + // Server's understanding of this address as complete address or not. + bool is_complete_address_; + + // This class is intentionally copyable. + DISALLOW_ASSIGN(Address); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_ADDRESS_H_ diff --git a/chromium/components/autofill/content/browser/wallet/wallet_address_unittest.cc b/chromium/components/autofill/content/browser/wallet/wallet_address_unittest.cc new file mode 100644 index 00000000000..9135f54570f --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_address_unittest.cc @@ -0,0 +1,478 @@ +// 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 "base/json/json_reader.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" +#include "components/autofill/core/browser/autofill_common_test.h" +#include "components/autofill/core/browser/autofill_profile.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\"," + " \"is_minimal_address\":true," + " \"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\"," + " \"type\":\"FULL\"" + "}"; + +} // 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, AddressEqualsIgnoreID) { + Address address1("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"), + "id1"); + // Same as address1, only id is different. + Address address2("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"), + "id2"); + // Has same id as address1, but name is different. + Address address3("country_name_code", + ASCIIToUTF16("a_different_name"), + ASCIIToUTF16("address_line_1"), + ASCIIToUTF16("address_line_2"), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id1"); + // Same as address1, but no id. + Address address4("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"), + std::string()); + + // Compare the address has id field to itself. + EXPECT_EQ(address1, address1); + EXPECT_TRUE(address1.EqualsIgnoreID(address1)); + + // Compare the address has no id field to itself + EXPECT_EQ(address4, address4); + EXPECT_TRUE(address4.EqualsIgnoreID(address4)); + + // Compare two addresses with different id. + EXPECT_NE(address1, address2); + EXPECT_TRUE(address1.EqualsIgnoreID(address2)); + EXPECT_TRUE(address2.EqualsIgnoreID(address1)); + + // Compare two different addresses. + EXPECT_NE(address1, address3); + EXPECT_FALSE(address1.EqualsIgnoreID(address3)); + EXPECT_FALSE(address3.EqualsIgnoreID(address1)); + + // Compare two same addresses, one has id, the other doesn't. + EXPECT_NE(address1, address4); + EXPECT_TRUE(address1.EqualsIgnoreID(address4)); + EXPECT_TRUE(address4.EqualsIgnoreID(address1)); +} + +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"), + std::string()); + EXPECT_EQ(address, *Address::CreateAddress(*dict_)); +} + +TEST_F(WalletAddressTest, CreateAddressWithIDMissingObjectId) { + SetUpDictionary(kAddressMissingObjectId); + EXPECT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressMissingCountryNameCode) { + SetUpDictionary(kAddressMissingCountryNameCode); + EXPECT_EQ(NULL, Address::CreateAddress(*dict_).get()); + EXPECT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressMissingRecipientName) { + SetUpDictionary(kAddressMissingRecipientName); + EXPECT_EQ(NULL, Address::CreateAddress(*dict_).get()); + EXPECT_EQ(NULL, Address::CreateAddressWithID(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateAddressMissingPostalCodeNumber) { + SetUpDictionary(kAddressMissingPostalCodeNumber); + EXPECT_EQ(NULL, Address::CreateAddress(*dict_).get()); + EXPECT_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"); + address.set_is_complete_address(false); + EXPECT_EQ(address, *Address::CreateAddress(*dict_)); + EXPECT_EQ(address, *Address::CreateAddressWithID(*dict_)); +} + +TEST_F(WalletAddressTest, CreateDisplayAddressMissingCountryNameCode) { + SetUpDictionary(kClientAddressMissingCountryCode); + EXPECT_EQ(NULL, Address::CreateDisplayAddress(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateDisplayAddressMissingName) { + SetUpDictionary(kClientAddressMissingName); + EXPECT_EQ(NULL, Address::CreateDisplayAddress(*dict_).get()); +} + +TEST_F(WalletAddressTest, CreateDisplayAddressMissingPostalCode) { + SetUpDictionary(kClientAddressMissingPostalCode); + EXPECT_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"), + std::string()); + EXPECT_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"), + std::string()); + + 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())); +} + +TEST_F(WalletAddressTest, FromAutofillProfile) { + { + AutofillProfile profile(test::GetFullProfile()); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("tx")); + Address address(profile); + EXPECT_EQ(ASCIIToUTF16("TX"), address.administrative_area_name()); + } + + { + AutofillProfile profile(test::GetFullProfile()); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("Texas")); + Address address(profile); + EXPECT_EQ(ASCIIToUTF16("TX"), address.administrative_area_name()); + } + + { + AutofillProfile profile(test::GetFullProfile()); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("TX")); + Address address(profile); + EXPECT_EQ(ASCIIToUTF16("TX"), address.administrative_area_name()); + } + + { + AutofillProfile profile(test::GetFullProfile()); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("txeas")); + Address address(profile); + EXPECT_TRUE(address.administrative_area_name().empty()); + } +} + +// Verifies that WalletAddress::GetInfo() can correctly return both country +// codes and localized country names. +TEST_F(WalletAddressTest, GetCountryInfo) { + Address address("FR", + 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"), + "id1"); + + AutofillType type = AutofillType(HTML_TYPE_COUNTRY_CODE, HTML_MODE_NONE); + EXPECT_EQ(ASCIIToUTF16("FR"), address.GetInfo(type, "en-US")); + + type = AutofillType(HTML_TYPE_COUNTRY_NAME, HTML_MODE_NONE); + EXPECT_EQ(ASCIIToUTF16("France"), address.GetInfo(type, "en-US")); + + type = AutofillType(ADDRESS_HOME_COUNTRY); + EXPECT_EQ(ASCIIToUTF16("France"), address.GetInfo(type, "en-US")); +} + +// Verifies that WalletAddress::GetInfo() can correctly return a concatenated +// full street address. +TEST_F(WalletAddressTest, GetStreetAddress) { + // Address has both lines 1 and 2. + Address address1("FR", + 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"), + "id1"); + AutofillType type = AutofillType(HTML_TYPE_STREET_ADDRESS, HTML_MODE_NONE); + EXPECT_EQ(ASCIIToUTF16("address_line_1, address_line_2"), + address1.GetInfo(type, "en-US")); + + // Address has only line 1. + Address address2("FR", + ASCIIToUTF16("recipient_name"), + ASCIIToUTF16("address_line_1"), + base::string16(), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id1"); + EXPECT_EQ(ASCIIToUTF16("address_line_1"), address2.GetInfo(type, "en-US")); + + // Address has no address lines. + Address address3("FR", + ASCIIToUTF16("recipient_name"), + base::string16(), + base::string16(), + ASCIIToUTF16("locality_name"), + ASCIIToUTF16("administrative_area_name"), + ASCIIToUTF16("postal_code_number"), + ASCIIToUTF16("phone_number"), + "id1"); + EXPECT_EQ(base::string16(), address3.GetInfo(type, "en-US")); +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_client.cc b/chromium/components/autofill/content/browser/wallet/wallet_client.cc new file mode 100644 index 00000000000..bb5c525273d --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_client.cc @@ -0,0 +1,911 @@ +// 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/content/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/message_loop/message_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/content/browser/wallet/form_field_error.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" +#include "components/autofill/content/browser/wallet/wallet_client_delegate.h" +#include "components/autofill/content/browser/wallet/wallet_items.h" +#include "components/autofill/content/browser/wallet/wallet_service_url.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "crypto/random.h" +#include "google_apis/google_api_keys.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" + +// TODO(ahutter): Change all VLOGs to DVLOGs after dogfood. +namespace autofill { +namespace wallet { + +namespace { + +const char kFormEncodedMimeType[] = "application/x-www-form-urlencoded"; +const char kJsonMimeType[] = "application/json"; +const char kEscrowNewInstrumentFormat[] = + "request_content_type=application/json&request=%s&cvn=%s&card_number=%s"; +const char kEscrowCardVerificationNumberFormat[] = + "request_content_type=application/json&request=%s&cvn=%s"; +const char kGetFullWalletRequestFormat[] = + "request_content_type=application/json&request=%s&otp=%s:%s"; +const size_t kOneTimePadLength = 6; + +// 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; + +std::string AutocheckoutStatusToString(AutocheckoutStatus status) { + switch (status) { + case MISSING_FIELDMAPPING: + return "MISSING_FIELDMAPPING"; + case MISSING_ADVANCE: + return "MISSING_ADVANCE"; + case MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING: + return "MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING"; + case MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING: + return "MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING"; + 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"; + case AUTOCHECKOUT_STATUS_NUM_STATUS: + NOTREACHED(); + } + 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"; +} + +std::string RiskCapabilityToString( + WalletClient::RiskCapability risk_capability) { + switch (risk_capability) { + case WalletClient::RELOGIN: + return "RELOGIN"; + case WalletClient::VERIFY_CVC: + return "VERIFY_CVC"; + } + NOTREACHED(); + return "NOT_POSSIBLE"; +} + +WalletClient::ErrorType StringToErrorType(const std::string& error_type) { + std::string trimmed; + TrimWhitespaceASCII(error_type, + TRIM_ALL, + &trimmed); + if (LowerCaseEqualsASCII(trimmed, "buyer_account_error")) + return WalletClient::BUYER_ACCOUNT_ERROR; + if (LowerCaseEqualsASCII(trimmed, "unsupported_merchant")) + return WalletClient::UNSUPPORTED_MERCHANT; + if (LowerCaseEqualsASCII(trimmed, "internal_error")) + return WalletClient::INTERNAL_ERROR; + if (LowerCaseEqualsASCII(trimmed, "invalid_params")) + return WalletClient::INVALID_PARAMS; + if (LowerCaseEqualsASCII(trimmed, "service_unavailable")) + return WalletClient::SERVICE_UNAVAILABLE; + if (LowerCaseEqualsASCII(trimmed, "unsupported_api_version")) + return WalletClient::UNSUPPORTED_API_VERSION; + + return WalletClient::UNKNOWN_ERROR; +} + +// Get the more specific WalletClient::ErrorType when the error is +// |BUYER_ACCOUNT_ERROR|. +WalletClient::ErrorType BuyerErrorStringToErrorType( + const std::string& buyer_error_type) { + std::string trimmed; + TrimWhitespaceASCII(buyer_error_type, + TRIM_ALL, + &trimmed); + if (LowerCaseEqualsASCII(trimmed, "bla_country_not_supported")) + return WalletClient::BUYER_LEGAL_ADDRESS_NOT_SUPPORTED; + if (LowerCaseEqualsASCII(trimmed, "buyer_kyc_error")) + return WalletClient::UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS; + + return WalletClient::BUYER_ACCOUNT_ERROR; +} + +// 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 base::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); + } + } +} + +void GetFormFieldErrors(const base::DictionaryValue& dict, + std::vector<FormFieldError>* form_errors) { + DCHECK(form_errors->empty()); + const base::ListValue* form_errors_list; + if (!dict.GetList("form_field_error", &form_errors_list)) + return; + + for (size_t i = 0; i < form_errors_list->GetSize(); ++i) { + const base::DictionaryValue* dictionary; + if (form_errors_list->GetDictionary(i, &dictionary)) + form_errors->push_back(FormFieldError::CreateFormFieldError(*dictionary)); + } +} + +// Converts the |error_type| to the corresponding value from the stable UMA +// metric enumeration. +AutofillMetrics::WalletErrorMetric ErrorTypeToUmaMetric( + WalletClient::ErrorType error_type) { + switch (error_type) { + case WalletClient::BAD_REQUEST: + return AutofillMetrics::WALLET_BAD_REQUEST; + case WalletClient::BUYER_LEGAL_ADDRESS_NOT_SUPPORTED: + return AutofillMetrics::WALLET_BUYER_LEGAL_ADDRESS_NOT_SUPPORTED; + case WalletClient::BUYER_ACCOUNT_ERROR: + return AutofillMetrics::WALLET_BUYER_ACCOUNT_ERROR; + case WalletClient::INTERNAL_ERROR: + return AutofillMetrics::WALLET_INTERNAL_ERROR; + case WalletClient::INVALID_PARAMS: + return AutofillMetrics::WALLET_INVALID_PARAMS; + case WalletClient::UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS: + return AutofillMetrics::WALLET_UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS; + case WalletClient::SERVICE_UNAVAILABLE: + return AutofillMetrics::WALLET_SERVICE_UNAVAILABLE; + case WalletClient::UNSUPPORTED_API_VERSION: + return AutofillMetrics::WALLET_UNSUPPORTED_API_VERSION; + case WalletClient::UNSUPPORTED_MERCHANT: + return AutofillMetrics::WALLET_UNSUPPORTED_MERCHANT; + case WalletClient::MALFORMED_RESPONSE: + return AutofillMetrics::WALLET_MALFORMED_RESPONSE; + case WalletClient::NETWORK_ERROR: + return AutofillMetrics::WALLET_NETWORK_ERROR; + case WalletClient::UNKNOWN_ERROR: + return AutofillMetrics::WALLET_UNKNOWN_ERROR; + } + + NOTREACHED(); + return AutofillMetrics::WALLET_UNKNOWN_ERROR; +} + +// Converts the |required_action| to the corresponding value from the stable UMA +// metric enumeration. +AutofillMetrics::WalletRequiredActionMetric RequiredActionToUmaMetric( + RequiredAction required_action) { + switch (required_action) { + case UNKNOWN_TYPE: + return AutofillMetrics::UNKNOWN_REQUIRED_ACTION; + case CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS: + return AutofillMetrics::CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS; + case SETUP_WALLET: + return AutofillMetrics::SETUP_WALLET; + case ACCEPT_TOS: + return AutofillMetrics::ACCEPT_TOS; + case GAIA_AUTH: + return AutofillMetrics::GAIA_AUTH; + case UPDATE_EXPIRATION_DATE: + return AutofillMetrics::UPDATE_EXPIRATION_DATE; + case UPGRADE_MIN_ADDRESS: + return AutofillMetrics::UPGRADE_MIN_ADDRESS; + case INVALID_FORM_FIELD: + return AutofillMetrics::INVALID_FORM_FIELD; + case VERIFY_CVV: + return AutofillMetrics::VERIFY_CVV; + case PASSIVE_GAIA_AUTH: + return AutofillMetrics::PASSIVE_GAIA_AUTH; + case REQUIRE_PHONE_NUMBER: + return AutofillMetrics::REQUIRE_PHONE_NUMBER; + } + + NOTREACHED(); + return AutofillMetrics::UNKNOWN_REQUIRED_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 kBuyerErrorTypeKey[] = "wallet_error.buyer_error_type"; +const char kEncryptedOtpKey[] = "encrypted_otp"; +const char kErrorTypeKey[] = "wallet_error.error_type"; +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 kInstrumentExpMonthKey[] = "instrument.credit_card.exp_month"; +const char kInstrumentExpYearKey[] = "instrument.credit_card.exp_year"; +const char kInstrumentType[] = "instrument.type"; +const char kInstrumentPhoneNumberKey[] = "instrument_phone_number"; +const char kMerchantDomainKey[] = "merchant_domain"; +const char kPhoneNumberRequired[] = "phone_number_required"; +const char kReasonKey[] = "reason"; +const char kRiskCapabilitiesKey[] = "supported_risk_challenge"; +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 kShippingAddressRequired[] = "shipping_address_required"; +const char kAutocheckoutStepsKey[] = "steps"; +const char kSuccessKey[] = "success"; +const char kUpgradedBillingAddressKey[] = "upgraded_billing_address"; +const char kUpgradedInstrumentIdKey[] = "upgraded_instrument_id"; +const char kUseMinimalAddresses[] = "use_minimal_addresses"; + +} // namespace + +WalletClient::FullWalletRequest::FullWalletRequest( + const std::string& instrument_id, + const std::string& address_id, + const GURL& source_url, + const std::string& google_transaction_id, + const std::vector<RiskCapability> risk_capabilities) + : instrument_id(instrument_id), + address_id(address_id), + source_url(source_url), + google_transaction_id(google_transaction_id), + risk_capabilities(risk_capabilities) {} + +WalletClient::FullWalletRequest::~FullWalletRequest() {} + +WalletClient::WalletClient(net::URLRequestContextGetter* context_getter, + WalletClientDelegate* delegate) + : context_getter_(context_getter), + delegate_(delegate), + request_type_(NO_PENDING_REQUEST), + one_time_pad_(kOneTimePadLength), + weak_ptr_factory_(this) { + DCHECK(context_getter_.get()); + DCHECK(delegate_); +} + +WalletClient::~WalletClient() {} + +void WalletClient::AcceptLegalDocuments( + const std::vector<WalletItems::LegalDocument*>& documents, + const std::string& google_transaction_id, + const GURL& source_url) { + if (documents.empty()) + return; + + std::vector<std::string> document_ids; + for (size_t i = 0; i < documents.size(); ++i) { + document_ids.push_back(documents[i]->id()); + } + DoAcceptLegalDocuments(document_ids, google_transaction_id, source_url); +} + +void WalletClient::AuthenticateInstrument( + const std::string& instrument_id, + const std::string& card_verification_number) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::AuthenticateInstrument, + base::Unretained(this), + instrument_id, + card_verification_number)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = AUTHENTICATE_INSTRUMENT; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kRiskParamsKey, delegate_->GetRiskData()); + request_dict.SetString(kInstrumentIdKey, instrument_id); + + std::string json_payload; + base::JSONWriter::Write(&request_dict, &json_payload); + + std::string escaped_card_verification_number = net::EscapeUrlEncodedData( + card_verification_number, true); + + std::string post_body = base::StringPrintf( + kEscrowCardVerificationNumberFormat, + net::EscapeUrlEncodedData(json_payload, true).c_str(), + escaped_card_verification_number.c_str()); + + MakeWalletRequest(GetAuthenticateInstrumentUrl(), + post_body, + kFormEncodedMimeType); +} + +void WalletClient::GetFullWallet(const FullWalletRequest& full_wallet_request) { + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::GetFullWallet, + base::Unretained(this), + full_wallet_request)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = GET_FULL_WALLET; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kRiskParamsKey, delegate_->GetRiskData()); + request_dict.SetBoolean(kUseMinimalAddresses, false); + request_dict.SetBoolean(kPhoneNumberRequired, true); + + request_dict.SetString(kSelectedInstrumentIdKey, + full_wallet_request.instrument_id); + request_dict.SetString(kSelectedAddressIdKey, full_wallet_request.address_id); + request_dict.SetString( + kMerchantDomainKey, + full_wallet_request.source_url.GetWithEmptyPath().spec()); + request_dict.SetString(kGoogleTransactionIdKey, + full_wallet_request.google_transaction_id); + request_dict.SetString(kFeatureKey, + DialogTypeToFeatureString(delegate_->GetDialogType())); + + scoped_ptr<base::ListValue> risk_capabilities_list(new base::ListValue()); + for (std::vector<RiskCapability>::const_iterator it = + full_wallet_request.risk_capabilities.begin(); + it != full_wallet_request.risk_capabilities.end(); + ++it) { + risk_capabilities_list->AppendString(RiskCapabilityToString(*it)); + } + request_dict.Set(kRiskCapabilitiesKey, risk_capabilities_list.release()); + + std::string json_payload; + base::JSONWriter::Write(&request_dict, &json_payload); + + crypto::RandBytes(&(one_time_pad_[0]), one_time_pad_.size()); + + size_t num_bits = one_time_pad_.size() * 8; + DCHECK_LE(num_bits, kMaxBits); + DCHECK_GE(num_bits, kMinBits); + + std::string post_body = base::StringPrintf( + kGetFullWalletRequestFormat, + net::EscapeUrlEncodedData(json_payload, true).c_str(), + base::HexEncode(&num_bits, 1).c_str(), + base::HexEncode(&(one_time_pad_[0]), one_time_pad_.size()).c_str()); + + MakeWalletRequest(GetGetFullWalletUrl(), post_body, kFormEncodedMimeType); +} + +void WalletClient::SaveToWallet(scoped_ptr<Instrument> instrument, + scoped_ptr<Address> address, + const GURL& source_url) { + DCHECK(instrument || address); + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::SaveToWallet, + base::Unretained(this), + base::Passed(&instrument), + base::Passed(&address), + source_url)); + return; + } + + DCHECK_EQ(NO_PENDING_REQUEST, request_type_); + request_type_ = SAVE_TO_WALLET; + + base::DictionaryValue request_dict; + request_dict.SetString(kApiKeyKey, google_apis::GetAPIKey()); + request_dict.SetString(kRiskParamsKey, delegate_->GetRiskData()); + request_dict.SetString(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + request_dict.SetBoolean(kUseMinimalAddresses, false); + request_dict.SetBoolean(kPhoneNumberRequired, true); + + std::string primary_account_number; + std::string card_verification_number; + if (instrument) { + primary_account_number = net::EscapeUrlEncodedData( + UTF16ToUTF8(instrument->primary_account_number()), true); + card_verification_number = net::EscapeUrlEncodedData( + UTF16ToUTF8(instrument->card_verification_number()), true); + + if (instrument->object_id().empty()) { + request_dict.Set(kInstrumentKey, instrument->ToDictionary().release()); + request_dict.SetString(kInstrumentPhoneNumberKey, + instrument->address()->phone_number()); + } else { + DCHECK(instrument->address() || + (instrument->expiration_month() > 0 && + instrument->expiration_year() > 0)); + + request_dict.SetString(kUpgradedInstrumentIdKey, + instrument->object_id()); + + if (instrument->address()) { + request_dict.SetString(kInstrumentPhoneNumberKey, + instrument->address()->phone_number()); + request_dict.Set( + kUpgradedBillingAddressKey, + instrument->address()->ToDictionaryWithoutID().release()); + } + + if (instrument->expiration_month() > 0 && + instrument->expiration_year() > 0) { + DCHECK(!instrument->card_verification_number().empty()); + request_dict.SetInteger(kInstrumentExpMonthKey, + instrument->expiration_month()); + request_dict.SetInteger(kInstrumentExpYearKey, + instrument->expiration_year()); + } + + if (request_dict.HasKey(kInstrumentKey)) + request_dict.SetString(kInstrumentType, "CREDIT_CARD"); + } + } + if (address) { + request_dict.Set(kShippingAddressKey, + address->ToDictionaryWithID().release()); + } + + std::string json_payload; + base::JSONWriter::Write(&request_dict, &json_payload); + + if (!card_verification_number.empty()) { + std::string post_body; + if (!primary_account_number.empty()) { + post_body = base::StringPrintf( + kEscrowNewInstrumentFormat, + net::EscapeUrlEncodedData(json_payload, true).c_str(), + card_verification_number.c_str(), + primary_account_number.c_str()); + } else { + post_body = base::StringPrintf( + kEscrowCardVerificationNumberFormat, + net::EscapeUrlEncodedData(json_payload, true).c_str(), + card_verification_number.c_str()); + } + MakeWalletRequest(GetSaveToWalletUrl(), post_body, kFormEncodedMimeType); + } else { + MakeWalletRequest(GetSaveToWalletNoEscrowUrl(), + json_payload, + kJsonMimeType); + } +} + +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(kMerchantDomainKey, + source_url.GetWithEmptyPath().spec()); + request_dict.SetBoolean(kShippingAddressRequired, + delegate_->IsShippingAddressRequired()); + request_dict.SetBoolean(kUseMinimalAddresses, false); + request_dict.SetBoolean(kPhoneNumberRequired, true); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetGetWalletItemsUrl(), post_body, kJsonMimeType); +} + +void WalletClient::SendAutocheckoutStatus( + AutocheckoutStatus status, + const GURL& source_url, + const std::vector<AutocheckoutStatistic>& latency_statistics, + const std::string& google_transaction_id) { + DVLOG(1) << "Sending Autocheckout Status: " << status + << " for: " << source_url; + if (HasRequestInProgress()) { + pending_requests_.push(base::Bind(&WalletClient::SendAutocheckoutStatus, + base::Unretained(this), + status, + source_url, + latency_statistics, + 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)); + if (!latency_statistics.empty()) { + scoped_ptr<base::ListValue> latency_statistics_json( + new base::ListValue()); + for (size_t i = 0; i < latency_statistics.size(); ++i) { + latency_statistics_json->Append( + latency_statistics[i].ToDictionary().release()); + } + request_dict.Set(kAutocheckoutStepsKey, + latency_statistics_json.release()); + } + request_dict.SetString(kGoogleTransactionIdKey, google_transaction_id); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetSendStatusUrl(), post_body, kJsonMimeType); +} + +bool WalletClient::HasRequestInProgress() const { + return request_; +} + +void WalletClient::CancelRequests() { + request_.reset(); + request_type_ = NO_PENDING_REQUEST; + while (!pending_requests_.empty()) { + pending_requests_.pop(); + } +} + +void WalletClient::DoAcceptLegalDocuments( + 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::DoAcceptLegalDocuments, + 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()); + scoped_ptr<base::ListValue> docs_list(new base::ListValue()); + for (std::vector<std::string>::const_iterator it = document_ids.begin(); + it != document_ids.end(); ++it) { + if (!it->empty()) + docs_list->AppendString(*it); + } + request_dict.Set(kAcceptedLegalDocumentKey, docs_list.release()); + + std::string post_body; + base::JSONWriter::Write(&request_dict, &post_body); + + MakeWalletRequest(GetAcceptLegalDocumentsUrl(), post_body, kJsonMimeType); +} + +void WalletClient::MakeWalletRequest(const GURL& url, + const std::string& post_body, + const std::string& mime_type) { + DCHECK(!HasRequestInProgress()); + + request_.reset(net::URLFetcher::Create( + 0, url, net::URLFetcher::POST, this)); + request_->SetRequestContext(context_getter_.get()); + VLOG(1) << "Making request to " << url << " with post_body=" << post_body; + request_->SetUploadData(mime_type, post_body); + request_->AddExtraRequestHeader("Authorization: GoogleLogin auth=" + + delegate_->GetWalletCookieValue()); + DVLOG(1) << "Setting authorization header value to " + << delegate_->GetWalletCookieValue(); + request_started_timestamp_ = base::Time::Now(); + request_->Start(); + + delegate_->GetMetricLogger().LogWalletErrorMetric( + delegate_->GetDialogType(), + AutofillMetrics::WALLET_ERROR_BASELINE_ISSUED_REQUEST); + delegate_->GetMetricLogger().LogWalletRequiredActionMetric( + delegate_->GetDialogType(), + AutofillMetrics::WALLET_REQUIRED_ACTION_BASELINE_ISSUED_REQUEST); +} + +// TODO(ahutter): Add manual retry logic if it's necessary. +void WalletClient::OnURLFetchComplete( + const net::URLFetcher* source) { + delegate_->GetMetricLogger().LogWalletApiCallDuration( + RequestTypeToUmaMetric(request_type_), + base::Time::Now() - request_started_timestamp_); + + DCHECK_EQ(source, request_.get()); + VLOG(1) << "Got response from " << source->GetOriginalURL(); + + // |request_|, which is aliased to |source|, might continue to be used in this + // |method, but should be freed once control leaves the method. + scoped_ptr<net::URLFetcher> scoped_request(request_.Pass()); + + // Prepare to start the next pending request. This is queued up as an + // asynchronous message because |this| WalletClient instance can be destroyed + // before the end of the method in response to the current incoming request. + base::MessageLoop::current()->PostTask( + FROM_HERE, + base::Bind(&WalletClient::StartNextPendingRequest, + weak_ptr_factory_.GetWeakPtr()));; + + std::string data; + source->GetResponseAsString(&data); + VLOG(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; + HandleWalletError(BAD_REQUEST); + 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; + + std::string error_type_string; + if (!response_dict->GetString(kErrorTypeKey, &error_type_string)) { + HandleWalletError(UNKNOWN_ERROR); + return; + } + WalletClient::ErrorType error_type = + StringToErrorType(error_type_string); + if (error_type == BUYER_ACCOUNT_ERROR) { + // If the error_type is |BUYER_ACCOUNT_ERROR|, then buyer_error_type + // field contains more specific information about the error. + std::string buyer_error_type_string; + if (response_dict->GetString(kBuyerErrorTypeKey, + &buyer_error_type_string)) { + error_type = BuyerErrorStringToErrorType(buyer_error_type_string); + } + } + + HandleWalletError(error_type); + return; + } + break; + } + + // Anything else is an error. + default: + request_type_ = NO_PENDING_REQUEST; + HandleWalletError(NETWORK_ERROR); + return; + } + + RequestType type = request_type_; + request_type_ = NO_PENDING_REQUEST; + + if (!(type == ACCEPT_LEGAL_DOCUMENTS || type == SEND_STATUS) && + !response_dict) { + HandleMalformedResponse(scoped_request.get()); + return; + } + + switch (type) { + case ACCEPT_LEGAL_DOCUMENTS: + delegate_->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); + delegate_->OnDidAuthenticateInstrument( + LowerCaseEqualsASCII(trimmed, "success")); + } else { + HandleMalformedResponse(scoped_request.get()); + } + break; + } + + case SEND_STATUS: + 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_); + LogRequiredActions(full_wallet->required_actions()); + delegate_->OnDidGetFullWallet(full_wallet.Pass()); + } else { + HandleMalformedResponse(scoped_request.get()); + } + break; + } + + case GET_WALLET_ITEMS: { + scoped_ptr<WalletItems> wallet_items( + WalletItems::CreateWalletItems(*response_dict)); + if (wallet_items) { + LogRequiredActions(wallet_items->required_actions()); + delegate_->OnDidGetWalletItems(wallet_items.Pass()); + } else { + HandleMalformedResponse(scoped_request.get()); + } + break; + } + + case SAVE_TO_WALLET: { + 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); + std::vector<FormFieldError> form_errors; + GetFormFieldErrors(*response_dict, &form_errors); + if (instrument_id.empty() && shipping_address_id.empty() && + required_actions.empty()) { + HandleMalformedResponse(scoped_request.get()); + } else { + LogRequiredActions(required_actions); + delegate_->OnDidSaveToWallet(instrument_id, + shipping_address_id, + required_actions, + form_errors); + } + break; + } + + case NO_PENDING_REQUEST: + NOTREACHED(); + } +} + +void WalletClient::StartNextPendingRequest() { + if (pending_requests_.empty()) + return; + + base::Closure next_request = pending_requests_.front(); + pending_requests_.pop(); + next_request.Run(); +} + +void WalletClient::HandleMalformedResponse(net::URLFetcher* request) { + // Called to inform exponential backoff logic of the error. + request->ReceivedContentWasMalformed(); + HandleWalletError(MALFORMED_RESPONSE); +} + +void WalletClient::HandleWalletError(WalletClient::ErrorType error_type) { + std::string error_message; + switch (error_type) { + case WalletClient::BAD_REQUEST: + error_message = "WALLET_BAD_REQUEST"; + break; + case WalletClient::BUYER_LEGAL_ADDRESS_NOT_SUPPORTED: + error_message = "WALLET_BUYER_LEGAL_ADDRESS_NOT_SUPPORTED"; + break; + case WalletClient::BUYER_ACCOUNT_ERROR: + error_message = "WALLET_BUYER_ACCOUNT_ERROR"; + break; + case WalletClient::INTERNAL_ERROR: + error_message = "WALLET_INTERNAL_ERROR"; + break; + case WalletClient::INVALID_PARAMS: + error_message = "WALLET_INVALID_PARAMS"; + break; + case WalletClient::UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS: + error_message = "WALLET_UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS"; + break; + case WalletClient::SERVICE_UNAVAILABLE: + error_message = "WALLET_SERVICE_UNAVAILABLE"; + break; + case WalletClient::UNSUPPORTED_API_VERSION: + error_message = "WALLET_UNSUPPORTED_API_VERSION"; + break; + case WalletClient::UNSUPPORTED_MERCHANT: + error_message = "WALLET_UNSUPPORTED_MERCHANT"; + break; + case WalletClient::MALFORMED_RESPONSE: + error_message = "WALLET_MALFORMED_RESPONSE"; + break; + case WalletClient::NETWORK_ERROR: + error_message = "WALLET_NETWORK_ERROR"; + break; + case WalletClient::UNKNOWN_ERROR: + error_message = "WALLET_UNKNOWN_ERROR"; + break; + } + + VLOG(1) << "Wallet encountered a " << error_message; + + delegate_->OnWalletError(error_type); + delegate_->GetMetricLogger().LogWalletErrorMetric( + delegate_->GetDialogType(), ErrorTypeToUmaMetric(error_type)); +} + +// Logs an UMA metric for each of the |required_actions|. +void WalletClient::LogRequiredActions( + const std::vector<RequiredAction>& required_actions) const { + for (size_t i = 0; i < required_actions.size(); ++i) { + delegate_->GetMetricLogger().LogWalletRequiredActionMetric( + delegate_->GetDialogType(), + RequiredActionToUmaMetric(required_actions[i])); + } +} + +AutofillMetrics::WalletApiCallMetric WalletClient::RequestTypeToUmaMetric( + RequestType request_type) const { + switch (request_type) { + case ACCEPT_LEGAL_DOCUMENTS: + return AutofillMetrics::ACCEPT_LEGAL_DOCUMENTS; + case AUTHENTICATE_INSTRUMENT: + return AutofillMetrics::AUTHENTICATE_INSTRUMENT; + case GET_FULL_WALLET: + return AutofillMetrics::GET_FULL_WALLET; + case GET_WALLET_ITEMS: + return AutofillMetrics::GET_WALLET_ITEMS; + case SAVE_TO_WALLET: + return AutofillMetrics::SAVE_TO_WALLET; + case SEND_STATUS: + return AutofillMetrics::SEND_STATUS; + case NO_PENDING_REQUEST: + NOTREACHED(); + return AutofillMetrics::UNKNOWN_API_CALL; + } + + NOTREACHED(); + return AutofillMetrics::UNKNOWN_API_CALL; +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_client.h b/chromium/components/autofill/content/browser/wallet/wallet_client.h new file mode 100644 index 00000000000..d4f96073127 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_client.h @@ -0,0 +1,270 @@ +// 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_CONTENT_BROWSER_WALLET_WALLET_CLIENT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_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/memory/weak_ptr.h" +#include "base/time/time.h" +#include "base/values.h" +#include "components/autofill/content/browser/autocheckout_statistic.h" +#include "components/autofill/content/browser/wallet/full_wallet.h" +#include "components/autofill/content/browser/wallet/wallet_items.h" +#include "components/autofill/core/browser/autofill_manager_delegate.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/common/autocheckout_status.h" +#include "net/url_request/url_fetcher_delegate.h" +#include "testing/gtest/include/gtest/gtest_prod.h" +#include "url/gurl.h" + +namespace net { +class URLFetcher; +class URLRequestContextGetter; +} + +namespace autofill { +namespace wallet { + +class Address; +class FullWallet; +class Instrument; +class WalletClientDelegate; + +// 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 +// SaveToWallet to set up their account before continuing. +// b) If the user has not accepted 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 SaveToWallet. +// b) The user may also choose to add a new instrument or address using +// SaveToWallet. +// 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: + // The Risk challenges supported by users of WalletClient. + enum RiskCapability { + RELOGIN, + VERIFY_CVC, + }; + + // The type of error returned by Online Wallet. + enum ErrorType { + // Errors to display to users. + BUYER_ACCOUNT_ERROR, // Risk deny, unsupported country, or + // account closed. + BUYER_LEGAL_ADDRESS_NOT_SUPPORTED, // User's Buyer Legal Address is + // unsupported by Online Wallet. + UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS, // User's "know your customer" KYC + // state is not verified (either + // KYC_REFER or KYC_FAIL). + UNSUPPORTED_MERCHANT, // Merchant is blacklisted due to + // compliance violation. + + // API errors. + BAD_REQUEST, // Request was very malformed or sent to the + // wrong endpoint. + INVALID_PARAMS, // API call had missing or invalid parameters. + UNSUPPORTED_API_VERSION, // The server API version of the request is no + // longer supported. + + // Server errors. + INTERNAL_ERROR, // Unknown server side error. + SERVICE_UNAVAILABLE, // Online Wallet is down. + + // Other errors. + MALFORMED_RESPONSE, // The response from Wallet was malformed. + NETWORK_ERROR, // The response code of the server was something + // other than a 200 or 400. + + UNKNOWN_ERROR, // Catch all error type. + }; + + struct FullWalletRequest { + public: + FullWalletRequest(const std::string& instrument_id, + const std::string& address_id, + const GURL& source_url, + const std::string& google_transaction_id, + const std::vector<RiskCapability> risk_capabilities); + ~FullWalletRequest(); + + // The ID of the backing instrument. Should have been selected by the user + // in some UI. + std::string instrument_id; + + // The ID of the shipping address. Should have been selected by the user + // in some UI. + std::string address_id; + + // The URL that Online Wallet usage is being initiated on. + GURL source_url; + + // The transaction ID from GetWalletItems. + std::string google_transaction_id; + + // The Risk challenges supported by the user of WalletClient + std::vector<RiskCapability> risk_capabilities; + + private: + DISALLOW_ASSIGN(FullWalletRequest); + }; + + // |context_getter| is reference counted so it has no lifetime or ownership + // requirements. |delegate| must outlive |this|. + WalletClient(net::URLRequestContextGetter* context_getter, + WalletClientDelegate* delegate); + + 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. + virtual 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 |google_transaction_id| is provided in the response to the + // GetWalletItems call. If |documents| are empty, |delegate_| will not receive + // a corresponding |OnDidAcceptLegalDocuments()| call. + virtual void AcceptLegalDocuments( + const std::vector<WalletItems::LegalDocument*>& documents, + 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|. |delegate_| is notified when the request is + // complete. Used to respond to Risk challenges. + virtual void AuthenticateInstrument( + const std::string& instrument_id, + const std::string& card_verification_number); + + // GetFullWallet retrieves the a FullWallet for the user. + virtual void GetFullWallet(const FullWalletRequest& full_wallet_request); + + // Saves the data in |instrument| and/or |address| to Wallet. |instrument| + // does not have to be complete if its being used to update an existing + // instrument, like in the case of expiration date or address only updates. + virtual void SaveToWallet(scoped_ptr<Instrument> instrument, + scoped_ptr<Address> address, + const GURL& source_url); + + // SendAutocheckoutStatus is used for tracking the success of Autocheckout + // flows. |status| is the result of the flow, |source_url| is the domain + // where the purchase occured, and |google_transaction_id| is the same as the + // one provided by GetWalletItems. |latency_statistics| contain statistics + // required to measure Autocheckout process. + virtual void SendAutocheckoutStatus( + autofill::AutocheckoutStatus status, + const GURL& source_url, + const std::vector<AutocheckoutStatistic>& latency_statistics, + const std::string& google_transaction_id); + + bool HasRequestInProgress() const; + + // Cancels and clears the current |request_| and |pending_requests_| (if any). + void CancelRequests(); + + private: + FRIEND_TEST_ALL_PREFIXES(WalletClientTest, PendingRequest); + FRIEND_TEST_ALL_PREFIXES(WalletClientTest, CancelRequests); + + enum RequestType { + NO_PENDING_REQUEST, + ACCEPT_LEGAL_DOCUMENTS, + AUTHENTICATE_INSTRUMENT, + GET_FULL_WALLET, + GET_WALLET_ITEMS, + SAVE_TO_WALLET, + SEND_STATUS, + }; + + // Like AcceptLegalDocuments, but takes a vector of document ids. + void DoAcceptLegalDocuments( + const std::vector<std::string>& document_ids, + const std::string& google_transaction_id, + const GURL& source_url); + + // Posts |post_body| to |url| with content type |mime_type| and notifies + // |delegate_| when the request is complete. + void MakeWalletRequest(const GURL& url, + const std::string& post_body, + const std::string& mime_type); + + // Performs bookkeeping tasks for any invalid requests. + void HandleMalformedResponse(net::URLFetcher* request); + void HandleNetworkError(int response_code); + void HandleWalletError(ErrorType error_type); + + // Start the next pending request (if any). + void StartNextPendingRequest(); + + // net::URLFetcherDelegate: + virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; + + // Logs an UMA metric for each of the |required_actions|. + void LogRequiredActions( + const std::vector<RequiredAction>& required_actions) const; + + // Converts |request_type| to an UMA metric. + AutofillMetrics::WalletApiCallMetric RequestTypeToUmaMetric( + RequestType request_type) const; + + // 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. + WalletClientDelegate* const delegate_; // 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_; + + // Requests that are waiting to be run. + std::queue<base::Closure> pending_requests_; + + // When the current request started. Used to track client side latency. + base::Time request_started_timestamp_; + + base::WeakPtrFactory<WalletClient> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(WalletClient); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_CLIENT_H_ diff --git a/chromium/components/autofill/content/browser/wallet/wallet_client_delegate.h b/chromium/components/autofill/content/browser/wallet/wallet_client_delegate.h new file mode 100644 index 00000000000..8de95046e5f --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_client_delegate.h @@ -0,0 +1,88 @@ +// 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_WALLET_CLIENT_OBSERVER_H_ +#define COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_OBSERVER_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "components/autofill/content/browser/wallet/form_field_error.h" +#include "components/autofill/content/browser/wallet/wallet_client.h" +#include "components/autofill/core/browser/autofill_manager_delegate.h" + +class AutofillMetrics; + +namespace autofill { +namespace wallet { + +class FullWallet; +class WalletItems; + +// WalletClientDelegate is to be implemented any classes making calls with +// WalletClient. The appropriate callback method will be called on +// WalletClientDelegate with the response from the Online Wallet backend. +class WalletClientDelegate { + public: + // -------------------------------------- + // Accessors called when making requests. + // -------------------------------------- + + // Returns the MetricLogger instance that should be used for logging Online + // Wallet metrics. + virtual const AutofillMetrics& GetMetricLogger() const = 0; + + // Returns the dialog type that the delegate corresponds to. + virtual DialogType GetDialogType() const = 0; + + // Returns the serialized fingerprint data to be sent to the Risk server. + virtual std::string GetRiskData() const = 0; + + // Returns the cookie value used for authorization when making requests to + // Wallet. + virtual std::string GetWalletCookieValue() const = 0; + + // Whether or not shipping address is required by the delegate. + virtual bool IsShippingAddressRequired() const = 0; + + // -------------------------------------------------------------------------- + // Callbacks called with responses from the Online Wallet backend. + // -------------------------------------------------------------------------- + + // 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 SaveToWallet 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. |form_field_errors| is + // populated with the actual form fields that are failing validation. + virtual void OnDidSaveToWallet( + const std::string& instrument_id, + const std::string& address_id, + const std::vector<RequiredAction>& required_actions, + const std::vector<FormFieldError>& form_field_errors) = 0; + + // Called when a request fails. + virtual void OnWalletError(WalletClient::ErrorType error_type) = 0; + + protected: + virtual ~WalletClientDelegate() {} +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_BROWSER_WALLET_WALLET_CLIENT_OBSERVER_H_ diff --git a/chromium/components/autofill/content/browser/wallet/wallet_client_unittest.cc b/chromium/components/autofill/content/browser/wallet/wallet_client_unittest.cc new file mode 100644 index 00000000000..031e5cd958b --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_client_unittest.cc @@ -0,0 +1,1764 @@ +// 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 "base/json/json_reader.h" +#include "base/json/json_writer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/run_loop.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/content/browser/autocheckout_steps.h" +#include "components/autofill/content/browser/wallet/full_wallet.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/wallet_client.h" +#include "components/autofill/content/browser/wallet/wallet_client_delegate.h" +#include "components/autofill/content/browser/wallet/wallet_items.h" +#include "components/autofill/content/browser/wallet/wallet_test_util.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/common/autocheckout_status.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "net/base/escape.h" +#include "net/base/net_errors.h" +#include "net/http/http_request_headers.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" +#include "url/gurl.h" + +namespace autofill { +namespace wallet { + +namespace { + +const char kGoogleTransactionId[] = "google-transaction-id"; +const char kMerchantUrl[] = "https://example.com/path?key=value"; + +const char kGetFullWalletValidResponse[] = + "{" + " \"expiration_month\":12," + " \"expiration_year\":3000," + " \"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\":\"US\"" + " }" + " }," + " \"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\":\"US\"" + " }" + " }," + " \"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\":\"4111\"," + " \"expiration_month\":12," + " \"expiration_year\":3000," + " \"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\":\"saved_address_id\"" + "}"; + +const char kSaveAddressWithRequiredActionsValidResponse[] = + "{" + " \"form_field_error\":" + " [" + " {" + " \"location\":\"SHIPPING_ADDRESS\"," + " \"type\":\"INVALID_POSTAL_CODE\"" + " }" + " ]," + " \"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[] = + "{" + " \"form_field_error\":" + " [" + " {" + " \"location\":\"SHIPPING_ADDRESS\"," + " \"type\":\"INVALID_POSTAL_CODE\"" + " }" + " ]," + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kSaveInstrumentAndAddressValidResponse[] = + "{" + " \"shipping_address_id\":\"saved_address_id\"," + " \"instrument_id\":\"saved_instrument_id\"" + "}"; + +const char kSaveInstrumentAndAddressWithRequiredActionsValidResponse[] = + "{" + " \"form_field_error\":" + " [" + " {" + " \"location\":\"SHIPPING_ADDRESS\"," + " \"type\":\"INVALID_POSTAL_CODE\"" + " }" + " ]," + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kUpdateInstrumentValidResponse[] = + "{" + " \"instrument_id\":\"instrument_id\"" + "}"; + +const char kUpdateAddressValidResponse[] = + "{" + " \"shipping_address_id\":\"shipping_address_id\"" + "}"; + +const char kUpdateWithRequiredActionsValidResponse[] = + "{" + " \"form_field_error\":" + " [" + " {" + " \"location\":\"SHIPPING_ADDRESS\"," + " \"type\":\"INVALID_POSTAL_CODE\"" + " }" + " ]," + " \"required_action\":" + " [" + " \" \\treqUIRE_PhOnE_number \\n\\r\"," + " \"INVALID_form_field\"" + " ]" + "}"; + +const char kUpdateMalformedResponse[] = + "{" + " \"cheese\":\"monkeys\"" + "}"; + +const char kAuthenticateInstrumentFailureResponse[] = + "{" + " \"auth_result\":\"anything else\"" + "}"; + +const char kAuthenticateInstrumentSuccessResponse[] = + "{" + " \"auth_result\":\"SUCCESS\"" + "}"; + +const char kErrorResponse[] = + "{" + " \"error_type\":\"APPLICATION_ERROR\"," + " \"error_detail\":\"error_detail\"," + " \"application_error\":\"application_error\"," + " \"debug_data\":" + " {" + " \"debug_message\":\"debug_message\"," + " \"stack_trace\":\"stack_trace\"" + " }," + " \"application_error_data\":\"application_error_data\"," + " \"wallet_error\":" + " {" + " \"error_type\":\"SERVICE_UNAVAILABLE\"," + " \"error_detail\":\"error_detail\"," + " \"message_for_user\":" + " {" + " \"text\":\"text\"," + " \"subtext\":\"subtext\"," + " \"details\":\"details\"" + " }" + " }" + "}"; + +const char kErrorTypeMissingInResponse[] = + "{" + " \"error_type\":\"Not APPLICATION_ERROR\"," + " \"error_detail\":\"error_detail\"," + " \"application_error\":\"application_error\"," + " \"debug_data\":" + " {" + " \"debug_message\":\"debug_message\"," + " \"stack_trace\":\"stack_trace\"" + " }," + " \"application_error_data\":\"application_error_data\"" + "}"; + +// 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_id_1\"," + "\"doc_id_2\"" + "]," + "\"google_transaction_id\":\"google-transaction-id\"," + "\"merchant_domain\":\"https://example.com/\"" + "}"; + +const char kAuthenticateInstrumentValidRequest[] = + "{" + "\"instrument_id\":\"instrument_id\"," + "\"risk_params\":\"risky business\"" + "}"; + +const char kGetFullWalletValidRequest[] = + "{" + "\"feature\":\"REQUEST_AUTOCOMPLETE\"," + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"risk_params\":\"risky business\"," + "\"selected_address_id\":\"shipping_address_id\"," + "\"selected_instrument_id\":\"instrument_id\"," + "\"supported_risk_challenge\":" + "[" + "]," + "\"use_minimal_addresses\":false" + "}"; + +const char kGetFullWalletWithRiskCapabilitesValidRequest[] = + "{" + "\"feature\":\"REQUEST_AUTOCOMPLETE\"," + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"risk_params\":\"risky business\"," + "\"selected_address_id\":\"shipping_address_id\"," + "\"selected_instrument_id\":\"instrument_id\"," + "\"supported_risk_challenge\":" + "[" + "\"VERIFY_CVC\"" + "]," + "\"use_minimal_addresses\":false" + "}"; + +const char kGetWalletItemsValidRequest[] = + "{" + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"shipping_address_required\":true," + "\"use_minimal_addresses\":false" + "}"; + +const char kGetWalletItemsNoShippingRequest[] = + "{" + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"shipping_address_required\":false," + "\"use_minimal_addresses\":false" + "}"; + +const char kSaveAddressValidRequest[] = + "{" + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"risk_params\":\"risky business\"," + "\"shipping_address\":" + "{" + "\"phone_number\":\"save_phone_number\"," + "\"postal_address\":" + "{" + "\"address_line\":" + "[" + "\"save_address_line_1\"," + "\"save_address_line_2\"" + "]," + "\"administrative_area_name\":\"save_admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"save_locality_name\"," + "\"postal_code_number\":\"save_postal_code_number\"," + "\"recipient_name\":\"save_recipient_name\"" + "}" + "}," + "\"use_minimal_addresses\":false" + "}"; + +const char kSaveInstrumentValidRequest[] = + "{" + "\"instrument\":" + "{" + "\"credit_card\":" + "{" + "\"address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"exp_month\":12," + "\"exp_year\":3000," + "\"fop_type\":\"VISA\"," + "\"last_4_digits\":\"4448\"" + "}," + "\"type\":\"CREDIT_CARD\"" + "}," + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"risk_params\":\"risky business\"," + "\"use_minimal_addresses\":false" + "}"; + +const char kSaveInstrumentAndAddressValidRequest[] = + "{" + "\"instrument\":" + "{" + "\"credit_card\":" + "{" + "\"address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"exp_month\":12," + "\"exp_year\":3000," + "\"fop_type\":\"VISA\"," + "\"last_4_digits\":\"4448\"" + "}," + "\"type\":\"CREDIT_CARD\"" + "}," + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"risk_params\":\"risky business\"," + "\"shipping_address\":" + "{" + "\"phone_number\":\"save_phone_number\"," + "\"postal_address\":" + "{" + "\"address_line\":" + "[" + "\"save_address_line_1\"," + "\"save_address_line_2\"" + "]," + "\"administrative_area_name\":\"save_admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"save_locality_name\"," + "\"postal_code_number\":\"save_postal_code_number\"," + "\"recipient_name\":\"save_recipient_name\"" + "}" + "}," + "\"use_minimal_addresses\":false" + "}"; + +const char kSendAutocheckoutStatusOfSuccessValidRequest[] = + "{" + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"success\":true" + "}"; + +const char kSendAutocheckoutStatusWithStatisticsValidRequest[] = + "{" + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"steps\":[{\"step_description\":\"1_AUTOCHECKOUT_STEP_SHIPPING\"" + ",\"time_taken\":100}]," + "\"success\":true" + "}"; + +const char kSendAutocheckoutStatusOfFailureValidRequest[] = + "{" + "\"google_transaction_id\":\"google_transaction_id\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"reason\":\"CANNOT_PROCEED\"," + "\"success\":false" + "}"; + +const char kUpdateAddressValidRequest[] = + "{" + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"risk_params\":\"risky business\"," + "\"shipping_address\":" + "{" + "\"id\":\"shipping_address_id\"," + "\"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\":\"US\"," + "\"locality_name\":\"ship_locality_name\"," + "\"postal_code_number\":\"ship_postal_code_number\"," + "\"recipient_name\":\"ship_recipient_name\"" + "}" + "}," + "\"use_minimal_addresses\":false" + "}"; + +const char kUpdateInstrumentAddressValidRequest[] = + "{" + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"risk_params\":\"risky business\"," + "\"upgraded_billing_address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"upgraded_instrument_id\":\"instrument_id\"," + "\"use_minimal_addresses\":false" + "}"; + +const char kUpdateInstrumentAddressWithNameChangeValidRequest[] = + "{" + "\"instrument_phone_number\":\"phone_number\"," + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"risk_params\":\"risky business\"," + "\"upgraded_billing_address\":" + "{" + "\"address_line\":" + "[" + "\"address_line_1\"," + "\"address_line_2\"" + "]," + "\"administrative_area_name\":\"admin_area_name\"," + "\"country_name_code\":\"US\"," + "\"locality_name\":\"locality_name\"," + "\"postal_code_number\":\"postal_code_number\"," + "\"recipient_name\":\"recipient_name\"" + "}," + "\"upgraded_instrument_id\":\"instrument_id\"," + "\"use_minimal_addresses\":false" + "}"; + +const char kUpdateInstrumentExpirationDateValidRequest[] = + "{" + "\"instrument\":" + "{" + "\"credit_card\":" + "{" + "\"exp_month\":12," + "\"exp_year\":3000" + "}," + "\"type\":\"CREDIT_CARD\"" + "}," + "\"merchant_domain\":\"https://example.com/\"," + "\"phone_number_required\":true," + "\"risk_params\":\"risky business\"," + "\"upgraded_instrument_id\":\"instrument_id\"," + "\"use_minimal_addresses\":false" + "}"; + +class MockAutofillMetrics : public AutofillMetrics { + public: + MockAutofillMetrics() {} + MOCK_CONST_METHOD2(LogWalletApiCallDuration, + void(WalletApiCallMetric metric, + const base::TimeDelta& duration)); + MOCK_CONST_METHOD2(LogWalletErrorMetric, + void(DialogType dialog_type, WalletErrorMetric metric)); + MOCK_CONST_METHOD2(LogWalletRequiredActionMetric, + void(DialogType dialog_type, + WalletRequiredActionMetric action)); + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillMetrics); +}; + +class MockWalletClientDelegate : public WalletClientDelegate { + public: + MockWalletClientDelegate() + : full_wallets_received_(0), + wallet_items_received_(0), + is_shipping_required_(true) {} + ~MockWalletClientDelegate() {} + + virtual const AutofillMetrics& GetMetricLogger() const OVERRIDE { + return metric_logger_; + } + + virtual DialogType GetDialogType() const OVERRIDE { + return DIALOG_TYPE_REQUEST_AUTOCOMPLETE; + } + + virtual std::string GetRiskData() const OVERRIDE { + return "risky business"; + } + + virtual std::string GetWalletCookieValue() const OVERRIDE { + return "gdToken"; + } + + virtual bool IsShippingAddressRequired() const OVERRIDE { + return is_shipping_required_; + } + + void SetIsShippingAddressRequired(bool is_shipping_required) { + is_shipping_required_ = is_shipping_required; + } + + void ExpectLogWalletApiCallDuration( + AutofillMetrics::WalletApiCallMetric metric, + size_t times) { + EXPECT_CALL(metric_logger_, + LogWalletApiCallDuration(metric, testing::_)).Times(times); + } + + void ExpectWalletErrorMetric(AutofillMetrics::WalletErrorMetric metric) { + EXPECT_CALL( + metric_logger_, + LogWalletErrorMetric( + DIALOG_TYPE_REQUEST_AUTOCOMPLETE, metric)).Times(1); + } + + void ExpectWalletRequiredActionMetric( + AutofillMetrics::WalletRequiredActionMetric metric) { + EXPECT_CALL( + metric_logger_, + LogWalletRequiredActionMetric( + DIALOG_TYPE_REQUEST_AUTOCOMPLETE, metric)).Times(1); + } + + void ExpectBaselineMetrics() { + EXPECT_CALL( + metric_logger_, + LogWalletErrorMetric( + DIALOG_TYPE_REQUEST_AUTOCOMPLETE, + AutofillMetrics::WALLET_ERROR_BASELINE_ISSUED_REQUEST)) + .Times(1); + ExpectWalletRequiredActionMetric( + AutofillMetrics::WALLET_REQUIRED_ACTION_BASELINE_ISSUED_REQUEST); + } + + MockAutofillMetrics* metric_logger() { + return &metric_logger_; + } + + MOCK_METHOD0(OnDidAcceptLegalDocuments, void()); + MOCK_METHOD1(OnDidAuthenticateInstrument, void(bool success)); + MOCK_METHOD4(OnDidSaveToWallet, + void(const std::string& instrument_id, + const std::string& shipping_address_id, + const std::vector<RequiredAction>& required_actions, + const std::vector<FormFieldError>& form_field_errors)); + MOCK_METHOD1(OnWalletError, void(WalletClient::ErrorType error_type)); + + 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_; + bool is_shipping_required_; + + testing::StrictMock<MockAutofillMetrics> metric_logger_; +}; + +} // namespace + +class WalletClientTest : public testing::Test { + public: + virtual void SetUp() OVERRIDE { + wallet_client_.reset( + new WalletClient(browser_context_.GetRequestContext(), &delegate_)); + } + + virtual void TearDown() OVERRIDE { + wallet_client_.reset(); + } + + void VerifyAndFinishRequest(net::HttpStatusCode response_code, + const std::string& request_body, + const std::string& response_body) { + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + + const std::string& upload_data = fetcher->upload_data(); + EXPECT_EQ(request_body, GetData(upload_data)); + net::HttpRequestHeaders request_headers; + fetcher->GetExtraRequestHeaders(&request_headers); + std::string auth_header_value; + EXPECT_TRUE(request_headers.GetHeader( + net::HttpRequestHeaders::kAuthorization, + &auth_header_value)); + EXPECT_EQ("GoogleLogin auth=gdToken", auth_header_value); + + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response_body); + fetcher->delegate()->OnURLFetchComplete(fetcher); + + // Pump the message loop to catch up to any asynchronous tasks that might + // have been posted from OnURLFetchComplete(). + base::RunLoop().RunUntilIdle(); + } + + void VerifyAndFinishFormEncodedRequest(net::HttpStatusCode response_code, + const std::string& json_payload, + const std::string& response_body, + size_t expected_parameter_number) { + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + + net::HttpRequestHeaders request_headers; + fetcher->GetExtraRequestHeaders(&request_headers); + std::string auth_header_value; + EXPECT_TRUE(request_headers.GetHeader( + net::HttpRequestHeaders::kAuthorization, + &auth_header_value)); + EXPECT_EQ("GoogleLogin auth=gdToken", auth_header_value); + + const std::string& upload_data = fetcher->upload_data(); + std::vector<std::pair<std::string, std::string> > tokens; + base::SplitStringIntoKeyValuePairs(upload_data, '=', '&', &tokens); + EXPECT_EQ(tokens.size(), expected_parameter_number); + + size_t num_params = 0U; + for (size_t i = 0; i < tokens.size(); ++i) { + const std::string& key = tokens[i].first; + const std::string& value = tokens[i].second; + + if (key == "request_content_type") { + EXPECT_EQ("application/json", value); + num_params++; + } + + if (key == "request") { + EXPECT_EQ(json_payload, + GetData( + net::UnescapeURLComponent( + value, net::UnescapeRule::URL_SPECIAL_CHARS | + net::UnescapeRule::REPLACE_PLUS_WITH_SPACE))); + num_params++; + } + + if (key == "cvn") { + EXPECT_EQ("123", value); + num_params++; + } + + if (key == "card_number") { + EXPECT_EQ("4444444444444448", value); + num_params++; + } + + if (key == "otp") { + EXPECT_FALSE(value.empty()); + num_params++; + } + } + EXPECT_EQ(expected_parameter_number, num_params); + + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response_body); + fetcher->delegate()->OnURLFetchComplete(fetcher); + } + + void TestWalletErrorCode( + const std::string& error_type_string, + const std::string& buyer_error_type_string, + WalletClient::ErrorType expected_error_type, + AutofillMetrics::WalletErrorMetric expected_autofill_metric) { + static const char kResponseTemplate[] = + "{" + " \"error_type\":\"APPLICATION_ERROR\"," + " \"error_detail\":\"error_detail\"," + " \"application_error\":\"application_error\"," + " \"debug_data\":" + " {" + " \"debug_message\":\"debug_message\"," + " \"stack_trace\":\"stack_trace\"" + " }," + " \"application_error_data\":\"application_error_data\"," + " \"wallet_error\":" + " {" + " \"error_type\":\"%s\"," + " %s" // Placeholder for |user_error_type|. + " \"error_detail\":\"error_detail\"," + " \"message_for_user\":" + " {" + " \"text\":\"text\"," + " \"subtext\":\"subtext\"," + " \"details\":\"details\"" + " }" + " }" + "}"; + EXPECT_CALL(delegate_, OnWalletError(expected_error_type)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(expected_autofill_metric); + + std::vector<AutocheckoutStatistic> statistics; + wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + statistics, + "google_transaction_id"); + std::string buyer_error; + if (!buyer_error_type_string.empty()) { + buyer_error = base::StringPrintf("\"buyer_error_type\":\"%s\",", + buyer_error_type_string.c_str()); + } + std::string response = base::StringPrintf(kResponseTemplate, + error_type_string.c_str(), + buyer_error.c_str()); + VerifyAndFinishRequest(net::HTTP_INTERNAL_SERVER_ERROR, + kSendAutocheckoutStatusOfSuccessValidRequest, + response); + } + + protected: + content::TestBrowserThreadBundle thread_bundle_; + scoped_ptr<WalletClient> wallet_client_; + TestingProfile browser_context_; + MockWalletClientDelegate delegate_; + + private: + std::string GetData(const std::string& upload_data) { + scoped_ptr<Value> root(base::JSONReader::Read(upload_data)); + + // If this is not a JSON dictionary, return plain text. + if (!root || !root->IsType(Value::TYPE_DICTIONARY)) + return upload_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); + std::string clean_upload_data; + base::JSONWriter::Write(dict, &clean_upload_data); + return clean_upload_data; + } + + net::TestURLFetcherFactory factory_; +}; + +TEST_F(WalletClientTest, WalletErrorCodes) { + struct { + std::string error_type_string; + std::string buyer_error_type_string; + WalletClient::ErrorType expected_error_type; + AutofillMetrics::WalletErrorMetric expected_autofill_metric; + } test_cases[] = { + // General |BUYER_ACCOUNT_ERROR| with no |buyer_error_type_string|. + { + "buyer_account_error", + "", + WalletClient::BUYER_ACCOUNT_ERROR, + AutofillMetrics::WALLET_BUYER_ACCOUNT_ERROR + }, + // |BUYER_ACCOUNT_ERROR| with "buyer_legal_address_not_supported" in + // buyer_error_type field. + { + "buyer_account_error", + "bla_country_not_supported", + WalletClient::BUYER_LEGAL_ADDRESS_NOT_SUPPORTED, + AutofillMetrics::WALLET_BUYER_LEGAL_ADDRESS_NOT_SUPPORTED + }, + // |BUYER_ACCOUNT_ERROR| with KYC error code in buyer_error_type field. + { + "buyer_account_error", + "buyer_kyc_error", + WalletClient::UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS, + AutofillMetrics::WALLET_UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS + }, + // |BUYER_ACCOUNT_ERROR| with un-recognizable |buyer_error_type|. + { + "buyer_account_error", + "random_string", + WalletClient::BUYER_ACCOUNT_ERROR, + AutofillMetrics::WALLET_BUYER_ACCOUNT_ERROR + }, + // The following are other error types we could get from Wallet. + { + "unsupported_merchant", + "", + WalletClient::UNSUPPORTED_MERCHANT, + AutofillMetrics::WALLET_UNSUPPORTED_MERCHANT + }, + { + "internal_error", + "", + WalletClient::INTERNAL_ERROR, + AutofillMetrics::WALLET_INTERNAL_ERROR + }, + { + "invalid_params", + "", + WalletClient::INVALID_PARAMS, + AutofillMetrics::WALLET_INVALID_PARAMS + }, + { + "service_unavailable", + "", + WalletClient::SERVICE_UNAVAILABLE, + AutofillMetrics::WALLET_SERVICE_UNAVAILABLE + }, + { + "unsupported_api_version", + "", + WalletClient::UNSUPPORTED_API_VERSION, + AutofillMetrics::WALLET_UNSUPPORTED_API_VERSION + }, + // Any un-recognizable |error_type| is a |UNKNOWN_ERROR|. + { + "random_string_1", + "", + WalletClient::UNKNOWN_ERROR, + AutofillMetrics::WALLET_UNKNOWN_ERROR + }, + { + "random_string_2", + "", + WalletClient::UNKNOWN_ERROR, + AutofillMetrics::WALLET_UNKNOWN_ERROR + }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + SCOPED_TRACE( + base::StringPrintf("%s - %s", + test_cases[i].error_type_string.c_str(), + test_cases[i].buyer_error_type_string.c_str())); + TestWalletErrorCode(test_cases[i].error_type_string, + test_cases[i].buyer_error_type_string, + test_cases[i].expected_error_type, + test_cases[i].expected_autofill_metric); + } +} + +TEST_F(WalletClientTest, WalletErrorResponseMissing) { + EXPECT_CALL(delegate_, OnWalletError( + WalletClient::UNKNOWN_ERROR)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_UNKNOWN_ERROR); + + std::vector<AutocheckoutStatistic> statistics; + wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + statistics, + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_INTERNAL_SERVER_ERROR, + kSendAutocheckoutStatusOfSuccessValidRequest, + kErrorTypeMissingInResponse); +} + +TEST_F(WalletClientTest, NetworkFailureOnExpectedVoidResponse) { + EXPECT_CALL(delegate_, OnWalletError(WalletClient::NETWORK_ERROR)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_NETWORK_ERROR); + + std::vector<AutocheckoutStatistic> statistics; + wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + statistics, + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_UNAUTHORIZED, + kSendAutocheckoutStatusOfSuccessValidRequest, + std::string()); +} + +TEST_F(WalletClientTest, NetworkFailureOnExpectedResponse) { + EXPECT_CALL(delegate_, OnWalletError(WalletClient::NETWORK_ERROR)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_NETWORK_ERROR); + + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_UNAUTHORIZED, + kGetWalletItemsValidRequest, + std::string()); +} + +TEST_F(WalletClientTest, RequestError) { + EXPECT_CALL(delegate_, OnWalletError(WalletClient::BAD_REQUEST)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_BAD_REQUEST); + + std::vector<AutocheckoutStatistic> statistics; + wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + statistics, + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_BAD_REQUEST, + kSendAutocheckoutStatusOfSuccessValidRequest, + std::string()); +} + +TEST_F(WalletClientTest, GetFullWalletSuccess) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + + WalletClient::FullWalletRequest full_wallet_request( + "instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + "google_transaction_id", + std::vector<WalletClient::RiskCapability>()); + wallet_client_->GetFullWallet(full_wallet_request); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kGetFullWalletValidRequest, + kGetFullWalletValidResponse, + 3U); + EXPECT_EQ(1U, delegate_.full_wallets_received()); +} + +TEST_F(WalletClientTest, GetFullWalletWithRiskCapabilitesSuccess) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + + std::vector<WalletClient::RiskCapability> risk_capabilities; + risk_capabilities.push_back(WalletClient::VERIFY_CVC); + WalletClient::FullWalletRequest full_wallet_request( + "instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + "google_transaction_id", + risk_capabilities); + wallet_client_->GetFullWallet(full_wallet_request); + + VerifyAndFinishFormEncodedRequest( + net::HTTP_OK, + kGetFullWalletWithRiskCapabilitesValidRequest, + kGetFullWalletValidResponse, + 3U); + EXPECT_EQ(1U, delegate_.full_wallets_received()); +} + + +TEST_F(WalletClientTest, GetFullWalletMalformedResponse) { + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_FULL_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + WalletClient::FullWalletRequest full_wallet_request( + "instrument_id", + "shipping_address_id", + GURL(kMerchantUrl), + "google_transaction_id", + std::vector<WalletClient::RiskCapability>()); + wallet_client_->GetFullWallet(full_wallet_request); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kGetFullWalletValidRequest, + kGetFullWalletInvalidResponse, + 3U); + EXPECT_EQ(0U, delegate_.full_wallets_received()); +} + +TEST_F(WalletClientTest, AcceptLegalDocuments) { + EXPECT_CALL(delegate_, OnDidAcceptLegalDocuments()).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::ACCEPT_LEGAL_DOCUMENTS, + 1); + delegate_.ExpectBaselineMetrics(); + + ScopedVector<WalletItems::LegalDocument> docs; + base::DictionaryValue document; + document.SetString("legal_document_id", "doc_id_1"); + document.SetString("display_name", "doc_1"); + docs.push_back( + WalletItems::LegalDocument::CreateLegalDocument(document).release()); + document.SetString("legal_document_id", "doc_id_2"); + document.SetString("display_name", "doc_2"); + docs.push_back( + WalletItems::LegalDocument::CreateLegalDocument(document).release()); + docs.push_back( + WalletItems::LegalDocument::CreatePrivacyPolicyDocument().release()); + wallet_client_->AcceptLegalDocuments(docs.get(), + kGoogleTransactionId, + GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kAcceptLegalDocumentsValidRequest, + ")}'"); // Invalid JSON. Should be ignored. +} + +TEST_F(WalletClientTest, AuthenticateInstrumentSucceeded) { + EXPECT_CALL(delegate_, OnDidAuthenticateInstrument(true)).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::AUTHENTICATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(); + + wallet_client_->AuthenticateInstrument("instrument_id", "123"); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kAuthenticateInstrumentValidRequest, + kAuthenticateInstrumentSuccessResponse, + 3U); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentFailed) { + EXPECT_CALL(delegate_, OnDidAuthenticateInstrument(false)).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::AUTHENTICATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(); + + wallet_client_->AuthenticateInstrument("instrument_id", "123"); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kAuthenticateInstrumentValidRequest, + kAuthenticateInstrumentFailureResponse, + 3U); +} + +TEST_F(WalletClientTest, AuthenticateInstrumentFailedMalformedResponse) { + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::AUTHENTICATE_INSTRUMENT, + 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + wallet_client_->AuthenticateInstrument("instrument_id", "123"); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kAuthenticateInstrumentValidRequest, + kSaveInvalidResponse, + 3U); +} + +// TODO(ahutter): Add failure tests for GetWalletItems. + +TEST_F(WalletClientTest, GetWalletItems) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectBaselineMetrics(); + + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + + VerifyAndFinishRequest(net::HTTP_OK, + kGetWalletItemsValidRequest, + kGetWalletItemsValidResponse); + EXPECT_EQ(1U, delegate_.wallet_items_received()); +} + +TEST_F(WalletClientTest, GetWalletItemsRespectsDelegateForShippingRequired) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectBaselineMetrics(); + delegate_.SetIsShippingAddressRequired(false); + + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + + VerifyAndFinishRequest(net::HTTP_OK, + kGetWalletItemsNoShippingRequest, + kGetWalletItemsValidResponse); + EXPECT_EQ(1U, delegate_.wallet_items_received()); +} + +TEST_F(WalletClientTest, SaveAddressSucceeded) { + EXPECT_CALL(delegate_, + OnDidSaveToWallet(std::string(), + "saved_address_id", + std::vector<RequiredAction>(), + std::vector<FormFieldError>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveToWallet(scoped_ptr<Instrument>(), + address.Pass(), + GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kSaveAddressValidRequest, + kSaveAddressValidResponse); +} + +TEST_F(WalletClientTest, SaveAddressWithRequiredActionsSucceeded) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::REQUIRE_PHONE_NUMBER); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::INVALID_FORM_FIELD); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + std::vector<FormFieldError> form_errors; + form_errors.push_back(FormFieldError(FormFieldError::INVALID_POSTAL_CODE, + FormFieldError::SHIPPING_ADDRESS)); + + EXPECT_CALL(delegate_, + OnDidSaveToWallet(std::string(), + std::string(), + required_actions, + form_errors)).Times(1); + + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveToWallet(scoped_ptr<Instrument>(), + address.Pass(), + GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kSaveAddressValidRequest, + kSaveAddressWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, SaveAddressFailedInvalidRequiredAction) { + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveToWallet(scoped_ptr<Instrument>(), + address.Pass(), + GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kSaveAddressValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, SaveAddressFailedMalformedResponse) { + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveToWallet(scoped_ptr<Instrument>(), + address.Pass(), + GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kSaveAddressValidRequest, + kSaveInvalidResponse); +} + +TEST_F(WalletClientTest, SaveInstrumentSucceeded) { + EXPECT_CALL(delegate_, + OnDidSaveToWallet("instrument_id", + std::string(), + std::vector<RequiredAction>(), + std::vector<FormFieldError>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + wallet_client_->SaveToWallet(instrument.Pass(), + scoped_ptr<Address>(), + GURL(kMerchantUrl)); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveInstrumentValidResponse, + 4U); +} + +TEST_F(WalletClientTest, SaveInstrumentWithRequiredActionsSucceeded) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::REQUIRE_PHONE_NUMBER); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::INVALID_FORM_FIELD); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + std::vector<FormFieldError> form_errors; + form_errors.push_back(FormFieldError(FormFieldError::INVALID_POSTAL_CODE, + FormFieldError::SHIPPING_ADDRESS)); + + EXPECT_CALL(delegate_, + OnDidSaveToWallet(std::string(), + std::string(), + required_actions, + form_errors)).Times(1); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + wallet_client_->SaveToWallet(instrument.Pass(), + scoped_ptr<Address>(), + GURL(kMerchantUrl)); + + VerifyAndFinishFormEncodedRequest( + net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveInstrumentWithRequiredActionsValidResponse, + 4U); +} + +TEST_F(WalletClientTest, SaveInstrumentFailedInvalidRequiredActions) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + wallet_client_->SaveToWallet(instrument.Pass(), + scoped_ptr<Address>(), + GURL(kMerchantUrl)); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveWithInvalidRequiredActionsResponse, + 4U); +} + +TEST_F(WalletClientTest, SaveInstrumentFailedMalformedResponse) { + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + wallet_client_->SaveToWallet(instrument.Pass(), + scoped_ptr<Address>(), + GURL(kMerchantUrl)); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kSaveInstrumentValidRequest, + kSaveInvalidResponse, + 4U); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressSucceeded) { + EXPECT_CALL(delegate_, + OnDidSaveToWallet("saved_instrument_id", + "saved_address_id", + std::vector<RequiredAction>(), + std::vector<FormFieldError>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::SAVE_TO_WALLET, + 1); + delegate_.ExpectBaselineMetrics(); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveToWallet(instrument.Pass(), + address.Pass(), + GURL(kMerchantUrl)); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveInstrumentAndAddressValidResponse, + 4U); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressWithRequiredActionsSucceeded) { + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::SAVE_TO_WALLET, + 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::REQUIRE_PHONE_NUMBER); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::INVALID_FORM_FIELD); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + std::vector<FormFieldError> form_errors; + form_errors.push_back(FormFieldError(FormFieldError::INVALID_POSTAL_CODE, + FormFieldError::SHIPPING_ADDRESS)); + + EXPECT_CALL(delegate_, + OnDidSaveToWallet(std::string(), + std::string(), + required_actions, + form_errors)).Times(1); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveToWallet(instrument.Pass(), + address.Pass(), + GURL(kMerchantUrl)); + + VerifyAndFinishFormEncodedRequest( + net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveInstrumentAndAddressWithRequiredActionsValidResponse, + 4U); +} + +TEST_F(WalletClientTest, SaveInstrumentAndAddressFailedInvalidRequiredAction) { + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration( + AutofillMetrics::SAVE_TO_WALLET, + 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Instrument> instrument = GetTestInstrument(); + scoped_ptr<Address> address = GetTestSaveableAddress(); + wallet_client_->SaveToWallet(instrument.Pass(), + address.Pass(), + GURL(kMerchantUrl)); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kSaveInstrumentAndAddressValidRequest, + kSaveWithInvalidRequiredActionsResponse, + 4U); +} + +TEST_F(WalletClientTest, UpdateAddressSucceeded) { + EXPECT_CALL(delegate_, + OnDidSaveToWallet(std::string(), + "shipping_address_id", + std::vector<RequiredAction>(), + std::vector<FormFieldError>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + + scoped_ptr<Address> address = GetTestShippingAddress(); + address->set_object_id("shipping_address_id"); + + wallet_client_->SaveToWallet(scoped_ptr<Instrument>(), + address.Pass(), + GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateAddressValidRequest, + kUpdateAddressValidResponse); +} + +TEST_F(WalletClientTest, UpdateAddressWithRequiredActionsSucceeded) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::REQUIRE_PHONE_NUMBER); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::INVALID_FORM_FIELD); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + std::vector<FormFieldError> form_errors; + form_errors.push_back(FormFieldError(FormFieldError::INVALID_POSTAL_CODE, + FormFieldError::SHIPPING_ADDRESS)); + + EXPECT_CALL(delegate_, OnDidSaveToWallet(std::string(), + std::string(), + required_actions, + form_errors)).Times(1); + + scoped_ptr<Address> address = GetTestShippingAddress(); + address->set_object_id("shipping_address_id"); + + wallet_client_->SaveToWallet(scoped_ptr<Instrument>(), + address.Pass(), + GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateAddressValidRequest, + kUpdateWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, UpdateAddressFailedInvalidRequiredAction) { + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Address> address = GetTestShippingAddress(); + address->set_object_id("shipping_address_id"); + + wallet_client_->SaveToWallet(scoped_ptr<Instrument>(), + address.Pass(), + GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateAddressValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, UpdateAddressMalformedResponse) { + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + scoped_ptr<Address> address = GetTestShippingAddress(); + address->set_object_id("shipping_address_id"); + + wallet_client_->SaveToWallet(scoped_ptr<Instrument>(), + address.Pass(), + GURL(kMerchantUrl)); + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateAddressValidRequest, + kUpdateMalformedResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentAddressSucceeded) { + EXPECT_CALL(delegate_, + OnDidSaveToWallet("instrument_id", + std::string(), + std::vector<RequiredAction>(), + std::vector<FormFieldError>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, + 1); + delegate_.ExpectBaselineMetrics(); + + wallet_client_->SaveToWallet(GetTestAddressUpgradeInstrument(), + scoped_ptr<Address>(), + GURL(kMerchantUrl)); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentAddressValidRequest, + kUpdateInstrumentValidResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentExpirationDateSuceeded) { + EXPECT_CALL(delegate_, + OnDidSaveToWallet("instrument_id", + std::string(), + std::vector<RequiredAction>(), + std::vector<FormFieldError>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, + 1); + delegate_.ExpectBaselineMetrics(); + + wallet_client_->SaveToWallet(GetTestExpirationDateChangeInstrument(), + scoped_ptr<Address>(), + GURL(kMerchantUrl)); + + VerifyAndFinishFormEncodedRequest(net::HTTP_OK, + kUpdateInstrumentExpirationDateValidRequest, + kUpdateInstrumentValidResponse, + 3U); +} + +TEST_F(WalletClientTest, UpdateInstrumentAddressWithNameChangeSucceeded) { + EXPECT_CALL(delegate_, + OnDidSaveToWallet("instrument_id", + std::string(), + std::vector<RequiredAction>(), + std::vector<FormFieldError>())).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, + 1); + delegate_.ExpectBaselineMetrics(); + + wallet_client_->SaveToWallet(GetTestAddressNameChangeInstrument(), + scoped_ptr<Address>(), + GURL(kMerchantUrl)); + + VerifyAndFinishFormEncodedRequest( + net::HTTP_OK, + kUpdateInstrumentAddressWithNameChangeValidRequest, + kUpdateInstrumentValidResponse, + 3U); +} + +TEST_F(WalletClientTest, UpdateInstrumentWithRequiredActionsSucceeded) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, + 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::REQUIRE_PHONE_NUMBER); + delegate_.ExpectWalletRequiredActionMetric( + AutofillMetrics::INVALID_FORM_FIELD); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(REQUIRE_PHONE_NUMBER); + required_actions.push_back(INVALID_FORM_FIELD); + + std::vector<FormFieldError> form_errors; + form_errors.push_back(FormFieldError(FormFieldError::INVALID_POSTAL_CODE, + FormFieldError::SHIPPING_ADDRESS)); + + EXPECT_CALL(delegate_, + OnDidSaveToWallet(std::string(), + std::string(), + required_actions, + form_errors)).Times(1); + + wallet_client_->SaveToWallet(GetTestAddressUpgradeInstrument(), + scoped_ptr<Address>(), + GURL(kMerchantUrl)); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentAddressValidRequest, + kUpdateWithRequiredActionsValidResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentFailedInvalidRequiredAction) { + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, + 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + wallet_client_->SaveToWallet(GetTestAddressUpgradeInstrument(), + scoped_ptr<Address>(), + GURL(kMerchantUrl)); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentAddressValidRequest, + kSaveWithInvalidRequiredActionsResponse); +} + +TEST_F(WalletClientTest, UpdateInstrumentMalformedResponse) { + EXPECT_CALL(delegate_, + OnWalletError(WalletClient::MALFORMED_RESPONSE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SAVE_TO_WALLET, + 1); + delegate_.ExpectBaselineMetrics(); + delegate_.ExpectWalletErrorMetric(AutofillMetrics::WALLET_MALFORMED_RESPONSE); + + wallet_client_->SaveToWallet(GetTestAddressUpgradeInstrument(), + scoped_ptr<Address>(), + GURL(kMerchantUrl)); + + VerifyAndFinishRequest(net::HTTP_OK, + kUpdateInstrumentAddressValidRequest, + kUpdateMalformedResponse); +} + +TEST_F(WalletClientTest, SendAutocheckoutOfStatusSuccess) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(); + + AutocheckoutStatistic statistic; + statistic.page_number = 1; + statistic.steps.push_back(AUTOCHECKOUT_STEP_SHIPPING); + statistic.time_taken = base::TimeDelta::FromMilliseconds(100); + std::vector<AutocheckoutStatistic> statistics; + statistics.push_back(statistic); + wallet_client_->SendAutocheckoutStatus(autofill::SUCCESS, + GURL(kMerchantUrl), + statistics, + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_OK, + kSendAutocheckoutStatusWithStatisticsValidRequest, + ")]}"); // Invalid JSON. Should be ignored. +} + +TEST_F(WalletClientTest, SendAutocheckoutStatusOfFailure) { + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::SEND_STATUS, 1); + delegate_.ExpectBaselineMetrics(); + + std::vector<AutocheckoutStatistic> statistics; + wallet_client_->SendAutocheckoutStatus(autofill::CANNOT_PROCEED, + GURL(kMerchantUrl), + statistics, + "google_transaction_id"); + VerifyAndFinishRequest(net::HTTP_OK, + kSendAutocheckoutStatusOfFailureValidRequest, + ")]}"); // Invalid JSON. Should be ignored. +} + +TEST_F(WalletClientTest, HasRequestInProgress) { + EXPECT_FALSE(wallet_client_->HasRequestInProgress()); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectBaselineMetrics(); + + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + EXPECT_TRUE(wallet_client_->HasRequestInProgress()); + + VerifyAndFinishRequest(net::HTTP_OK, + kGetWalletItemsValidRequest, + kGetWalletItemsValidResponse); + EXPECT_FALSE(wallet_client_->HasRequestInProgress()); +} + +TEST_F(WalletClientTest, PendingRequest) { + EXPECT_EQ(0U, wallet_client_->pending_requests_.size()); + + // Shouldn't queue the first request. + delegate_.ExpectBaselineMetrics(); + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + EXPECT_EQ(0U, wallet_client_->pending_requests_.size()); + testing::Mock::VerifyAndClear(delegate_.metric_logger()); + + wallet_client_->GetWalletItems(GURL(kMerchantUrl)); + EXPECT_EQ(1U, wallet_client_->pending_requests_.size()); + + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectBaselineMetrics(); + VerifyAndFinishRequest(net::HTTP_OK, + kGetWalletItemsValidRequest, + kGetWalletItemsValidResponse); + EXPECT_EQ(0U, wallet_client_->pending_requests_.size()); + testing::Mock::VerifyAndClear(delegate_.metric_logger()); + + EXPECT_CALL(delegate_, OnWalletError( + WalletClient::SERVICE_UNAVAILABLE)).Times(1); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 1); + delegate_.ExpectWalletErrorMetric( + AutofillMetrics::WALLET_SERVICE_UNAVAILABLE); + + // Finish the second request. + VerifyAndFinishRequest(net::HTTP_INTERNAL_SERVER_ERROR, + kGetWalletItemsValidRequest, + kErrorResponse); +} + +TEST_F(WalletClientTest, CancelRequests) { + ASSERT_EQ(0U, wallet_client_->pending_requests_.size()); + delegate_.ExpectLogWalletApiCallDuration(AutofillMetrics::GET_WALLET_ITEMS, + 0); + delegate_.ExpectBaselineMetrics(); + + 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_->CancelRequests(); + EXPECT_EQ(0U, wallet_client_->pending_requests_.size()); + EXPECT_FALSE(wallet_client_->HasRequestInProgress()); +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_items.cc b/chromium/components/autofill/content/browser/wallet/wallet_items.cc new file mode 100644 index 00000000000..03cf3f442c2 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_items.cc @@ -0,0 +1,525 @@ +// 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/content/browser/wallet/wallet_items.h" + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/credit_card.h" +#include "grit/component_strings.h" +#include "grit/webkit_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/image/image.h" +#include "url/gurl.h" + +namespace autofill { +namespace wallet { + +namespace { + +const char kLegalDocumentUrl[] = + "https://wallet.google.com/legaldocument?docId="; +const char kPrivacyNoticeUrl[] = "https://wallet.google.com/files/privacy.html"; + +// TODO(estade): move to base/. +template<class T> +bool VectorsAreEqual(const std::vector<T*>& a, const std::vector<T*>& b) { + if (a.size() != b.size()) + return false; + + for (size_t i = 0; i < a.size(); ++i) { + if (*a[i] != *b[i]) + return false; + } + + return true; +} + +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 == "AMEX_NOT_SUPPORTED") + return WalletItems::MaskedInstrument::AMEX_NOT_SUPPORTED; + 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 base::string16& descriptive_name, + const WalletItems::MaskedInstrument::Type& type, + const std::vector<base::string16>& supported_currencies, + const base::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>(); + } + + base::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<base::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) { + base::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"; + + base::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); +} + +const WalletItems::MaskedInstrument* WalletItems::GetInstrumentById( + const std::string& object_id) const { + if (object_id.empty()) + return NULL; + + for (size_t i = 0; i < instruments_.size(); ++i) { + if (instruments_[i]->object_id() == object_id) + return instruments_[i]; + } + + return NULL; +} + +bool WalletItems::HasRequiredAction(RequiredAction action) const { + DCHECK(ActionAppliesToWalletItems(action)); + return std::find(required_actions_.begin(), + required_actions_.end(), + action) != required_actions_.end(); +} + +base::string16 WalletItems::MaskedInstrument::DisplayName() const { +#if defined(OS_ANDROID) + // TODO(aruslan): improve this stub implementation. + return descriptive_name(); +#else + return descriptive_name(); +#endif +} + +base::string16 WalletItems::MaskedInstrument::DisplayNameDetail() const { +#if defined(OS_ANDROID) + // TODO(aruslan): improve this stub implementation. + return address().DisplayName(); +#else + return base::string16(); +#endif +} + +base::string16 WalletItems::MaskedInstrument::TypeAndLastFourDigits() const { + base::string16 display_type; + + if (type_ == AMEX) + display_type = CreditCard::TypeForDisplay(kAmericanExpressCard); + else if (type_ == DISCOVER) + display_type = CreditCard::TypeForDisplay(kDiscoverCard); + else if (type_ == MASTER_CARD) + display_type = CreditCard::TypeForDisplay(kMasterCard); + else if (type_ == VISA) + display_type = CreditCard::TypeForDisplay(kVisaCard); + else + display_type = CreditCard::TypeForDisplay(kGenericCard); + + // TODO(dbeam): i18n. + return display_type + ASCIIToUTF16(" - ") + last_four_digits(); +} + +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 VISA: + idr = IDR_AUTOFILL_CC_VISA; + break; + + case SOLO: + case MAESTRO: + case SWITCH: + case UNKNOWN: + idr = IDR_AUTOFILL_CC_GENERIC; + break; + } + + return ResourceBundle::GetSharedInstance().GetImageNamed(idr); +} + +base::string16 WalletItems::MaskedInstrument::GetInfo( + const AutofillType& type, + const std::string& app_locale) const { + if (type.group() != CREDIT_CARD) + return address().GetInfo(type, app_locale); + + switch (type.GetStorableType()) { + case CREDIT_CARD_NAME: + return address().recipient_name(); + + case CREDIT_CARD_NUMBER: + return DisplayName(); + + case CREDIT_CARD_EXP_4_DIGIT_YEAR: + return base::IntToString16(expiration_year()); + + case CREDIT_CARD_VERIFICATION_CODE: + break; + + default: + NOTREACHED(); + } + + return base::string16(); +} + +WalletItems::LegalDocument::~LegalDocument() {} + +scoped_ptr<WalletItems::LegalDocument> + WalletItems::LegalDocument::CreateLegalDocument( + const base::DictionaryValue& dictionary) { + std::string id; + if (!dictionary.GetString("legal_document_id", &id)) { + DLOG(ERROR) << "Response from Google Wallet missing legal document id"; + return scoped_ptr<LegalDocument>(); + } + + base::string16 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(id, display_name)); +} + +scoped_ptr<WalletItems::LegalDocument> + WalletItems::LegalDocument::CreatePrivacyPolicyDocument() { + return scoped_ptr<LegalDocument>(new LegalDocument( + GURL(kPrivacyNoticeUrl), + l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_PRIVACY_POLICY_LINK))); +} + +bool WalletItems::LegalDocument::operator==(const LegalDocument& other) const { + return id_ == other.id_ && + url_ == other.url_ && + display_name_ == other.display_name_; +} + +bool WalletItems::LegalDocument::operator!=(const LegalDocument& other) const { + return !(*this == other); +} + +WalletItems::LegalDocument::LegalDocument(const std::string& id, + const base::string16& display_name) + : id_(id), + url_(kLegalDocumentUrl + id), + display_name_(display_name) {} + +WalletItems::LegalDocument::LegalDocument(const GURL& url, + const base::string16& display_name) + : url_(url), + display_name_(display_name) {} + +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>(); + } + } + } + + if (!legal_docs->empty()) { + // Always append the privacy policy link as well. + wallet_items->AddLegalDocument( + LegalDocument::CreatePrivacyPolicyDocument()); + } + } 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 { + 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_ && + VectorsAreEqual<MaskedInstrument>(instruments(), + other.instruments()) && + VectorsAreEqual<Address>(addresses(), other.addresses()) && + VectorsAreEqual<LegalDocument>(legal_documents(), + other.legal_documents()); +} + +bool WalletItems::operator!=(const WalletItems& other) const { + return !(*this == other); +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_items.h b/chromium/components/autofill/content/browser/wallet/wallet_items.h new file mode 100644 index 00000000000..53a49a17d05 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_items.h @@ -0,0 +1,300 @@ +// 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_CONTENT_BROWSER_WALLET_WALLET_ITEMS_H_ +#define COMPONENTS_AUTOFILL_CONTENT_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/strings/string16.h" +#include "components/autofill/content/browser/wallet/required_action.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" +#include "url/gurl.h" + +namespace base { +class DictionaryValue; +} + +namespace gfx { +class Image; +} + +namespace autofill { + +class AutofillType; + +FORWARD_DECLARE_TEST(WalletInstrumentWrapperTest, GetInfoCreditCardExpMonth); +FORWARD_DECLARE_TEST(WalletInstrumentWrapperTest, + GetDisplayTextEmptyWhenExpired); + +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 { + AMEX_NOT_SUPPORTED, + BILLING_INCOMPLETE, + DECLINED, + DISABLED_FOR_THIS_MERCHANT, // Deprecated. + 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; + + // Returns a pair of strings that summarizes this CC, + // suitable for display to the user. + base::string16 DisplayName() const; + base::string16 DisplayNameDetail() const; + + // Gets info that corresponds with |type|. + base::string16 GetInfo(const AutofillType& type, + const std::string& app_locale) const; + + // Returns the display type of the and last four digits (e.g. Visa - 4444). + base::string16 TypeAndLastFourDigits() const; + + const base::string16& descriptive_name() const { return descriptive_name_; } + const Type& type() const { return type_; } + const std::vector<base::string16>& supported_currencies() const { + return supported_currencies_; + } + const base::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 scoped_ptr<MaskedInstrument> GetTestMaskedInstrumentWithDetails( + const std::string&, scoped_ptr<Address> address, + Type type, Status status); + FRIEND_TEST_ALL_PREFIXES(::autofill::WalletInstrumentWrapperTest, + GetInfoCreditCardExpMonth); + FRIEND_TEST_ALL_PREFIXES(::autofill::WalletInstrumentWrapperTest, + GetDisplayTextEmptyWhenExpired); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateMaskedInstrument); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, CreateWalletItems); + + MaskedInstrument(const base::string16& descriptive_name, + const Type& type, + const std::vector<base::string16>& supported_currencies, + const base::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". + base::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<base::string16> supported_currencies_; + + // The last four digits of the primary account number of the instrument. + base::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. + static scoped_ptr<LegalDocument> + CreateLegalDocument(const base::DictionaryValue& dictionary); + + // Returns a document for the privacy policy (acceptance of which is not + // tracked by the server). + static scoped_ptr<LegalDocument> CreatePrivacyPolicyDocument(); + + bool operator==(const LegalDocument& other) const; + bool operator!=(const LegalDocument& other) const; + + const std::string& id() { return id_; } + const GURL& url() const { return url_; } + const base::string16& 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, LegalDocumentUrl); + FRIEND_TEST_ALL_PREFIXES(WalletItemsTest, LegalDocumentEmptyId); + LegalDocument(const std::string& id, + const base::string16& display_name); + LegalDocument(const GURL& url, + const base::string16& display_name); + + // Externalized Online Wallet id for the document, or an empty string for + // documents not tracked by the server (such as the privacy policy). + std::string id_; + // The human-visitable URL that displays the document. + GURL url_; + // User displayable name for the document. + base::string16 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()); + } + + // Return the corresponding instrument for |id| or NULL if it doesn't exist. + const WalletItems::MaskedInstrument* GetInstrumentById( + const std::string& object_id) const; + + // 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 scoped_ptr<WalletItems> GetTestWalletItems(); + 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_CONTENT_BROWSER_WALLET_WALLET_ITEMS_H_ diff --git a/chromium/components/autofill/content/browser/wallet/wallet_items_unittest.cc b/chromium/components/autofill/content/browser/wallet/wallet_items_unittest.cc new file mode 100644 index 00000000000..83f8b1939fc --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_items_unittest.cc @@ -0,0 +1,570 @@ +// 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 "base/json/json_reader.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/required_action.h" +#include "components/autofill/content/browser/wallet/wallet_items.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.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\"," + " \"type\":\"FULL\"" + " }," + " \"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\"," + " \" CHOOse_ANother_INSTRUMENT_OR_ADDRESS\"," + " \"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\":" + " [" + " \"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\"" + " }" + " }" + " ]," + " \"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\"," + " \"type\":\"FULL\"" + " }," + " \"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\":" + " [" + " \"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\"" + " }" + " }" + " ]," + " \"default_address_id\":\"default_address_id\"," + " \"obfuscated_gaia_id\":\"obfuscated_gaia_id\""; + +const char kRequiredLegalDocument[] = + " ," + " \"required_legal_document\":" + " [" + " {" + " \"legal_document_id\":\"doc_id\"," + " \"display_name\":\"display_name\"" + " }" + " ]"; + +const char kCloseJson[] = "}"; + +} // 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::string())); + std::vector<base::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", ASCIIToUTF16("display_name")); + EXPECT_EQ(expected, + *WalletItems::LegalDocument::CreateLegalDocument(*dict)); +} + +TEST_F(WalletItemsTest, LegalDocumentUrl) { + WalletItems::LegalDocument legal_doc("doc_id", ASCIIToUTF16("display_name")); + EXPECT_EQ("https://wallet.google.com/legaldocument?docId=doc_id", + legal_doc.url().spec()); +} + +TEST_F(WalletItemsTest, LegalDocumentEmptyId) { + WalletItems::LegalDocument legal_doc(GURL("http://example.com"), + ASCIIToUTF16("display_name")); + EXPECT_TRUE(legal_doc.id().empty()); +} + +TEST_F(WalletItemsTest, CreateWalletItemsWithRequiredActions) { + SetUpDictionary(kWalletItemsWithRequiredActions); + + std::vector<RequiredAction> required_actions; + required_actions.push_back(SETUP_WALLET); + required_actions.push_back(CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS); + 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(std::string(kWalletItems) + std::string(kCloseJson)); + 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::string())); + std::vector<base::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_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")); + expected.AddAddress(shipping_address.Pass()); + EXPECT_EQ(expected, *WalletItems::CreateWalletItems(*dict)); + + // Now try with a legal document as well. + SetUpDictionary(std::string(kWalletItems) + + std::string(kRequiredLegalDocument) + + std::string(kCloseJson)); + scoped_ptr<WalletItems::LegalDocument> legal_document( + new WalletItems::LegalDocument("doc_id", + ASCIIToUTF16("display_name"))); + expected.AddLegalDocument(legal_document.Pass()); + expected.AddLegalDocument( + WalletItems::LegalDocument::CreatePrivacyPolicyDocument()); + + EXPECT_EQ(expected, *WalletItems::CreateWalletItems(*dict)); +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_service_url.cc b/chromium/components/autofill/content/browser/wallet/wallet_service_url.cc new file mode 100644 index 00000000000..e95a201a446 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_service_url.cc @@ -0,0 +1,148 @@ +// 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/content/browser/wallet/wallet_service_url.h" + +#include <string> + +#include "base/command_line.h" +#include "base/metrics/field_trial.h" +#include "components/autofill/core/common/autofill_switches.h" +#include "google_apis/gaia/gaia_urls.h" +#include "net/base/url_util.h" +#include "url/gurl.h" + +namespace autofill { +namespace { + +const char kProdWalletServiceUrl[] = "https://wallet.google.com/"; + +// TODO(ahutter): Remove this once production is ready. +const char kSandboxWalletServiceUrl[] = + "https://payments-form-dogfood.sandbox.google.com/"; + +// TODO(ahutter): Remove this once production is ready. +const char kSandboxWalletSecureServiceUrl[] = + "https://wallet-web.sandbox.google.com/"; + +bool IsWalletProductionEnabled() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + return command_line.HasSwitch(switches::kWalletServiceUseProd) || + base::FieldTrialList::FindFullName("WalletProductionService") == "Yes" || + base::FieldTrialList::FindFullName("Autocheckout") == "Yes"; +} + +GURL GetWalletHostUrl() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + std::string wallet_service_hostname = + command_line.GetSwitchValueASCII(switches::kWalletServiceUrl); + if (!wallet_service_hostname.empty()) + return GURL(wallet_service_hostname); + if (IsWalletProductionEnabled()) + return GURL(kProdWalletServiceUrl); + return GURL(kSandboxWalletServiceUrl); +} + +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); + if (!wallet_secure_url.empty()) + return GURL(wallet_secure_url); + if (IsWalletProductionEnabled()) + return GURL(kProdWalletServiceUrl); + return GURL(kSandboxWalletSecureServiceUrl); +} + +GURL GetBaseEncryptedFrontendUrl() { + const CommandLine& command_line = *CommandLine::ForCurrentProcess(); + // TODO(ahutter): Stop checking these switches once we switch over to prod. + GURL base_url = IsWalletProductionEnabled() || + command_line.HasSwitch(switches::kWalletServiceUrl) ? + GetWalletHostUrl() : GetBaseSecureUrl(); + return base_url.Resolve("online-secure/v2/autocheckout/v1/"); +} + +} // namespace + +namespace wallet { + +GURL GetGetWalletItemsUrl() { + return GetBaseAutocheckoutUrl().Resolve("getWalletItemsJwtless"); +} + +GURL GetGetFullWalletUrl() { + return GetBaseEncryptedFrontendUrl().Resolve("getFullWalletJwtless?s7e=otp"); +} + +GURL GetManageInstrumentsUrl() { + return GetBaseSecureUrl().Resolve("manage/paymentMethods"); +} + +GURL GetManageAddressesUrl() { + return GetBaseSecureUrl().Resolve("manage/settings/addresses"); +} + +GURL GetAcceptLegalDocumentsUrl() { + return GetBaseAutocheckoutUrl().Resolve("acceptLegalDocument"); +} + +GURL GetAuthenticateInstrumentUrl() { + return GetBaseEncryptedFrontendUrl() + .Resolve("authenticateInstrument?s7e=cvn"); +} + +GURL GetSendStatusUrl() { + return GetBaseAutocheckoutUrl().Resolve("reportStatus"); +} + +GURL GetSaveToWalletNoEscrowUrl() { + return GetBaseAutocheckoutUrl().Resolve("saveToWallet"); +} + +GURL GetSaveToWalletUrl() { + return GetBaseEncryptedFrontendUrl() + .Resolve("saveToWallet?s7e=card_number%3Bcvn"); +} + +GURL GetPassiveAuthUrl() { + return GetBaseWalletUrl().Resolve("passiveauth?isChromePayments=true"); +} + +GURL GetSignInUrl() { + GURL url(GaiaUrls::GetInstance()->service_login_url()); + url = net::AppendQueryParameter(url, "service", "toolbar"); + url = net::AppendQueryParameter(url, "nui", "1"); + 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(); +} + +bool IsUsingProd() { + return GetWalletHostUrl() == GURL(kProdWalletServiceUrl); +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_service_url.h b/chromium/components/autofill/content/browser/wallet/wallet_service_url.h new file mode 100644 index 00000000000..61bceca0c64 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_service_url.h @@ -0,0 +1,42 @@ +// 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_CONTENT_BROWSER_WALLET_WALLET_SERVICE_URL_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_SERVICE_URL_H_ + +class GURL; + +namespace autofill { +namespace wallet { + +GURL GetGetWalletItemsUrl(); +GURL GetGetFullWalletUrl(); +GURL GetManageInstrumentsUrl(); +GURL GetManageAddressesUrl(); +GURL GetAcceptLegalDocumentsUrl(); +GURL GetAuthenticateInstrumentUrl(); +GURL GetSendStatusUrl(); +GURL GetSaveToWalletNoEscrowUrl(); +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); + +// Whether calls to Online Wallet are hitting the production server rather than +// a sandbox or some malicious endpoint. +bool IsUsingProd(); + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_SERVICE_URL_H_ diff --git a/chromium/components/autofill/content/browser/wallet/wallet_service_url_unittest.cc b/chromium/components/autofill/content/browser/wallet/wallet_service_url_unittest.cc new file mode 100644 index 00000000000..3ed3600fc0a --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_service_url_unittest.cc @@ -0,0 +1,61 @@ +// 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 "base/command_line.h" +#include "components/autofill/content/browser/wallet/wallet_service_url.h" +#include "components/autofill/core/common/autofill_switches.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +namespace autofill { +namespace wallet { + +TEST(WalletServiceUrl, CheckDefaultUrls) { + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" + "autocheckout/v1/getWalletItemsJwtless", + GetGetWalletItemsUrl().spec()); + EXPECT_EQ("https://wallet-web.sandbox.google.com/online-secure/v2/" + "autocheckout/v1/getFullWalletJwtless?s7e=otp", + GetGetFullWalletUrl().spec()); + EXPECT_EQ("https://wallet-web.sandbox.google.com/manage/paymentMethods", + GetManageInstrumentsUrl().spec()); + EXPECT_EQ("https://wallet-web.sandbox.google.com/manage/settings/addresses", + GetManageAddressesUrl().spec()); + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" + "autocheckout/v1/acceptLegalDocument", + GetAcceptLegalDocumentsUrl().spec()); + EXPECT_EQ("https://wallet-web.sandbox.google.com/online-secure/v2/" + "autocheckout/v1/authenticateInstrument?s7e=cvn", + GetAuthenticateInstrumentUrl().spec()); + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" + "autocheckout/v1/reportStatus", + GetSendStatusUrl().spec()); + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/wallet/" + "autocheckout/v1/saveToWallet", + GetSaveToWalletNoEscrowUrl().spec()); + EXPECT_EQ("https://wallet-web.sandbox.google.com/online-secure/v2/" + "autocheckout/v1/saveToWallet?s7e=card_number%3Bcvn", + GetSaveToWalletUrl().spec()); + EXPECT_EQ("https://payments-form-dogfood.sandbox.google.com/online/v2/" + "passiveauth?isChromePayments=true", + GetPassiveAuthUrl().spec()); +} + +TEST(WalletServiceUrl, IsUsingProd) { + // The sandbox servers are the default (for now). Update if this changes. + EXPECT_FALSE(IsUsingProd()); + + CommandLine* command_line = CommandLine::ForCurrentProcess(); + command_line->AppendSwitch(switches::kWalletServiceUseProd); + EXPECT_TRUE(IsUsingProd()); + + const GURL prod_get_items_url = GetGetWalletItemsUrl(); + command_line->AppendSwitchASCII(switches::kWalletServiceUrl, "http://goo.gl"); + EXPECT_FALSE(IsUsingProd()); + + ASSERT_NE(prod_get_items_url, GetGetWalletItemsUrl()); +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_signin_helper.cc b/chromium/components/autofill/content/browser/wallet/wallet_signin_helper.cc new file mode 100644 index 00000000000..beeb1e7467c --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_signin_helper.cc @@ -0,0 +1,322 @@ +// 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/content/browser/wallet/wallet_signin_helper.h" + +#include "base/callback_helpers.h" +#include "base/json/json_reader.h" +#include "base/logging.h" +#include "base/rand_util.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/time/time.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/wallet_service_url.h" +#include "components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h" +#include "content/public/browser/browser_thread.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "net/base/escape.h" +#include "net/cookies/canonical_cookie.h" +#include "net/cookies/cookie_monster.h" +#include "net/cookies/cookie_options.h" +#include "net/cookies/cookie_store.h" +#include "net/url_request/url_fetcher.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" + +namespace autofill { +namespace wallet { + +namespace { + +// Toolbar::GetAccountInfo API URL (JSON). +const char kGetAccountInfoUrlFormat[] = + "https://clients1.google.com/tbproxy/getaccountinfo?key=%d&rv=2&requestor=chrome"; + +const char kWalletCookieName[] = "gdtoken"; + +// Callback for retrieving Google Wallet cookies. |callback| is passed the +// retrieved cookies and posted back to the UI thread. |cookies| is any Google +// Wallet cookies. +void GetGoogleCookiesCallback( + const base::Callback<void(const std::string&)>& callback, + const net::CookieList& cookies) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + + std::string wallet_cookie; + for (size_t i = 0; i < cookies.size(); ++i) { + if (LowerCaseEqualsASCII(cookies[i].Name(), kWalletCookieName)) { + wallet_cookie = cookies[i].Value(); + break; + } + } + content::BrowserThread::PostTask(content::BrowserThread::UI, + FROM_HERE, + base::Bind(callback, wallet_cookie)); +} + +// Gets Google Wallet cookies. Must be called on the IO thread. +// |request_context_getter| is a getter for the current request context. +// |callback| is called when retrieving cookies is completed. +void GetGoogleCookies( + scoped_refptr<net::URLRequestContextGetter> request_context_getter, + const base::Callback<void(const std::string&)>& callback) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + + net::URLRequestContext* url_request_context = + request_context_getter->GetURLRequestContext(); + if (!url_request_context) { + content::BrowserThread::PostTask(content::BrowserThread::UI, + FROM_HERE, + base::Bind(callback, std::string())); + return; + } + + net::CookieStore* cookie_store = url_request_context->cookie_store(); + if (!cookie_store) { + content::BrowserThread::PostTask(content::BrowserThread::UI, + FROM_HERE, + base::Bind(callback, std::string())); + return; + } + + net::CookieMonster* cookie_monster = cookie_store->GetCookieMonster(); + if (!cookie_monster) { + content::BrowserThread::PostTask(content::BrowserThread::UI, + FROM_HERE, + base::Bind(callback, std::string())); + return; + } + + net::CookieOptions cookie_options; + cookie_options.set_include_httponly(); + cookie_monster->GetAllCookiesForURLWithOptionsAsync( + wallet::GetPassiveAuthUrl().GetWithEmptyPath(), + cookie_options, + base::Bind(&GetGoogleCookiesCallback, callback)); +} + +} // namespace + +WalletSigninHelper::WalletSigninHelper( + WalletSigninHelperDelegate* delegate, + net::URLRequestContextGetter* getter) + : delegate_(delegate), + getter_(getter), + state_(IDLE), + weak_ptr_factory_(this) { + DCHECK(delegate_); +} + +WalletSigninHelper::~WalletSigninHelper() { +} + +void WalletSigninHelper::StartPassiveSignin() { + DCHECK_EQ(IDLE, state_); + DCHECK(!url_fetcher_); + + state_ = PASSIVE_EXECUTING_SIGNIN; + username_.clear(); + const GURL& url = wallet::GetPassiveAuthUrl(); + url_fetcher_.reset(net::URLFetcher::Create( + 0, url, net::URLFetcher::GET, this)); + url_fetcher_->SetRequestContext(getter_); + url_fetcher_->Start(); +} + +void WalletSigninHelper::StartUserNameFetch() { + DCHECK_EQ(state_, IDLE); + DCHECK(!url_fetcher_); + + state_ = USERNAME_FETCHING_USERINFO; + username_.clear(); + StartFetchingUserNameFromSession(); +} + +void WalletSigninHelper::StartWalletCookieValueFetch() { + scoped_refptr<net::URLRequestContextGetter> request_context(getter_); + if (!request_context.get()) { + ReturnWalletCookieValue(std::string()); + return; + } + + base::Callback<void(const std::string&)> callback = base::Bind( + &WalletSigninHelper::ReturnWalletCookieValue, + weak_ptr_factory_.GetWeakPtr()); + + base::Closure task = base::Bind(&GetGoogleCookies, request_context, callback); + content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, task); +} + +std::string WalletSigninHelper::GetGetAccountInfoUrlForTesting() const { + return base::StringPrintf(kGetAccountInfoUrlFormat, 0); +} + +void WalletSigninHelper::OnServiceError(const GoogleServiceAuthError& error) { + const State state_with_error = state_; + state_ = IDLE; + url_fetcher_.reset(); + + switch(state_with_error) { + case IDLE: + NOTREACHED(); + break; + + case PASSIVE_EXECUTING_SIGNIN: /*FALLTHROUGH*/ + case PASSIVE_FETCHING_USERINFO: + delegate_->OnPassiveSigninFailure(error); + break; + + case USERNAME_FETCHING_USERINFO: + delegate_->OnUserNameFetchFailure(error); + break; + } +} + +void WalletSigninHelper::OnOtherError() { + OnServiceError(GoogleServiceAuthError::AuthErrorNone()); +} + +void WalletSigninHelper::OnURLFetchComplete( + const net::URLFetcher* fetcher) { + DCHECK_EQ(url_fetcher_.get(), fetcher); + if (!fetcher->GetStatus().is_success() || + fetcher->GetResponseCode() < 200 || + fetcher->GetResponseCode() >= 300) { + LOG(ERROR) << "URLFetchFailure: state=" << state_ + << " r=" << fetcher->GetResponseCode() + << " s=" << fetcher->GetStatus().status() + << " e=" << fetcher->GetStatus().error(); + OnOtherError(); + return; + } + + switch (state_) { + case USERNAME_FETCHING_USERINFO: /*FALLTHROUGH*/ + case PASSIVE_FETCHING_USERINFO: + ProcessGetAccountInfoResponseAndFinish(); + break; + + case PASSIVE_EXECUTING_SIGNIN: + if (ParseSignInResponse()) { + url_fetcher_.reset(); + state_ = PASSIVE_FETCHING_USERINFO; + StartFetchingUserNameFromSession(); + } + break; + + default: + NOTREACHED() << "unexpected state_=" << state_; + } +} + +void WalletSigninHelper::StartFetchingUserNameFromSession() { + const int random_number = static_cast<int>(base::RandUint64() % INT_MAX); + url_fetcher_.reset( + net::URLFetcher::Create( + 0, + GURL(base::StringPrintf(kGetAccountInfoUrlFormat, random_number)), + net::URLFetcher::GET, + this)); + url_fetcher_->SetRequestContext(getter_); + url_fetcher_->Start(); // This will result in OnURLFetchComplete callback. +} + +void WalletSigninHelper::ProcessGetAccountInfoResponseAndFinish() { + std::string email; + if (!ParseGetAccountInfoResponse(url_fetcher_.get(), &email)) { + LOG(ERROR) << "failed to get the user email"; + OnOtherError(); + return; + } + + username_ = email; + const State finishing_state = state_; + state_ = IDLE; + url_fetcher_.reset(); + switch(finishing_state) { + case USERNAME_FETCHING_USERINFO: + delegate_->OnUserNameFetchSuccess(username_); + break; + + case PASSIVE_FETCHING_USERINFO: + delegate_->OnPassiveSigninSuccess(username_); + break; + + default: + NOTREACHED() << "unexpected state_=" << finishing_state; + } +} + +bool WalletSigninHelper::ParseSignInResponse() { + if (!url_fetcher_) { + NOTREACHED(); + return false; + } + + std::string data; + if (!url_fetcher_->GetResponseAsString(&data)) { + DVLOG(1) << "failed to GetResponseAsString"; + OnOtherError(); + return false; + } + + if (!LowerCaseEqualsASCII(data, "yes")) { + OnServiceError( + GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP)); + return false; + } + + return true; +} + +bool WalletSigninHelper::ParseGetAccountInfoResponse( + const net::URLFetcher* fetcher, std::string* email) { + DCHECK(email); + + std::string data; + if (!fetcher->GetResponseAsString(&data)) { + DVLOG(1) << "failed to GetResponseAsString"; + return false; + } + + scoped_ptr<base::Value> value(base::JSONReader::Read(data)); + if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY) { + DVLOG(1) << "failed to parse JSON response"; + return false; + } + + DictionaryValue* dict = static_cast<base::DictionaryValue*>(value.get()); + base::ListValue* user_info; + if (!dict->GetListWithoutPathExpansion("user_info", &user_info)) { + DVLOG(1) << "no user_info in JSON response"; + return false; + } + + // |user_info| will contain each signed in user in the cookie jar. + // We only support the first user at the moment. http://crbug.com/259543 + // will change that. + base::DictionaryValue* user_info_detail; + if (!user_info->GetDictionary(0, &user_info_detail)) { + DVLOG(1) << "empty list in JSON response"; + return false; + } + + if (!user_info_detail->GetStringWithoutPathExpansion("email", email)) { + DVLOG(1) << "no email in JSON response"; + return false; + } + + return !email->empty(); +} + +void WalletSigninHelper::ReturnWalletCookieValue( + const std::string& cookie_value) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); + + delegate_->OnDidFetchWalletCookieValue(cookie_value); +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_signin_helper.h b/chromium/components/autofill/content/browser/wallet/wallet_signin_helper.h new file mode 100644 index 00000000000..d7cf5ff42a9 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_signin_helper.h @@ -0,0 +1,125 @@ +// 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_CONTENT_BROWSER_WALLET_WALLET_SIGNIN_HELPER_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_SIGNIN_HELPER_H_ + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "net/url_request/url_fetcher_delegate.h" + +namespace net { +class URLFetcher; +class URLRequestContextGetter; +class URLRequestStatus; +} + +class GoogleServiceAuthError; + +namespace autofill { +namespace wallet { + +class WalletSigninHelperDelegate; + +// Authenticates the user against the Online Wallet service. +// This class is not thread-safe. An instance may be used on any thread, but +// should not be accessed from multiple threads. +class WalletSigninHelper : public net::URLFetcherDelegate { + public: + // Constructs a helper that works with a given |delegate| and uses a given + // |getter| to obtain a context for URL. Both |delegate| and |getter| shall + // remain valid over the entire lifetime of the created instance. + WalletSigninHelper(WalletSigninHelperDelegate* delegate, + net::URLRequestContextGetter* getter); + + virtual ~WalletSigninHelper(); + + // Initiates an attempt to passively sign the user into the Online Wallet. + // A passive sign-in is a non-interactive refresh of content area cookies, + // and it succeeds as long as the Online Wallet service could safely accept + // or refresh the existing area cookies, and the user doesn't need to be + // fully reauthenticated with the service. + // Either OnPassiveSigninSuccess or OnPassiveSigninFailure will be called + // on the original thread. + void StartPassiveSignin(); + + // Initiates a fetch of the user name of a signed-in user. + // Either OnUserNameFetchSuccess or OnUserNameFetchFailure will + // be called on the original thread. + void StartUserNameFetch(); + + // Initiates the fetch of the user's Google Wallet cookie. + void StartWalletCookieValueFetch(); + + protected: + // Sign-in helper states (for tests). + enum State { + IDLE, + PASSIVE_EXECUTING_SIGNIN, + PASSIVE_FETCHING_USERINFO, + USERNAME_FETCHING_USERINFO, + }; + + // (For tests) Current state of the sign-in helper. + State state() const { return state_; } + + // (For tests) URL used to fetch the currently signed-in user info. + std::string GetGetAccountInfoUrlForTesting() const; + + private: + // Called if a service authentication error occurs. + void OnServiceError(const GoogleServiceAuthError& error); + + // Called if any other error occurs. + void OnOtherError(); + + // URLFetcherDelegate implementation. + virtual void OnURLFetchComplete(const net::URLFetcher* fetcher) OVERRIDE; + + // Initiates fetching of the currently signed-in user information. + void StartFetchingUserNameFromSession(); + + // Processes the user information received from the server by url_fetcher_ + // and calls the delegate callbacks on success/failure. + void ProcessGetAccountInfoResponseAndFinish(); + + // Attempts to parse a response from the Online Wallet sign-in. + // Returns true if the response is correct and the sign-in has succeeded. + // Otherwise, it calls OnServiceError() and returns false. + bool ParseSignInResponse(); + + // Attempts to parse the GetAccountInfo response from the server. + // Returns true on success; the obtained email address is stored into |email|. + bool ParseGetAccountInfoResponse(const net::URLFetcher* fetcher, + std::string* email); + + // Callback for when the Google Wallet cookie has been retrieved. + void ReturnWalletCookieValue(const std::string& cookie_value); + + // Should be valid throughout the lifetime of the instance. + WalletSigninHelperDelegate* const delegate_; + + // URLRequestContextGetter to be used for URLFetchers. + net::URLRequestContextGetter* const getter_; + + // While passive login/merge session URL fetches are going on: + scoped_ptr<net::URLFetcher> url_fetcher_; + + // User account name (email) fetched from OnGetUserInfoSuccess(). + std::string username_; + + // Current internal state of the helper. + State state_; + + base::WeakPtrFactory<WalletSigninHelper> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(WalletSigninHelper); +}; + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_SIGNIN_HELPER_H_ diff --git a/chromium/components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h b/chromium/components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h new file mode 100644 index 00000000000..d09499d8c21 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h @@ -0,0 +1,43 @@ +// 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 CHROME_BROWSER_UI_AUTOFILL_WALLET_SIGNIN_HELPER_DELEGATE_H_ +#define CHROME_BROWSER_UI_AUTOFILL_WALLET_SIGNIN_HELPER_DELEGATE_H_ + +#include <string> + +class GoogleServiceAuthError; + +namespace autofill { +namespace wallet { + +// An interface that defines the callbacks for objects that +// WalletSigninHelper can return data to. +class WalletSigninHelperDelegate { + public: + virtual ~WalletSigninHelperDelegate() {} + + // Called on a successful passive sign-in. + // |username| is the signed-in user account name (email). + virtual void OnPassiveSigninSuccess(const std::string& username) = 0; + + // Called on a failed passive sign-in; |error| describes the error. + virtual void OnPassiveSigninFailure(const GoogleServiceAuthError& error) = 0; + + // Called on a successful fetch of the signed-in account name. + // |username| is the signed-in user account name (email). + virtual void OnUserNameFetchSuccess(const std::string& username) = 0; + + // Called on a failed fetch of the signed-in account name. + // |error| described the error. + virtual void OnUserNameFetchFailure(const GoogleServiceAuthError& error) = 0; + + // Called when the Google Wallet cookie value has been retrieved. + virtual void OnDidFetchWalletCookieValue(const std::string& cookie_value) = 0; +}; + +} // namespace wallet +} // namespace autofill + +#endif // CHROME_BROWSER_UI_AUTOFILL_WALLET_SIGNIN_HELPER_DELEGATE_H_ diff --git a/chromium/components/autofill/content/browser/wallet/wallet_signin_helper_unittest.cc b/chromium/components/autofill/content/browser/wallet/wallet_signin_helper_unittest.cc new file mode 100644 index 00000000000..e9662a51ec5 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_signin_helper_unittest.cc @@ -0,0 +1,245 @@ +// 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/content/browser/wallet/wallet_signin_helper.h" + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/run_loop.h" +#include "base/strings/stringprintf.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/content/browser/wallet/wallet_service_url.h" +#include "components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "google_apis/gaia/gaia_constants.h" +#include "google_apis/gaia/gaia_urls.h" +#include "google_apis/gaia/google_service_auth_error.h" +#include "net/cookies/canonical_cookie.h" +#include "net/cookies/cookie_monster.h" +#include "net/cookies/cookie_options.h" +#include "net/http/http_status_code.h" +#include "net/url_request/test_url_fetcher_factory.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" +#include "net/url_request/url_request_status.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; + +namespace autofill { +namespace wallet { + +namespace { + +const char kGetTokenPairValidResponse[] = + "{" + " \"refresh_token\": \"rt1\"," + " \"access_token\": \"at1\"," + " \"expires_in\": 3600," + " \"token_type\": \"Bearer\"" + "}"; + +const char kGetAccountInfoValidResponseFormat[] = + "{\"user_info\":[" + " {" + " \"email\": \"%s\"" + " }" + "]}"; + +class MockWalletSigninHelperDelegate : public WalletSigninHelperDelegate { + public: + MOCK_METHOD1(OnPassiveSigninSuccess, void(const std::string& username)); + MOCK_METHOD1(OnUserNameFetchSuccess, void(const std::string& username)); + MOCK_METHOD1(OnPassiveSigninFailure, + void(const GoogleServiceAuthError& error)); + MOCK_METHOD1(OnUserNameFetchFailure, + void(const GoogleServiceAuthError& error)); + MOCK_METHOD1(OnDidFetchWalletCookieValue, + void(const std::string& cookie_value)); +}; + +class WalletSigninHelperForTesting : public WalletSigninHelper { + public: + WalletSigninHelperForTesting(WalletSigninHelperDelegate* delegate, + net::URLRequestContextGetter* getter) + : WalletSigninHelper(delegate, getter) { + } + + // Bring in the test-only getters. + using WalletSigninHelper::GetGetAccountInfoUrlForTesting; + using WalletSigninHelper::state; + + // Bring in the State enum. + using WalletSigninHelper::State; + using WalletSigninHelper::IDLE; +}; + +} // namespace + +class WalletSigninHelperTest : public testing::Test { + protected: + virtual void SetUp() OVERRIDE { + signin_helper_.reset(new WalletSigninHelperForTesting( + &mock_delegate_, + browser_context_.GetRequestContext())); + EXPECT_EQ(WalletSigninHelperForTesting::IDLE, state()); + } + + virtual void TearDown() OVERRIDE { + signin_helper_.reset(); + } + + // Sets up a response for the mock URLFetcher and completes the request. + void SetUpFetcherResponseAndCompleteRequest( + const std::string& url, + int response_code, + const net::ResponseCookies& cookies, + const std::string& response_string) { + net::TestURLFetcher* fetcher = factory_.GetFetcherByID(0); + ASSERT_TRUE(fetcher); + ASSERT_TRUE(fetcher->delegate()); + + fetcher->set_url(GURL(url)); + fetcher->set_status(net::URLRequestStatus()); + fetcher->set_response_code(response_code); + fetcher->SetResponseString(response_string); + fetcher->set_cookies(cookies); + fetcher->delegate()->OnURLFetchComplete(fetcher); + } + + void MockSuccessfulGetAccountInfoResponse(const std::string& username) { + SetUpFetcherResponseAndCompleteRequest( + signin_helper_->GetGetAccountInfoUrlForTesting(), net::HTTP_OK, + net::ResponseCookies(), + base::StringPrintf( + kGetAccountInfoValidResponseFormat, + username.c_str())); + } + + void MockFailedGetAccountInfoResponse404() { + SetUpFetcherResponseAndCompleteRequest( + signin_helper_->GetGetAccountInfoUrlForTesting(), + net::HTTP_NOT_FOUND, + net::ResponseCookies(), + std::string()); + } + + void MockSuccessfulPassiveSignInResponse() { + SetUpFetcherResponseAndCompleteRequest(wallet::GetPassiveAuthUrl().spec(), + net::HTTP_OK, + net::ResponseCookies(), + "YES"); + } + + void MockFailedPassiveSignInResponseNo() { + SetUpFetcherResponseAndCompleteRequest(wallet::GetPassiveAuthUrl().spec(), + net::HTTP_OK, + net::ResponseCookies(), + "NOOOOOOOOOOOOOOO"); + } + + void MockFailedPassiveSignInResponse404() { + SetUpFetcherResponseAndCompleteRequest(wallet::GetPassiveAuthUrl().spec(), + net::HTTP_NOT_FOUND, + net::ResponseCookies(), + std::string()); + } + + WalletSigninHelperForTesting::State state() const { + return signin_helper_->state(); + } + + content::TestBrowserThreadBundle thread_bundle_; + scoped_ptr<WalletSigninHelperForTesting> signin_helper_; + MockWalletSigninHelperDelegate mock_delegate_; + TestingProfile browser_context_; + + private: + net::TestURLFetcherFactory factory_; +}; + +TEST_F(WalletSigninHelperTest, PassiveSigninSuccessful) { + EXPECT_CALL(mock_delegate_, OnPassiveSigninSuccess("user@gmail.com")); + signin_helper_->StartPassiveSignin(); + MockSuccessfulPassiveSignInResponse(); + MockSuccessfulGetAccountInfoResponse("user@gmail.com"); +} + +TEST_F(WalletSigninHelperTest, PassiveSigninFailedSignin404) { + EXPECT_CALL(mock_delegate_, OnPassiveSigninFailure(_)); + signin_helper_->StartPassiveSignin(); + MockFailedPassiveSignInResponse404(); +} + +TEST_F(WalletSigninHelperTest, PassiveSigninFailedSigninNo) { + EXPECT_CALL(mock_delegate_, OnPassiveSigninFailure(_)); + signin_helper_->StartPassiveSignin(); + MockFailedPassiveSignInResponseNo(); +} + +TEST_F(WalletSigninHelperTest, PassiveSigninFailedUserInfo) { + EXPECT_CALL(mock_delegate_, OnPassiveSigninFailure(_)); + signin_helper_->StartPassiveSignin(); + MockSuccessfulPassiveSignInResponse(); + MockFailedGetAccountInfoResponse404(); +} + +TEST_F(WalletSigninHelperTest, PassiveUserInfoSuccessful) { + EXPECT_CALL(mock_delegate_, OnUserNameFetchSuccess("user@gmail.com")); + signin_helper_->StartUserNameFetch(); + MockSuccessfulGetAccountInfoResponse("user@gmail.com"); +} + +TEST_F(WalletSigninHelperTest, PassiveUserInfoFailedUserInfo) { + EXPECT_CALL(mock_delegate_, OnUserNameFetchFailure(_)); + signin_helper_->StartUserNameFetch(); + MockFailedGetAccountInfoResponse404(); +} + +TEST_F(WalletSigninHelperTest, GetWalletCookieValueWhenPresent) { + EXPECT_CALL(mock_delegate_, OnDidFetchWalletCookieValue("gdToken")); + net::CookieMonster* cookie_monster = new net::CookieMonster(NULL, NULL); + net::CookieOptions httponly_options; + httponly_options.set_include_httponly(); + scoped_ptr<net::CanonicalCookie> cookie( + net::CanonicalCookie::Create(GetPassiveAuthUrl().GetWithEmptyPath(), + "gdToken=gdToken; HttpOnly", + base::Time::Now(), + httponly_options)); + + net::CookieList cookie_list; + cookie_list.push_back(*cookie); + cookie_monster->InitializeFrom(cookie_list); + browser_context_.GetRequestContext()->GetURLRequestContext() + ->set_cookie_store(cookie_monster); + signin_helper_->StartWalletCookieValueFetch(); + base::RunLoop().RunUntilIdle(); +} + +TEST_F(WalletSigninHelperTest, GetWalletCookieValueWhenMissing) { + EXPECT_CALL(mock_delegate_, OnDidFetchWalletCookieValue(std::string())); + net::CookieMonster* cookie_monster = new net::CookieMonster(NULL, NULL); + net::CookieOptions httponly_options; + httponly_options.set_include_httponly(); + scoped_ptr<net::CanonicalCookie> cookie( + net::CanonicalCookie::Create(GetPassiveAuthUrl().GetWithEmptyPath(), + "fake_cookie=monkeys; HttpOnly", + base::Time::Now(), + httponly_options)); + + net::CookieList cookie_list; + cookie_list.push_back(*cookie); + cookie_monster->InitializeFrom(cookie_list); + browser_context_.GetRequestContext()->GetURLRequestContext() + ->set_cookie_store(cookie_monster); + signin_helper_->StartWalletCookieValueFetch(); + base::RunLoop().RunUntilIdle(); +} + +// TODO(aruslan): http://crbug.com/188317 Need more tests. + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_test_util.cc b/chromium/components/autofill/content/browser/wallet/wallet_test_util.cc new file mode 100644 index 00000000000..15f916afdc0 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_test_util.cc @@ -0,0 +1,237 @@ +// 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/content/browser/wallet/wallet_test_util.h" + +#include <string> +#include <vector> + +#include "base/strings/string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/autofill/content/browser/wallet/full_wallet.h" +#include "components/autofill/content/browser/wallet/instrument.h" +#include "components/autofill/content/browser/wallet/required_action.h" +#include "components/autofill/content/browser/wallet/wallet_address.h" + +namespace autofill { +namespace wallet { + +namespace { + +int FutureYear() { + // "In the Year 3000." - Richie "LaBamba" Rosenberg + return 3000; +} + +} // namespace + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentWithDetails( + const std::string& id, + scoped_ptr<Address> address, + WalletItems::MaskedInstrument::Type type, + WalletItems::MaskedInstrument::Status status) { + return scoped_ptr<WalletItems::MaskedInstrument>( + new WalletItems::MaskedInstrument(ASCIIToUTF16("descriptive_name"), + type, + std::vector<base::string16>(), + ASCIIToUTF16("1111"), + 12, + FutureYear(), + address.Pass(), + status, + id)); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentWithId( + const std::string& id) { + return GetTestMaskedInstrumentWithDetails( + id, + GetTestAddress(), + WalletItems::MaskedInstrument::VISA, + WalletItems::MaskedInstrument::VALID); +} + +scoped_ptr<WalletItems::MaskedInstrument> +GetTestMaskedInstrumentWithIdAndAddress( + const std::string& id, scoped_ptr<Address> address) { + return GetTestMaskedInstrumentWithDetails( + id, + address.Pass(), + WalletItems::MaskedInstrument::VISA, + WalletItems::MaskedInstrument::VALID); +} + +scoped_ptr<Address> GetTestAddress() { + return scoped_ptr<Address>(new Address("US", + 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())); +} + +scoped_ptr<Address> GetTestMinimalAddress() { + scoped_ptr<Address> address = GetTestAddress(); + address->set_is_complete_address(false); + return address.Pass(); +} + +scoped_ptr<FullWallet> GetTestFullWallet() { + scoped_ptr<FullWallet> wallet(new FullWallet(FutureYear(), + 12, + "528512", + "5ec4feecf9d6", + GetTestAddress(), + GetTestShippingAddress(), + std::vector<RequiredAction>())); + std::vector<uint8> one_time_pad; + base::HexStringToBytes("5F04A8704183", &one_time_pad); + wallet->set_one_time_pad(one_time_pad); + return wallet.Pass(); +} + +scoped_ptr<FullWallet> GetTestFullWalletInstrumentOnly() { + scoped_ptr<FullWallet> wallet(new FullWallet(FutureYear(), + 12, + "528512", + "5ec4feecf9d6", + GetTestAddress(), + scoped_ptr<Address>(), + std::vector<RequiredAction>())); + std::vector<uint8> one_time_pad; + base::HexStringToBytes("5F04A8704183", &one_time_pad); + wallet->set_one_time_pad(one_time_pad); + return wallet.Pass(); +} + +scoped_ptr<Instrument> GetTestInstrument() { + return scoped_ptr<Instrument>(new Instrument(ASCIIToUTF16("4444444444444448"), + ASCIIToUTF16("123"), + 12, + FutureYear(), + Instrument::VISA, + GetTestAddress())); +} + +scoped_ptr<Instrument> GetTestAddressUpgradeInstrument() { + scoped_ptr<Instrument> instrument(new Instrument(base::string16(), + base::string16(), + 0, + 0, + Instrument::UNKNOWN, + GetTestAddress())); + instrument->set_object_id("instrument_id"); + return instrument.Pass(); +} + +scoped_ptr<Instrument> GetTestExpirationDateChangeInstrument() { + scoped_ptr<Instrument> instrument(new Instrument(base::string16(), + ASCIIToUTF16("123"), + 12, + FutureYear(), + Instrument::UNKNOWN, + scoped_ptr<Address>())); + instrument->set_object_id("instrument_id"); + return instrument.Pass(); +} + +scoped_ptr<Instrument> GetTestAddressNameChangeInstrument() { + scoped_ptr<Instrument> instrument(new Instrument(base::string16(), + ASCIIToUTF16("123"), + 0, + 0, + Instrument::UNKNOWN, + GetTestAddress())); + instrument->set_object_id("instrument_id"); + return instrument.Pass(); +} + +scoped_ptr<WalletItems::LegalDocument> GetTestLegalDocument() { + base::DictionaryValue dict; + dict.SetString("legal_document_id", "document_id"); + dict.SetString("display_name", "display_name"); + return wallet::WalletItems::LegalDocument::CreateLegalDocument(dict); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrument() { + return GetTestMaskedInstrumentWithId("default_instrument_id"); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentExpired() { + return GetTestMaskedInstrumentWithDetails( + "default_instrument_id", + GetTestAddress(), + WalletItems::MaskedInstrument::VISA, + WalletItems::MaskedInstrument::EXPIRED); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentInvalid() { + return GetTestMaskedInstrumentWithDetails( + "default_instrument_id", + GetTestAddress(), + WalletItems::MaskedInstrument::VISA, + WalletItems::MaskedInstrument::DECLINED); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentAmex() { + return GetTestMaskedInstrumentWithDetails( + "default_instrument_id", + GetTestAddress(), + WalletItems::MaskedInstrument::AMEX, + // Amex cards are marked with status AMEX_NOT_SUPPORTED by the server. + WalletItems::MaskedInstrument::AMEX_NOT_SUPPORTED); +} + +scoped_ptr<WalletItems::MaskedInstrument> GetTestNonDefaultMaskedInstrument() { + return GetTestMaskedInstrumentWithId("instrument_id"); +} + +scoped_ptr<Address> GetTestSaveableAddress() { + return scoped_ptr<Address>(new Address( + "US", + ASCIIToUTF16("save_recipient_name"), + ASCIIToUTF16("save_address_line_1"), + ASCIIToUTF16("save_address_line_2"), + ASCIIToUTF16("save_locality_name"), + ASCIIToUTF16("save_admin_area_name"), + ASCIIToUTF16("save_postal_code_number"), + ASCIIToUTF16("save_phone_number"), + std::string())); +} + +scoped_ptr<Address> GetTestShippingAddress() { + return scoped_ptr<Address>(new Address( + "US", + 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"), + "default_address_id")); +} + +scoped_ptr<Address> GetTestNonDefaultShippingAddress() { + scoped_ptr<Address> address = GetTestShippingAddress(); + address->set_object_id("address_id"); + return address.Pass(); +} + +scoped_ptr<WalletItems> GetTestWalletItems() { + return scoped_ptr<WalletItems>( + new wallet::WalletItems(std::vector<RequiredAction>(), + "google_transaction_id", + "default_instrument_id", + "default_address_id", + "obfuscated_gaia_id")); +} + +} // namespace wallet +} // namespace autofill diff --git a/chromium/components/autofill/content/browser/wallet/wallet_test_util.h b/chromium/components/autofill/content/browser/wallet/wallet_test_util.h new file mode 100644 index 00000000000..fd78b05b865 --- /dev/null +++ b/chromium/components/autofill/content/browser/wallet/wallet_test_util.h @@ -0,0 +1,43 @@ +// 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_CONTENT_BROWSER_WALLET_WALLET_TEST_UTIL_H_ +#define COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_TEST_UTIL_H_ + +#include "base/memory/scoped_ptr.h" +#include "components/autofill/content/browser/wallet/wallet_items.h" + +namespace autofill { +namespace wallet { + +class Address; +class FullWallet; +class Instrument; + +scoped_ptr<Address> GetTestAddress(); +scoped_ptr<Address> GetTestMinimalAddress(); +scoped_ptr<FullWallet> GetTestFullWallet(); +scoped_ptr<FullWallet> GetTestFullWalletInstrumentOnly(); +scoped_ptr<Instrument> GetTestInstrument(); +scoped_ptr<Instrument> GetTestAddressUpgradeInstrument(); +scoped_ptr<Instrument> GetTestExpirationDateChangeInstrument(); +scoped_ptr<Instrument> GetTestAddressNameChangeInstrument(); +scoped_ptr<WalletItems::LegalDocument> GetTestLegalDocument(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrument(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentExpired(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentInvalid(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestMaskedInstrumentAmex(); +scoped_ptr<WalletItems::MaskedInstrument> GetTestNonDefaultMaskedInstrument(); +scoped_ptr<WalletItems::MaskedInstrument> + GetTestMaskedInstrumentWithIdAndAddress( + const std::string& id, scoped_ptr<Address> address); +scoped_ptr<Address> GetTestSaveableAddress(); +scoped_ptr<Address> GetTestShippingAddress(); +scoped_ptr<Address> GetTestNonDefaultShippingAddress(); +scoped_ptr<WalletItems> GetTestWalletItems(); + +} // namespace wallet +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_BROWSER_WALLET_WALLET_TEST_UTIL_H_ diff --git a/chromium/components/autofill/content/renderer/DEPS b/chromium/components/autofill/content/renderer/DEPS new file mode 100644 index 00000000000..dbd28cf1ed0 --- /dev/null +++ b/chromium/components/autofill/content/renderer/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+content/public/common", + "+content/public/renderer", +] diff --git a/chromium/components/autofill/content/renderer/autofill_agent.cc b/chromium/components/autofill/content/renderer/autofill_agent.cc new file mode 100644 index 00000000000..e8509c74750 --- /dev/null +++ b/chromium/components/autofill/content/renderer/autofill_agent.cc @@ -0,0 +1,785 @@ +// 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/content/renderer/autofill_agent.h" + +#include "base/bind.h" +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "components/autofill/content/renderer/form_autofill_util.h" +#include "components/autofill/content/renderer/page_click_tracker.h" +#include "components/autofill/content/renderer/password_autofill_agent.h" +#include "components/autofill/core/common/autofill_constants.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/common/autofill_switches.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_data_predictions.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/web_element_descriptor.h" +#include "content/public/common/password_form.h" +#include "content/public/common/ssl_status.h" +#include "content/public/common/url_constants.h" +#include "content/public/renderer/render_view.h" +#include "grit/component_strings.h" +#include "net/cert/cert_status_flags.h" +#include "third_party/WebKit/public/platform/WebRect.h" +#include "third_party/WebKit/public/platform/WebURLRequest.h" +#include "third_party/WebKit/public/web/WebDataSource.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebFormControlElement.h" +#include "third_party/WebKit/public/web/WebFormElement.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" +#include "third_party/WebKit/public/web/WebNode.h" +#include "third_party/WebKit/public/web/WebNodeCollection.h" +#include "third_party/WebKit/public/web/WebOptionElement.h" +#include "third_party/WebKit/public/web/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; + + +// Gets all the data list values (with corresponding label) for the given +// element. +void GetDataListSuggestions(const WebKit::WebInputElement& element, + std::vector<base::string16>* values, + std::vector<base::string16>* labels) { + WebNodeCollection options = element.dataListOptions(); + if (options.isNull()) + return; + + base::string16 prefix = element.editingValue(); + if (element.isMultiple() && + element.formControlType() == WebString::fromUTF8("email")) { + std::vector<base::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(base::string16()); + } +} + +// Trim the vector before sending it to the browser process to ensure we +// don't send too much data through the IPC. +void TrimStringVectorForIPC(std::vector<base::string16>* strings) { + // Limit the size of the vector. + if (strings->size() > kMaximumDataListSizeForAutofill) + strings->resize(kMaximumDataListSizeForAutofill); + + // Limit the size of the strings in the vector. + for (size_t i = 0; i < strings->size(); ++i) { + if ((*strings)[i].length() > kMaximumTextSizeForAutofill) + (*strings)[i].resize(kMaximumTextSizeForAutofill); + } +} + +gfx::RectF GetScaledBoundingBox(float scale, WebInputElement* element) { + gfx::Rect bounding_box(element->boundsInViewportSpace()); + return gfx::RectF(bounding_box.x() * scale, + bounding_box.y() * scale, + bounding_box.width() * scale, + bounding_box.height() * scale); +} + +} // namespace + +namespace autofill { + +AutofillAgent::AutofillAgent(content::RenderView* render_view, + PasswordAutofillAgent* password_autofill_agent) + : content::RenderViewObserver(render_view), + password_autofill_agent_(password_autofill_agent), + 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), + is_autocheckout_supported_(false), + has_new_forms_for_browser_(false), + ignore_text_changes_(false), + weak_ptr_factory_(this) { + render_view->GetWebView()->setAutofillClient(this); + + // The PageClickTracker is a RenderViewObserver, and hence will be freed when + // the RenderView is destroyed. + new PageClickTracker(render_view, this); +} + +AutofillAgent::~AutofillAgent() {} + +bool AutofillAgent::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AutofillAgent, message) + IPC_MESSAGE_HANDLER(AutofillMsg_GetAllForms, OnGetAllForms) + 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_HANDLER(AutofillMsg_AutocheckoutSupported, + OnAutocheckoutSupported) + IPC_MESSAGE_HANDLER(AutofillMsg_PageShown, + OnPageShown) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void AutofillAgent::DidFinishDocumentLoad(WebFrame* frame) { + // Record timestamp on document load. This is used to record overhead of + // Autofill feature. + forms_seen_timestamp_ = base::TimeTicks::Now(); + + // The document has now been fully loaded. Scan for forms to be sent up to + // the browser. + std::vector<FormData> forms; + bool has_more_forms = false; + if (!frame->parent()) { + topmost_frame_ = frame; + form_elements_.clear(); + has_more_forms = form_cache_.ExtractFormsAndFormElements( + *frame, kRequiredAutofillFields, &forms, &form_elements_); + } else { + form_cache_.ExtractForms(*frame, &forms); + } + + autofill::FormsSeenState state = has_more_forms ? + autofill::PARTIAL_FORMS_SEEN : autofill::NO_SPECIAL_FORMS_SEEN; + + // Always communicate to browser process for topmost frame. + if (!forms.empty() || !frame->parent()) { + Send(new AutofillHostMsg_FormsSeen(routing_id(), forms, + forms_seen_timestamp_, + state)); + } +} + +void AutofillAgent::DidStartProvisionalLoad(WebFrame* frame) { + if (!frame->parent()) { + is_autocheckout_supported_ = false; + topmost_frame_ = NULL; + if (click_timer_.IsRunning()) { + click_timer_.Stop(); + autocheckout_click_in_progress_ = true; + } + } +} + +void AutofillAgent::DidFailProvisionalLoad(WebFrame* frame, + const WebKit::WebURLError& error) { + if (!frame->parent() && autocheckout_click_in_progress_) { + autocheckout_click_in_progress_ = false; + ClickFailed(); + } +} + +void AutofillAgent::DidCommitProvisionalLoad(WebFrame* frame, + bool is_new_navigation) { + in_flight_request_form_.reset(); + if (!frame->parent() && autocheckout_click_in_progress_) { + autocheckout_click_in_progress_ = false; + CompleteAutocheckoutPage(SUCCESS); + } +} + +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. + HideAutofillUI(); +} + +void AutofillAgent::FocusedNodeChanged(const WebKit::WebNode& node) { + if (node.isNull() || !node.isElementNode()) + return; + + WebKit::WebElement web_element = node.toConst<WebKit::WebElement>(); + + if (!web_element.document().frame()) + return; + + const WebInputElement* element = toWebInputElement(&web_element); + + if (!element || !element->isEnabled() || element->isReadOnly() || + !element->isTextField() || element->isPasswordField()) + return; + + element_ = *element; + + MaybeShowAutocheckoutBubble(); +} + +void AutofillAgent::MaybeShowAutocheckoutBubble() { + if (element_.isNull() || !element_.focused()) + return; + + FormData form; + FormFieldData field; + // This must be called to short circuit this method if it fails. + if (!FindFormAndFieldForInputElement(element_, &form, &field, REQUIRE_NONE)) + return; + + Send(new AutofillHostMsg_MaybeShowAutocheckoutBubble( + routing_id(), + form, + GetScaledBoundingBox(web_view_->pageScaleFactor(), &element_))); +} + +void AutofillAgent::DidChangeScrollOffset(WebKit::WebFrame*) { + HideAutofillUI(); +} + +void AutofillAgent::didRequestAutocomplete(WebKit::WebFrame* frame, + const WebFormElement& form) { + GURL url(frame->document().url()); + content::SSLStatus ssl_status = render_view()->GetSSLStatusOfFrame(frame); + FormData form_data; + if (!in_flight_request_form_.isNull() || + (url.SchemeIs(chrome::kHttpsScheme) && + (net::IsCertStatusError(ssl_status.cert_status) || + net::IsCertStatusMinorError(ssl_status.cert_status))) || + !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_; + HideAutofillUI(); + + in_flight_request_form_ = form; + Send(new AutofillHostMsg_RequestAutocomplete(routing_id(), form_data, url)); +} + +void AutofillAgent::setIgnoreTextChanges(bool ignore) { + ignore_text_changes_ = ignore; +} + +void AutofillAgent::InputElementClicked(const WebInputElement& element, + bool was_focused, + bool is_focused) { + if (was_focused) + ShowSuggestions(element, true, false, true); +} + +void AutofillAgent::InputElementLostFocus() { + HideAutofillUI(); +} + +void AutofillAgent::didClearAutofillSelection(const WebNode& node) { + if (password_autofill_agent_->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::textFieldDidEndEditing(const WebInputElement& element) { + password_autofill_agent_->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(); + base::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_agent_->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_agent_->TextFieldHandlingKeyDown(element, event)) { + element_ = element; + return; + } + + if (event.windowsKeyCode == ui::VKEY_DOWN || + event.windowsKeyCode == ui::VKEY_UP) + ShowSuggestions(element, true, true, true); +} + +void AutofillAgent::AcceptDataListSuggestion( + const base::string16& suggested_value) { + base::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<base::string16> parts; + + base::SplitStringDontTrim(element_.editingValue(), ',', &parts); + if (parts.size() == 0) + parts.push_back(base::string16()); + + base::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 base::string16& value) { + SetNodeText(value, &element_); +} + +void AutofillAgent::OnAcceptDataListSuggestion(const base::string16& value) { + AcceptDataListSuggestion(value); +} + +void AutofillAgent::OnAcceptPasswordAutofillSuggestion( + const base::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_agent_->DidAcceptAutofillSuggestion( + element_, + value); + DCHECK(handled); +} + +void AutofillAgent::OnGetAllForms() { + form_elements_.clear(); + + // Force fetch all non empty forms. + std::vector<FormData> forms; + form_cache_.ExtractFormsAndFormElements( + *topmost_frame_, 0, &forms, &form_elements_); + + // OnGetAllForms should only be called if AutofillAgent reported to + // AutofillManager that there are more forms + DCHECK(!forms.empty()); + + // Report to AutofillManager that all forms are being sent. + Send(new AutofillHostMsg_FormsSeen(routing_id(), forms, + forms_seen_timestamp_, + NO_SPECIAL_FORMS_SEEN)); +} + +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_); + if (!in_flight_request_form_.checkValidityWithoutDispatchingEvents()) + result = WebFormElement::AutocompleteResultErrorInvalid; + } + + in_flight_request_form_.finishRequestAutocomplete(result); + in_flight_request_form_.reset(); +} + +void AutofillAgent::OnFillFormsAndClick( + const std::vector<FormData>& forms, + const std::vector<WebElementDescriptor>& click_elements_before_form_fill, + const std::vector<WebElementDescriptor>& click_elements_after_form_fill, + const WebElementDescriptor& click_element_descriptor) { + DCHECK_EQ(forms.size(), form_elements_.size()); + + // Click elements in click_elements_before_form_fill. + for (size_t i = 0; i < click_elements_before_form_fill.size(); ++i) { + if (!ClickElement(topmost_frame_->document(), + click_elements_before_form_fill[i])) { + CompleteAutocheckoutPage(MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING); + return; + } + } + + // Fill the form. + for (size_t i = 0; i < forms.size(); ++i) + FillFormForAllElements(forms[i], form_elements_[i]); + + // Click elements in click_elements_after_form_fill. + for (size_t i = 0; i < click_elements_after_form_fill.size(); ++i) { + if (!ClickElement(topmost_frame_->document(), + click_elements_after_form_fill[i])) { + CompleteAutocheckoutPage(MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING); + return; + } + } + + // Exit early if there is nothing to click. + if (click_element_descriptor.retrieval_method == WebElementDescriptor::NONE) { + CompleteAutocheckoutPage(SUCCESS); + return; + } + + // 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)) { + CompleteAutocheckoutPage(MISSING_ADVANCE); + } +} + +void AutofillAgent::OnAutocheckoutSupported() { + is_autocheckout_supported_ = true; + if (has_new_forms_for_browser_) + MaybeSendDynamicFormsSeen(); + MaybeShowAutocheckoutBubble(); +} + +void AutofillAgent::OnPageShown() { + if (is_autocheckout_supported_) + MaybeShowAutocheckoutBubble(); +} + +void AutofillAgent::CompleteAutocheckoutPage( + autofill::AutocheckoutStatus status) { + click_timer_.Stop(); + Send(new AutofillHostMsg_AutocheckoutPageCompleted(routing_id(), status)); +} + +void AutofillAgent::ClickFailed() { + CompleteAutocheckoutPage(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. + HideAutofillUI(); + return; + } + + element_ = element; + if (password_autofill_agent_->ShowSuggestions(element)) + return; + + // If autocomplete is disabled at the field level, ensure that the native + // UI won't try to show a warning, since that may conflict with a custom + // popup. 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. + const base::string16 autocomplete_attribute = + element.getAttribute("autocomplete"); + if (LowerCaseEqualsASCII(autocomplete_attribute, "off")) + display_warning_if_disabled = false; + + 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 + 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::RectF bounding_box_scaled = + GetScaledBoundingBox(web_view_->pageScaleFactor(), &element_); + + // Find the datalist values and send them to the browser process. + std::vector<base::string16> data_list_values; + std::vector<base::string16> data_list_labels; + GetDataListSuggestions(element_, &data_list_values, &data_list_labels); + TrimStringVectorForIPC(&data_list_values); + TrimStringVectorForIPC(&data_list_labels); + + Send(new AutofillHostMsg_SetDataList(routing_id(), + data_list_values, + data_list_labels)); + + 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 base::string16& value, + WebKit::WebInputElement* node) { + did_set_node_text_ = true; + base::string16 substring = value; + substring = substring.substr(0, node->maxLength()); + + node->setEditingValue(substring); +} + +void AutofillAgent::HideAutofillUI() { + Send(new AutofillHostMsg_HideAutofillUI(routing_id())); +} + +void AutofillAgent::didAssociateFormControls( + const WebKit::WebVector<WebKit::WebNode>& nodes) { + for (size_t i = 0; i < nodes.size(); ++i) { + WebKit::WebNode node = nodes[i]; + if (node.document().frame() == topmost_frame_) { + forms_seen_timestamp_ = base::TimeTicks::Now(); + has_new_forms_for_browser_ = true; + break; + } + } + + if (has_new_forms_for_browser_ && is_autocheckout_supported_) + MaybeSendDynamicFormsSeen(); +} + +void AutofillAgent::MaybeSendDynamicFormsSeen() { + has_new_forms_for_browser_ = false; + form_elements_.clear(); + std::vector<FormData> forms; + // This will only be called for Autocheckout flows, so send all forms to + // save an IPC. + form_cache_.ExtractFormsAndFormElements( + *topmost_frame_, 0, &forms, &form_elements_); + autofill::FormsSeenState state = autofill::DYNAMIC_FORMS_SEEN; + + if (!forms.empty()) { + if (click_timer_.IsRunning()) + click_timer_.Stop(); + Send(new AutofillHostMsg_FormsSeen(routing_id(), forms, + forms_seen_timestamp_, + state)); + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/renderer/autofill_agent.h b/chromium/components/autofill/content/renderer/autofill_agent.h new file mode 100644 index 00000000000..9236246c18d --- /dev/null +++ b/chromium/components/autofill/content/renderer/autofill_agent.h @@ -0,0 +1,277 @@ +// 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_CONTENT_RENDERER_AUTOFILL_AGENT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_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/time/time.h" +#include "base/timer/timer.h" +#include "components/autofill/content/renderer/form_cache.h" +#include "components/autofill/content/renderer/page_click_listener.h" +#include "components/autofill/core/common/autocheckout_status.h" +#include "components/autofill/core/common/forms_seen_state.h" +#include "content/public/renderer/render_view_observer.h" +#include "third_party/WebKit/public/web/WebAutofillClient.h" +#include "third_party/WebKit/public/web/WebFormElement.h" +#include "third_party/WebKit/public/web/WebInputElement.h" + +namespace WebKit { +class WebNode; +class WebView; +} + +namespace autofill { + +struct FormData; +struct FormFieldData; +struct WebElementDescriptor; +class PasswordAutofillAgent; + +// 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: + // PasswordAutofillAgent is guaranteed to outlive AutofillAgent. + AutofillAgent(content::RenderView* render_view, + PasswordAutofillAgent* 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; + virtual void FocusedNodeChanged(const WebKit::WebNode& node) OVERRIDE; + + // PageClickListener: + virtual void InputElementClicked(const WebKit::WebInputElement& element, + bool was_focused, + bool is_focused) OVERRIDE; + virtual void InputElementLostFocus() OVERRIDE; + + // WebKit::WebAutofillClient: + virtual void didClearAutofillSelection(const WebKit::WebNode& node) 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; + virtual void didAssociateFormControls( + const WebKit::WebVector<WebKit::WebNode>& nodes) OVERRIDE; + + 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 base::string16& value); + void OnAcceptDataListSuggestion(const base::string16& value); + void OnAcceptPasswordAutofillSuggestion(const base::string16& value); + void OnGetAllForms(); + + // 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 std::vector<WebElementDescriptor>& click_elements_before_form_fill, + const std::vector<WebElementDescriptor>& click_elements_after_form_fill, + const WebElementDescriptor& element_descriptor); + + // Called when |topmost_frame_| is supported for Autocheckout. + void OnAutocheckoutSupported(); + + // Called when the page is actually shown in the browser, as opposed to simply + // being preloaded. + void OnPageShown(); + + // Called when an Autocheckout page is completed by the renderer. + void CompleteAutocheckoutPage(autofill::AutocheckoutStatus status); + + // 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); + + // Sets the element value to reflect the selected |suggested_value|. + void AcceptDataListSuggestion(const base::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 base::string16& value, WebKit::WebInputElement* node); + + // Hides any currently showing Autofill UI. + void HideAutofillUI(); + + void MaybeSendDynamicFormsSeen(); + + // Send |AutofillHostMsg_MaybeShowAutocheckoutBubble| to browser if needed. + void MaybeShowAutocheckoutBubble(); + + FormCache form_cache_; + + PasswordAutofillAgent* password_autofill_agent_; // 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 |topmost_frame_| is whitelisted for Autocheckout. + bool is_autocheckout_supported_; + + // Whether or not new forms/fields have been dynamically added + // since the last loaded forms were sent to the browser process. + bool has_new_forms_for_browser_; + + // 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_; + + // Timestamp of first time forms are seen. + base::TimeTicks forms_seen_timestamp_; + + base::WeakPtrFactory<AutofillAgent> weak_ptr_factory_; + + friend class PasswordAutofillAgentTest; + FRIEND_TEST_ALL_PREFIXES(ChromeRenderViewTest, FillFormElement); + FRIEND_TEST_ALL_PREFIXES(ChromeRenderViewTest, SendForms); + FRIEND_TEST_ALL_PREFIXES(ChromeRenderViewTest, SendDynamicForms); + FRIEND_TEST_ALL_PREFIXES(ChromeRenderViewTest, ShowAutofillWarning); + FRIEND_TEST_ALL_PREFIXES(PasswordAutofillAgentTest, WaitUsername); + FRIEND_TEST_ALL_PREFIXES(PasswordAutofillAgentTest, SuggestionAccept); + FRIEND_TEST_ALL_PREFIXES(PasswordAutofillAgentTest, SuggestionSelect); + + DISALLOW_COPY_AND_ASSIGN(AutofillAgent); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_RENDERER_AUTOFILL_AGENT_H_ diff --git a/chromium/components/autofill/content/renderer/form_autofill_util.cc b/chromium/components/autofill/content/renderer/form_autofill_util.cc new file mode 100644 index 00000000000..55f3a03e7c9 --- /dev/null +++ b/chromium/components/autofill/content/renderer/form_autofill_util.cc @@ -0,0 +1,1059 @@ +// 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/content/renderer/form_autofill_util.h" + +#include <map> + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/metrics/field_trial.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/common/autofill_switches.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/web_element_descriptor.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebVector.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebElement.h" +#include "third_party/WebKit/public/web/WebExceptionCode.h" +#include "third_party/WebKit/public/web/WebFormControlElement.h" +#include "third_party/WebKit/public/web/WebFormElement.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebInputElement.h" +#include "third_party/WebKit/public/web/WebLabelElement.h" +#include "third_party/WebKit/public/web/WebNode.h" +#include "third_party/WebKit/public/web/WebNodeList.h" +#include "third_party/WebKit/public/web/WebOptionElement.h" +#include "third_party/WebKit/public/web/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 autofill { +namespace { + +// The maximum length allowed for form data. +const size_t kMaxDataLength = 1024; + +// A bit field mask for FillForm functions to not fill some fields. +enum FieldFilterMask { + FILTER_NONE = 0, + FILTER_DISABLED_ELEMENTS = 1 << 0, + FILTER_READONLY_ELEMENTS = 1 << 1, + FILTER_NON_FOCUSABLE_ELEMENTS = 1 << 2, + FILTER_ALL_NON_EDITIABLE_ELEMENTS = FILTER_DISABLED_ELEMENTS | + FILTER_READONLY_ELEMENTS | + FILTER_NON_FOCUSABLE_ELEMENTS, +}; + +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); +} + +bool IsAutocheckoutEnabled() { + return base::FieldTrialList::FindFullName("Autocheckout") == "Yes" || + CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableExperimentalFormFilling); +} + +// 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() || IsAutocheckoutEnabled(); +} + +// 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 base::string16 CombineAndCollapseWhitespace( + const base::string16& prefix, + const base::string16& suffix, + bool force_whitespace) { + base::string16 prefix_trimmed; + TrimPositions prefix_trailing_whitespace = + TrimWhitespace(prefix, TRIM_TRAILING, &prefix_trimmed); + + // Recursively compute the children's text. + base::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. +base::string16 FindChildTextInner(const WebNode& node, int depth) { + if (depth <= 0 || node.isNull()) + return base::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 base::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 base::string16(); + } + } + + // Extract the text exactly at this node. + base::string16 node_text = node.nodeValue(); + + // Recursively compute the children's text. + // Preserve inter-element whitespace separation. + base::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. + base::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. +base::string16 FindChildText(const WebNode& node) { + if (node.isTextNode()) + return node.nodeValue(); + + WebNode child = node.firstChild(); + + const int kChildSearchDepth = 10; + base::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 ...>. +base::string16 InferLabelFromPrevious(const WebFormControlElement& element) { + base::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)) { + base::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. + base::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> +base::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 base::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> +base::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 base::string16(); + + // Check all previous siblings, skipping non-element nodes, until we find a + // non-empty text block. + base::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> +base::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 base::string16(); + + // Check all previous siblings, skipping non-element nodes, until we find a + // non-empty text block. + base::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> +base::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. + base::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> +base::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 base::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 base::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. +base::string16 InferLabelForElement(const WebFormControlElement& element) { + base::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<base::string16>* option_values, + std::vector<base::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, + FieldFilterMask filters, + bool force_override, + Callback callback) { + std::vector<WebFormControlElement> control_elements; + ExtractAutofillableElements(form_element, 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 (base::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 (((filters & FILTER_DISABLED_ELEMENTS) && !element->isEnabled()) || + ((filters & FILTER_READONLY_ELEMENTS) && element->isReadOnly()) || + ((filters & FILTER_NON_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 WebElementDescriptor::RetrievalMethod& method) { + switch (method) { + case WebElementDescriptor::CSS_SELECTOR: + return "CSS_SELECTOR"; + case WebElementDescriptor::ID: + return "ID"; + case WebElementDescriptor::NONE: + return "NONE"; + } + NOTREACHED(); + return "UNKNOWN"; +} + +} // namespace + +const size_t kMaxParseableFields = 100; + +// All text fields, including password fields, should be extracted. +bool IsTextInput(const WebInputElement* element) { + return element && element->isTextField(); +} + +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 base::string16 GetFormIdentifier(const WebFormElement& form) { + base::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->is_checkable = IsCheckableElement(input_element); + field->is_checked = input_element->isChecked(); + field->should_autocomplete = input_element->autoComplete(); + field->text_direction = input_element->directionForFormData() == "rtl" ? + base::i18n::RIGHT_TO_LEFT : base::i18n::LEFT_TO_RIGHT; + } 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; + + base::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<base::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>(); + + base::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<base::string16, FormFieldData*>::iterator iter = + name_map.find(element_name); + if (iter != name_map.end()) { + base::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, + FILTER_ALL_NON_EDITIABLE_ELEMENTS, + false, /* dont force override */ + &FillFormField); +} + +void FillFormIncludingNonFocusableElements(const FormData& form_data, + const WebFormElement& form_element) { + if (form_element.isNull()) + return; + + FieldFilterMask filter_mask = static_cast<FieldFilterMask>( + FILTER_DISABLED_ELEMENTS | FILTER_READONLY_ELEMENTS); + ForEachMatchingFormField(form_element, + WebInputElement(), + form_data, + filter_mask, + true, /* force override */ + &FillFormField); +} + +void FillFormForAllElements(const FormData& form_data, + const WebFormElement& form_element) { + if (form_element.isNull()) + return; + + ForEachMatchingFormField(form_element, + WebInputElement(), + form_data, + FILTER_NONE, + 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, + FILTER_ALL_NON_EDITIABLE_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/chromium/components/autofill/content/renderer/form_autofill_util.h b/chromium/components/autofill/content/renderer/form_autofill_util.h new file mode 100644 index 00000000000..5a1241897f8 --- /dev/null +++ b/chromium/components/autofill/content/renderer/form_autofill_util.h @@ -0,0 +1,144 @@ +// 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_CONTENT_RENDERER_FORM_AUTOFILL_UTIL_H_ +#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_FORM_AUTOFILL_UTIL_H_ + +#include <vector> + +#include "base/strings/string16.h" + +namespace WebKit { +class WebDocument; +class WebFormElement; +class WebFormControlElement; +class WebInputElement; +} + +namespace autofill { + +struct FormData; +struct FormFieldData; +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 base::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); + +// Fills all (including disabled, read-only and non-focusable) form control +// elements within |form_element| with field data from |form_data|. +void FillFormForAllElements( + 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_CONTENT_RENDERER_FORM_AUTOFILL_UTIL_H_ diff --git a/chromium/components/autofill/content/renderer/form_cache.cc b/chromium/components/autofill/content/renderer/form_cache.cc new file mode 100644 index 00000000000..ba4baebf2fe --- /dev/null +++ b/chromium/components/autofill/content/renderer/form_cache.cc @@ -0,0 +1,297 @@ +// 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/content/renderer/form_cache.h" + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/content/renderer/form_autofill_util.h" +#include "components/autofill/core/common/autofill_constants.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_data_predictions.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/form_field_data_predictions.h" +#include "grit/component_strings.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/platform/WebVector.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebFormControlElement.h" +#include "third_party/WebKit/public/web/WebFormElement.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebInputElement.h" +#include "third_party/WebKit/public/web/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 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, kRequiredAutofillFields, forms, NULL); +} + +bool FormCache::ExtractFormsAndFormElements( + const WebFrame& frame, + size_t minimum_required_fields, + 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 false; + + web_documents_.insert(document); + + WebVector<WebFormElement> web_forms; + document.forms(web_forms); + + size_t num_fields_seen = 0; + bool has_skipped_forms = false; + 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 < minimum_required_fields && + control_elements.size() > 0) { + has_skipped_forms = true; + 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() >= minimum_required_fields) { + forms->push_back(form); + if (web_form_elements) + web_form_elements->push_back(form_element); + } else { + has_skipped_forms = true; + } + } + + // Return true if there are any WebFormElements skipped, else false. + return has_skipped_forms; +} + +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(base::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, base::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. + base::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 (base::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; + base::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/chromium/components/autofill/content/renderer/form_cache.h b/chromium/components/autofill/content/renderer/form_cache.h new file mode 100644 index 00000000000..9e70a3f992e --- /dev/null +++ b/chromium/components/autofill/content/renderer/form_cache.h @@ -0,0 +1,77 @@ +// 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_CONTENT_RENDERER_FORM_CACHE_H_ +#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_FORM_CACHE_H_ + +#include <map> +#include <set> +#include <vector> + +#include "base/strings/string16.h" + +namespace WebKit { +class WebDocument; +class WebFormElement; +class WebFrame; +class WebInputElement; +class WebSelectElement; +} + +namespace autofill { + +struct FormData; +struct FormDataPredictions; + +// Manages the forms in a RenderView. +class FormCache { + public: + FormCache(); + ~FormCache(); + + // Scans the DOM in |frame| extracting and storing forms. + // Fills |forms| with extracted forms. + void ExtractForms(const WebKit::WebFrame& frame, + std::vector<FormData>* forms); + + // Scans the DOM in |frame| extracting and storing forms. + // Fills |forms| with extracted forms and |web_form_elements| with associated + // web form elements. Returns true if there are unextracted forms due to + // |minimum_required_fields| limit, else false. + bool ExtractFormsAndFormElements( + const WebKit::WebFrame& frame, + size_t minimum_required_fields, + 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, base::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_CONTENT_RENDERER_FORM_CACHE_H_ diff --git a/chromium/components/autofill/content/renderer/page_click_listener.h b/chromium/components/autofill/content/renderer/page_click_listener.h new file mode 100644 index 00000000000..a7490a00dab --- /dev/null +++ b/chromium/components/autofill/content/renderer/page_click_listener.h @@ -0,0 +1,36 @@ +// 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_CONTENT_RENDERER_PAGE_CLICK_LISTENER_H_ +#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_PAGE_CLICK_LISTENER_H_ + +namespace WebKit { +class WebInputElement; +} + +namespace autofill { + +// 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. + virtual void 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. + virtual void InputElementLostFocus() = 0; + + protected: + virtual ~PageClickListener() {} +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_RENDERER_PAGE_CLICK_LISTENER_H_ diff --git a/chromium/components/autofill/content/renderer/page_click_tracker.cc b/chromium/components/autofill/content/renderer/page_click_tracker.cc new file mode 100644 index 00000000000..ff7cde31a89 --- /dev/null +++ b/chromium/components/autofill/content/renderer/page_click_tracker.cc @@ -0,0 +1,146 @@ +// 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/content/renderer/page_click_tracker.h" + +#include "components/autofill/content/renderer/form_autofill_util.h" +#include "components/autofill/content/renderer/page_click_listener.h" +#include "content/public/renderer/render_view.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/web/WebDOMMouseEvent.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebInputElement.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" +#include "third_party/WebKit/public/web/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 + +namespace autofill { + +PageClickTracker::PageClickTracker(content::RenderView* render_view, + PageClickListener* listener) + : content::RenderViewObserver(render_view), + was_focused_(false), + listener_(listener) { +} + +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()); + listener_->InputElementClicked(input_element, was_focused_, is_focused); +} + +void PageClickTracker::DidFinishDocumentLoad(WebKit::WebFrame* frame) { + tracked_frames_.push_back(frame); + frame->document().addEventListener("mousedown", this, false); +} + +void PageClickTracker::FrameDetached(WebKit::WebFrame* frame) { + std::vector<WebKit::WebFrame*>::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)) + listener_->InputElementLostFocus(); +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/renderer/page_click_tracker.h b/chromium/components/autofill/content/renderer/page_click_tracker.h new file mode 100644 index 00000000000..8328bd1ca39 --- /dev/null +++ b/chromium/components/autofill/content/renderer/page_click_tracker.h @@ -0,0 +1,71 @@ +// 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_CONTENT_RENDERER_PAGE_CLICK_TRACKER_H_ +#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_PAGE_CLICK_TRACKER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "content/public/renderer/render_view_observer.h" +#include "third_party/WebKit/public/web/WebDOMEventListener.h" +#include "third_party/WebKit/public/web/WebNode.h" + +namespace autofill { + +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: + // The |listener| will be notified when an element is clicked. It must + // outlive this class. + PageClickTracker(content::RenderView* render_view, + PageClickListener* listener); + virtual ~PageClickTracker(); + + 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. + std::vector<WebKit::WebFrame*> tracked_frames_; + + // The listener getting the actual notifications. + PageClickListener* listener_; + + DISALLOW_COPY_AND_ASSIGN(PageClickTracker); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_RENDERER_PAGE_CLICK_TRACKER_H_ diff --git a/chromium/components/autofill/content/renderer/password_autofill_agent.cc b/chromium/components/autofill/content/renderer/password_autofill_agent.cc new file mode 100644 index 00000000000..b6294abf59e --- /dev/null +++ b/chromium/components/autofill/content/renderer/password_autofill_agent.cc @@ -0,0 +1,680 @@ +// 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/content/renderer/password_autofill_agent.h" + +#include "base/bind.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/metrics/histogram.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/content/renderer/form_autofill_util.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/password_form_fill_data.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/public/platform/WebVector.h" +#include "third_party/WebKit/public/web/WebAutofillClient.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebElement.h" +#include "third_party/WebKit/public/web/WebFormElement.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebInputEvent.h" +#include "third_party/WebKit/public/web/WebSecurityOrigin.h" +#include "third_party/WebKit/public/web/WebView.h" +#include "ui/base/keycodes/keyboard_codes.h" + +namespace autofill { +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<base::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; + } + + // Only fill saved passwords into password fields and usernames into + // text fields. + WebKit::WebInputElement input_element = + temp_elements[i].to<WebKit::WebInputElement>(); + if (input_element.isPasswordField() != + (data.fields[j].form_control_type == "password")) + continue; + + // 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] = input_element; + 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<base::string16, base::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; + + // Don't fill forms with uneditable fields or fields with autocomplete + // disabled. + if (!IsElementEditable(element) || !element.autoComplete()) + return; + } + + for (FormInputElementMap::iterator it = fe->input_elements.begin(); + it != fe->input_elements.end(); ++it) { + WebKit::WebInputElement element = it->second; + + // 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 base::string16& username1, + const base::string16& username2, + bool exact_match) { + if (exact_match) + return username1 == username2; + return StartsWith(username1, username2, true); +} + +} // namespace + +//////////////////////////////////////////////////////////////////////////////// +// PasswordAutofillAgent, public: + +PasswordAutofillAgent::PasswordAutofillAgent(content::RenderView* render_view) + : content::RenderViewObserver(render_view), + usernames_usage_(NOTHING_TO_AUTOFILL), + web_view_(render_view->GetWebView()), + weak_ptr_factory_(this) { +} + +PasswordAutofillAgent::~PasswordAutofillAgent() { +} + +bool PasswordAutofillAgent::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 PasswordAutofillAgent::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(base::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 PasswordAutofillAgent::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 PasswordAutofillAgent::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 PasswordAutofillAgent::DidClearAutofillSelection( + const WebKit::WebNode& node) { + WebKit::WebInputElement input; + PasswordInfo password; + return FindLoginInfo(node, &input, &password); +} + +bool PasswordAutofillAgent::ShowSuggestions( + const WebKit::WebInputElement& element) { + LoginToPasswordInfoMap::const_iterator iter = + login_to_password_info_.find(element); + if (iter == login_to_password_info_.end()) + return false; + + return ShowSuggestionPopup(iter->second.fill_data, element); +} + +void PasswordAutofillAgent::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 PasswordAutofillAgent::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(PasswordAutofillAgent, message) + IPC_MESSAGE_HANDLER(AutofillMsg_FillPasswordForm, OnFillPasswordForm) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void PasswordAutofillAgent::DidStartLoading() { + if (usernames_usage_ != NOTHING_TO_AUTOFILL) { + UMA_HISTOGRAM_ENUMERATION("PasswordManager.OtherPossibleUsernamesUsage", + usernames_usage_, OTHER_POSSIBLE_USERNAMES_MAX); + usernames_usage_ = NOTHING_TO_AUTOFILL; + } +} + +void PasswordAutofillAgent::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 PasswordAutofillAgent::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 PasswordAutofillAgent::FrameDetached(WebKit::WebFrame* frame) { + FrameClosing(frame); +} + +void PasswordAutofillAgent::FrameWillClose(WebKit::WebFrame* frame) { + FrameClosing(frame); +} + +void PasswordAutofillAgent::OnFillPasswordForm( + const PasswordFormFillData& form_data) { + if (usernames_usage_ == NOTHING_TO_AUTOFILL) { + if (form_data.other_possible_usernames.size()) + usernames_usage_ = OTHER_POSSIBLE_USERNAMES_PRESENT; + else if (usernames_usage_ == NOTHING_TO_AUTOFILL) + usernames_usage_ = OTHER_POSSIBLE_USERNAMES_ABSENT; + } + + 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)); + } +} + +//////////////////////////////////////////////////////////////////////////////// +// PasswordAutofillAgent, private: + +void PasswordAutofillAgent::GetSuggestions( + const PasswordFormFillData& fill_data, + const base::string16& input, + std::vector<base::string16>* suggestions, + std::vector<base::string16>* realms) { + if (StartsWith(fill_data.basic_data.fields[0].value, input, false)) { + suggestions->push_back(fill_data.basic_data.fields[0].value); + realms->push_back(UTF8ToUTF16(fill_data.preferred_realm)); + } + + for (PasswordFormFillData::LoginCollection::const_iterator iter = + fill_data.additional_logins.begin(); + iter != fill_data.additional_logins.end(); ++iter) { + if (StartsWith(iter->first, input, false)) { + suggestions->push_back(iter->first); + realms->push_back(UTF8ToUTF16(iter->second.realm)); + } + } + + for (PasswordFormFillData::UsernamesCollection::const_iterator iter = + fill_data.other_possible_usernames.begin(); + iter != fill_data.other_possible_usernames.end(); ++iter) { + for (size_t i = 0; i < iter->second.size(); ++i) { + if (StartsWith(iter->second[i], input, false)) { + usernames_usage_ = OTHER_POSSIBLE_USERNAME_SHOWN; + suggestions->push_back(iter->second[i]); + realms->push_back(UTF8ToUTF16(iter->first.realm)); + } + } + } +} + +bool PasswordAutofillAgent::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<base::string16> suggestions; + std::vector<base::string16> realms; + GetSuggestions(fill_data, user_input.value(), &suggestions, &realms); + DCHECK_EQ(suggestions.size(), realms.size()); + + 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, + realms)); + return !suggestions.empty(); +} + +bool PasswordAutofillAgent::FillUserNameAndPassword( + WebKit::WebInputElement* username_element, + WebKit::WebInputElement* password_element, + const PasswordFormFillData& fill_data, + bool exact_username_match, + bool set_selection) { + base::string16 current_username = username_element->value(); + // username and password will contain the match found if any. + base::string16 username; + base::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.password; + break; + } + } + + // Check possible usernames. + if (username.empty() && password.empty()) { + for (PasswordFormFillData::UsernamesCollection::const_iterator iter = + fill_data.other_possible_usernames.begin(); + iter != fill_data.other_possible_usernames.end(); ++iter) { + for (size_t i = 0; i < iter->second.size(); ++i) { + if (DoUsernamesMatch(iter->second[i], current_username, + exact_username_match)) { + usernames_usage_ = OTHER_POSSIBLE_USERNAME_SELECTED; + username = iter->second[i]; + password = iter->first.password; + break; + } + } + if (!username.empty() && !password.empty()) + 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 PasswordAutofillAgent::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 PasswordAutofillAgent::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 PasswordAutofillAgent::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/chromium/components/autofill/content/renderer/password_autofill_agent.h b/chromium/components/autofill/content/renderer/password_autofill_agent.h new file mode 100644 index 00000000000..30fef628c15 --- /dev/null +++ b/chromium/components/autofill/content/renderer/password_autofill_agent.h @@ -0,0 +1,133 @@ +// 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_CONTENT_RENDERER_PASSWORD_AUTOFILL_AGENT_H_ +#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_PASSWORD_AUTOFILL_AGENT_H_ + +#include <map> +#include <vector> + +#include "base/memory/weak_ptr.h" +#include "components/autofill/core/common/password_form_fill_data.h" +#include "content/public/renderer/render_view_observer.h" +#include "third_party/WebKit/public/web/WebInputElement.h" + +namespace WebKit { +class WebInputElement; +class WebKeyboardEvent; +class WebView; +} + +namespace autofill { + +// This class is responsible for filling password forms. +// There is one PasswordAutofillAgent per RenderView. +class PasswordAutofillAgent : public content::RenderViewObserver { + public: + explicit PasswordAutofillAgent(content::RenderView* render_view); + virtual ~PasswordAutofillAgent(); + + // 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. 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); + // Shows an Autofill popup with username suggestions for |element|. + // Returns true if any suggestions were shown, false otherwise. + bool ShowSuggestions(const WebKit::WebInputElement& element); + + private: + friend class PasswordAutofillAgentTest; + + enum OtherPossibleUsernamesUsage { + NOTHING_TO_AUTOFILL, + OTHER_POSSIBLE_USERNAMES_ABSENT, + OTHER_POSSIBLE_USERNAMES_PRESENT, + OTHER_POSSIBLE_USERNAME_SHOWN, + OTHER_POSSIBLE_USERNAME_SELECTED, + OTHER_POSSIBLE_USERNAMES_MAX + }; + + 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 DidStartLoading() 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; + + // RenderView IPC handlers: + void OnFillPasswordForm(const PasswordFormFillData& form_data); + + // 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 base::string16& input, + std::vector<base::string16>* suggestions, + std::vector<base::string16>* realms); + + 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 for UMA stats. + OtherPossibleUsernamesUsage usernames_usage_; + + // Pointer to the WebView. Used to access page scale factor. + WebKit::WebView* web_view_; + + base::WeakPtrFactory<PasswordAutofillAgent> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(PasswordAutofillAgent); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CONTENT_RENDERER_PASSWORD_AUTOFILL_AGENT_H_ diff --git a/chromium/components/autofill/content/renderer/password_generation_manager.cc b/chromium/components/autofill/content/renderer/password_generation_manager.cc new file mode 100644 index 00000000000..aa50cdc0b4d --- /dev/null +++ b/chromium/components/autofill/content/renderer/password_generation_manager.cc @@ -0,0 +1,229 @@ +// 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/content/renderer/password_generation_manager.h" + +#include "base/logging.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/common/password_generation_util.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/public/platform/WebCString.h" +#include "third_party/WebKit/public/platform/WebRect.h" +#include "third_party/WebKit/public/platform/WebVector.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebFormElement.h" +#include "third_party/WebKit/public/web/WebFrame.h" +#include "third_party/WebKit/public/web/WebInputElement.h" +#include "third_party/WebKit/public/web/WebSecurityOrigin.h" +#include "third_party/WebKit/public/web/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()->setPasswordGeneratorClient(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; +} + +void PasswordGenerationManager::openPasswordGenerator( + WebKit::WebInputElement& element) { + WebKit::WebElement button(element.passwordGeneratorButtonElement()); + gfx::Rect rect(button.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); +} + +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 base::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].passwordGeneratorButtonElement().setAttribute("style", + "display:block"); + password_generation::LogPasswordGenerationEvent( + password_generation::ICON_SHOWN); + return; + } + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/content/renderer/password_generation_manager.h b/chromium/components/autofill/content/renderer/password_generation_manager.h new file mode 100644 index 00000000000..a6154286167 --- /dev/null +++ b/chromium/components/autofill/content/renderer/password_generation_manager.h @@ -0,0 +1,82 @@ +// 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_CONTENT_RENDERER_PASSWORD_GENERATION_MANAGER_H_ +#define COMPONENTS_AUTOFILL_CONTENT_RENDERER_PASSWORD_GENERATION_MANAGER_H_ + +#include <map> +#include <utility> +#include <vector> + +#include "content/public/renderer/render_view_observer.h" +#include "third_party/WebKit/public/web/WebInputElement.h" +#include "third_party/WebKit/public/web/WebPasswordGeneratorClient.h" +#include "url/gurl.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::WebPasswordGeneratorClient { + 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; + + // WebPasswordGeneratorClient: + virtual void openPasswordGenerator(WebKit::WebInputElement& element) OVERRIDE; + + // Message handlers. + void OnFormNotBlacklisted(const content::PasswordForm& form); + void OnPasswordAccepted(const base::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_CONTENT_RENDERER_PASSWORD_GENERATION_MANAGER_H_ diff --git a/chromium/components/autofill/core/DEPS b/chromium/components/autofill/core/DEPS new file mode 100644 index 00000000000..caaebde2f13 --- /dev/null +++ b/chromium/components/autofill/core/DEPS @@ -0,0 +1,10 @@ +include_rules = [ + # TODO(blundell): 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. + "!content/public/common/common_param_traits.h", + "!content/public/common/common_param_traits_macros.h", + "!content/public/common/password_form.h", + "!third_party/WebKit/public/web/WebFormElement.h", +] diff --git a/chromium/components/autofill/core/browser/DEPS b/chromium/components/autofill/core/browser/DEPS new file mode 100644 index 00000000000..c59b4e28000 --- /dev/null +++ b/chromium/components/autofill/core/browser/DEPS @@ -0,0 +1,53 @@ +include_rules = [ + "+components/webdata/common", + "+crypto/random.h", + "+google_apis/gaia", + "+google_apis/google_api_keys.h", + "+net", + "+sql", + "+third_party/libjingle", + "+third_party/libphonenumber", # For phone number i18n. + + # TODO(blundell): 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. + "!components/autofill/content/browser/autocheckout/whitelist_manager.h", + "!components/autofill/content/browser/autocheckout_manager.h", + "!components/autofill/content/browser/autocheckout_page_meta_data.h", + "!components/autofill/content/browser/autocheckout_steps.h", + "!content/public/browser/android/content_view_core.h", + "!content/public/browser/browser_context.h", + "!content/public/browser/browser_thread.h", + "!content/public/browser/render_view_host.h", + "!content/public/browser/web_contents.h", + "!content/public/browser/web_contents_observer.h", + "!content/public/browser/web_contents_view.h", + "!content/public/common/url_constants.h", + "!third_party/WebKit/public/web/WebAutofillClient.h", + "!third_party/WebKit/public/web/WebInputElement.h", +] + +specific_include_rules = { + '.*_[a-z]*test\.cc': [ + "+content/public/test", + + # TODO(joi): 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/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/ui/autofill/tab_autofill_manager_delegate.h", + "!chrome/browser/webdata/web_data_service.h", + "!chrome/browser/webdata/web_data_service_factory.h", + "!chrome/common/chrome_paths.h", + "!chrome/test/base/chrome_render_view_host_test_harness.h", + "!chrome/test/base/testing_profile.h", + ], +} diff --git a/chromium/components/autofill/core/browser/address.cc b/chromium/components/autofill/core/browser/address.cc new file mode 100644 index 00000000000..6f7f2a93ac7 --- /dev/null +++ b/chromium/components/autofill/core/browser/address.cc @@ -0,0 +1,174 @@ +// 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/core/browser/address.h" + +#include <stddef.h> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_type.h" + +namespace { + +const char16 kAddressSplitChars[] = {'-', ',', '#', '.', ' ', 0}; + +} // namespace + +namespace autofill { + +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; +} + +base::string16 Address::GetRawInfo(ServerFieldType type) const { + // TODO(isherman): Is GetStorableType even necessary? + switch (AutofillType(type).GetStorableType()) { + case ADDRESS_HOME_LINE1: + return line1_; + + case ADDRESS_HOME_LINE2: + return line2_; + + case ADDRESS_HOME_CITY: + return city_; + + case ADDRESS_HOME_STATE: + return state_; + + case ADDRESS_HOME_ZIP: + return zip_code_; + + case ADDRESS_HOME_COUNTRY: + return ASCIIToUTF16(country_code_); + + default: + return base::string16(); + } +} + +void Address::SetRawInfo(ServerFieldType type, const base::string16& value) { + // TODO(isherman): Is GetStorableType even necessary? + switch (AutofillType(type).GetStorableType()) { + case ADDRESS_HOME_LINE1: + line1_ = value; + break; + + case ADDRESS_HOME_LINE2: + line2_ = value; + break; + + case ADDRESS_HOME_CITY: + city_ = value; + break; + + case ADDRESS_HOME_STATE: + state_ = value; + break; + + case ADDRESS_HOME_COUNTRY: + DCHECK(value.empty() || + (value.length() == 2u && IsStringASCII(value))); + country_code_ = UTF16ToASCII(value); + break; + + case ADDRESS_HOME_ZIP: + zip_code_ = value; + break; + + default: + NOTREACHED(); + } +} + +base::string16 Address::GetInfo(const AutofillType& type, + const std::string& app_locale) const { + if (type.html_type() == HTML_TYPE_COUNTRY_CODE) { + return ASCIIToUTF16(country_code_); + } else if (type.html_type() == HTML_TYPE_STREET_ADDRESS) { + base::string16 address = line1_; + if (!line2_.empty()) + address += ASCIIToUTF16(", ") + line2_; + return address; + } + + ServerFieldType storable_type = type.GetStorableType(); + if (storable_type == ADDRESS_HOME_COUNTRY && !country_code_.empty()) + return AutofillCountry(country_code_, app_locale).name(); + + return GetRawInfo(storable_type); +} + +bool Address::SetInfo(const AutofillType& type, + const base::string16& value, + const std::string& app_locale) { + if (type.html_type() == HTML_TYPE_COUNTRY_CODE) { + if (!value.empty() && (value.size() != 2u || !IsStringASCII(value))) { + country_code_ = std::string(); + return false; + } + + country_code_ = StringToUpperASCII(UTF16ToASCII(value)); + return true; + } else if (type.html_type() == HTML_TYPE_STREET_ADDRESS) { + // Don't attempt to parse the address into lines, since this is potentially + // a user-entered address in the user's own format, so the code would have + // to rely on iffy heuristics at best. Instead, just give up when importing + // addresses like this. + line1_ = line2_ = base::string16(); + return false; + } + + ServerFieldType storable_type = type.GetStorableType(); + if (storable_type == ADDRESS_HOME_COUNTRY && !value.empty()) { + country_code_ = AutofillCountry::GetCountryCode(value, app_locale); + return !country_code_.empty(); + } + + SetRawInfo(storable_type, value); + return true; +} + +void Address::GetMatchingTypes(const base::string16& text, + const std::string& app_locale, + ServerFieldTypeSet* 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); +} + +void Address::GetSupportedTypes(ServerFieldTypeSet* 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); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/address.h b/chromium/components/autofill/core/browser/address.h new file mode 100644 index 00000000000..f62c32ea092 --- /dev/null +++ b/chromium/components/autofill/core/browser/address.h @@ -0,0 +1,59 @@ +// 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_CORE_BROWSER_ADDRESS_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_ADDRESS_H_ + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/form_group.h" + +namespace autofill { + +// 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 base::string16 GetRawInfo(ServerFieldType type) const OVERRIDE; + virtual void SetRawInfo(ServerFieldType type, + const base::string16& value) OVERRIDE; + virtual base::string16 GetInfo(const AutofillType& type, + const std::string& app_locale) const OVERRIDE; + virtual bool SetInfo(const AutofillType& type, + const base::string16& value, + const std::string& app_locale) OVERRIDE; + virtual void GetMatchingTypes( + const base::string16& text, + const std::string& app_locale, + ServerFieldTypeSet* matching_types) const OVERRIDE; + + private: + // FormGroup: + virtual void GetSupportedTypes( + ServerFieldTypeSet* supported_types) const OVERRIDE; + + // The address, sans country info. + base::string16 line1_; + base::string16 line2_; + base::string16 city_; + base::string16 state_; + base::string16 zip_code_; + + // The ISO 3166 2-letter country code, or an empty string if there is no + // country data specified for this address. + std::string country_code_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_ADDRESS_H_ diff --git a/chromium/components/autofill/core/browser/address_field.cc b/chromium/components/autofill/core/browser/address_field.cc new file mode 100644 index 00000000000..ba7c09e23d1 --- /dev/null +++ b/chromium/components/autofill/core/browser/address_field.cc @@ -0,0 +1,341 @@ +// 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/core/browser/address_field.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_regex_constants.h" +#include "components/autofill/core/browser/autofill_scanner.h" +#include "components/autofill/core/browser/field_types.h" +#include "ui/base/l10n/l10n_util.h" + +namespace autofill { + +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(); + + base::string16 attention_ignored = UTF8ToUTF16(autofill::kAttentionIgnoredRe); + base::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_) { + base::string16 name = StringToLowerASCII(company_->name); + return AddressTypeFromText(name); + } + if (address1_) { + base::string16 name = StringToLowerASCII(address1_->name); + return AddressTypeFromText(name); + } + if (address2_) { + base::string16 name = StringToLowerASCII(address2_->name); + return AddressTypeFromText(name); + } + if (city_) { + base::string16 name = StringToLowerASCII(city_->name); + return AddressTypeFromText(name); + } + if (zip_) { + base::string16 name = StringToLowerASCII(zip_->name); + return AddressTypeFromText(name); + } + if (state_) { + base::string16 name = StringToLowerASCII(state_->name); + return AddressTypeFromText(name); + } + if (country_) { + base::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(ServerFieldTypeMap* map) const { + ServerFieldType address_company; + ServerFieldType address_line1; + ServerFieldType address_line2; + ServerFieldType address_city; + ServerFieldType address_state; + ServerFieldType address_zip; + ServerFieldType 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; + + base::string16 pattern = UTF8ToUTF16(autofill::kAddressLine1Re); + base::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 surplus lines, which we will promptly discard. + if (address_field->address2_ != NULL) { + pattern = UTF8ToUTF16(autofill::kAddressLinesExtraRe); + while (ParseField(scanner, pattern, NULL)) { + // Consumed a surplus line, try for another. + } + } + + 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; + + base::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 base::string16 &text) { + size_t same_as = text.find(UTF8ToUTF16(autofill::kAddressTypeSameAsRe)); + size_t use_shipping = text.find(UTF8ToUTF16(autofill::kAddressTypeUseMyRe)); + if (same_as != base::string16::npos || use_shipping != base::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 == base::string16::npos && ship == base::string16::npos) + return kGenericAddress; + + if (bill != base::string16::npos && ship == base::string16::npos) + return kBillingAddress; + + if (bill == base::string16::npos && ship != base::string16::npos) + return kShippingAddress; + + if (bill > ship) + return kBillingAddress; + + return kShippingAddress; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/address_field.h b/chromium/components/autofill/core/browser/address_field.h new file mode 100644 index 00000000000..a12d5981608 --- /dev/null +++ b/chromium/components/autofill/core/browser/address_field.h @@ -0,0 +1,88 @@ +// 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_CORE_BROWSER_ADDRESS_FIELD_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_ADDRESS_FIELD_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/form_field.h" + +namespace autofill { + +class AutofillField; +class AutofillScanner; + +class AddressField : public FormField { + public: + static FormField* Parse(AutofillScanner* scanner); + + protected: + // FormField: + virtual bool ClassifyField(ServerFieldTypeMap* 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 base::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); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_ADDRESS_FIELD_H_ diff --git a/chromium/components/autofill/core/browser/address_field_unittest.cc b/chromium/components/autofill/core/browser/address_field_unittest.cc new file mode 100644 index 00000000000..6949ef75230 --- /dev/null +++ b/chromium/components/autofill/core/browser/address_field_unittest.cc @@ -0,0 +1,298 @@ +// 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 "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/address_field.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_scanner.h" +#include "components/autofill/core/common/form_field_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +class AddressFieldTest : public testing::Test { + public: + AddressFieldTest() {} + + protected: + ScopedVector<const AutofillField> list_; + scoped_ptr<AddressField> field_; + ServerFieldTypeMap 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 = base::string16(); + field.name = base::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 = base::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")]); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/address_unittest.cc b/chromium/components/autofill/core/browser/address_unittest.cc new file mode 100644 index 00000000000..b5bcfafaf53 --- /dev/null +++ b/chromium/components/autofill/core/browser/address_unittest.cc @@ -0,0 +1,198 @@ +// 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 <string> + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/address.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +// Test that country data can be properly returned as either a country code or a +// localized country name. +TEST(AddressTest, GetCountry) { + Address address; + EXPECT_EQ(base::string16(), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + + // Make sure that nothing breaks when the country code is missing. + base::string16 country = + address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(base::string16(), country); + + address.SetInfo( + AutofillType(ADDRESS_HOME_COUNTRY), ASCIIToUTF16("US"), "en-US"); + country = address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(ASCIIToUTF16("United States"), country); + country = address.GetInfo( + AutofillType(HTML_TYPE_COUNTRY_NAME, HTML_MODE_NONE), "en-US"); + EXPECT_EQ(ASCIIToUTF16("United States"), country); + country = address.GetInfo( + AutofillType(HTML_TYPE_COUNTRY_CODE, HTML_MODE_NONE), "en-US"); + EXPECT_EQ(ASCIIToUTF16("US"), country); + + address.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("CA")); + country = address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(ASCIIToUTF16("Canada"), country); + country = address.GetInfo( + AutofillType(HTML_TYPE_COUNTRY_NAME, HTML_MODE_NONE), "en-US"); + EXPECT_EQ(ASCIIToUTF16("Canada"), country); + country = address.GetInfo( + AutofillType(HTML_TYPE_COUNTRY_CODE, HTML_MODE_NONE), "en-US"); + EXPECT_EQ(ASCIIToUTF16("CA"), country); +} + +// Test that we properly detect country codes appropriate for each country. +TEST(AddressTest, SetCountry) { + Address address; + EXPECT_EQ(base::string16(), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + + // Test basic conversion. + address.SetInfo( + AutofillType(ADDRESS_HOME_COUNTRY), ASCIIToUTF16("United States"), + "en-US"); + base::string16 country = + address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(ASCIIToUTF16("US"), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + EXPECT_EQ(ASCIIToUTF16("United States"), country); + + // Test basic synonym detection. + address.SetInfo( + AutofillType(ADDRESS_HOME_COUNTRY), ASCIIToUTF16("USA"), "en-US"); + country = address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(ASCIIToUTF16("US"), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + EXPECT_EQ(ASCIIToUTF16("United States"), country); + + // Test case-insensitivity. + address.SetInfo( + AutofillType(ADDRESS_HOME_COUNTRY), ASCIIToUTF16("canADA"), "en-US"); + country = address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(ASCIIToUTF16("CA"), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + EXPECT_EQ(ASCIIToUTF16("Canada"), country); + + // Test country code detection. + address.SetInfo( + AutofillType(ADDRESS_HOME_COUNTRY), ASCIIToUTF16("JP"), "en-US"); + country = address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(ASCIIToUTF16("JP"), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + EXPECT_EQ(ASCIIToUTF16("Japan"), country); + + // Test that we ignore unknown countries. + address.SetInfo( + AutofillType(ADDRESS_HOME_COUNTRY), ASCIIToUTF16("Unknown"), "en-US"); + country = address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(base::string16(), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + EXPECT_EQ(base::string16(), country); + + // Test setting the country based on an HTML field type. + AutofillType html_type_country_code = + AutofillType(HTML_TYPE_COUNTRY_CODE, HTML_MODE_NONE); + address.SetInfo(html_type_country_code, ASCIIToUTF16("US"), "en-US"); + country = address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(ASCIIToUTF16("US"), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + EXPECT_EQ(ASCIIToUTF16("United States"), country); + + // Test case-insensitivity when setting the country based on an HTML field + // type. + address.SetInfo(html_type_country_code, ASCIIToUTF16("cA"), "en-US"); + country = address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(ASCIIToUTF16("CA"), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + EXPECT_EQ(ASCIIToUTF16("Canada"), country); + + // Test setting the country based on invalid data with an HTML field type. + address.SetInfo(html_type_country_code, ASCIIToUTF16("unknown"), "en-US"); + country = address.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), "en-US"); + EXPECT_EQ(base::string16(), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + EXPECT_EQ(base::string16(), country); +} + +// Test that we properly match typed values to stored country data. +TEST(AddressTest, IsCountry) { + Address address; + address.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("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]); + ServerFieldTypeSet 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) { + ServerFieldTypeSet 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.SetRawInfo(ADDRESS_HOME_COUNTRY, base::string16()); + EXPECT_EQ(base::string16(), address.GetRawInfo(ADDRESS_HOME_COUNTRY)); + ServerFieldTypeSet matching_types; + address.GetMatchingTypes(ASCIIToUTF16("Garbage"), "US", &matching_types); + EXPECT_EQ(0U, matching_types.size()); +} + +// Verifies that Address::GetInfo() can correctly return a concatenated full +// street address. +TEST(AddressTest, GetStreetAddress) { + // Address has no address lines. + Address address; + EXPECT_TRUE(address.GetRawInfo(ADDRESS_HOME_LINE1).empty()); + EXPECT_TRUE(address.GetRawInfo(ADDRESS_HOME_LINE2).empty()); + + AutofillType type = AutofillType(HTML_TYPE_STREET_ADDRESS, HTML_MODE_NONE); + EXPECT_EQ(base::string16(), address.GetInfo(type, "en-US")); + + // Address has only line 1. + address.SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16("123 Example Ave.")); + EXPECT_FALSE(address.GetRawInfo(ADDRESS_HOME_LINE1).empty()); + EXPECT_TRUE(address.GetRawInfo(ADDRESS_HOME_LINE2).empty()); + + EXPECT_EQ(ASCIIToUTF16("123 Example Ave."), + address.GetInfo(type, "en-US")); + + // Address has lines 1 and 2. + address.SetRawInfo(ADDRESS_HOME_LINE2, ASCIIToUTF16("Apt. 42")); + EXPECT_FALSE(address.GetRawInfo(ADDRESS_HOME_LINE1).empty()); + EXPECT_FALSE(address.GetRawInfo(ADDRESS_HOME_LINE2).empty()); + + EXPECT_EQ(ASCIIToUTF16("123 Example Ave., Apt. 42"), + address.GetInfo(type, "en-US")); +} + +// Verifies that Address::SetInfo() rejects setting data for +// HTML_TYPE_STREET_ADDRESS, as there is no good general way to parse that data +// into the consituent address lines. +TEST(AddressTest, SetStreetAddress) { + // Address has no address lines. + Address address; + address.SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16("123 Example Ave.")); + address.SetRawInfo(ADDRESS_HOME_LINE2, ASCIIToUTF16("Apt. 42")); + EXPECT_FALSE(address.GetRawInfo(ADDRESS_HOME_LINE1).empty()); + EXPECT_FALSE(address.GetRawInfo(ADDRESS_HOME_LINE2).empty()); + + AutofillType type = AutofillType(HTML_TYPE_STREET_ADDRESS, HTML_MODE_NONE); + base::string16 street_address = ASCIIToUTF16("456 New St., Apt. 17"); + EXPECT_FALSE(address.SetInfo(type, street_address, "en-US")); + EXPECT_TRUE(address.GetRawInfo(ADDRESS_HOME_LINE1).empty()); + EXPECT_TRUE(address.GetRawInfo(ADDRESS_HOME_LINE2).empty()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/android/auxiliary_profile_loader_android.cc b/chromium/components/autofill/core/browser/android/auxiliary_profile_loader_android.cc new file mode 100644 index 00000000000..4361791f66c --- /dev/null +++ b/chromium/components/autofill/core/browser/android/auxiliary_profile_loader_android.cc @@ -0,0 +1,122 @@ +// 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/core/browser/android/auxiliary_profile_loader_android.h" + +#include <vector> + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "jni/PersonalAutofillPopulator_jni.h" + +#define JAVA_METHOD(__jmethod__) Java_PersonalAutofillPopulator_##__jmethod__( \ + env_, \ + populator_.obj()) + +namespace { + +base::string16 SafeJavaStringToUTF16( + const ScopedJavaLocalRef<jstring>& jstring) { + if (jstring.is_null()) + return base::string16(); + + return ConvertJavaStringToUTF16(jstring); +} + +void SafeJavaStringArrayToStringVector( + const ScopedJavaLocalRef<jobjectArray>& jarray, + JNIEnv* env, + std::vector<base::string16>* string_vector) { + if (!jarray.is_null()) { + base::android::AppendJavaStringArrayToStringVector(env, + jarray.obj(), + string_vector); + } +} + +} // namespace + +namespace autofill { + +bool RegisterAuxiliaryProfileLoader(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +AuxiliaryProfileLoaderAndroid::AuxiliaryProfileLoaderAndroid() {} + +AuxiliaryProfileLoaderAndroid::~AuxiliaryProfileLoaderAndroid() {} + +void AuxiliaryProfileLoaderAndroid::Init(JNIEnv* env, const jobject& context) { + env_ = env; + populator_ = Java_PersonalAutofillPopulator_create(env_, context); +} + +bool AuxiliaryProfileLoaderAndroid::GetHasPermissions() const { + return (bool)JAVA_METHOD(getHasPermissions); +} + +// Address info +base::string16 AuxiliaryProfileLoaderAndroid::GetStreet() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getStreet)); +} + +base::string16 AuxiliaryProfileLoaderAndroid::GetPostOfficeBox() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getPobox)); +} + +base::string16 AuxiliaryProfileLoaderAndroid::GetNeighborhood() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getNeighborhood)); +} + +base::string16 AuxiliaryProfileLoaderAndroid::GetRegion() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getRegion)); +} + +base::string16 AuxiliaryProfileLoaderAndroid::GetCity() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getCity)); +} + +base::string16 AuxiliaryProfileLoaderAndroid::GetPostalCode() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getPostalCode)); +} + +base::string16 AuxiliaryProfileLoaderAndroid::GetCountry() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getCountry)); +} + +// Name info +base::string16 AuxiliaryProfileLoaderAndroid::GetFirstName() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getFirstName)); +} + +base::string16 AuxiliaryProfileLoaderAndroid::GetMiddleName() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getMiddleName)); +} + +base::string16 AuxiliaryProfileLoaderAndroid::GetLastName() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getLastName)); +} + +base::string16 AuxiliaryProfileLoaderAndroid::GetSuffix() const { + return SafeJavaStringToUTF16(JAVA_METHOD(getSuffix)); +} + +// Email info +void AuxiliaryProfileLoaderAndroid::GetEmailAddresses( + std::vector<base::string16>* email_addresses) const { + SafeJavaStringArrayToStringVector(JAVA_METHOD(getEmailAddresses), + env_, + email_addresses); +} + +// Phone info +void AuxiliaryProfileLoaderAndroid::GetPhoneNumbers( + std::vector<base::string16>* phone_numbers) const { + SafeJavaStringArrayToStringVector(JAVA_METHOD(getPhoneNumbers), + env_, + phone_numbers); +} + +} // namespace diff --git a/chromium/components/autofill/core/browser/android/auxiliary_profile_loader_android.h b/chromium/components/autofill/core/browser/android/auxiliary_profile_loader_android.h new file mode 100644 index 00000000000..2185e83e377 --- /dev/null +++ b/chromium/components/autofill/core/browser/android/auxiliary_profile_loader_android.h @@ -0,0 +1,75 @@ +// 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_CORE_BROWSER_ANDROID_AUXILIARY_PROFILE_LOADER_ANDROID_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_AUXILIARY_PROFILE_LOADER_ANDROID_H_ + +#include <vector> + +#include "base/android/jni_android.h" +#include "base/strings/string16.h" + +namespace autofill { + +bool RegisterAuxiliaryProfileLoader(JNIEnv* env); + +// This class loads user's contact information from their device. +// The populated data corresponds to the user's 'Me' profile in their +// contact list. +class AuxiliaryProfileLoaderAndroid { + public: + AuxiliaryProfileLoaderAndroid(); + virtual ~AuxiliaryProfileLoaderAndroid(); + + // Init method should be called after object initialization. + // context parameter is an Android context. + void Init(JNIEnv* env, const jobject& context); + + // Returns true IFF the application has priviledges to access the user's + // contact information. + virtual bool GetHasPermissions() const; + // Returns address street. + virtual base::string16 GetStreet() const; + // Returns address post office box. + virtual base::string16 GetPostOfficeBox() const; + // Returns address neighborhood (e.g. Noe Valley, Nob Hill, Twin Peaks, ...). + virtual base::string16 GetNeighborhood() const; + // Returns address region such as state or province information + // (e.g. Ontario, California, Hubei). + virtual base::string16 GetRegion() const; + // Returns address city. + virtual base::string16 GetCity() const; + // Returns address postal code or zip code. + virtual base::string16 GetPostalCode() const; + // Returns address country. + virtual base::string16 GetCountry() const; + + // Returns contact's first name. + virtual base::string16 GetFirstName() const; + // Returns contact's middle name. + virtual base::string16 GetMiddleName() const; + // Returns contact's last name. + virtual base::string16 GetLastName() const; + // Returns contact's suffix (e.g. Ph.D, M.D., ...). + virtual base::string16 GetSuffix() const; + + // Populates string vector parameter with contact's email addresses. + virtual void GetEmailAddresses( + std::vector<base::string16>* email_addresses) const; + + // Populates string vector parameter with contact's phones numbers. + virtual void GetPhoneNumbers( + std::vector<base::string16>* phone_numbers) const; + + private: + JNIEnv* env_; + // The reference to java |PersonalAutofillPopulator| which + // actually extracts users contact information from the physical device + base::android::ScopedJavaLocalRef<jobject> populator_; + DISALLOW_COPY_AND_ASSIGN(AuxiliaryProfileLoaderAndroid); +}; + +} // namespace + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_AUXILIARY_PROFILE_LOADER_ANDROID_H_ diff --git a/chromium/components/autofill/core/browser/android/auxiliary_profile_unittest_android.cc b/chromium/components/autofill/core/browser/android/auxiliary_profile_unittest_android.cc new file mode 100644 index 00000000000..b8f8dce1306 --- /dev/null +++ b/chromium/components/autofill/core/browser/android/auxiliary_profile_unittest_android.cc @@ -0,0 +1,163 @@ +// 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 "base/memory/scoped_vector.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/android/auxiliary_profile_loader_android.h" +#include "components/autofill/core/browser/android/auxiliary_profiles_android.h" +#include "components/autofill/core/browser/android/test_auxiliary_profile_loader_android.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +class AuxiliaryProfileAndroidTest : public testing::Test { + public: + AuxiliaryProfileAndroidTest() {} + + AutofillProfile* GetAndLoadProfile() { + autofill::AuxiliaryProfilesAndroid impl(profile_loader_, "en-US"); + profile_ = impl.LoadContactsProfile(); + return profile_.get(); + } + + TestAuxiliaryProfileLoader& profile_loader() { + return profile_loader_; + } + + private: + TestAuxiliaryProfileLoader profile_loader_; + scoped_ptr<AutofillProfile> profile_; +}; + +TEST_F(AuxiliaryProfileAndroidTest, SetNameInfo) { + base::string16 first_name = ASCIIToUTF16("John"); + base::string16 middle_name = ASCIIToUTF16("H."); + base::string16 last_name = ASCIIToUTF16("Waston"); + + profile_loader().SetFirstName(first_name); + profile_loader().SetMiddleName(middle_name); + profile_loader().SetLastName(last_name); + + AutofillProfile* profile = GetAndLoadProfile(); + + EXPECT_EQ(profile->GetRawInfo(NAME_FIRST), first_name); + EXPECT_EQ(profile->GetRawInfo(NAME_MIDDLE), middle_name); + EXPECT_EQ(profile->GetRawInfo(NAME_LAST), last_name); +} + +TEST_F(AuxiliaryProfileAndroidTest, SetNameInfoEmpty) { + AutofillProfile* profile = GetAndLoadProfile(); + + EXPECT_EQ(profile->GetRawInfo(NAME_FIRST), base::string16()); + EXPECT_EQ(profile->GetRawInfo(NAME_MIDDLE), base::string16()); + EXPECT_EQ(profile->GetRawInfo(NAME_LAST), base::string16()); +} + +TEST_F(AuxiliaryProfileAndroidTest, SetEmailInfo) { + std::vector<base::string16> email_addresses; + email_addresses.push_back(ASCIIToUTF16("sherlock@holmes.com")); + email_addresses.push_back(ASCIIToUTF16("watson@holmes.com")); + profile_loader().SetEmailAddresses(email_addresses); + + AutofillProfile* profile = GetAndLoadProfile(); + std::vector<base::string16> loaded_email_addresses; + profile->GetRawMultiInfo(EMAIL_ADDRESS, &loaded_email_addresses); + EXPECT_EQ(loaded_email_addresses, email_addresses); +} + +TEST_F(AuxiliaryProfileAndroidTest, SetEmailInfoEmpty) { + std::vector<base::string16> expected_email_addresses; + expected_email_addresses.push_back(base::string16()); + std::vector<base::string16> loaded_email_addresses; + AutofillProfile* profile = GetAndLoadProfile(); + profile->GetRawMultiInfo(EMAIL_ADDRESS, &loaded_email_addresses); + EXPECT_EQ(loaded_email_addresses, expected_email_addresses); +} + +TEST_F(AuxiliaryProfileAndroidTest, SetPhoneInfo) { + std::vector<base::string16> phone_numbers; + phone_numbers.push_back(ASCIIToUTF16("6502530000")); + profile_loader().SetPhoneNumbers(phone_numbers); + + std::vector<base::string16> loaded_phone_numbers; + AutofillProfile* profile = GetAndLoadProfile(); + profile->GetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, &loaded_phone_numbers); + EXPECT_EQ(loaded_phone_numbers, phone_numbers); +} + +TEST_F(AuxiliaryProfileAndroidTest, SetPhoneInfoEmpty) { + std::vector<base::string16> expected_phone_numbers; + expected_phone_numbers.push_back(base::string16()); + + std::vector<base::string16> loaded_phone_numbers; + AutofillProfile* profile = GetAndLoadProfile(); + profile->GetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, &loaded_phone_numbers); + EXPECT_EQ(loaded_phone_numbers, expected_phone_numbers); +} + +// +// Android user's profile contact does not parse its address +// into constituent parts. Instead we just Get a long string blob. +// Disable address population tests until we implement a way to parse the +// data. +// + +#if 0 +TEST_F(AuxiliaryProfileAndroidTest, SetAddressInfo) { + base::string16 street = ASCIIToUTF16("221 B Baker Street"); + base::string16 city = ASCIIToUTF16("London"); + base::string16 postal_code = ASCIIToUTF16("123456"); + base::string16 region = ASCIIToUTF16("Georgian Terrace"); + base::string16 country = ASCIIToUTF16("England"); + + profile_loader().SetStreet(street); + profile_loader().SetCity(city); + profile_loader().SetPostalCode(postal_code); + profile_loader().SetRegion(region); + profile_loader().SetCountry(country); + + AutofillProfile* profile = GetAndLoadProfile(); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_LINE1), street); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_CITY), city); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_ZIP), postal_code); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_STATE), region); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_COUNTRY), country); +} + +base::string16 post_office_box= ASCIIToUTF16("222"); +base::string16 neighborhood = ASCIIToUTF16("Doyle"); +TEST_F(AuxiliaryProfileAndroidTest, SetAddressInfoCompoundFields1) { + profile_loader().SetPostOfficeBox(post_office_box); + profile_loader().SetNeighborhood(neighborhood); + base::string16 expectedLine2= ASCIIToUTF16("222, Doyle"); + AutofillProfile* profile = GetAndLoadProfile(); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_LINE2), expectedLine2); +} + +TEST_F(AuxiliaryProfileAndroidTest, SetAddressInfoCompoundFields2) { + profile_loader().SetPostOfficeBox(post_office_box); + AutofillProfile* profile = GetAndLoadProfile(); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_LINE2), post_office_box); +} + +TEST_F(AuxiliaryProfileAndroidTest, SetAddressInfoCompoundFields3) { + profile_loader().SetNeighborhood(neighborhood); + AutofillProfile* profile = GetAndLoadProfile(); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_LINE2), neighborhood); +} + +TEST_F(AuxiliaryProfileAndroidTest, SetAddressInfoEmpty) { + AutofillProfile* profile = GetAndLoadProfile(); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_LINE1), base::string16()); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_LINE2), base::string16()); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_CITY), base::string16()); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_ZIP), base::string16()); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_STATE), base::string16()); + EXPECT_EQ(profile->GetRawInfo(ADDRESS_HOME_COUNTRY), base::string16()); +} +#endif + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/android/auxiliary_profiles_android.cc b/chromium/components/autofill/core/browser/android/auxiliary_profiles_android.cc new file mode 100644 index 00000000000..6d0b65b74d8 --- /dev/null +++ b/chromium/components/autofill/core/browser/android/auxiliary_profiles_android.cc @@ -0,0 +1,122 @@ +// 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. + +// Populates default autofill profile from user's own Android contact. +#include "components/autofill/core/browser/android/auxiliary_profiles_android.h" + +#include <vector> + +#include "base/guid.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/android/auxiliary_profile_loader_android.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/phone_number.h" + +// Generates the autofill profile by accessing the Android +// ContactsContract.Profile API through PersonalAutofillPopulator via JNI. +// The generated profile corresponds to the user's "ME" contact in the +// "People" app. The caller passes a vector of profiles into the constructor +// then initiates a fetch using |GetContactsProfile()| method. This clears +// any existing addresses. + +// Randomly generated guid. The Autofillprofile class requires a consistent +// unique guid or else things break. +const char kAndroidMeContactA[] = "9A9E1C06-7A3B-48FA-AA4F-135CA6FC25D9"; + +// The origin string for all profiles loaded from the Android contacts list. +const char kAndroidContactsOrigin[] = "Android Contacts"; + +namespace { + +// Takes misc. address information strings from Android API and collapses +// into single string for "address line 2" +base::string16 CollapseAddress(const base::string16& post_office_box, + const base::string16& neighborhood) { + std::vector<base::string16> accumulator; + if (!post_office_box.empty()) + accumulator.push_back(post_office_box); + if (!neighborhood.empty()) + accumulator.push_back(neighborhood); + + return JoinString(accumulator, ASCIIToUTF16(", ")); +} + +} // namespace + +namespace autofill { + +AuxiliaryProfilesAndroid::AuxiliaryProfilesAndroid( + const AuxiliaryProfileLoaderAndroid& profileLoader, + const std::string& app_locale) + : profile_loader_(profileLoader), + app_locale_(app_locale) {} + +AuxiliaryProfilesAndroid::~AuxiliaryProfilesAndroid() { +} + +scoped_ptr<AutofillProfile> AuxiliaryProfilesAndroid::LoadContactsProfile() { + scoped_ptr<AutofillProfile> profile( + new AutofillProfile(kAndroidMeContactA, kAndroidContactsOrigin)); + LoadName(profile.get()); + LoadEmailAddress(profile.get()); + LoadPhoneNumbers(profile.get()); + + // Android user's profile contact does not parse its address + // into constituent parts. Instead we just get a long string blob. + // Disable address population until we implement a way to parse the + // data. + // http://crbug.com/178838 + // LoadAddress(profile.get()); + + return profile.Pass(); +} + +void AuxiliaryProfilesAndroid::LoadAddress(AutofillProfile* profile) { + base::string16 street = profile_loader_.GetStreet(); + base::string16 post_office_box = profile_loader_.GetPostOfficeBox(); + base::string16 neighborhood = profile_loader_.GetNeighborhood(); + base::string16 city = profile_loader_.GetCity(); + base::string16 postal_code = profile_loader_.GetPostalCode(); + base::string16 region = profile_loader_.GetRegion(); + base::string16 country = profile_loader_.GetCountry(); + + base::string16 street2 = CollapseAddress(post_office_box, neighborhood); + + profile->SetRawInfo(ADDRESS_HOME_LINE1, street); + profile->SetRawInfo(ADDRESS_HOME_LINE2, street2); + profile->SetRawInfo(ADDRESS_HOME_CITY, city); + profile->SetRawInfo(ADDRESS_HOME_STATE, region); + profile->SetRawInfo(ADDRESS_HOME_ZIP, postal_code); + profile->SetInfo(AutofillType(ADDRESS_HOME_COUNTRY), country, app_locale_); +} + +void AuxiliaryProfilesAndroid::LoadName(AutofillProfile* profile) { + base::string16 first_name = profile_loader_.GetFirstName(); + base::string16 middle_name = profile_loader_.GetMiddleName(); + base::string16 last_name = profile_loader_.GetLastName(); + + profile->SetRawInfo(NAME_FIRST, first_name); + profile->SetRawInfo(NAME_MIDDLE, middle_name); + profile->SetRawInfo(NAME_LAST, last_name); +} + +void AuxiliaryProfilesAndroid::LoadEmailAddress(AutofillProfile* profile) { + std::vector<base::string16> emails; + profile_loader_.GetEmailAddresses(&emails); + profile->SetRawMultiInfo(EMAIL_ADDRESS, emails); +} + +void AuxiliaryProfilesAndroid::LoadPhoneNumbers(AutofillProfile* profile) { + std::vector<base::string16> phone_numbers; + profile_loader_.GetPhoneNumbers(&phone_numbers); + profile->SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, phone_numbers); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/android/auxiliary_profiles_android.h b/chromium/components/autofill/core/browser/android/auxiliary_profiles_android.h new file mode 100644 index 00000000000..5f81fd1f6df --- /dev/null +++ b/chromium/components/autofill/core/browser/android/auxiliary_profiles_android.h @@ -0,0 +1,54 @@ +// 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_CORE_BROWSER_ANDROID_AUXILIARY_PROFILES_ANDROID_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_AUXILIARY_PROFILES_ANDROID_H_ + +#include <jni.h> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/android/auxiliary_profile_loader_android.h" + +namespace autofill { + +class AutofillProfile; +class AuxiliaryProfileLoaderAndroid; + + // This class is used to populate an AutofillProfile vector with + // a 'default' auxiliary profile. It depends on an + // AuxiliaryProfileLoaderAndroid object to provide contact information + // that is re-organized into an Autofill profile. +class AuxiliaryProfilesAndroid { + public: + // Takes in an AuxiliaryProfileLoader object which provides contact + // information methods. + AuxiliaryProfilesAndroid( + const autofill::AuxiliaryProfileLoaderAndroid& profile_loader, + const std::string& app_locale); + ~AuxiliaryProfilesAndroid(); + + // Returns autofill profile constructed from profile_loader_. + scoped_ptr<AutofillProfile> LoadContactsProfile(); + + private: + // Inserts contact's address data into profile. + void LoadAddress(AutofillProfile* profile); + // Inserts contact's name data into profile. + void LoadName(AutofillProfile* profile); + // Inserts contact's email address data into profile. + void LoadEmailAddress(AutofillProfile* profile); + // Inserts contact's phone number data into profile. + void LoadPhoneNumbers(AutofillProfile* profile); + + const AuxiliaryProfileLoaderAndroid& profile_loader_; + std::string app_locale_; + + DISALLOW_COPY_AND_ASSIGN(AuxiliaryProfilesAndroid); +}; + +} // namespace + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_AUXILIARY_PROFILES_ANDROID_H_ diff --git a/chromium/components/autofill/core/browser/android/component_jni_registrar.cc b/chromium/components/autofill/core/browser/android/component_jni_registrar.cc new file mode 100644 index 00000000000..f3aac1f02ad --- /dev/null +++ b/chromium/components/autofill/core/browser/android/component_jni_registrar.cc @@ -0,0 +1,23 @@ +// 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/core/browser/android/component_jni_registrar.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "components/autofill/core/browser/android/auxiliary_profile_loader_android.h" + +namespace autofill { + +static base::android::RegistrationMethod kComponentRegisteredMethods[] = { + { "RegisterAuxiliaryProfileLoader", + autofill::RegisterAuxiliaryProfileLoader }, +}; + +bool RegisterAutofillAndroidJni(JNIEnv* env) { + return RegisterNativeMethods(env, + kComponentRegisteredMethods, arraysize(kComponentRegisteredMethods)); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/android/component_jni_registrar.h b/chromium/components/autofill/core/browser/android/component_jni_registrar.h new file mode 100644 index 00000000000..6cc0900cfdd --- /dev/null +++ b/chromium/components/autofill/core/browser/android/component_jni_registrar.h @@ -0,0 +1,18 @@ +// 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_CORE_BROWSER_ANDROID_COMPONENT_JNI_REGISTRAR_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_COMPONENT_JNI_REGISTRAR_H_ + +#include <jni.h> + +namespace autofill { + +// Register all JNI bindings necessary for the autofill +// component. +bool RegisterAutofillAndroidJni(JNIEnv* env); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_COMPONENT_JNI_REGISTRAR_H_ diff --git a/chromium/components/autofill/core/browser/android/java/src/org/chromium/components/browser/autofill/PersonalAutofillPopulator.java b/chromium/components/autofill/core/browser/android/java/src/org/chromium/components/browser/autofill/PersonalAutofillPopulator.java new file mode 100644 index 00000000000..b00ed61df29 --- /dev/null +++ b/chromium/components/autofill/core/browser/android/java/src/org/chromium/components/browser/autofill/PersonalAutofillPopulator.java @@ -0,0 +1,319 @@ +// 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. + +// Populates data fields from Android contacts profile API (i.e. "me" contact). + +package org.chromium.components.browser.autofill; + +import android.app.Activity; +import android.content.ContentProviderOperation; +import android.content.ContentResolver; +import android.content.Context; +import android.content.OperationApplicationException; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.net.Uri; +import android.os.Bundle; +import android.os.RemoteException; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Profile; +import android.provider.ContactsContract; +import android.util.Log; +import android.view.View.OnClickListener; +import android.view.View; +import android.widget.Button; +import android.widget.Toast; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; + +import java.util.ArrayList; + +/** + * Loads user profile information stored under the "Me" contact. + * Requires permissions: READ_CONTACTS and READ_PROFILE. + */ +@JNINamespace("autofill") +public class PersonalAutofillPopulator { + /** + * SQL query definitions for obtaining specific profile information. + */ + private abstract static class ProfileQuery { + Uri profileDataUri = Uri.withAppendedPath( + ContactsContract.Profile.CONTENT_URI, + ContactsContract.Contacts.Data.CONTENT_DIRECTORY + ); + public abstract String[] projection(); + public abstract String mimeType(); + } + + private static class EmailProfileQuery extends ProfileQuery { + private static final int EMAIL_ADDRESS = 0; + + @Override + public String[] projection() { + return new String[] { + ContactsContract.CommonDataKinds.Email.ADDRESS, + }; + } + + @Override + public String mimeType() { + return ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE; + } + } + + private static class PhoneProfileQuery extends ProfileQuery { + private static final int NUMBER = 0; + + @Override + public String[] projection() { + return new String[] { + ContactsContract.CommonDataKinds.Phone.NUMBER, + }; + } + + @Override + public String mimeType() { + return ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE; + } + } + + private static class AddressProfileQuery extends ProfileQuery { + private static final int STREET = 0; + private static final int POBOX = 1; + private static final int NEIGHBORHOOD = 2; + private static final int CITY = 3; + private static final int REGION = 4; + private static final int POSTALCODE = 5; + private static final int COUNTRY = 6; + + @Override + public String[] projection() { + return new String[] { + ContactsContract.CommonDataKinds.StructuredPostal.STREET, + ContactsContract.CommonDataKinds.StructuredPostal.POBOX, + ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD, + ContactsContract.CommonDataKinds.StructuredPostal.CITY, + ContactsContract.CommonDataKinds.StructuredPostal.REGION, + ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE, + ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY, + }; + } + + @Override + public String mimeType() { + return ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE; + } + } + + private static class NameProfileQuery extends ProfileQuery { + private static final int GIVEN_NAME = 0; + private static final int MIDDLE_NAME = 1; + private static final int FAMILY_NAME = 2; + private static final int SUFFIX = 3; + + @Override + public String[] projection() { + return new String[] { + ContactsContract.CommonDataKinds.StructuredName.GIVEN_NAME, + ContactsContract.CommonDataKinds.StructuredName.MIDDLE_NAME, + ContactsContract.CommonDataKinds.StructuredName.FAMILY_NAME, + ContactsContract.CommonDataKinds.StructuredName.SUFFIX + }; + } + + @Override + public String mimeType() { + return ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE; + } + } + + /** + * Takes a query object, transforms into actual query and returns cursor. + * Primary contact values will be first. + */ + private Cursor cursorFromProfileQuery(ProfileQuery query, ContentResolver contentResolver) { + String sortDescriptor = ContactsContract.Contacts.Data.IS_PRIMARY + " DESC"; + return contentResolver.query( + query.profileDataUri, + query.projection(), + ContactsContract.Contacts.Data.MIMETYPE + " = ?", + new String[]{query.mimeType()}, + sortDescriptor + ); + } + // Extracted data variables. + private String[] mEmailAddresses; + private String mGivenName; + private String mMiddleName; + private String mFamilyName; + private String mSuffix; + private String mPobox; + private String mStreet; + private String mNeighborhood; + private String mCity; + private String mRegion; + private String mCountry; + private String mPostalCode; + private String[] mPhoneNumbers; + private boolean mHasPermissions; + + /** + * Constructor + * @param context a valid android context reference + */ + PersonalAutofillPopulator(Context context) { + mHasPermissions = hasPermissions(context); + if (mHasPermissions) { + ContentResolver contentResolver = context.getContentResolver(); + populateName(contentResolver); + populateEmail(contentResolver); + populateAddress(contentResolver); + populatePhone(contentResolver); + } + } + + // Check if the user has granted permissions. + private boolean hasPermissions(Context context) { + String [] permissions = { + "android.permission.READ_CONTACTS", + "android.permission.READ_PROFILE" + }; + for (String permission : permissions) { + int res = context.checkCallingOrSelfPermission(permission); + if (res != PackageManager.PERMISSION_GRANTED) return false; + } + return true; + } + + // Populating data fields. + private void populateName(ContentResolver contentResolver) { + NameProfileQuery nameProfileQuery = new NameProfileQuery(); + Cursor nameCursor = cursorFromProfileQuery(nameProfileQuery, contentResolver); + if (nameCursor.moveToNext()) { + mGivenName = nameCursor.getString(nameProfileQuery.GIVEN_NAME); + mMiddleName = nameCursor.getString(nameProfileQuery.MIDDLE_NAME); + mFamilyName = nameCursor.getString(nameProfileQuery.FAMILY_NAME); + mSuffix = nameCursor.getString(nameProfileQuery.SUFFIX); + } + nameCursor.close(); + } + + private void populateEmail(ContentResolver contentResolver) { + EmailProfileQuery emailProfileQuery = new EmailProfileQuery(); + Cursor emailCursor = cursorFromProfileQuery(emailProfileQuery, contentResolver); + mEmailAddresses = new String[emailCursor.getCount()]; + for (int i = 0; emailCursor.moveToNext(); i++) { + mEmailAddresses[i] = emailCursor.getString(emailProfileQuery.EMAIL_ADDRESS); + } + emailCursor.close(); + } + + private void populateAddress(ContentResolver contentResolver) { + AddressProfileQuery addressProfileQuery = new AddressProfileQuery(); + Cursor addressCursor = cursorFromProfileQuery(addressProfileQuery, contentResolver); + if(addressCursor.moveToNext()) { + mPobox = addressCursor.getString(addressProfileQuery.POBOX); + mStreet = addressCursor.getString(addressProfileQuery.STREET); + mNeighborhood = addressCursor.getString(addressProfileQuery.NEIGHBORHOOD); + mCity = addressCursor.getString(addressProfileQuery.CITY); + mRegion = addressCursor.getString(addressProfileQuery.REGION); + mPostalCode = addressCursor.getString(addressProfileQuery.POSTALCODE); + mCountry = addressCursor.getString(addressProfileQuery.COUNTRY); + } + addressCursor.close(); + } + + private void populatePhone(ContentResolver contentResolver) { + PhoneProfileQuery phoneProfileQuery = new PhoneProfileQuery(); + Cursor phoneCursor = cursorFromProfileQuery(phoneProfileQuery, contentResolver); + mPhoneNumbers = new String[phoneCursor.getCount()]; + for (int i = 0; phoneCursor.moveToNext(); i++) { + mPhoneNumbers[i] = phoneCursor.getString(phoneProfileQuery.NUMBER); + } + phoneCursor.close(); + } + + /** + * Static factory method for instance creation. + * @param context valid Android context. + * @return PersonalAutofillPopulator new instance of PersonalAutofillPopulator. + */ + @CalledByNative + static PersonalAutofillPopulator create(Context context) { + return new PersonalAutofillPopulator(context); + } + + @CalledByNative + private String getFirstName() { + return mGivenName; + } + + @CalledByNative + private String getLastName() { + return mFamilyName; + } + + @CalledByNative + private String getMiddleName() { + return mMiddleName; + } + + @CalledByNative + private String getSuffix() { + return mSuffix; + } + + @CalledByNative + private String[] getEmailAddresses() { + return mEmailAddresses; + } + + @CalledByNative + private String getStreet() { + return mStreet; + } + + @CalledByNative + private String getPobox() { + return mPobox; + } + + @CalledByNative + private String getNeighborhood() { + return mNeighborhood; + } + + @CalledByNative + private String getCity() { + return mCity; + } + + @CalledByNative + private String getRegion() { + return mRegion; + } + + @CalledByNative + private String getPostalCode() { + return mPostalCode; + } + + @CalledByNative + private String getCountry() { + return mCountry; + } + + @CalledByNative + private String[] getPhoneNumbers() { + return mPhoneNumbers; + } + + @CalledByNative + private boolean getHasPermissions() { + return mHasPermissions; + } +} diff --git a/chromium/components/autofill/core/browser/android/personal_data_manager_android.cc b/chromium/components/autofill/core/browser/android/personal_data_manager_android.cc new file mode 100644 index 00000000000..9a6e1c647a3 --- /dev/null +++ b/chromium/components/autofill/core/browser/android/personal_data_manager_android.cc @@ -0,0 +1,25 @@ +// 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. + +// Populates default autofill profile from user's own Android contact. + +#include "components/autofill/core/browser/android/auxiliary_profile_loader_android.h" +#include "components/autofill/core/browser/android/auxiliary_profiles_android.h" +#include "components/autofill/core/browser/personal_data_manager.h" + +namespace autofill { + +void PersonalDataManager::LoadAuxiliaryProfiles() { + auxiliary_profiles_.clear(); + autofill::AuxiliaryProfileLoaderAndroid profile_loader; + profile_loader.Init( + base::android::AttachCurrentThread(), + base::android::GetApplicationContext()); + if (profile_loader.GetHasPermissions()) { + autofill::AuxiliaryProfilesAndroid impl(profile_loader, app_locale_); + auxiliary_profiles_.push_back(impl.LoadContactsProfile().release()); + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/android/test_auxiliary_profile_loader_android.cc b/chromium/components/autofill/core/browser/android/test_auxiliary_profile_loader_android.cc new file mode 100644 index 00000000000..ced72c8a603 --- /dev/null +++ b/chromium/components/autofill/core/browser/android/test_auxiliary_profile_loader_android.cc @@ -0,0 +1,132 @@ +// 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/core/browser/android/test_auxiliary_profile_loader_android.h" + +namespace autofill { + +TestAuxiliaryProfileLoader::TestAuxiliaryProfileLoader() { +} + +TestAuxiliaryProfileLoader::~TestAuxiliaryProfileLoader() { +} + +bool TestAuxiliaryProfileLoader::GetHasPermissions() const { + return true; +} + +base::string16 TestAuxiliaryProfileLoader::GetFirstName() const { + return first_name_; +} + +base::string16 TestAuxiliaryProfileLoader::GetMiddleName() const { + return middle_name_; +} + +base::string16 TestAuxiliaryProfileLoader::GetLastName() const { + return last_name_; +} + +base::string16 TestAuxiliaryProfileLoader::GetSuffix() const { + return suffix_; +} + +base::string16 TestAuxiliaryProfileLoader::GetStreet() const { + return street_; +} + +base::string16 TestAuxiliaryProfileLoader::GetPostOfficeBox() const { + return post_office_box_; +} + +base::string16 TestAuxiliaryProfileLoader::GetCity() const { + return city_; +} + +base::string16 TestAuxiliaryProfileLoader::GetNeighborhood() const { + return neighborhood_; +} + +base::string16 TestAuxiliaryProfileLoader::GetRegion() const { + return region_; +} + +base::string16 TestAuxiliaryProfileLoader::GetPostalCode() const { + return postal_code_; +} + +base::string16 TestAuxiliaryProfileLoader::GetCountry() const { + return country_; +} + +void TestAuxiliaryProfileLoader::GetEmailAddresses( + std::vector<base::string16>* email_addresses) const { + *email_addresses = email_addresses_; +} + +void TestAuxiliaryProfileLoader::GetPhoneNumbers( + std::vector<base::string16>* phone_numbers) const { + *phone_numbers = phone_numbers_; +} + +void TestAuxiliaryProfileLoader::SetFirstName( + const base::string16& first_name) { + first_name_ = first_name; +} + +void TestAuxiliaryProfileLoader::SetMiddleName( + const base::string16& middle_name) { + middle_name_ = middle_name; +} + +void TestAuxiliaryProfileLoader::SetLastName(const base::string16& last_name) { + last_name_ = last_name; +} + +void TestAuxiliaryProfileLoader::SetSuffix(const base::string16& suffix) { + suffix_ = suffix; +} + +void TestAuxiliaryProfileLoader::SetStreet(const base::string16& street) { + street_ = street; +} + +void TestAuxiliaryProfileLoader::SetPostOfficeBox( + const base::string16& post_office_box) { + post_office_box_ = post_office_box; +} + +void TestAuxiliaryProfileLoader::SetNeighborhood( + const base::string16& neighborhood) { + neighborhood_ = neighborhood; +} + +void TestAuxiliaryProfileLoader::SetRegion(const base::string16& region) { + region_ = region; +} + +void TestAuxiliaryProfileLoader::SetCity(const base::string16& city) { + city_ = city; +} + +void TestAuxiliaryProfileLoader::SetPostalCode( + const base::string16& postal_code) { + postal_code_ = postal_code; +} + +void TestAuxiliaryProfileLoader::SetCountry(const base::string16& country) { + country_ = country; +} + +void TestAuxiliaryProfileLoader::SetEmailAddresses( + const std::vector<base::string16>& addresses) { + email_addresses_ = addresses; +} + +void TestAuxiliaryProfileLoader::SetPhoneNumbers( + const std::vector<base::string16>& phone_numbers) { + phone_numbers_ = phone_numbers; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/android/test_auxiliary_profile_loader_android.h b/chromium/components/autofill/core/browser/android/test_auxiliary_profile_loader_android.h new file mode 100644 index 00000000000..34d424c9adf --- /dev/null +++ b/chromium/components/autofill/core/browser/android/test_auxiliary_profile_loader_android.h @@ -0,0 +1,74 @@ +// 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_CORE_BROWSER_ANDROID_TEST_AUXILIARY_PROFILE_LOADER_ANDROID_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_TEST_AUXILIARY_PROFILE_LOADER_ANDROID_H_ + +#include "base/compiler_specific.h" +#include "components/autofill/core/browser/android/auxiliary_profile_loader_android.h" + +namespace autofill { + +class TestAuxiliaryProfileLoader + : public autofill::AuxiliaryProfileLoaderAndroid { + // Mock object for unit testing |AuxiliaryProfilesAndroid| + public: + TestAuxiliaryProfileLoader(); + virtual ~TestAuxiliaryProfileLoader(); + + virtual bool GetHasPermissions() const OVERRIDE; + + virtual base::string16 GetFirstName() const OVERRIDE; + virtual base::string16 GetMiddleName() const OVERRIDE; + virtual base::string16 GetLastName() const OVERRIDE; + virtual base::string16 GetSuffix() const OVERRIDE; + + virtual base::string16 GetStreet() const OVERRIDE; + virtual base::string16 GetCity() const OVERRIDE; + virtual base::string16 GetNeighborhood() const OVERRIDE; + virtual base::string16 GetPostalCode() const OVERRIDE; + virtual base::string16 GetRegion() const OVERRIDE; + virtual base::string16 GetPostOfficeBox() const OVERRIDE; + virtual base::string16 GetCountry() const OVERRIDE; + + virtual void GetEmailAddresses( + std::vector<base::string16>* email_addresses) const OVERRIDE; + virtual void GetPhoneNumbers( + std::vector<base::string16>* phone_numbers) const OVERRIDE; + + void SetFirstName(const base::string16& first_name); + void SetMiddleName(const base::string16& middle_name); + void SetLastName(const base::string16& last_name); + void SetSuffix(const base::string16& suffix); + + void SetStreet(const base::string16& street); + void SetPostOfficeBox(const base::string16& post_office_box); + void SetNeighborhood(const base::string16& neighborhood); + void SetRegion(const base::string16& region); + void SetCity(const base::string16& city); + void SetPostalCode(const base::string16& postal_code); + void SetCountry(const base::string16& country); + + void SetEmailAddresses(const std::vector<base::string16>& email_addresses); + void SetPhoneNumbers(const std::vector<base::string16>& phone_numbers); + + private: + base::string16 street_; + base::string16 post_office_box_; + base::string16 neighborhood_; + base::string16 region_; + base::string16 city_; + base::string16 postal_code_; + base::string16 country_; + base::string16 first_name_; + base::string16 middle_name_; + base::string16 last_name_; + base::string16 suffix_; + std::vector<base::string16> email_addresses_; + std::vector<base::string16> phone_numbers_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_ANDROID_TEST_AUXILIARY_PROFILE_LOADER_ANDROID_H_ diff --git a/chromium/components/autofill/core/browser/autocheckout_bubble_state.h b/chromium/components/autofill/core/browser/autocheckout_bubble_state.h new file mode 100644 index 00000000000..b015cb01d30 --- /dev/null +++ b/chromium/components/autofill/core/browser/autocheckout_bubble_state.h @@ -0,0 +1,22 @@ +// 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_CORE_BROWSER_AUTOCHECKOUT_BUBBLE_STATE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOCHECKOUT_BUBBLE_STATE_H_ + +namespace autofill { + +// Specifies the state of Autocheckout bubble. +enum AutocheckoutBubbleState { + // User clicked on continue. + AUTOCHECKOUT_BUBBLE_ACCEPTED = 0, + // User clicked on "No Thanks". + AUTOCHECKOUT_BUBBLE_CANCELED = 1, + // Bubble is ignored. + AUTOCHECKOUT_BUBBLE_IGNORED = 2, +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOCHECKOUT_BUBBLE_STATE_H_ diff --git a/chromium/components/autofill/core/browser/autocomplete_history_manager.cc b/chromium/components/autofill/core/browser/autocomplete_history_manager.cc new file mode 100644 index 00000000000..171c24596ed --- /dev/null +++ b/chromium/components/autofill/core/browser/autocomplete_history_manager.cc @@ -0,0 +1,205 @@ +// 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/core/browser/autocomplete_history_manager.h" + +#include <vector> + +#include "base/prefs/pref_service.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_driver.h" +#include "components/autofill/core/browser/autofill_external_delegate.h" +#include "components/autofill/core/browser/autofill_manager_delegate.h" +#include "components/autofill/core/browser/validation.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/common/autofill_pref_names.h" +#include "components/autofill/core/common/form_data.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" + +using content::BrowserContext; +using content::WebContents; + +namespace autofill { +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; + +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"; +} + +} // namespace + +AutocompleteHistoryManager::AutocompleteHistoryManager( + AutofillDriver* driver, + AutofillManagerDelegate* manager_delegate) + : browser_context_(driver->GetWebContents()->GetBrowserContext()), + driver_(driver), + autofill_data_( + AutofillWebDataService::FromBrowserContext(browser_context_)), + pending_query_handle_(0), + query_id_(0), + external_delegate_(NULL), + manager_delegate_(manager_delegate), + send_ipc_(true) { + DCHECK(manager_delegate_); +} + +AutocompleteHistoryManager::~AutocompleteHistoryManager() { + CancelPendingQuery(); +} + +void AutocompleteHistoryManager::OnWebDataServiceRequestDone( + WebDataServiceBase::Handle h, + const WDTypedResult* result) { + DCHECK(pending_query_handle_); + pending_query_handle_ = 0; + + if (!manager_delegate_->IsAutocompleteEnabled()) { + 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<base::string16> >* autofill_result = + static_cast<const WDResult<std::vector<base::string16> >*>(result); + std::vector<base::string16> suggestions = autofill_result->GetValue(); + SendSuggestions(&suggestions); +} + +void AutocompleteHistoryManager::OnGetAutocompleteSuggestions( + int query_id, + const base::string16& name, + const base::string16& prefix, + const std::vector<base::string16>& autofill_values, + const std::vector<base::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 (!manager_delegate_->IsAutocompleteEnabled()) { + SendSuggestions(NULL); + return; + } + + if (autofill_data_.get()) { + pending_query_handle_ = autofill_data_->GetFormValuesForElementName( + name, prefix, kMaxAutocompleteMenuItems, this); + } +} + +void AutocompleteHistoryManager::OnFormSubmitted(const FormData& form) { + if (!manager_delegate_->IsAutocompleteEnabled()) + 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) && + !autofill::IsSSN(iter->value)) { + values.push_back(*iter); + } + } + + if (!values.empty() && autofill_data_.get()) + autofill_data_->AddFormFields(values); +} + +void AutocompleteHistoryManager::OnRemoveAutocompleteEntry( + const base::string16& name, const base::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_.get()) + autofill_data_->CancelRequest(pending_query_handle_); + pending_query_handle_ = 0; + } +} + +void AutocompleteHistoryManager::SendSuggestions( + const std::vector<base::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(base::string16()); + autofill_icons_.push_back(base::string16()); + autofill_unique_ids_.push_back(0); // 0 means no profile. + } + } + } + + external_delegate_->OnSuggestionsReturned(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(); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autocomplete_history_manager.h b/chromium/components/autofill/core/browser/autocomplete_history_manager.h new file mode 100644 index 00000000000..15c2ac625a5 --- /dev/null +++ b/chromium/components/autofill/core/browser/autocomplete_history_manager.h @@ -0,0 +1,103 @@ +// 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_CORE_BROWSER_AUTOCOMPLETE_HISTORY_MANAGER_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOCOMPLETE_HISTORY_MANAGER_H_ + +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/prefs/pref_member.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service.h" +#include "components/webdata/common/web_data_service_consumer.h" + +namespace content { +class BrowserContext; +class WebContents; +} + +namespace autofill { + +class AutofillDriver; +class AutofillExternalDelegate; +class AutofillManagerDelegate; +struct FormData; + +// 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 WebDataServiceConsumer { + public: + AutocompleteHistoryManager(AutofillDriver* driver, + AutofillManagerDelegate* delegate); + virtual ~AutocompleteHistoryManager(); + + // 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 base::string16& name, + const base::string16& prefix, + const std::vector<base::string16>& autofill_values, + const std::vector<base::string16>& autofill_labels, + const std::vector<base::string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids); + virtual void OnFormSubmitted(const FormData& form); + + // Must be public for the external delegate to use. + void OnRemoveAutocompleteEntry(const base::string16& name, + const base::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<base::string16>* suggestions); + + // Used by tests to disable sending IPC. + void set_send_ipc(bool send_ipc) { send_ipc_ = send_ipc; } + + private: + // Cancels the currently pending WebDataService query, if there is one. + void CancelPendingQuery(); + + content::BrowserContext* browser_context_; + // Provides driver-level context. Must outlive this object. + AutofillDriver* driver_; + scoped_refptr<AutofillWebDataService> autofill_data_; + + // 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<base::string16> autofill_values_; + std::vector<base::string16> autofill_labels_; + std::vector<base::string16> autofill_icons_; + std::vector<int> autofill_unique_ids_; + + // Delegate to perform external processing (display, selection) on + // our behalf. Weak. + AutofillExternalDelegate* external_delegate_; + + // Delegate to provide whether or not autocomplete functionality is enabled. + AutofillManagerDelegate* const manager_delegate_; + + // Whether IPC is sent. + bool send_ipc_; + + DISALLOW_COPY_AND_ASSIGN(AutocompleteHistoryManager); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOCOMPLETE_HISTORY_MANAGER_H_ diff --git a/chromium/components/autofill/core/browser/autocomplete_history_manager_unittest.cc b/chromium/components/autofill/core/browser/autocomplete_history_manager_unittest.cc new file mode 100644 index 00000000000..f2cf35788ab --- /dev/null +++ b/chromium/components/autofill/core/browser/autocomplete_history_manager_unittest.cc @@ -0,0 +1,262 @@ +// 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 <vector> + +#include "base/memory/ref_counted.h" +#include "base/prefs/testing_pref_service.h" +#include "base/run_loop.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/waitable_event.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_profile.h" +#include "components/autofill/core/browser/autocomplete_history_manager.h" +#include "components/autofill/core/browser/autofill_external_delegate.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "components/autofill/core/browser/test_autofill_driver.h" +#include "components/autofill/core/browser/test_autofill_manager_delegate.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service.h" +#include "components/autofill/core/common/form_data.h" +#include "components/webdata/common/web_data_service_test_util.h" +#include "content/public/test/test_browser_thread.h" +#include "content/public/test/test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" + +using content::WebContents; +using testing::_; + +namespace autofill { + +namespace { + +class MockWebDataService : public AutofillWebDataService { + public: + MockWebDataService() + : AutofillWebDataService() { + current_mock_web_data_service_ = this; + } + + MOCK_METHOD1(AddFormFields, void(const std::vector<FormFieldData>&)); + + static scoped_refptr<MockWebDataService> GetCurrent() { + if (!current_mock_web_data_service_) { + return new MockWebDataService(); + } + return current_mock_web_data_service_; + } + + 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 MockWebDataServiceWrapperCurrent : public MockWebDataServiceWrapperBase { + public: + static BrowserContextKeyedService* Build(content::BrowserContext* profile) { + return new MockWebDataServiceWrapperCurrent(); + } + + MockWebDataServiceWrapperCurrent() {} + + virtual scoped_refptr<AutofillWebDataService> GetAutofillWebData() OVERRIDE { + return MockWebDataService::GetCurrent(); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MockWebDataServiceWrapperCurrent); +}; + +class MockAutofillManagerDelegate + : public autofill::TestAutofillManagerDelegate { + public: + MockAutofillManagerDelegate() {} + virtual ~MockAutofillManagerDelegate() {} + virtual PrefService* GetPrefs() OVERRIDE { return &prefs_; } + + private: + TestingPrefServiceSimple prefs_; + + DISALLOW_COPY_AND_ASSIGN(MockAutofillManagerDelegate); +}; + +} // namespace + +class AutocompleteHistoryManagerTest : public ChromeRenderViewHostTestHarness { + protected: + virtual void SetUp() OVERRIDE { + ChromeRenderViewHostTestHarness::SetUp(); + web_data_service_ = new MockWebDataService(); + WebDataServiceFactory::GetInstance()->SetTestingFactory( + profile(), MockWebDataServiceWrapperCurrent::Build); + autofill_driver_.reset(new TestAutofillDriver(web_contents())); + autocomplete_manager_.reset( + new AutocompleteHistoryManager(autofill_driver_.get(), + &manager_delegate)); + } + + virtual void TearDown() OVERRIDE { + autocomplete_manager_.reset(); + web_data_service_ = NULL; + ChromeRenderViewHostTestHarness::TearDown(); + } + + scoped_refptr<MockWebDataService> web_data_service_; + scoped_ptr<AutocompleteHistoryManager> autocomplete_manager_; + scoped_ptr<AutofillDriver> autofill_driver_; + 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_.get(), 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_.get(), 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: + MockAutofillExternalDelegate(content::WebContents* web_contents, + AutofillManager* autofill_manager, + AutofillDriver* autofill_driver) + : AutofillExternalDelegate(web_contents, autofill_manager, + autofill_driver) {} + virtual ~MockAutofillExternalDelegate() {} + + MOCK_METHOD5(OnSuggestionsReturned, + void(int query_id, + const std::vector<base::string16>& autofill_values, + const std::vector<base::string16>& autofill_labels, + const std::vector<base::string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillExternalDelegate); +}; + +class AutocompleteHistoryManagerNoIPC : public AutocompleteHistoryManager { + public: + AutocompleteHistoryManagerNoIPC(AutofillDriver* driver, + AutofillManagerDelegate* delegate) + : AutocompleteHistoryManager(driver, delegate) { + // Ensure that IPC is not sent during the test. + set_send_ipc(false); + } + + using AutocompleteHistoryManager::SendSuggestions; +}; + +} // namespace + +// Make sure our external delegate is called at the right time. +TEST_F(AutocompleteHistoryManagerTest, ExternalDelegate) { + AutocompleteHistoryManagerNoIPC autocomplete_history_manager( + autofill_driver_.get(), &manager_delegate); + + scoped_ptr<AutofillManager> autofill_manager(new AutofillManager( + autofill_driver_.get(), + &manager_delegate, + "en-US", + AutofillManager::ENABLE_AUTOFILL_DOWNLOAD_MANAGER)); + + MockAutofillExternalDelegate external_delegate(web_contents(), + autofill_manager.get(), + autofill_driver_.get()); + 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); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill-inl.h b/chromium/components/autofill/core/browser/autofill-inl.h new file mode 100644 index 00000000000..33e6a880d08 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill-inl.h @@ -0,0 +1,39 @@ +// 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_CORE_BROWSER_AUTOFILL_INL_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_INL_H_ + +namespace autofill { + +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(); +} + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_INL_H_ diff --git a/chromium/components/autofill/core/browser/autofill_common_test.cc b/chromium/components/autofill/core/browser/autofill_common_test.cc new file mode 100644 index 00000000000..f477b25c2e6 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_common_test.cc @@ -0,0 +1,194 @@ +// 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/core/browser/autofill_common_test.h" + +#include "base/guid.h" +#include "base/prefs/pref_service.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/common/autofill_pref_names.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/user_prefs/user_prefs.h" +#include "components/webdata/encryptor/encryptor.h" +#include "content/public/browser/browser_context.h" + +namespace autofill { +namespace test { + +namespace { + +const char kSettingsOrigin[] = "Chrome settings"; + +} // namespace + +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; +} + +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; + test::CreateTestFormField("First Name", "firstname", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("Middle Name", "middlename", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("Last Name", "lastname", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("Address Line 1", "addr1", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("Address Line 2", "addr2", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("City", "city", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("State", "state", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("Postal Code", "zipcode", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("Country", "country", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("Phone Number", "phonenumber", "", "tel", &field); + form->fields.push_back(field); + test::CreateTestFormField("Email", "email", "", "email", &field); + form->fields.push_back(field); +} + +inline void check_and_set( + FormGroup* profile, ServerFieldType type, const char* value) { + if (value) + profile->SetRawInfo(type, UTF8ToUTF16(value)); +} + +AutofillProfile GetFullProfile() { + AutofillProfile profile(base::GenerateGUID(), "http://www.example.com/"); + SetProfileInfo(&profile, + "John", + "H.", + "Doe", + "johndoe@hades.com", + "Underworld", + "666 Erebus St.", + "Apt 8", + "Elysium", "CA", + "91111", + "US", + "16502111111"); + return profile; +} + +AutofillProfile GetFullProfile2() { + AutofillProfile profile(base::GenerateGUID(), "https://www.example.com/"); + SetProfileInfo(&profile, + "Jane", + "A.", + "Smith", + "jsmith@example.com", + "ACME", + "123 Main Street", + "Unit 1", + "Greensdale", "MI", + "48838", + "US", + "13105557889"); + return profile; +} + +AutofillProfile GetVerifiedProfile() { + AutofillProfile profile(GetFullProfile()); + profile.set_origin(kSettingsOrigin); + return profile; +} + +AutofillProfile GetVerifiedProfile2() { + AutofillProfile profile(GetFullProfile2()); + profile.set_origin(kSettingsOrigin); + return profile; +} + +CreditCard GetCreditCard() { + CreditCard credit_card(base::GenerateGUID(), "http://www.example.com"); + SetCreditCardInfo( + &credit_card, "Test User", "4111111111111111" /* Visa */, "11", "2017"); + return credit_card; +} + +CreditCard GetVerifiedCreditCard() { + CreditCard credit_card(GetCreditCard()); + credit_card.set_origin(kSettingsOrigin); + return credit_card; +} + +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(content::BrowserContext* browser_context) { + // 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 (browser_context) { + user_prefs::UserPrefs::Get(browser_context)->SetBoolean( + prefs::kAutofillAuxiliaryProfilesEnabled, false); + } +} + +} // namespace test +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_common_test.h b/chromium/components/autofill/core/browser/autofill_common_test.h new file mode 100644 index 00000000000..b0818f70df7 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_common_test.h @@ -0,0 +1,84 @@ +// 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_CORE_BROWSER_AUTOFILL_COMMON_TEST_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_COMMON_TEST_H_ + +namespace content { +class BrowserContext; +} + +namespace autofill { + +class AutofillProfile; +class CreditCard; +struct FormData; +struct FormFieldData; + +// Common utilities shared amongst Autofill tests. +namespace 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); + +// 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); + +// Returns a profile full of dummy info. +AutofillProfile GetFullProfile(); + +// Returns a profile full of dummy info, different to the above. +AutofillProfile GetFullProfile2(); + +// Returns a verified profile full of dummy info. +AutofillProfile GetVerifiedProfile(); + +// Returns a verified profile full of dummy info, different to the above. +AutofillProfile GetVerifiedProfile2(); + +// Returns a credit card full of dummy info. +CreditCard GetCreditCard(); + +// Returns a verified credit card full of dummy info. +CreditCard GetVerifiedCreditCard(); + +// 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(content::BrowserContext* browser_context); + +} // namespace test +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_COMMON_TEST_H_ diff --git a/chromium/components/autofill/core/browser/autofill_country.cc b/chromium/components/autofill/core/browser/autofill_country.cc new file mode 100644 index 00000000000..45c89c7b711 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_country.cc @@ -0,0 +1,1111 @@ +// 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/core/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/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "grit/component_strings.h" +#include "third_party/icu/source/common/unicode/locid.h" +#include "third_party/icu/source/common/unicode/uloc.h" +#include "third_party/icu/source/common/unicode/unistr.h" +#include "third_party/icu/source/common/unicode/urename.h" +#include "third_party/icu/source/common/unicode/utypes.h" +#include "third_party/icu/source/i18n/unicode/coll.h" +#include "third_party/icu/source/i18n/unicode/ucol.h" +#include "ui/base/l10n/l10n_util.h" + +namespace autofill { +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; + AddressRequiredFields address_required_fields; +}; + +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, + ADDRESS_REQUIRES_STATE } }, + { "AE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_EMIRATE, + ADDRESS_REQUIRES_STATE } }, + { "AF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "AG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_ADDRESS_LINE_1_ONLY } }, + { "AI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "AL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "AM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "AN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "AO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "AQ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "AR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "AS", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "AT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "AU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "AW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "AX", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "AZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BB", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PARISH, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "BF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BJ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "BM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "BS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "BZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "CC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "CI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "CO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CX", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "CZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "DE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "DJ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "DK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "DM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "DO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "DZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "EC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "EE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "EG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "EH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "ER", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "ES", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "ET", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "FI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "FJ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "FK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "FM", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "FO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "FR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "GA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "GB", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_COUNTY, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "GD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "GE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "GF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "GG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "GH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "GI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_ADDRESS_LINE_1_ONLY } }, + { "GL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "GM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "GN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "GP", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "GQ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "GR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "GS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "GT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "GU", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "GW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "GY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "HK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_AREA, + ADDRESS_REQUIRES_STATE } }, + { "HM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "HN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE } }, + { "HR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "HT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "HU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "ID", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "IE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_COUNTY, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "IL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "IM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "IN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "IO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "IQ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE } }, + { "IS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "IT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "JE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "JM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PARISH, + ADDRESS_REQUIRES_CITY_STATE } }, + { "JO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "JP", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PREFECTURE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "KE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "KG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "KH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "KI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "KM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "KN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND, + ADDRESS_REQUIRES_CITY_STATE } }, + { "KP", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "KR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "KW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "KY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND, + ADDRESS_REQUIRES_STATE } }, + { "KZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "LA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "LB", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "LC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "LI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "LK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "LR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "LS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "LT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "LU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "LV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "LY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "ME", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "MG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MH", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "MK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "ML", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_ADDRESS_LINE_1_ONLY } }, + { "MP", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "MQ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "MR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "MX", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "MY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "MZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "NA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "NC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "NE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "NF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "NG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "NI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_DEPARTMENT, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "NL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "NO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "NP", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "NR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_DISTRICT, + ADDRESS_REQUIRES_STATE } }, + { "NU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "NZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "OM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "PA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "PE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "PF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "PG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE } }, + { "PH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY } }, + { "PK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "PL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "PM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "PN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "PR", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "PS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "PT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "PW", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "PY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "QA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "RE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "RO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "RS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "RU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "RW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "SA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "SB", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "SC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "SE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "SG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_ZIP } }, + { "SH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "SI", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "SJ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "SK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "SL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "SM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_ZIP } }, + { "SN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "SO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE } }, + { "SR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "ST", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "SV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE } }, + { "SZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "TD", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TH", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TJ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TK", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TL", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TO", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TR", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "TT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TV", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_ISLAND, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "TW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_COUNTY, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "TZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "UA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "UG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "UM", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE } }, + { "US", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "UY", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "UZ", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "VA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "VC", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "VE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_STATE } }, + { "VG", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_ADDRESS_LINE_1_ONLY } }, + { "VI", { IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE, + IDS_AUTOFILL_FIELD_LABEL_STATE, + ADDRESS_REQUIRES_CITY_STATE_ZIP } }, + { "VN", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY } }, + { "VU", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "WF", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "WS", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, + { "YE", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY } }, + { "YT", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "ZA", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY_ZIP } }, + { "ZM", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIRES_CITY } }, + { "ZW", { IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE, + IDS_AUTOFILL_FIELD_LABEL_PROVINCE, + ADDRESS_REQUIREMENTS_UNKNOWN } }, +}; + +// 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 country code corresponding to |country|, which should be a + // country code or country name localized to |locale|. + const std::string GetCountryCode(const base::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 base::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 base::string16& str, + scoped_ptr<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_; + + DISALLOW_COPY_AND_ASSIGN(CountryNames); +}; + +// static +CountryNames* CountryNames::GetInstance() { + return Singleton<CountryNames>::get(); +} + +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 base::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_ptr<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; + base::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 base::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_ptr<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 base::string16& str, + scoped_ptr<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); + address_required_fields_ = data.address_required_fields; +} + +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 base::string16& country, + const std::string& locale) { + return CountryNames::GetInstance()->GetCountryCode(country, locale); +} + +AutofillCountry::AutofillCountry(const std::string& country_code, + const base::string16& name, + const base::string16& postal_code_label, + const base::string16& state_label) + : country_code_(country_code), + name_(name), + postal_code_label_(postal_code_label), + state_label_(state_label) { +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_country.h b/chromium/components/autofill/core/browser/autofill_country.h new file mode 100644 index 00000000000..17bb74c49f6 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_country.h @@ -0,0 +1,110 @@ +// 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_CORE_BROWSER_AUTOFILL_COUNTRY_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_COUNTRY_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/strings/string16.h" + +namespace autofill { + +// The minimal required fields for an address to be complete for a given +// country. +enum AddressRequiredFields { + ADDRESS_REQUIRES_CITY = 1 << 0, + ADDRESS_REQUIRES_STATE = 1 << 1, + ADDRESS_REQUIRES_ZIP = 1 << 2, + + // Composite versions (for data). + ADDRESS_REQUIRES_CITY_STATE = + ADDRESS_REQUIRES_CITY | ADDRESS_REQUIRES_STATE, + ADDRESS_REQUIRES_STATE_ZIP = + ADDRESS_REQUIRES_STATE | ADDRESS_REQUIRES_ZIP, + ADDRESS_REQUIRES_CITY_ZIP = + ADDRESS_REQUIRES_CITY |ADDRESS_REQUIRES_ZIP, + ADDRESS_REQUIRES_CITY_STATE_ZIP = + ADDRESS_REQUIRES_CITY | ADDRESS_REQUIRES_STATE | ADDRESS_REQUIRES_ZIP, + + // Policy for countries that don't have city, state or zip requirements. + ADDRESS_REQUIRES_ADDRESS_LINE_1_ONLY = 0, + + // Policy for countries for which we do not have information about valid + // address format. + ADDRESS_REQUIREMENTS_UNKNOWN = ADDRESS_REQUIRES_CITY_STATE_ZIP, +}; + +// 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 base::string16& country, + const std::string& locale); + + const std::string country_code() const { return country_code_; } + const base::string16 name() const { return name_; } + const base::string16 postal_code_label() const { return postal_code_label_; } + const base::string16 state_label() const { return state_label_; } + + // City is expected in a complete address for this country. + bool requires_city() const { + return (address_required_fields_ & ADDRESS_REQUIRES_CITY) != 0; + } + + // State is expected in a complete address for this country. + bool requires_state() const { + return (address_required_fields_ & ADDRESS_REQUIRES_STATE) != 0; + } + + // Zip is expected in a complete address for this country. + bool requires_zip() const { + return (address_required_fields_ & ADDRESS_REQUIRES_ZIP) != 0; + } + + private: + AutofillCountry(const std::string& country_code, + const base::string16& name, + const base::string16& postal_code_label, + const base::string16& state_label); + + // The two-letter ISO-3166 country code. + std::string country_code_; + + // The country's name, localized to the app locale. + base::string16 name_; + + // The localized label for the postal code (or zip code) field. + base::string16 postal_code_label_; + + // The localized label for the state (or province, district, etc.) field. + base::string16 state_label_; + + // Address requirement field codes for the country. + AddressRequiredFields address_required_fields_; + + DISALLOW_COPY_AND_ASSIGN(AutofillCountry); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_COUNTRY_H_ diff --git a/chromium/components/autofill/core/browser/autofill_country_unittest.cc b/chromium/components/autofill/core/browser/autofill_country_unittest.cc new file mode 100644 index 00000000000..dc43a52a1aa --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_country_unittest.cc @@ -0,0 +1,90 @@ +// 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 <string> + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +// 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")); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_data_model.cc b/chromium/components/autofill/core/browser/autofill_data_model.cc new file mode 100644 index 00000000000..2d1edcf221d --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_data_model.cc @@ -0,0 +1,189 @@ +// 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/core/browser/autofill_data_model.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/state_names.h" +#include "components/autofill/core/browser/validation.h" +#include "components/autofill/core/common/form_field_data.h" +#include "grit/component_strings.h" +#include "ui/base/l10n/l10n_util.h" +#include "url/gurl.h" + +namespace autofill { +namespace { + +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 base::string16& value, + FormFieldData* field) { + base::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 base::string16& value, + FormFieldData* field) { + base::string16 full, abbreviation; + state_names::GetNameAndAbbreviation(value, &full, &abbreviation); + + // Try the abbreviation first. + if (!abbreviation.empty() && SetSelectControlValue(abbreviation, field)) + return true; + + return !full.empty() && SetSelectControlValue(full, field); +} + +bool FillExpirationMonthSelectControl(const base::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 base::string16& value, + FormFieldData* field) { + // Try stripping off spaces. + base::string16 value_stripped; + RemoveChars(StringToLowerASCII(value), kWhitespaceUTF16, &value_stripped); + + for (size_t i = 0; i < field->option_values.size(); ++i) { + base::string16 option_value_lowercase; + RemoveChars(StringToLowerASCII(field->option_values[i]), kWhitespaceUTF16, + &option_value_lowercase); + base::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 + +AutofillDataModel::AutofillDataModel(const std::string& guid, + const std::string& origin) + : guid_(guid), + origin_(origin) {} +AutofillDataModel::~AutofillDataModel() {} + +void AutofillDataModel::FillSelectControl(const AutofillType& type, + const std::string& app_locale, + FormFieldData* field) const { + DCHECK(field); + DCHECK_EQ("select-one", field->form_control_type); + DCHECK_EQ(field->option_values.size(), field->option_contents.size()); + + base::string16 field_text = GetInfo(type, app_locale); + base::string16 field_text_lower = StringToLowerASCII(field_text); + if (field_text.empty()) + return; + + base::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; + } + + ServerFieldType storable_type = type.GetStorableType(); + if (storable_type == ADDRESS_HOME_STATE) { + FillStateSelectControl(field_text, field); + } else if (storable_type == ADDRESS_HOME_COUNTRY) { + FillCountrySelectControl(app_locale, field); + } else if (storable_type == CREDIT_CARD_EXP_MONTH) { + FillExpirationMonthSelectControl(field_text, field); + } else if (storable_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(AutofillType(CREDIT_CARD_EXP_2_DIGIT_YEAR), app_locale, + field); + } else if (storable_type == CREDIT_CARD_TYPE) { + FillCreditCardTypeSelectControl(field_text, field); + } +} + +bool AutofillDataModel::FillCountrySelectControl( + const std::string& app_locale, + FormFieldData* field_data) const { + return false; +} + +bool AutofillDataModel::IsVerified() const { + return !origin_.empty() && !GURL(origin_).is_valid(); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_data_model.h b/chromium/components/autofill/core/browser/autofill_data_model.h new file mode 100644 index 00000000000..0f2cd33f0ff --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_data_model.h @@ -0,0 +1,70 @@ +// 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_CORE_BROWSER_AUTOFILL_DATA_MODEL_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_DATA_MODEL_H_ + +#include <string> + +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/form_group.h" + +namespace autofill { + +class AutofillField; +struct FormFieldData; + +// This class is an interface for the primary data models that back Autofill. +// The information in objects of this class is managed by the +// PersonalDataManager. +class AutofillDataModel : public FormGroup { + public: + AutofillDataModel(const std::string& guid, const std::string& origin); + virtual ~AutofillDataModel(); + + // Set |field_data|'s value based on |field| and contents of |this| (using + // data variant |variant|). + virtual void FillFormField(const AutofillField& field, + size_t variant, + const std::string& app_locale, + FormFieldData* field_data) const = 0; + + // Fills in select control with data matching |type| from |this|. + // Public for testing purposes. + void FillSelectControl(const AutofillType& type, + const std::string& app_locale, + FormFieldData* field_data) const; + + // Returns true if the data in this model was entered directly by the user, + // rather than automatically aggregated. + bool IsVerified() const; + + std::string guid() const { return guid_; } + void set_guid(const std::string& guid) { guid_ = guid; } + + std::string origin() const { return origin_; } + void set_origin(const std::string& origin) { origin_ = origin; } + + protected: + // Fills in a select control for a country from data in |this|. Returns true + // for success. + virtual bool FillCountrySelectControl(const std::string& app_locale, + FormFieldData* field_data) const; + + private: + // A globally unique ID for this object. + std::string guid_; + + // The origin of this data. This should be + // (a) a web URL for the domain of the form from which the data was + // automatically aggregated, e.g. https://www.example.com/register, + // (b) some other non-empty string, which cannot be interpreted as a web + // URL, identifying the origin for non-aggregated data, or + // (c) an empty string, indicating that the origin for this data is unknown. + std::string origin_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_DATA_MODEL_H_ diff --git a/chromium/components/autofill/core/browser/autofill_data_model_unittest.cc b/chromium/components/autofill/core/browser/autofill_data_model_unittest.cc new file mode 100644 index 00000000000..8004df46425 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_data_model_unittest.cc @@ -0,0 +1,68 @@ +// 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/core/browser/autofill_data_model.h" + +#include "base/compiler_specific.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +namespace { + +// Provides concrete implementations for pure virtual methods. +class TestAutofillDataModel : public AutofillDataModel { + public: + TestAutofillDataModel(const std::string& guid, const std::string& origin) + : AutofillDataModel(guid, origin) {} + virtual ~TestAutofillDataModel() {} + + private: + virtual base::string16 GetRawInfo(ServerFieldType type) const OVERRIDE { + return base::string16(); + } + virtual void SetRawInfo(ServerFieldType type, + const base::string16& value) OVERRIDE {} + virtual void GetSupportedTypes( + ServerFieldTypeSet* supported_types) const OVERRIDE {} + virtual void FillFormField(const AutofillField& field, + size_t variant, + const std::string& app_locale, + FormFieldData* field_data) const OVERRIDE {} + + DISALLOW_COPY_AND_ASSIGN(TestAutofillDataModel); +}; + +} // namespace + +TEST(AutofillDataModelTest, IsVerified) { + TestAutofillDataModel model("guid", std::string()); + EXPECT_FALSE(model.IsVerified()); + + model.set_origin("http://www.example.com"); + EXPECT_FALSE(model.IsVerified()); + + model.set_origin("https://www.example.com"); + EXPECT_FALSE(model.IsVerified()); + + model.set_origin("file:///tmp/example.txt"); + EXPECT_FALSE(model.IsVerified()); + + model.set_origin("data:text/plain;charset=utf-8;base64,ZXhhbXBsZQ=="); + EXPECT_FALSE(model.IsVerified()); + + model.set_origin("chrome://settings/autofill"); + EXPECT_FALSE(model.IsVerified()); + + model.set_origin("Chrome Settings"); + EXPECT_TRUE(model.IsVerified()); + + model.set_origin("Some gibberish string"); + EXPECT_TRUE(model.IsVerified()); + + model.set_origin(std::string()); + EXPECT_FALSE(model.IsVerified()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_download.cc b/chromium/components/autofill/core/browser/autofill_download.cc new file mode 100644 index 00000000000..9bf81e2c72b --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_download.cc @@ -0,0 +1,352 @@ +// 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/core/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/strings/string_util.h" +#include "components/autofill/core/browser/autofill_download_url.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/browser/autofill_xml_parser.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/common/autofill_pref_names.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.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" +#include "url/gurl.h" + +using content::BrowserContext; + +namespace autofill { + +namespace { +const char kAutofillQueryServerNameStartInHeader[] = "GFE/"; + +const size_t kMaxFormCacheSize = 16; + +// Generate field assignments xml that can be manually changed and then fed back +// into the Autofill server as experiment data. +static void LogFieldAssignments( + const FormStructure& form, + const ServerFieldTypeSet& available_field_types) { + std::string form_xml; + if (!form.EncodeFieldAssignments(available_field_types, &form_xml)) + return; + + VLOG(1) << "AutofillDownloadManager FieldAssignments for " + << form.source_url() + << " :\n" + << form_xml; +} + +} // namespace + +// 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 = user_prefs::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, form signatures: " + << GetCombinedSignature(request_data.form_signatures); + observer_->OnLoadedServerPredictions(query_data); + return true; + } + + return StartRequest(form_xml, request_data); +} + +bool AutofillDownloadManager::StartUploadRequest( + const FormStructure& form, + bool form_was_autofilled, + const ServerFieldTypeSet& available_field_types) { + std::string form_xml; + if (!form.EncodeUploadRequest(available_field_types, form_was_autofilled, + &form_xml)) + return false; + + LogFieldAssignments(form, available_field_types); + + 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 = user_prefs::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 = user_prefs::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); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_download.h b/chromium/components/autofill/core/browser/autofill_download.h new file mode 100644 index 00000000000..bba1d719762 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_download.h @@ -0,0 +1,170 @@ +// 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_CORE_BROWSER_AUTOFILL_DOWNLOAD_H_ +#define COMPONENTS_AUTOFILL_CORE_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/time.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "net/url_request/url_fetcher_delegate.h" + +namespace content { +class BrowserContext; +} // namespace content + +namespace net { +class URLFetcher; +} // namespace net + +namespace autofill { + +class AutofillMetrics; +class FormStructure; + +// 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 ServerFieldTypeSet& 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_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_DOWNLOAD_H_ diff --git a/chromium/components/autofill/core/browser/autofill_download_unittest.cc b/chromium/components/autofill/core/browser/autofill_download_unittest.cc new file mode 100644 index 00000000000..9035c9cdafa --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_download_unittest.cc @@ -0,0 +1,491 @@ +// 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 <list> + +#include "base/message_loop/message_loop.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/test/test_timeouts.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/core/browser/autofill_download.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/common/form_data.h" +#include "content/public/test/test_browser_thread_bundle.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/public/web/WebInputElement.h" + +using WebKit::WebInputElement; + +namespace autofill { + +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) { + } + + 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_; + + content::TestBrowserThreadBundle thread_bundle_; + TestingProfile profile_; + AutofillDownloadManager download_manager_; +}; + +TEST_F(AutofillDownloadTest, QueryAndUploadTest) { + // 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 = base::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 = base::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, ServerFieldTypeSet())); + // Request with id 2. + EXPECT_TRUE(download_manager_.StartUploadRequest( + *(form_structures[1]), false, ServerFieldTypeSet())); + + 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, ServerFieldTypeSet())); + EXPECT_FALSE(download_manager_.StartUploadRequest( + *(form_structures[1]), false, ServerFieldTypeSet())); + 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, ServerFieldTypeSet())); + 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, ServerFieldTypeSet())); + fetcher = factory.GetFetcherByID(5); + EXPECT_EQ(NULL, fetcher); +} + +TEST_F(AutofillDownloadTest, CacheQueryTest) { + // 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); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_download_url.cc b/chromium/components/autofill/core/browser/autofill_download_url.cc new file mode 100644 index 00000000000..657e69e9ee4 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_download_url.cc @@ -0,0 +1,47 @@ +// 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/core/browser/autofill_download_url.h" + +#include <string> + +#include "base/command_line.h" +#include "components/autofill/core/common/autofill_switches.h" +#include "url/gurl.h" + +namespace autofill { +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; +} + +} // namespace + +GURL GetAutofillQueryUrl() { + std::string baseAutofillServiceUrl = GetBaseAutofillUrl(); + return GURL(baseAutofillServiceUrl + "query?client=" + kClientName); +} + +GURL GetAutofillUploadUrl() { + std::string baseAutofillServiceUrl = GetBaseAutofillUrl(); + return GURL(baseAutofillServiceUrl + "upload?client=" + kClientName); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_download_url.h b/chromium/components/autofill/core/browser/autofill_download_url.h new file mode 100644 index 00000000000..894a060dd98 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_download_url.h @@ -0,0 +1,18 @@ +// 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_CORE_BROWSER_AUTOFILL_DOWNLOAD_URL_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_DOWNLOAD_URL_H_ + +class GURL; + +namespace autofill { + +GURL GetAutofillQueryUrl(); +GURL GetAutofillUploadUrl(); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_DOWNLOAD_URL_H_ + diff --git a/chromium/components/autofill/core/browser/autofill_download_url_unittest.cc b/chromium/components/autofill/core/browser/autofill_download_url_unittest.cc new file mode 100644 index 00000000000..123ec6cf3e7 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_download_url_unittest.cc @@ -0,0 +1,26 @@ +// 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/core/browser/autofill_download_url.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "url/gurl.h" + +using testing::StartsWith; + +namespace autofill { + +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=")); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_driver.h b/chromium/components/autofill/core/browser/autofill_driver.h new file mode 100644 index 00000000000..2c1e175ee35 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_driver.h @@ -0,0 +1,67 @@ +// 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_CORE_BROWSER_AUTOFILL_DRIVER_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_DRIVER_H_ + +#include <vector> + +#include "components/autofill/core/common/form_data.h" + +namespace content { +class WebContents; +} + +namespace autofill { + +class FormStructure; + +// Interface that allows Autofill core code to interact with its driver (i.e., +// obtain information from it and give information to it). A concrete +// implementation must be provided by the driver. +class AutofillDriver { + public: + // The possible actions that the renderer can take on receiving form data. + enum RendererFormDataAction { + // The renderer should fill the form data. + FORM_DATA_ACTION_FILL, + // The renderer should preview the form data. + FORM_DATA_ACTION_PREVIEW + }; + + virtual ~AutofillDriver() {} + + // TODO(blundell): Remove this method once shared code no longer needs to + // know about WebContents. + virtual content::WebContents* GetWebContents() = 0; + + // Returns true iff the renderer is available for communication. + virtual bool RendererIsAvailable() = 0; + + // Informs the renderer what action to take with the next form data that it + // receives. Must be called before each call to |SendFormDataToRenderer|. + virtual void SetRendererActionOnFormDataReception( + RendererFormDataAction action) = 0; + + // Forwards |data| to the renderer. |query_id| is the id of the renderer's + // original request for the data. This method is a no-op if the renderer is + // not currently available. + virtual void SendFormDataToRenderer(int query_id, const FormData& data) = 0; + + // Sends the field type predictions specified in |forms| to the renderer. This + // method is a no-op if the renderer is not available or the appropriate + // command-line flag is not set. + virtual void SendAutofillTypePredictionsToRenderer( + const std::vector<FormStructure*>& forms) = 0; + + // Tells the renderer to clear the currently filled Autofill results. + virtual void RendererShouldClearFilledForm() = 0; + + // Tells the renderer to clear the currently previewed Autofill results. + virtual void RendererShouldClearPreviewedForm() = 0; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_DRIVER_H_ diff --git a/chromium/components/autofill/core/browser/autofill_external_delegate.cc b/chromium/components/autofill/core/browser/autofill_external_delegate.cc new file mode 100644 index 00000000000..95119bfad99 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_external_delegate.cc @@ -0,0 +1,381 @@ +// 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/core/browser/autofill_external_delegate.h" + +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autocomplete_history_manager.h" +#include "components/autofill/core/browser/autofill_driver.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "grit/component_strings.h" +#include "third_party/WebKit/public/web/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; + +namespace autofill { + +AutofillExternalDelegate::AutofillExternalDelegate( + content::WebContents* web_contents, + AutofillManager* autofill_manager, + AutofillDriver* autofill_driver) + : web_contents_(web_contents), + autofill_manager_(autofill_manager), + autofill_driver_(autofill_driver), + 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), + weak_ptr_factory_(this) { + DCHECK(autofill_manager); +} + +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<base::string16>& autofill_values, + const std::vector<base::string16>& autofill_labels, + const std::vector<base::string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids) { + if (query_id != autofill_query_id_) + return; + + std::vector<base::string16> values(autofill_values); + std::vector<base::string16> labels(autofill_labels); + std::vector<base::string16> icons(autofill_icons); + std::vector<int> ids(autofill_unique_ids); + + // Add or hide warnings as appropriate. + ApplyAutofillWarnings(&values, &labels, &icons, &ids); + + // Add a separator to go between the values and menu items. + values.push_back(base::string16()); + labels.push_back(base::string16()); + icons.push_back(base::string16()); + ids.push_back(WebAutofillClient::MenuItemIDSeparator); + + // 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. + DCHECK_GT(ids.size(), 0U); + if (ids.back() == WebAutofillClient::MenuItemIDSeparator) { + values.pop_back(); + labels.pop_back(); + icons.pop_back(); + ids.pop_back(); + } + + // If anything else is added to modify the values after inserting the data + // list, AutofillPopupControllerImpl::UpdateDataListValues will need to be + // updated to match. + 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_, + autofill_query_field_.text_direction, + values, + labels, + icons, + ids, + GetWeakPtr()); + } +} + +void AutofillExternalDelegate::OnShowPasswordSuggestions( + const std::vector<base::string16>& suggestions, + const std::vector<base::string16>& realms, + 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<base::string16> empty(suggestions.size()); + std::vector<int> password_ids(suggestions.size(), + WebAutofillClient::MenuItemIDPasswordEntry); + autofill_manager_->delegate()->ShowAutofillPopup( + element_bounds_, + autofill_query_field_.text_direction, + suggestions, + realms, + empty, + password_ids, + GetWeakPtr()); +} + +void AutofillExternalDelegate::SetCurrentDataListValues( + const std::vector<base::string16>& data_list_values, + const std::vector<base::string16>& data_list_labels) { + data_list_values_ = data_list_values; + data_list_labels_ = data_list_labels; + + autofill_manager_->delegate()->UpdateAutofillPopupDataListValues( + data_list_values, + data_list_labels); +} + +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 ((!web_contents_->IsBeingDestroyed()) && + (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 base::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'. + autofill_driver_->RendererShouldClearFilledForm(); + } else if (identifier == WebAutofillClient::MenuItemIDPasswordEntry) { + bool success = password_autofill_manager_.DidAcceptAutofillSuggestion( + autofill_query_field_, value); + DCHECK(success); + } 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 base::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() { + autofill_driver_->RendererShouldClearPreviewedForm(); +} + +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); +} + +base::WeakPtr<AutofillExternalDelegate> AutofillExternalDelegate::GetWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); +} + +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; + + AutofillDriver::RendererFormDataAction renderer_action = is_preview ? + AutofillDriver::FORM_DATA_ACTION_PREVIEW : + AutofillDriver::FORM_DATA_ACTION_FILL; + + DCHECK(autofill_driver_->RendererIsAvailable()); + autofill_driver_->SetRendererActionOnFormDataReception(renderer_action); + // 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<base::string16>* autofill_values, + std::vector<base::string16>* autofill_labels, + std::vector<base::string16>* autofill_icons, + std::vector<int>* autofill_unique_ids) { + if (!autofill_query_field_.should_autocomplete) { + // Autofill is disabled. If there were some profile or credit card + // suggestions to show, show a warning instead. Otherwise, clear out the + // list of suggestions. + if (!autofill_unique_ids->empty() && (*autofill_unique_ids)[0] > 0) { + // 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, base::string16()); + autofill_icons->assign(1, base::string16()); + autofill_unique_ids->assign(1, + WebAutofillClient::MenuItemIDWarningMessage); + } else { + autofill_values->clear(); + autofill_labels->clear(); + autofill_icons->clear(); + autofill_unique_ids->clear(); + } + } 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<base::string16>* autofill_values, + std::vector<base::string16>* autofill_labels, + std::vector<base::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(base::string16()); + autofill_icons->push_back(base::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(base::string16()); + autofill_icons->push_back(base::string16()); + autofill_unique_ids->push_back(WebAutofillClient::MenuItemIDAutofillOptions); +} + +void AutofillExternalDelegate::InsertDataListValues( + std::vector<base::string16>* autofill_values, + std::vector<base::string16>* autofill_labels, + std::vector<base::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(), base::string16()); + autofill_labels->insert(autofill_labels->begin(), base::string16()); + autofill_icons->insert(autofill_icons->begin(), base::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()); + + // Set the values that all datalist elements share. + autofill_icons->insert(autofill_icons->begin(), + data_list_values_.size(), + base::string16()); + autofill_unique_ids->insert(autofill_unique_ids->begin(), + data_list_values_.size(), + WebAutofillClient::MenuItemIDDataListEntry); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_external_delegate.h b/chromium/components/autofill/core/browser/autofill_external_delegate.h new file mode 100644 index 00000000000..3362ff8e19b --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_external_delegate.h @@ -0,0 +1,185 @@ +// 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_CORE_BROWSER_AUTOFILL_EXTERNAL_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_EXTERNAL_DELEGATE_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/autofill_popup_delegate.h" +#include "components/autofill/core/browser/password_autofill_manager.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/password_form_fill_data.h" +#include "ui/gfx/rect.h" + +namespace gfx { +class Rect; +} + +namespace content { +class RenderViewHost; +class WebContents; +} + +namespace autofill { + +class AutofillDriver; +class AutofillManager; + +// 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 AutofillPopupDelegate { + public: + // Creates an AutofillExternalDelegate for the specified contents, + // AutofillManager, and AutofillDriver. + AutofillExternalDelegate(content::WebContents* web_contents, + AutofillManager* autofill_manager, + AutofillDriver* autofill_driver); + virtual ~AutofillExternalDelegate(); + + // 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 base::string16& value, + int identifier) OVERRIDE; + virtual void RemoveSuggestion(const base::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<base::string16>& autofill_values, + const std::vector<base::string16>& autofill_labels, + const std::vector<base::string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids); + + // Show password suggestions in the popup. + void OnShowPasswordSuggestions(const std::vector<base::string16>& suggestions, + const std::vector<base::string16>& realms, + const FormFieldData& field, + const gfx::RectF& bounds); + + // Set the data list value associated with the current field. + void SetCurrentDataListValues( + const std::vector<base::string16>& data_list_values, + const std::vector<base::string16>& data_list_labels); + + // 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: + content::WebContents* web_contents() { return web_contents_; } + + base::WeakPtr<AutofillExternalDelegate> GetWeakPtr(); + + 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<base::string16>* autofill_values, + std::vector<base::string16>* autofill_labels, + std::vector<base::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<base::string16>* autofill_values, + std::vector<base::string16>* autofill_labels, + std::vector<base::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<base::string16>* autofill_values, + std::vector<base::string16>* autofill_labels, + std::vector<base::string16>* autofill_icons, + std::vector<int>* autofill_unique_ids); + + // The web_contents associated with this delegate. + content::WebContents* web_contents_; // weak; owns me. + AutofillManager* autofill_manager_; // weak. + + // Provides driver-level context to the shared code of the component. Must + // outlive this object. + AutofillDriver* autofill_driver_; // 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_; + + // 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<base::string16> data_list_values_; + std::vector<base::string16> data_list_labels_; + + base::WeakPtrFactory<AutofillExternalDelegate> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(AutofillExternalDelegate); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_EXTERNAL_DELEGATE_H_ diff --git a/chromium/components/autofill/core/browser/autofill_external_delegate_unittest.cc b/chromium/components/autofill/core/browser/autofill_external_delegate_unittest.cc new file mode 100644 index 00000000000..ae863ca6a38 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_external_delegate_unittest.cc @@ -0,0 +1,473 @@ +// 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 <vector> + +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "chrome/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "components/autofill/core/browser/test_autofill_driver.h" +#include "components/autofill/core/browser/test_autofill_external_delegate.h" +#include "components/autofill/core/browser/test_autofill_manager_delegate.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/password_form_fill_data.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/web/WebAutofillClient.h" +#include "ui/gfx/rect.h" + +using testing::_; +using WebKit::WebAutofillClient; + +namespace autofill { + +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 MockAutofillDriver : public TestAutofillDriver { + public: + explicit MockAutofillDriver(content::WebContents* web_contents) + : TestAutofillDriver(web_contents) {} + + // Mock methods to enable testability. + MOCK_METHOD1(SetRendererActionOnFormDataReception, + void(RendererFormDataAction action)); + MOCK_METHOD0(RendererShouldClearFilledForm, void()); + MOCK_METHOD0(RendererShouldClearPreviewedForm, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillDriver); +}; + +class MockAutofillManagerDelegate + : public autofill::TestAutofillManagerDelegate { + public: + MockAutofillManagerDelegate() {} + + MOCK_METHOD7(ShowAutofillPopup, + void(const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction, + const std::vector<base::string16>& values, + const std::vector<base::string16>& labels, + const std::vector<base::string16>& icons, + const std::vector<int>& identifiers, + base::WeakPtr<AutofillPopupDelegate> delegate)); + + MOCK_METHOD2(UpdateAutofillPopupDataListValues, + void(const std::vector<base::string16>& values, + const std::vector<base::string16>& lables)); + + MOCK_METHOD0(HideAutofillPopup, void()); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillManagerDelegate); +}; + +class MockAutofillManager : public AutofillManager { + public: + MockAutofillManager(AutofillDriver* driver, + 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(driver, 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 { + protected: + virtual void SetUp() OVERRIDE { + ChromeRenderViewHostTestHarness::SetUp(); + autofill_driver_.reset(new MockAutofillDriver(web_contents())); + autofill_manager_.reset( + new MockAutofillManager(autofill_driver_.get(), + &manager_delegate_)); + external_delegate_.reset( + new AutofillExternalDelegate( + web_contents(), + autofill_manager_.get(), autofill_driver_.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(); + autofill_driver_.reset(); + ChromeRenderViewHostTestHarness::TearDown(); + } + + // 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, true); + } + + MockAutofillManagerDelegate manager_delegate_; + scoped_ptr<MockAutofillDriver> autofill_driver_; + scoped_ptr<MockAutofillManager> autofill_manager_; + scoped_ptr<AutofillExternalDelegate> external_delegate_; +}; + +// 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)), + _)); + + // This should call ShowAutofillPopup. + std::vector<base::string16> autofill_item; + autofill_item.push_back(base::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(*autofill_driver_, SetRendererActionOnFormDataReception( + AutofillDriver::FORM_DATA_ACTION_FILL)); + + 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<base::string16> data_list_items; + data_list_items.push_back(base::string16()); + + external_delegate_->SetCurrentDataListValues(data_list_items, + data_list_items); + + // 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)), + _)); + + // This should call ShowAutofillPopup. + std::vector<base::string16> autofill_item; + autofill_item.push_back(base::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)), + _)); + + autofill_item = std::vector<base::string16>(); + autofill_ids = std::vector<int>(); + external_delegate_->OnSuggestionsReturned(kQueryId, + autofill_item, + autofill_item, + autofill_item, + autofill_ids); +} + +// Test that datalist values can get updated while a popup is showing. +TEST_F(AutofillExternalDelegateUnitTest, UpdateDataListWhileShowingPopup) { + IssueOnQuery(kQueryId); + + EXPECT_CALL(manager_delegate_, + ShowAutofillPopup(_, _, _, _, _, _, _)).Times(0); + + // Make sure just setting the data list values doesn't cause the popup to + // appear. + std::vector<base::string16> data_list_items; + data_list_items.push_back(base::string16()); + + external_delegate_->SetCurrentDataListValues(data_list_items, + data_list_items); + + // 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)), + _)); + + // Ensure the popup is displayed. + std::vector<base::string16> autofill_item; + autofill_item.push_back(base::string16()); + std::vector<int> autofill_ids; + autofill_ids.push_back(kAutofillProfileId); + external_delegate_->OnSuggestionsReturned(kQueryId, + autofill_item, + autofill_item, + autofill_item, + autofill_ids); + + // This would normally get called from ShowAutofillPopup, but it is mocked + // we need to call OnPopupShown ourselves. + external_delegate_->OnPopupShown(NULL); + + // Update the current data list and ensure the popup is updated. + data_list_items.push_back(base::string16()); + + // The enums must be cast to ints to prevent compile errors on linux_rel. + EXPECT_CALL(manager_delegate_, + UpdateAutofillPopupDataListValues(data_list_items, + data_list_items)); + + external_delegate_->SetCurrentDataListValues(data_list_items, + data_list_items); +} + +// Test that the Autofill popup is able to display warnings explaining why +// Autofill is disabled for a website. +// Regression test for http://crbug.com/247880 +TEST_F(AutofillExternalDelegateUnitTest, AutofillWarnings) { + IssueOnQuery(kQueryId); + + // 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::MenuItemIDWarningMessage)), + _)); + + // This should call ShowAutofillPopup. + std::vector<base::string16> autofill_item; + autofill_item.push_back(base::string16()); + std::vector<int> autofill_ids; + autofill_ids.push_back(WebAutofillClient::MenuItemIDWarningMessage); + external_delegate_->OnSuggestionsReturned(kQueryId, + autofill_item, + autofill_item, + autofill_item, + autofill_ids); +} + +// Test that the Autofill popup doesn't display a warning explaining why +// Autofill is disabled for a website when there are no Autofill suggestions. +// Regression test for http://crbug.com/105636 +TEST_F(AutofillExternalDelegateUnitTest, NoAutofillWarningsWithoutSuggestions) { + const FormData form; + FormFieldData field; + field.is_focusable = true; + field.should_autocomplete = false; + const gfx::RectF element_bounds; + + external_delegate_->OnQuery(kQueryId, form, field, element_bounds, true); + + EXPECT_CALL(manager_delegate_, + ShowAutofillPopup(_, _, _, _, _, _, _)).Times(0); + EXPECT_CALL(manager_delegate_, HideAutofillPopup()).Times(1); + + // This should not call ShowAutofillPopup. + std::vector<base::string16> autofill_item; + autofill_item.push_back(base::string16()); + std::vector<int> autofill_ids; + autofill_ids.push_back(WebAutofillClient::MenuItemIDAutocompleteEntry); + 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(*autofill_driver_, + SetRendererActionOnFormDataReception(_)).Times(0); + EXPECT_CALL(*autofill_driver_, RendererShouldClearPreviewedForm()).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); + EXPECT_CALL(*autofill_driver_, + SetRendererActionOnFormDataReception(_)).Times(0); + external_delegate_->DidAcceptSuggestion(base::string16(), -1); +} + +// Test that the ClearPreview call is only sent if 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(*autofill_driver_, RendererShouldClearPreviewedForm()).Times(1); + external_delegate_->DidSelectSuggestion( + WebAutofillClient::MenuItemIDPasswordEntry); + + EXPECT_CALL(*autofill_driver_, RendererShouldClearPreviewedForm()).Times(1); + EXPECT_CALL(*autofill_driver_, SetRendererActionOnFormDataReception( + AutofillDriver::FORM_DATA_ACTION_PREVIEW)); + 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) { + static const base::string16 kUsername = ASCIIToUTF16("username"); + static const base::string16 kSignonRealm = ASCIIToUTF16("http://foo.com/"); + std::vector<base::string16> suggestions; + suggestions.push_back(kUsername); + std::vector<base::string16> realms; + realms.push_back(kSignonRealm); + + FormFieldData field; + field.is_focusable = true; + field.should_autocomplete = true; + const gfx::RectF element_bounds; + + FormFieldData username_field_data; + username_field_data.value = kUsername; + PasswordFormFillData password_form_fill_data; + password_form_fill_data.basic_data.fields.push_back(username_field_data); + external_delegate_->AddPasswordFormMapping(field, password_form_fill_data); + + // 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_->OnShowPasswordSuggestions(suggestions, + realms, + field, + element_bounds); + + 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); +} + +// Test that the driver is directed to clear the form after being notified that +// the user accepted the suggestion to clear the form. +TEST_F(AutofillExternalDelegateUnitTest, ExternalDelegateClearForm) { + EXPECT_CALL(manager_delegate_, HideAutofillPopup()); + EXPECT_CALL(*autofill_driver_, RendererShouldClearFilledForm()); + + external_delegate_->DidAcceptSuggestion( + base::string16(), + WebAutofillClient::MenuItemIDClearForm); +} + +TEST_F(AutofillExternalDelegateUnitTest, ExternalDelegateHideWarning) { + // Set up a field that shouldn't get autocompleted or display warnings. + const FormData form; + FormFieldData field; + field.is_focusable = true; + field.should_autocomplete = false; + const gfx::RectF element_bounds; + + external_delegate_->OnQuery(kQueryId, form, field, element_bounds, false); + + std::vector<base::string16> autofill_items; + autofill_items.push_back(base::string16()); + std::vector<int> autofill_ids; + autofill_ids.push_back(WebAutofillClient::MenuItemIDAutocompleteEntry); + + // Ensure the popup tries to hide itself, since it is not allowed to show + // anything. + EXPECT_CALL(manager_delegate_, HideAutofillPopup()); + + external_delegate_->OnSuggestionsReturned(kQueryId, + autofill_items, + autofill_items, + autofill_items, + autofill_ids); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_field.cc b/chromium/components/autofill/core/browser/autofill_field.cc new file mode 100644 index 00000000000..4bcb1185366 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_field.cc @@ -0,0 +1,106 @@ +// 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/core/browser/autofill_field.h" + +#include "base/logging.h" +#include "base/sha1.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_type.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 + +namespace autofill { + +AutofillField::AutofillField() + : server_type_(NO_SERVER_DATA), + heuristic_type_(UNKNOWN_TYPE), + html_type_(HTML_TYPE_UNKNOWN), + html_mode_(HTML_MODE_NONE), + phone_part_(IGNORED) { +} + +AutofillField::AutofillField(const FormFieldData& field, + const base::string16& unique_name) + : FormFieldData(field), + unique_name_(unique_name), + server_type_(NO_SERVER_DATA), + heuristic_type_(UNKNOWN_TYPE), + html_type_(HTML_TYPE_UNKNOWN), + html_mode_(HTML_MODE_NONE), + phone_part_(IGNORED) { +} + +AutofillField::~AutofillField() {} + +void AutofillField::set_heuristic_type(ServerFieldType 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(ServerFieldType 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; +} + +void AutofillField::SetHtmlType(HtmlFieldType type, HtmlFieldMode mode) { + html_type_ = type; + html_mode_ = mode; + + if (type == HTML_TYPE_TEL_LOCAL_PREFIX) + phone_part_ = AutofillField::PHONE_PREFIX; + else if (type == HTML_TYPE_TEL_LOCAL_SUFFIX) + phone_part_ = AutofillField::PHONE_SUFFIX; +} + +AutofillType AutofillField::Type() const { + if (html_type_ != HTML_TYPE_UNKNOWN) + return AutofillType(html_type_, html_mode_); + + if (server_type_ != NO_SERVER_DATA) + return AutofillType(server_type_); + + return AutofillType(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().IsUnknown(); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_field.h b/chromium/components/autofill/core/browser/autofill_field.h new file mode 100644 index 00000000000..6f57484d535 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_field.h @@ -0,0 +1,103 @@ +// 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_CORE_BROWSER_AUTOFILL_FIELD_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_FIELD_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/common/form_field_data.h" + +namespace autofill { + +class AutofillType; + +class AutofillField : public FormFieldData { + public: + enum PhonePart { + IGNORED = 0, + PHONE_PREFIX = 1, + PHONE_SUFFIX = 2, + }; + + AutofillField(); + AutofillField(const FormFieldData& field, const base::string16& unique_name); + virtual ~AutofillField(); + + const base::string16& unique_name() const { return unique_name_; } + + const std::string& section() const { return section_; } + ServerFieldType heuristic_type() const { return heuristic_type_; } + ServerFieldType server_type() const { return server_type_; } + HtmlFieldType html_type() const { return html_type_; } + HtmlFieldMode html_mode() const { return html_mode_; } + const ServerFieldTypeSet& possible_types() const { return possible_types_; } + PhonePart phone_part() const { return phone_part_; } + + // Setters for the detected type and section for this field. + void set_section(const std::string& section) { section_ = section; } + void set_heuristic_type(ServerFieldType type); + void set_server_type(ServerFieldType type); + void set_possible_types(const ServerFieldTypeSet& possible_types) { + possible_types_ = possible_types; + } + void SetHtmlType(HtmlFieldType type, HtmlFieldMode mode); + + // This function automatically chooses between server and heuristic autofill + // type, depending on the data available. + AutofillType 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. + base::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. + ServerFieldType server_type_; + + // The type of the field, as determined by the local heuristics. + ServerFieldType heuristic_type_; + + // The type of the field, as specified by the site author in HTML. + HtmlFieldType html_type_; + + // The "mode" of the field, as specified by the site author in HTML. + // Currently this is used to distinguish between billing and shipping fields. + HtmlFieldMode html_mode_; + + // The set of possible types for this field. + ServerFieldTypeSet 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); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_FIELD_H_ diff --git a/chromium/components/autofill/core/browser/autofill_field_unittest.cc b/chromium/components/autofill/core/browser/autofill_field_unittest.cc new file mode 100644 index 00000000000..3c263023783 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_field_unittest.cc @@ -0,0 +1,99 @@ +// 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 "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/field_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { +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().GetStorableType()); + + // Set the heuristic type and check it. + field.set_heuristic_type(NAME_FIRST); + EXPECT_EQ(NAME_FIRST, field.Type().GetStorableType()); + EXPECT_EQ(NAME, field.Type().group()); + + // Set the server type and check it. + field.set_server_type(ADDRESS_BILLING_LINE1); + EXPECT_EQ(ADDRESS_HOME_LINE1, field.Type().GetStorableType()); + EXPECT_EQ(ADDRESS_BILLING, field.Type().group()); + + // 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().GetStorableType()); + EXPECT_EQ(NAME, field.Type().group()); +} + +TEST(AutofillFieldTest, IsEmpty) { + AutofillField field; + ASSERT_EQ(base::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(base::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().GetStorableType()); + + // 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 +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_ie_toolbar_import_win.cc b/chromium/components/autofill/core/browser/autofill_ie_toolbar_import_win.cc new file mode 100644 index 00000000000..388e079f3ef --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_ie_toolbar_import_win.cc @@ -0,0 +1,312 @@ +// 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/core/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/strings/string16.h" +#include "base/win/registry.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/crypto/rc4_decryptor.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/form_group.h" +#include "components/autofill/core/browser/personal_data_manager.h" +#include "components/autofill/core/browser/personal_data_manager_observer.h" +#include "components/autofill/core/browser/phone_number.h" +#include "components/autofill/core/browser/phone_number_i18n.h" +#include "components/webdata/encryptor/encryptor.h" + +using base::win::RegKey; + +namespace autofill { + +// Forward declaration. This function is not in unnamed namespace as it +// is referenced in the unittest. +bool ImportCurrentUserProfiles(const std::string& app_locale, + 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 string is stored along with saved addresses and credit cards in the +// WebDB, and hence should not be modified, so that it remains consistent over +// time. +const char kIEToolbarImportOrigin[] = "Imported from Internet Explorer"; + +// 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; +} + +base::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 base::string16(); + std::string 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 (Encryptor::DecryptString(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 base::string16( + reinterpret_cast<const wchar_t *>(out_data.c_str())); + } + } + } + return base::string16(); +} + +struct { + ServerFieldType 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, ServerFieldType> 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, + const std::string& app_locale, + FormGroup* form_group, + PhoneNumber::PhoneCombineHelper* phone) { + if (!key.Valid()) + return false; + + bool has_non_empty_fields = false; + + 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. + + base::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(AutofillType(it->second), field_value)) { + has_non_empty_fields = true; + form_group->SetInfo(AutofillType(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 std::string& app_locale, + const RegKey& key, + const RegToFieldMap& reg_to_field, + AutofillProfile* profile) { + PhoneNumber::PhoneCombineHelper phone; + bool has_non_empty_fields = + ImportSingleFormGroup(key, reg_to_field, app_locale, profile, &phone); + + // Now re-construct the phones if needed. + base::string16 constructed_number; + 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(personal_data_manager_->app_locale(), + &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(const std::string& app_locale, + 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; + profile.set_origin(kIEToolbarImportOrigin); + if (ImportSingleProfile(app_locale, key, reg_to_field, &profile)) { + // Combine phones into whole phone #. + profiles->push_back(profile); + } + } + base::string16 password_hash; + base::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; + credit_card.set_origin(kIEToolbarImportOrigin); + if (ImportSingleFormGroup( + key, reg_to_field, app_locale, &credit_card, NULL)) { + base::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(); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_ie_toolbar_import_win.h b/chromium/components/autofill/core/browser/autofill_ie_toolbar_import_win.h new file mode 100644 index 00000000000..830aac2555f --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_ie_toolbar_import_win.h @@ -0,0 +1,24 @@ +// 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_CORE_BROWSER_AUTOFILL_IE_TOOLBAR_IMPORT_WIN_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_IE_TOOLBAR_IMPORT_WIN_H_ + +namespace autofill { + +// 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); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_IE_TOOLBAR_IMPORT_WIN_H_ diff --git a/chromium/components/autofill/core/browser/autofill_ie_toolbar_import_win_unittest.cc b/chromium/components/autofill/core/browser/autofill_ie_toolbar_import_win_unittest.cc new file mode 100644 index 00000000000..61917bcaf37 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_ie_toolbar_import_win_unittest.cc @@ -0,0 +1,211 @@ +// 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/core/browser/autofill_ie_toolbar_import_win.h" + +#include "base/basictypes.h" +#include "base/strings/string16.h" +#include "base/win/registry.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/webdata/encryptor/encryptor.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::win::RegKey; + +namespace autofill { + +// Defined in autofill_ie_toolbar_import_win.cc. Not exposed in the header file. +bool ImportCurrentUserProfiles(const std::string& app_locale, + 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::string encrypted_data; + Encryptor::EncryptString(data, &encrypted_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("en-US", &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(AutofillType(PHONE_HOME_COUNTRY_CODE), "US")); + EXPECT_EQ(profile1[6].value, + profiles[1].GetInfo(AutofillType(PHONE_HOME_CITY_CODE), "US")); + EXPECT_EQ(L"5555555", + profiles[1].GetInfo(AutofillType(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("en-US", &profiles, &credit_cards)); + // Profiles are not protected. + EXPECT_EQ(2U, profiles.size()); + // Credit cards are. + EXPECT_EQ(0U, credit_cards.size()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_manager.cc b/chromium/components/autofill/core/browser/autofill_manager.cc new file mode 100644 index 00000000000..4c66699b819 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_manager.cc @@ -0,0 +1,1219 @@ +// 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/core/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/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/sequenced_worker_pool.h" +#include "components/autofill/content/browser/autocheckout/whitelist_manager.h" +#include "components/autofill/content/browser/autocheckout_manager.h" +#include "components/autofill/core/browser/autocomplete_history_manager.h" +#include "components/autofill/core/browser/autofill_data_model.h" +#include "components/autofill/core/browser/autofill_driver.h" +#include "components/autofill/core/browser/autofill_external_delegate.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_manager_delegate.h" +#include "components/autofill/core/browser/autofill_manager_test_delegate.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/browser/personal_data_manager.h" +#include "components/autofill/core/browser/phone_number.h" +#include "components/autofill/core/browser/phone_number_i18n.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/common/autofill_pref_names.h" +#include "components/autofill/core/common/autofill_switches.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_data_predictions.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/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/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 "grit/component_strings.h" +#include "third_party/WebKit/public/web/WebAutofillClient.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/rect.h" +#include "url/gurl.h" + +namespace autofill { + +typedef PersonalDataManager::GUIDPair GUIDPair; + +using base::TimeTicks; +using content::BrowserThread; +using content::RenderViewHost; +using WebKit::WebFormElement; + +namespace { + +// 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<base::string16>* values, + std::vector<base::string16>* labels, + std::vector<base::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<base::string16, base::string16> > seen_suggestions; + std::vector<base::string16> values_copy; + std::vector<base::string16> labels_copy; + std::vector<base::string16> icons_copy; + std::vector<int> unique_ids_copy; + + for (size_t i = 0; i < values->size(); ++i) { + const std::pair<base::string16, base::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); + base::string16 value = CollapseWhitespace(field->value, false); + + ServerFieldTypeSet 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); + } +} + +// Returns true if server returned known field types to one or more fields in +// this form. +bool HasServerSpecifiedFieldTypes(const FormStructure& form_structure) { + for (size_t i = 0; i < form_structure.field_count(); ++i) { + if (form_structure.field(i)->server_type() != NO_SERVER_DATA) + return true; + } + return false; +} + +} // namespace + +AutofillManager::AutofillManager( + AutofillDriver* driver, + autofill::AutofillManagerDelegate* delegate, + const std::string& app_locale, + AutofillDownloadManagerState enable_download_manager) + : driver_(driver), + manager_delegate_(delegate), + app_locale_(app_locale), + personal_data_(delegate->GetPersonalDataManager()), + autocomplete_history_manager_( + new AutocompleteHistoryManager(driver, delegate)), + 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), + external_delegate_(NULL), + test_delegate_(NULL), + weak_ptr_factory_(this) { + if (enable_download_manager == ENABLE_AUTOFILL_DOWNLOAD_MANAGER) { + download_manager_.reset( + new AutofillDownloadManager( + driver->GetWebContents()->GetBrowserContext(), this)); + } +} + +AutofillManager::~AutofillManager() {} + +// static +void AutofillManager::RegisterProfilePrefs( + user_prefs::PrefRegistrySyncable* registry) { + registry->RegisterBooleanPref( + prefs::kAutofillEnabled, + true, + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); +#if defined(OS_MACOSX) || defined(OS_ANDROID) + registry->RegisterBooleanPref( + prefs::kAutofillAuxiliaryProfilesEnabled, + true, + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); +#else + registry->RegisterBooleanPref( + prefs::kAutofillAuxiliaryProfilesEnabled, + false, + user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); +#endif + registry->RegisterDoublePref( + prefs::kAutofillPositiveUploadRate, + kAutofillPositiveUploadRateDefaultValue, + user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); + registry->RegisterDoublePref( + prefs::kAutofillNegativeUploadRate, + kAutofillNegativeUploadRateDefaultValue, + user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); +} + +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::OnFormSubmitted(const FormData& form, + const TimeTicks& timestamp) { + // Let Autocomplete know as well. + autocomplete_history_manager_->OnFormSubmitted(form); + + if (!IsAutofillEnabled()) + return false; + + if (driver_->GetWebContents()->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); + // Don't prompt the user to save data entered by Autocheckout. + if (submitted_form->IsAutofillable(true) && + !submitted_form->filled_by_autocheckout()) + 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_->GetCreditCards(); + 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, + app_locale_, + 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, + autofill::FormsSeenState state) { + bool is_post_document_load = state == autofill::DYNAMIC_FORMS_SEEN; + bool has_more_forms = state == autofill::PARTIAL_FORMS_SEEN; + // If new forms were added dynamically, and the autocheckout manager + // doesn't tell us to ignore ajax on this page, treat as a new page. + if (is_post_document_load) { + if (autocheckout_manager_.ShouldIgnoreAjax()) + return; + + Reset(); + } + + RenderViewHost* host = driver_->GetWebContents()->GetRenderViewHost(); + if (!host) + return; + + if (!GetAutocheckoutURLPrefix().empty()) { + // If whitelisted URL, fetch all the forms. + if (has_more_forms) + host->Send(new AutofillMsg_GetAllForms(host->GetRoutingID())); + if (!is_post_document_load) { + host->Send( + new AutofillMsg_AutocheckoutSupported(host->GetRoutingID())); + } + // Now return early, as OnFormsSeen will get called again with all forms. + if (has_more_forms) + return; + } + + 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_) { + autocheckout_manager_.set_should_show_bubble(false); + 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) { + if (autocheckout_manager_.is_autocheckout_bubble_showing()) + return; + + std::vector<base::string16> values; + std::vector<base::string16> labels; + std::vector<base::string16> icons; + std::vector<int> unique_ids; + + 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)) { + AutofillType type = autofill_field->Type(); + bool is_filling_credit_card = (type.group() == 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, base::string16()); + icons.assign(1, base::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(), base::string16()); + icons.assign(icons.size(), base::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; + } + } + } + } + + // 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 AutofillDataModel* data_model = NULL; + size_t variant = 0; + FormStructure* form_structure = NULL; + AutofillField* autofill_field = NULL; + // NOTE: GetHost may invalidate |data_model| because it causes the + // PersonalDataManager to reload Mac address book entries. Thus it must + // come before GetProfileOrCreditCard. + if (!GetHost(&host) || + !GetProfileOrCreditCard(unique_id, &data_model, &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) { + data_model->FillFormField( + *autofill_field, variant, app_locale_, &(*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; + } + } + + driver_->SendFormDataToRenderer(query_id, result); + return; + } + + // Cache the field type for the field from which the user initiated autofill. + FieldTypeGroup initiating_group_type = 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 = cached_field->Type().group(); + if (field_group_type != 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; + } + data_model->FillFormField(*cached_field, + use_variant, + app_locale_, + &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(); + + driver_->SendFormDataToRenderer(query_id, result); +} + +void AutofillManager::OnShowAutofillDialog() { + manager_delegate_->ShowAutofillSettings(); +} + +void AutofillManager::OnDidPreviewAutofillFormData() { + if (test_delegate_) + test_delegate_->DidPreviewFormData(); +} + +void AutofillManager::OnDidFillAutofillFormData(const TimeTicks& timestamp) { + if (test_delegate_) + test_delegate_->DidFillFormData(); + + 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) { + if (test_delegate_) + test_delegate_->DidShowSuggestions(); + + 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::OnHideAutofillUI() { + if (!IsAutofillEnabled()) + return; + + manager_delegate_->HideAutofillPopup(); + manager_delegate_->HideAutocheckoutBubble(); +} + +void AutofillManager::RemoveAutofillProfileOrCreditCard(int unique_id) { + const AutofillDataModel* data_model = NULL; + size_t variant = 0; + if (!GetProfileOrCreditCard(unique_id, &data_model, &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(data_model->guid()); +} + +void AutofillManager::RemoveAutocompleteEntry(const base::string16& name, + const base::string16& value) { + autocomplete_history_manager_->OnRemoveAutocompleteEntry(name, value); +} + +content::WebContents* AutofillManager::GetWebContents() const { + return driver_->GetWebContents(); +} + +const std::vector<FormStructure*>& AutofillManager::GetFormStructures() { + return form_structures_.get(); +} + +void AutofillManager::ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + autofill::DialogType dialog_type, + const base::Callback<void(const FormStructure*, + const std::string&)>& callback) { + manager_delegate_->ShowRequestAutocompleteDialog( + form, source_url, dialog_type, callback); +} + +void AutofillManager::SetTestDelegate( + autofill::AutofillManagerTestDelegate* delegate) { + test_delegate_ = delegate; +} + +void AutofillManager::OnAddPasswordFormMapping( + const FormFieldData& form, + const PasswordFormFillData& fill_data) { + external_delegate_->AddPasswordFormMapping(form, fill_data); +} + +void AutofillManager::OnShowPasswordSuggestions( + const FormFieldData& field, + const gfx::RectF& bounds, + const std::vector<base::string16>& suggestions, + const std::vector<base::string16>& realms) { + external_delegate_->OnShowPasswordSuggestions(suggestions, + realms, + field, + bounds); +} + +void AutofillManager::OnSetDataList(const std::vector<base::string16>& values, + const std::vector<base::string16>& labels) { + if (values.size() != labels.size()) + return; + + external_delegate_->SetCurrentDataListValues(values, labels); +} + +void AutofillManager::OnRequestAutocomplete( + const FormData& form, + const GURL& frame_url) { + if (!IsAutofillEnabled()) { + ReturnAutocompleteResult(WebFormElement::AutocompleteResultErrorDisabled, + FormData()); + return; + } + + base::Callback<void(const FormStructure*, const std::string&)> callback = + base::Bind(&AutofillManager::ReturnAutocompleteData, + weak_ptr_factory_.GetWeakPtr()); + ShowRequestAutocompleteDialog( + form, frame_url, autofill::DIALOG_TYPE_REQUEST_AUTOCOMPLETE, callback); +} + +void AutofillManager::ReturnAutocompleteResult( + WebFormElement::AutocompleteResult result, const FormData& form_data) { + // driver_->GetWebContents() will be NULL when the interactive autocomplete + // is closed due to a tab or browser window closing. + if (!driver_->GetWebContents()) + return; + + RenderViewHost* host = driver_->GetWebContents()->GetRenderViewHost(); + if (!host) + return; + + host->Send(new AutofillMsg_RequestAutocompleteResult(host->GetRoutingID(), + result, + form_data)); +} + +void AutofillManager::ReturnAutocompleteData( + const FormStructure* result, + const std::string& unused_transaction_id) { + 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_); + + if (page_meta_data->IsInAutofillableFlow()) { + RenderViewHost* host = driver_->GetWebContents()->GetRenderViewHost(); + if (host) + host->Send(new AutofillMsg_AutocheckoutSupported(host->GetRoutingID())); + } + + // TODO(ahutter): Remove this once Autocheckout is implemented on other + // platforms. See http://crbug.com/173416. +#if defined(TOOLKIT_VIEWS) + if (!GetAutocheckoutURLPrefix().empty()) + autocheckout_manager_.OnLoadedPageMetaData(page_meta_data.Pass()); +#endif // #if defined(TOOLKIT_VIEWS) + + // If the corresponding flag is set, annotate forms with the predicted types. + driver_->SendAutofillTypePredictionsToRenderer(form_structures_.get()); +} + +void AutofillManager::OnDidEndTextFieldEditing() { + external_delegate_->DidEndTextFieldEditing(); +} + +void AutofillManager::OnAutocheckoutPageCompleted( + autofill::AutocheckoutStatus status) { + autocheckout_manager_.OnAutocheckoutPageCompleted(status); +} + +std::string AutofillManager::GetAutocheckoutURLPrefix() const { + if (!driver_->GetWebContents()) + return std::string(); + + autofill::autocheckout::WhitelistManager* whitelist_manager = + manager_delegate_->GetAutocheckoutWhitelistManager(); + + return whitelist_manager ? whitelist_manager->GetMatchedURLPrefix( + driver_->GetWebContents()->GetURL()) : std::string(); +} + +bool AutofillManager::IsAutofillEnabled() const { + return manager_delegate_->GetPrefs()->GetBoolean(prefs::kAutofillEnabled); +} + +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::OnMaybeShowAutocheckoutBubble( + const FormData& form, + const gfx::RectF& bounding_box) { + if (!IsAutofillEnabled()) + return; + + // Don't show bubble if corresponding FormStructure doesn't have anything to + // autofill. + FormStructure* cached_form; + if (!FindCachedForm(form, &cached_form)) + return; + + // Don't offer Autocheckout bubble if Autofill server is not aware of this + // form in the context of Autocheckout experiment. + if (!HasServerSpecifiedFieldTypes(*cached_form)) + return; + + autocheckout_manager_.MaybeShowAutocheckoutBubble(form.origin, bounding_box); +} + +void AutofillManager::UploadFormData(const FormStructure& submitted_form) { + if (!download_manager_) + 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; + } + + ServerFieldTypeSet 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(); + external_delegate_->Reset(); +} + +AutofillManager::AutofillManager(AutofillDriver* driver, + autofill::AutofillManagerDelegate* delegate, + PersonalDataManager* personal_data) + : driver_(driver), + manager_delegate_(delegate), + app_locale_("en-US"), + personal_data_(personal_data), + autocomplete_history_manager_( + new AutocompleteHistoryManager(driver, delegate)), + 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), + external_delegate_(NULL), + test_delegate_(NULL), + weak_ptr_factory_(this) { + DCHECK(driver_); + DCHECK(driver_->GetWebContents()); + DCHECK(manager_delegate_); +} + +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_->GetCreditCards().empty()) { + return false; + } + + if (!driver_->RendererIsAvailable()) + return false; + + *host = driver_->GetWebContents()->GetRenderViewHost(); + return true; +} + +bool AutofillManager::GetProfileOrCreditCard( + int unique_id, + const AutofillDataModel** data_model, + 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)) { + *data_model = personal_data_->GetProfileByGUID(profile_guid.first); + *variant = profile_guid.second; + } else if (base::IsValidGUID(credit_card_guid.first)) { + *data_model = personal_data_->GetCreditCardByGUID(credit_card_guid.first); + *variant = credit_card_guid.second; + } + + return !!*data_model; +} + +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<base::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<base::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); + driver_->SendAutofillTypePredictionsToRenderer(forms); + + return true; +} + +void AutofillManager::GetProfileSuggestions( + FormStructure* form, + const FormFieldData& field, + const AutofillType& type, + std::vector<base::string16>* values, + std::vector<base::string16>* labels, + std::vector<base::string16>* icons, + std::vector<int>* unique_ids) const { + std::vector<ServerFieldType> field_types(form->field_count()); + for (size_t i = 0; i < form->field_count(); ++i) { + field_types.push_back(form->field(i)->Type().GetStorableType()); + } + 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, + const AutofillType& type, + std::vector<base::string16>* values, + std::vector<base::string16>* labels, + std::vector<base::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 (form_structures_.empty()) { + // Call OnLoadedPageMetaData with no page metadata immediately if there is + // no form in the page. This give |autocheckout_manager| a chance to + // terminate Autocheckout and send Autocheckout status. + autocheckout_manager_.OnLoadedPageMetaData( + scoped_ptr<autofill::AutocheckoutPageMetaData>()); + } else if (download_manager_) { + // Query the server if we have at least one of the forms were parsed. + 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. + driver_->SendAutofillTypePredictionsToRenderer(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; + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_manager.h b/chromium/components/autofill/core/browser/autofill_manager.h new file mode 100644 index 00000000000..b9e4d311522 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_manager.h @@ -0,0 +1,407 @@ +// 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_CORE_BROWSER_AUTOFILL_MANAGER_H_ +#define COMPONENTS_AUTOFILL_CORE_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/strings/string16.h" +#include "base/time/time.h" +#include "components/autofill/content/browser/autocheckout_manager.h" +#include "components/autofill/core/browser/autocomplete_history_manager.h" +#include "components/autofill/core/browser/autofill_download.h" +#include "components/autofill/core/browser/autofill_manager_delegate.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/browser/personal_data_manager.h" +#include "components/autofill/core/common/autocheckout_status.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/forms_seen_state.h" +#include "third_party/WebKit/public/web/WebFormElement.h" + +class GURL; + +namespace content { +class RenderViewHost; +class WebContents; +} + +namespace gfx { +class Rect; +class RectF; +} + +namespace user_prefs { +class PrefRegistrySyncable; +} + +namespace autofill { + +class AutofillDriver; +class AutofillDataModel; +class AutofillDownloadManager; +class AutofillExternalDelegate; +class AutofillField; +class AutofillManagerDelegate; +class AutofillManagerTestDelegate; +class AutofillMetrics; +class AutofillProfile; +class AutofillType; +class CreditCard; +class FormStructureBrowserTest; + +struct FormData; +struct FormFieldData; +struct PasswordFormFillData; + +// Manages saving and restoring the user's personal information entered into web +// forms. +class AutofillManager : public AutofillDownloadManager::Observer { + public: + enum AutofillDownloadManagerState { + ENABLE_AUTOFILL_DOWNLOAD_MANAGER, + DISABLE_AUTOFILL_DOWNLOAD_MANAGER, + }; + + // Registers our Enable/Disable Autofill pref. + static void RegisterProfilePrefs(user_prefs::PrefRegistrySyncable* registry); + + AutofillManager(AutofillDriver* driver, + autofill::AutofillManagerDelegate* delegate, + const std::string& app_locale, + AutofillDownloadManagerState enable_download_manager); + virtual ~AutofillManager(); + + // Sets an external delegate. + void SetExternalDelegate(AutofillExternalDelegate* delegate); + + // 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(); + + // 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 base::string16& name, + const base::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, + autofill::DialogType dialog_type, + const base::Callback<void(const FormStructure*, + const std::string&)>& callback); + + // Happens when the autocomplete dialog runs its callback when being closed. + void RequestAutocompleteDialogClosed(); + + autofill::AutofillManagerDelegate* delegate() const { + return manager_delegate_; + } + + const std::string& app_locale() const { return app_locale_; } + + // Only for testing. + void SetTestDelegate(autofill::AutofillManagerTestDelegate* delegate); + + void OnFormsSeen(const std::vector<FormData>& forms, + const base::TimeTicks& timestamp, + autofill::FormsSeenState state); + + // Processes the submitted |form|, saving any new Autofill data and uploading + // the possible field types for the submitted fields to the crowdsourcing + // server. Returns false if this form is not relevant for Autofill. + bool OnFormSubmitted(const FormData& form, + 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 OnHideAutofillUI(); + void OnAddPasswordFormMapping( + const FormFieldData& form, + const PasswordFormFillData& fill_data); + void OnShowPasswordSuggestions( + const FormFieldData& field, + const gfx::RectF& bounds, + const std::vector<base::string16>& suggestions, + const std::vector<base::string16>& realms); + void OnSetDataList(const std::vector<base::string16>& values, + const std::vector<base::string16>& labels); + + // Requests an interactive autocomplete UI be shown. + void OnRequestAutocomplete(const FormData& form, + const GURL& frame_url); + + // Called to signal a page is completed in renderer in the Autocheckout flow. + void OnAutocheckoutPageCompleted(autofill::AutocheckoutStatus status); + + // Shows the Autocheckout bubble if conditions are right. See comments for + // AutocheckoutManager::MaybeShowAutocheckoutBubble. Input element requesting + // bubble belongs to |form|. |bounding_box| is the bounding box of the input + // field in focus. + virtual void OnMaybeShowAutocheckoutBubble(const FormData& form, + const gfx::RectF& bounding_box); + + // Resets cache. + virtual void Reset(); + + autofill::AutocheckoutManager* autocheckout_manager() { + return &autocheckout_manager_; + } + + protected: + // Test code should prefer to use this constructor. + AutofillManager(AutofillDriver* driver, + 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); + + // 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_; + } + + // Tell the renderer the current interactive autocomplete finished. + virtual void ReturnAutocompleteResult( + WebKit::WebFormElement::AutocompleteResult result, + const FormData& form_data); + + private: + + // AutofillDownloadManager::Observer: + virtual void OnLoadedServerPredictions( + const std::string& response_xml) OVERRIDE; + + // Passes return data for an OnRequestAutocomplete call back to the page. + void ReturnAutocompleteData(const FormStructure* result, + const std::string& unused_transaction_id); + + // Returns the matched whitelist URL prefix for the current tab's url. + virtual 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 AutofillDataModel** data_model, + 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, + const AutofillType& type, + std::vector<base::string16>* values, + std::vector<base::string16>* labels, + std::vector<base::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, + const AutofillType& type, + std::vector<base::string16>* values, + std::vector<base::string16>* labels, + std::vector<base::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); + + // Provides driver-level context to the shared code of the component. Must + // outlive this object. + AutofillDriver* driver_; + + autofill::AutofillManagerDelegate* const manager_delegate_; + + std::string app_locale_; + + // 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. Will be NULL if + // the download manager functionality is disabled. + scoped_ptr<AutofillDownloadManager> download_manager_; + + // Handles single-field autocomplete form data. + scoped_ptr<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_; + + // 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_; + + // Delegate used in test to get notifications on certain events. + autofill::AutofillManagerTestDelegate* test_delegate_; + + base::WeakPtrFactory<AutofillManager> weak_ptr_factory_; + + friend class AutofillManagerTest; + friend class autofill::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); + FRIEND_TEST_ALL_PREFIXES(AutofillManagerTest, + FormSubmittedAutocompleteEnabled); + DISALLOW_COPY_AND_ASSIGN(AutofillManager); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_MANAGER_H_ diff --git a/chromium/components/autofill/core/browser/autofill_manager_delegate.h b/chromium/components/autofill/core/browser/autofill_manager_delegate.h new file mode 100644 index 00000000000..374c105fd5d --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_manager_delegate.h @@ -0,0 +1,149 @@ +// 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_CORE_BROWSER_AUTOFILL_MANAGER_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_MANAGER_DELEGATE_H_ + +#include <vector> + +#include "base/callback_forward.h" +#include "base/i18n/rtl.h" +#include "base/memory/weak_ptr.h" +#include "base/strings/string16.h" +#include "components/autofill/content/browser/autocheckout_steps.h" +#include "components/autofill/core/browser/autocheckout_bubble_state.h" + +namespace content { +struct PasswordForm; +} + +namespace gfx { +class Rect; +class RectF; +} + +class GURL; +class InfoBarService; +class PrefService; + +namespace autofill { + +class AutofillMetrics; +class AutofillPopupDelegate; +class CreditCard; +class FormStructure; +class PasswordGenerator; +class PersonalDataManager; +struct FormData; + +namespace autocheckout { +class WhitelistManager; +} + +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; + + // Gets the autocheckout::WhitelistManager instance associated with the + // delegate. + virtual autocheckout::WhitelistManager* + GetAutocheckoutWhitelistManager() const = 0; + + // Hides the associated request autocomplete dialog (if it exists). + virtual void HideRequestAutocompleteDialog() = 0; + + // Causes an error explaining that Autocheckout has failed to be displayed to + // the user. + virtual void OnAutocheckoutError() = 0; + + // Called when an Autocheckout flow has succeeded. Causes a notification + // explaining that they must confirm their purchase to be displayed to the + // user. + virtual void OnAutocheckoutSuccess() = 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 Autocheckout bubble UI to be displayed. |bounding_box| is the + // anchor for the bubble. |is_google_user| is whether or not the user is + // logged into or has been logged into accounts.google.com. |callback| is run + // if the bubble is accepted. The returned boolean informs the caller whether + // or not the bubble is successfully shown. + virtual bool ShowAutocheckoutBubble( + const gfx::RectF& bounding_box, + bool is_google_user, + const base::Callback<void(AutocheckoutBubbleState)>& callback) = 0; + + // Causes the dialog for request autocomplete feature to be shown. + virtual void ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + DialogType dialog_type, + const base::Callback<void(const FormStructure*, + const std::string&)>& callback) = 0; + + // Hide the Autocheckout bubble if one is currently showing. + virtual void HideAutocheckoutBubble() = 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, + base::i18n::TextDirection text_direction, + const std::vector<base::string16>& values, + const std::vector<base::string16>& labels, + const std::vector<base::string16>& icons, + const std::vector<int>& identifiers, + base::WeakPtr<AutofillPopupDelegate> delegate) = 0; + + // Update the data list values shown by the Autofill popup, if visible. + virtual void UpdateAutofillPopupDataListValues( + const std::vector<base::string16>& values, + const std::vector<base::string16>& labels) = 0; + + // Hide the Autofill popup if one is currently showing. + virtual void HideAutofillPopup() = 0; + + // Whether the Autocomplete feature of Autofill should be enabled. + virtual bool IsAutocompleteEnabled() = 0; + + // Update progress of the Autocheckout flow as displayed to the user. + virtual void AddAutocheckoutStep(AutocheckoutStepType step_type) = 0; + virtual void UpdateAutocheckoutStep( + AutocheckoutStepType step_type, + AutocheckoutStepStatus step_status) = 0; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_MANAGER_DELEGATE_H_ diff --git a/chromium/components/autofill/core/browser/autofill_manager_test_delegate.h b/chromium/components/autofill/core/browser/autofill_manager_test_delegate.h new file mode 100644 index 00000000000..222a8df2925 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_manager_test_delegate.h @@ -0,0 +1,26 @@ +// 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_CORE_BROWSER_AUTOFILL_MANAGER_TEST_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_MANAGER_TEST_DELEGATE_H_ + +namespace autofill { + +class AutofillManagerTestDelegate { + public: + virtual ~AutofillManagerTestDelegate() {} + + // Called when a form is previewed with Autofill suggestions. + virtual void DidPreviewFormData() = 0; + + // Called when a form is filled with Autofill suggestions. + virtual void DidFillFormData() = 0; + + // Called when a popup with Autofill suggestions is shown. + virtual void DidShowSuggestions() = 0; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_MANAGER_TEST_DELEGATE_H_ diff --git a/chromium/components/autofill/core/browser/autofill_manager_unittest.cc b/chromium/components/autofill/core/browser/autofill_manager_unittest.cc new file mode 100644 index 00000000000..7aeb81ec6d5 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_manager_unittest.cc @@ -0,0 +1,3271 @@ +// 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 <algorithm> +#include <vector> + +#include "base/command_line.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/prefs/pref_service.h" +#include "base/strings/string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/tuple.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/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/core/browser/autocomplete_history_manager.h" +#include "components/autofill/core/browser/autofill_common_test.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/personal_data_manager.h" +#include "components/autofill/core/browser/test_autofill_driver.h" +#include "components/autofill/core/browser/test_autofill_external_delegate.h" +#include "components/autofill/core/browser/test_autofill_manager_delegate.h" +#include "components/autofill/core/common/autofill_messages.h" +#include "components/autofill/core/common/autofill_pref_names.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/forms_seen_state.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/web_contents.h" +#include "content/public/test/mock_render_process_host.h" +#include "content/public/test/test_utils.h" +#include "grit/component_strings.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/public/web/WebAutofillClient.h" +#include "third_party/WebKit/public/web/WebFormElement.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/rect.h" +#include "url/gurl.h" + +using content::WebContents; +using testing::_; +using WebKit::WebFormElement; + +namespace autofill { + +typedef PersonalDataManager::GUIDPair GUIDPair; + +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; + +class TestPersonalDataManager : public PersonalDataManager { + public: + TestPersonalDataManager() : PersonalDataManager("en-US") { + 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 BrowserContextKeyedService* Build(content::BrowserContext* 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; + 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; + 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; + 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; + 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; + 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; + 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; + 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 3 fields and a field with autocomplete attribute. +void CreateTestFormWithAutocompleteAttribute(FormData* form) { + form->name = ASCIIToUTF16("UserSpecified"); + form->method = ASCIIToUTF16("POST"); + form->origin = GURL("http://myform.com/userspecified.html"); + form->action = GURL("http://myform.com/submit.html"); + form->user_submitted = true; + + FormFieldData field; + test::CreateTestFormField("First Name", "firstname", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("Middle Name", "middlename", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("Last Name", "lastname", "", "text", &field); + form->fields.push_back(field); + field.autocomplete_attribute="cc-type"; + test::CreateTestFormField("cc-type", "cc-type", "", "text", &field); + form->fields.push_back(field); +} + +// Populates |form| with data corresponding to a simple shipping options form. +void CreateTestShippingOptionsFormData(FormData* form) { + form->name = ASCIIToUTF16("Shipping Options"); + form->method = ASCIIToUTF16("POST"); + form->origin = GURL("http://myform.com/shipping.html"); + form->action = GURL("http://myform.com/submit.html"); + form->user_submitted = true; + + FormFieldData field; + test::CreateTestFormField("Shipping1", "option", "option1", "radio", &field); + form->fields.push_back(field); + test::CreateTestFormField("Shipping2", "option", "option2", "radio", &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; + test::CreateTestFormField("Name on Card", "nameoncard", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("Card Number", "cardnumber", "", "text", &field); + form->fields.push_back(field); + if (use_month_type) { + test::CreateTestFormField( + "Expiration Date", "ccmonth", "", "month", &field); + form->fields.push_back(field); + } else { + test::CreateTestFormField("Expiration Date", "ccmonth", "", "text", &field); + form->fields.push_back(field); + test::CreateTestFormField("", "ccyear", "", "text", &field); + form->fields.push_back(field); + } +} + +void ExpectSuggestions(int page_id, + const std::vector<base::string16>& values, + const std::vector<base::string16>& labels, + const std::vector<base::string16>& icons, + const std::vector<int>& unique_ids, + int expected_page_id, + size_t expected_num_suggestions, + const base::string16 expected_values[], + const base::string16 expected_labels[], + const base::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(base::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 MockAutocompleteHistoryManager : public AutocompleteHistoryManager { + public: + MockAutocompleteHistoryManager(AutofillDriver* driver, + AutofillManagerDelegate* delegate) + : AutocompleteHistoryManager(driver, delegate) {} + + MOCK_METHOD1(OnFormSubmitted, void(const FormData& form)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutocompleteHistoryManager); +}; + +class MockAutofillDriver : public TestAutofillDriver { + public: + explicit MockAutofillDriver(content::WebContents* web_contents) + : TestAutofillDriver(web_contents) {} + + // Mock methods to enable testability. + MOCK_METHOD2(SendFormDataToRenderer, void(int query_id, + const FormData& data)); + + private: + DISALLOW_COPY_AND_ASSIGN(MockAutofillDriver); +}; + +class TestAutofillManager : public AutofillManager { + public: + TestAutofillManager(AutofillDriver* driver, + autofill::AutofillManagerDelegate* delegate, + TestPersonalDataManager* personal_data) + : AutofillManager(driver, delegate, personal_data), + personal_data_(personal_data), + autofill_enabled_(true) {} + virtual ~TestAutofillManager() {} + + virtual bool IsAutofillEnabled() const OVERRIDE { return autofill_enabled_; } + + void set_autofill_enabled(bool autofill_enabled) { + autofill_enabled_ = autofill_enabled; + } + + void set_autocheckout_url_prefix(const std::string& autocheckout_url_prefix) { + autocheckout_url_prefix_ = autocheckout_url_prefix; + } + + virtual std::string GetAutocheckoutURLPrefix() const OVERRIDE { + return autocheckout_url_prefix_; + } + + 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<ServerFieldTypeSet>& 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 { + message_loop_runner_->Quit(); + + // 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( + base::StringPrintf( + "Field %d with value %s", static_cast<int>(i), + UTF16ToUTF8(submitted_form->field(i)->value).c_str())); + const ServerFieldTypeSet& possible_types = + submitted_form->field(i)->possible_types(); + EXPECT_EQ(expected_submitted_field_types_[i].size(), + possible_types.size()); + for (ServerFieldTypeSet::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(*it).ToString(); + } + } + } + + AutofillManager::UploadFormDataAsyncCallback(submitted_form, + load_time, + interaction_time, + submission_time); + } + + virtual void OnMaybeShowAutocheckoutBubble( + const FormData& form, + const gfx::RectF& bounding_box) OVERRIDE { + AutofillManager::OnMaybeShowAutocheckoutBubble(form, bounding_box); + // Needed for AutocheckoutManager to post task on IO thread. + content::RunAllPendingInMessageLoop(content::BrowserThread::IO); + } + + // Resets the MessageLoopRunner so that it can wait for an asynchronous form + // submission to complete. + void ResetMessageLoopRunner() { + message_loop_runner_ = new content::MessageLoopRunner(); + } + + // Wait for the asynchronous OnFormSubmitted() call to complete. + void WaitForAsyncFormSubmit() { + message_loop_runner_->Run(); + } + + virtual void UploadFormData(const FormStructure& submitted_form) OVERRIDE { + submitted_form_signature_ = submitted_form.FormSignature(); + } + + 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); + } + + void ClearFormStructures() { + form_structures()->clear(); + } + + virtual void ReturnAutocompleteResult( + WebFormElement::AutocompleteResult result, + const FormData& form_data) OVERRIDE { + request_autocomplete_results_.push_back(std::make_pair(result, form_data)); + } + + // Set autocheckout manager's page meta data to first page on Autocheckout + // flow. + void MarkAsFirstPageInAutocheckoutFlow() { + scoped_ptr<AutocheckoutPageMetaData> start_of_flow( + new AutocheckoutPageMetaData()); + start_of_flow->current_page_number = 0; + start_of_flow->total_pages = 3; + WebElementDescriptor* proceed_element = + &start_of_flow->proceed_element_descriptor; + proceed_element->descriptor = "#foo"; + proceed_element->retrieval_method = WebElementDescriptor::ID; + autocheckout_manager()->OnLoadedPageMetaData(start_of_flow.Pass()); + } + + // Set autocheckout manager's page meta data to first page on Autocheckout + // flow. + void MarkAsFirstPageInAutocheckoutFlowIgnoringAjax() { + scoped_ptr<AutocheckoutPageMetaData> start_of_flow( + new AutocheckoutPageMetaData()); + start_of_flow->current_page_number = 0; + start_of_flow->total_pages = 3; + start_of_flow->ignore_ajax = true; + WebElementDescriptor* proceed_element = + &start_of_flow->proceed_element_descriptor; + proceed_element->descriptor = "#foo"; + proceed_element->retrieval_method = WebElementDescriptor::ID; + autocheckout_manager()->OnLoadedPageMetaData(start_of_flow.Pass()); + } + + private: + // Weak reference. + TestPersonalDataManager* personal_data_; + + bool autofill_enabled_; + std::vector<std::pair<WebFormElement::AutocompleteResult, FormData> > + request_autocomplete_results_; + + scoped_refptr<content::MessageLoopRunner> message_loop_runner_; + + std::string autocheckout_url_prefix_; + std::string submitted_form_signature_; + std::vector<ServerFieldTypeSet> expected_submitted_field_types_; + + DISALLOW_COPY_AND_ASSIGN(TestAutofillManager); +}; + +class TestAutofillExternalDelegate : public AutofillExternalDelegate { + public: + explicit TestAutofillExternalDelegate(content::WebContents* web_contents, + AutofillManager* autofill_manager, + AutofillDriver* autofill_driver) + : AutofillExternalDelegate(web_contents, autofill_manager, + autofill_driver), + on_query_seen_(false), + on_suggestions_returned_seen_(false) {} + virtual ~TestAutofillExternalDelegate() {} + + virtual void OnQuery(int query_id, + const FormData& form, + const FormFieldData& field, + const gfx::RectF& bounds, + bool display_warning) OVERRIDE { + on_query_seen_ = true; + on_suggestions_returned_seen_ = false; + } + + virtual void OnSuggestionsReturned( + int query_id, + const std::vector<base::string16>& autofill_values, + const std::vector<base::string16>& autofill_labels, + const std::vector<base::string16>& autofill_icons, + const std::vector<int>& autofill_unique_ids) OVERRIDE { + on_suggestions_returned_seen_ = true; + + query_id_ = query_id; + autofill_values_ = autofill_values; + autofill_labels_ = autofill_labels; + autofill_icons_ = autofill_icons; + autofill_unique_ids_ = autofill_unique_ids; + } + + void CheckSuggestions(int expected_page_id, + size_t expected_num_suggestions, + const base::string16 expected_values[], + const base::string16 expected_labels[], + const base::string16 expected_icons[], + const int expected_unique_ids[]) { + // Ensure that these results are from the most recent query. + EXPECT_TRUE(on_suggestions_returned_seen_); + + EXPECT_EQ(expected_page_id, query_id_); + ASSERT_EQ(expected_num_suggestions, autofill_values_.size()); + ASSERT_EQ(expected_num_suggestions, autofill_labels_.size()); + ASSERT_EQ(expected_num_suggestions, autofill_icons_.size()); + ASSERT_EQ(expected_num_suggestions, autofill_unique_ids_.size()); + for (size_t i = 0; i < expected_num_suggestions; ++i) { + SCOPED_TRACE(base::StringPrintf("i: %" PRIuS, i)); + EXPECT_EQ(expected_values[i], autofill_values_[i]); + EXPECT_EQ(expected_labels[i], autofill_labels_[i]); + EXPECT_EQ(expected_icons[i], autofill_icons_[i]); + EXPECT_EQ(expected_unique_ids[i], autofill_unique_ids_[i]); + } + } + + bool on_query_seen() const { + return on_query_seen_; + } + + bool on_suggestions_returned_seen() const { + return on_suggestions_returned_seen_; + } + + private: + // Records if OnQuery has been called yet. + bool on_query_seen_; + + // Records if OnSuggestionsReturned has been called after the most recent + // call to OnQuery. + bool on_suggestions_returned_seen_; + + // The query id of the most recent Autofill query. + int query_id_; + + // The results returned by the most recent Autofill query. + std::vector<base::string16> autofill_values_; + std::vector<base::string16> autofill_labels_; + std::vector<base::string16> autofill_icons_; + std::vector<int> autofill_unique_ids_; + + DISALLOW_COPY_AND_ASSIGN(TestAutofillExternalDelegate); +}; + +} // namespace + +class AutofillManagerTest : public ChromeRenderViewHostTestHarness { + public: + virtual void SetUp() OVERRIDE { + ChromeRenderViewHostTestHarness::SetUp(); + + autofill::PersonalDataManagerFactory::GetInstance()->SetTestingFactory( + profile(), TestPersonalDataManager::Build); + + + autofill::TabAutofillManagerDelegate::CreateForWebContents(web_contents()); + + personal_data_.SetBrowserContext(profile()); + autofill_driver_.reset(new MockAutofillDriver(web_contents())); + autofill_manager_.reset(new TestAutofillManager( + autofill_driver_.get(), + autofill::TabAutofillManagerDelegate::FromWebContents(web_contents()), + &personal_data_)); + + external_delegate_.reset(new TestAutofillExternalDelegate( + web_contents(), + autofill_manager_.get(), + autofill_driver_.get())); + autofill_manager_->SetExternalDelegate(external_delegate_.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(); + autofill_driver_.reset(); + ChromeRenderViewHostTestHarness::TearDown(); + + // Remove the BrowserContext so TestPersonalDataManager does not need to + // care about removing self as an observer in destruction. + personal_data_.SetBrowserContext(NULL); + } + + 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<base::string16>& result) { + autofill_manager_->autocomplete_history_manager_->SendSuggestions(&result); + } + + void FormsSeen(const std::vector<FormData>& forms) { + autofill_manager_->OnFormsSeen(forms, base::TimeTicks(), + autofill::NO_SPECIAL_FORMS_SEEN); + } + + void PartialFormsSeen(const std::vector<FormData>& forms) { + autofill_manager_->OnFormsSeen(forms, base::TimeTicks(), + autofill::PARTIAL_FORMS_SEEN); + } + + void DynamicFormsSeen(const std::vector<FormData>& forms) { + autofill_manager_->OnFormsSeen(forms, base::TimeTicks(), + autofill::DYNAMIC_FORMS_SEEN); + } + + void FormSubmitted(const FormData& form) { + autofill_manager_->ResetMessageLoopRunner(); + 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); + } + + // Calls |autofill_manager_->OnFillAutofillFormData()| with the specified + // input parameters after setting up the expectation that the mock driver's + // |SendFormDataToRenderer()| method will be called and saving the parameters + // of that call into the |response_query_id| and |response_data| output + // parameters. + void FillAutofillFormDataAndSaveResults(int input_query_id, + const FormData& input_form, + const FormFieldData& input_field, + int unique_id, + int* response_query_id, + FormData* response_data) { + EXPECT_CALL(*autofill_driver_, SendFormDataToRenderer(_, _)). + WillOnce((DoAll(testing::SaveArg<0>(response_query_id), + testing::SaveArg<1>(response_data)))); + FillAutofillFormData(input_query_id, input_form, input_field, unique_id); + } + + int PackGUIDs(const GUIDPair& cc_guid, const GUIDPair& profile_guid) const { + return autofill_manager_->PackGUIDs(cc_guid, profile_guid); + } + + + bool HasSeenAutofillGetAllFormsMessage() { + const uint32 kMsgID = AutofillMsg_GetAllForms::ID; + const IPC::Message* message = + process()->sink().GetFirstMessageMatching(kMsgID); + return message != NULL; + } + + protected: + scoped_ptr<MockAutofillDriver> autofill_driver_; + scoped_ptr<TestAutofillManager> autofill_manager_; + scoped_ptr<TestAutofillExternalDelegate> external_delegate_; + 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_; +}; + +class TestFormStructure : public FormStructure { + public: + explicit TestFormStructure(const FormData& form) + : FormStructure(form, std::string()) {} + virtual ~TestFormStructure() {} + + void SetFieldTypes(const std::vector<ServerFieldType>& heuristic_types, + const std::vector<ServerFieldType>& 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 browser asks for all forms when Autocheckout is enabled. +TEST_F(AutofillManagerTest, GetAllForms) { + FormData form; + test::CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + // Enable autocheckout. + autofill_manager_->set_autocheckout_url_prefix("test-prefix"); + + PartialFormsSeen(forms); + + ASSERT_TRUE(HasSeenAutofillGetAllFormsMessage()); +} + +// 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; + test::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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles") + }; + // Inferred labels include full first relevant field, which in this case is + // the address line 1. + base::string16 expected_labels[] = { + ASCIIToUTF16("3734 Elvis Presley Blvd."), + ASCIIToUTF16("123 Apple St.") + }; + base::string16 expected_icons[] = {base::string16(), base::string16()}; + int expected_unique_ids[] = {1, 2}; + external_delegate_->CheckSuggestions( + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// Test that in the case of Autocheckout, forms seen are in order supplied. +TEST_F(AutofillManagerTest, AutocheckoutFormsSeen) { + FormData shipping_options; + CreateTestShippingOptionsFormData(&shipping_options); + FormData user_supplied; + CreateTestFormWithAutocompleteAttribute(&user_supplied); + FormData address; + test::CreateTestAddressFormData(&address); + + // Push user_supplied before address and observe order changing when + // Autocheckout is not enabled.. + std::vector<FormData> forms; + forms.push_back(shipping_options); + forms.push_back(user_supplied); + forms.push_back(address); + + // Test without enabling Autocheckout. FormStructure should only contain + // form1. Shipping Options form will not qualify as parsable form. + FormsSeen(forms); + std::vector<FormStructure*> form_structures; + form_structures = autofill_manager_->GetFormStructures(); + ASSERT_EQ(2U, form_structures.size()); + EXPECT_EQ("/form.html", form_structures[0]->source_url().path()); + EXPECT_EQ("/userspecified.html", form_structures[1]->source_url().path()); + autofill_manager_->ClearFormStructures(); + + // Test after enabling Autocheckout. Order should be shipping_options, + // userspecified and then address form. + autofill_manager_->set_autocheckout_url_prefix("yes-autocheckout"); + FormsSeen(forms); + form_structures = autofill_manager_->GetFormStructures(); + ASSERT_EQ(3U, form_structures.size()); + EXPECT_EQ("/shipping.html", form_structures[0]->source_url().path()); + EXPECT_EQ("/userspecified.html", form_structures[1]->source_url().path()); + EXPECT_EQ("/form.html", form_structures[2]->source_url().path()); +} + +// Test that in the case of Autocheckout, forms seen are in order supplied. +TEST_F(AutofillManagerTest, DynamicFormsSeen) { + FormData shipping_options; + CreateTestShippingOptionsFormData(&shipping_options); + FormData user_supplied; + CreateTestFormWithAutocompleteAttribute(&user_supplied); + FormData address; + test::CreateTestAddressFormData(&address); + + autofill_manager_->set_autocheckout_url_prefix("test-prefix"); + // Push user_supplied only + std::vector<FormData> forms; + forms.push_back(user_supplied); + + // Make sure normal form is handled correctly. + FormsSeen(forms); + std::vector<FormStructure*> form_structures; + form_structures = autofill_manager_->GetFormStructures(); + ASSERT_EQ(1U, form_structures.size()); + EXPECT_EQ("/userspecified.html", form_structures[0]->source_url().path()); + + // Push other forms + forms.push_back(shipping_options); + forms.push_back(address); + + // FormStructure should contain three and only three forms. Otherwise, it + // would indicate that the manager didn't reset upon being notified of + // the new forms; + DynamicFormsSeen(forms); + form_structures = autofill_manager_->GetFormStructures(); + ASSERT_EQ(3U, form_structures.size()); + EXPECT_EQ("/userspecified.html", form_structures[0]->source_url().path()); + EXPECT_EQ("/shipping.html", form_structures[1]->source_url().path()); + EXPECT_EQ("/form.html", form_structures[2]->source_url().path()); +} + +// 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; + test::CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + FormFieldData field; + 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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = {ASCIIToUTF16("Elvis")}; + base::string16 expected_labels[] = {ASCIIToUTF16("3734 Elvis Presley Blvd.")}; + base::string16 expected_icons[] = {base::string16()}; + int expected_unique_ids[] = {1}; + external_delegate_->CheckSuggestions( + 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; + test::CreateTestFormField("Username", "username", "", "text",&field); + form.fields.push_back(field); + test::CreateTestFormField("Password", "password", "", "password",&field); + form.fields.push_back(field); + test::CreateTestFormField("Quest", "quest", "", "quest", &field); + form.fields.push_back(field); + test::CreateTestFormField("Color", "color", "", "text", &field); + form.fields.push_back(field); + + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GetAutofillSuggestions(form, field); + EXPECT_FALSE(external_delegate_->on_suggestions_returned_seen()); +} + +// Test that we cull duplicate profile suggestions. +TEST_F(AutofillManagerTest, GetProfileSuggestionsWithDuplicates) { + // Set up our form data. + FormData form; + test::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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles") + }; + base::string16 expected_labels[] = { + ASCIIToUTF16("3734 Elvis Presley Blvd."), + ASCIIToUTF16("123 Apple St.") + }; + base::string16 expected_icons[] = {base::string16(), base::string16()}; + int expected_unique_ids[] = {1, 2}; + external_delegate_->CheckSuggestions( + 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; + test::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(external_delegate_->on_suggestions_returned_seen()); +} + +// 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; + test::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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_FORM_DISABLED) + }; + base::string16 expected_labels[] = {base::string16()}; + base::string16 expected_icons[] = {base::string16()}; + int expected_unique_ids[] = + {WebKit::WebAutofillClient::MenuItemIDWarningMessage}; + external_delegate_->CheckSuggestions( + 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<base::string16> suggestions; + suggestions.push_back(ASCIIToUTF16("Jay")); + suggestions.push_back(ASCIIToUTF16("Jason")); + AutocompleteSuggestionsReturned(suggestions); + + base::string16 expected_values2[] = { + l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_FORM_DISABLED), + ASCIIToUTF16("Jay"), + ASCIIToUTF16("Jason") + }; + base::string16 expected_labels2[] = { base::string16(), base::string16(), + base::string16()}; + base::string16 expected_icons2[] = { base::string16(), base::string16(), + base::string16()}; + int expected_unique_ids2[] = {-1, 0, 0}; + external_delegate_->CheckSuggestions( + 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(external_delegate_->on_suggestions_returned_seen()); +} + +// 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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("************3456"), + ASCIIToUTF16("************8765") + }; + base::string16 expected_labels[] = { ASCIIToUTF16("*3456"), + ASCIIToUTF16("*8765")}; + base::string16 expected_icons[] = { + ASCIIToUTF16(kVisaCard), + ASCIIToUTF16(kMasterCard) + }; + int expected_unique_ids[] = { + autofill_manager_->GetPackedCreditCardID(4), + autofill_manager_->GetPackedCreditCardID(5) + }; + external_delegate_->CheckSuggestions( + 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; + 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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = {ASCIIToUTF16("************3456")}; + base::string16 expected_labels[] = {ASCIIToUTF16("*3456")}; + base::string16 expected_icons[] = {ASCIIToUTF16(kVisaCard)}; + int expected_unique_ids[] = {autofill_manager_->GetPackedCreditCardID(4)}; + external_delegate_->CheckSuggestions( + 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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("Elvis Presley"), + ASCIIToUTF16("Buddy Holly") + }; + base::string16 expected_labels[] = { ASCIIToUTF16("*3456"), + ASCIIToUTF16("*8765") }; + base::string16 expected_icons[] = { + ASCIIToUTF16(kVisaCard), + ASCIIToUTF16(kMasterCard) + }; + int expected_unique_ids[] = { + autofill_manager_->GetPackedCreditCardID(4), + autofill_manager_->GetPackedCreditCardID(5) + }; + external_delegate_->CheckSuggestions( + 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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_INSECURE_CONNECTION) + }; + base::string16 expected_labels[] = {base::string16()}; + base::string16 expected_icons[] = {base::string16()}; + int expected_unique_ids[] = {-1}; + external_delegate_->CheckSuggestions( + 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<base::string16> suggestions; + suggestions.push_back(ASCIIToUTF16("Jay")); + suggestions.push_back(ASCIIToUTF16("Jason")); + AutocompleteSuggestionsReturned(suggestions); + + base::string16 expected_values2[] = { + l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_INSECURE_CONNECTION), + ASCIIToUTF16("Jay"), + ASCIIToUTF16("Jason") + }; + base::string16 expected_labels2[] = { base::string16(), base::string16(), + base::string16() }; + base::string16 expected_icons2[] = { base::string16(), base::string16(), + base::string16() }; + int expected_unique_ids2[] = {-1, 0, 0}; + external_delegate_->CheckSuggestions( + 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(external_delegate_->on_suggestions_returned_seen()); +} + +// 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; + 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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("************3456"), + ASCIIToUTF16("************8765"), + ASCIIToUTF16("************3456") + }; + base::string16 expected_labels[] = { + ASCIIToUTF16("*3456"), + ASCIIToUTF16("*8765"), + ASCIIToUTF16("*3456"), + }; + base::string16 expected_icons[] = { + ASCIIToUTF16(kVisaCard), + ASCIIToUTF16(kMasterCard), + ASCIIToUTF16(kMasterCard) + }; + int expected_unique_ids[] = { + autofill_manager_->GetPackedCreditCardID(4), + autofill_manager_->GetPackedCreditCardID(5), + autofill_manager_->GetPackedCreditCardID(7) + }; + external_delegate_->CheckSuggestions( + 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; + test::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<base::string16>()); + + // Test that we sent the right address suggestions to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles") + }; + base::string16 expected_labels[] = { + ASCIIToUTF16("3734 Elvis Presley Blvd."), + ASCIIToUTF16("123 Apple St.") + }; + base::string16 expected_icons[] = {base::string16(), base::string16()}; + int expected_unique_ids[] = {1, 2}; + external_delegate_->CheckSuggestions( + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); + + const int kPageID2 = 2; + 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<base::string16>()); + + // Test that we sent the credit card suggestions to the external delegate. + base::string16 expected_values2[] = { + ASCIIToUTF16("************3456"), + ASCIIToUTF16("************8765") + }; + base::string16 expected_labels2[] = { ASCIIToUTF16("*3456"), + ASCIIToUTF16("*8765")}; + base::string16 expected_icons2[] = { + ASCIIToUTF16(kVisaCard), + ASCIIToUTF16(kMasterCard) + }; + int expected_unique_ids2[] = { + autofill_manager_->GetPackedCreditCardID(4), + autofill_manager_->GetPackedCreditCardID(5) + }; + external_delegate_->CheckSuggestions( + 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; + test::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<base::string16>()); + + // Test that we sent the right suggestions to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles") + }; + base::string16 expected_labels[] = { + ASCIIToUTF16("3734 Elvis Presley Blvd."), + ASCIIToUTF16("123 Apple St.") + }; + base::string16 expected_icons[] = {base::string16(), base::string16()}; + int expected_unique_ids[] = {1, 2}; + external_delegate_->CheckSuggestions( + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); + + 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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values2[] = { + l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_INSECURE_CONNECTION) + }; + base::string16 expected_labels2[] = {base::string16()}; + base::string16 expected_icons2[] = {base::string16()}; + int expected_unique_ids2[] = {-1}; + external_delegate_->CheckSuggestions( + 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(external_delegate_->on_suggestions_returned_seen()); +} + +// Test that we correctly combine autofill and autocomplete suggestions. +TEST_F(AutofillManagerTest, GetCombinedAutofillAndAutocompleteSuggestions) { + // Set up our form data. + FormData form; + test::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<base::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 values to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles"), + ASCIIToUTF16("Jay"), + ASCIIToUTF16("Jason") + }; + base::string16 expected_labels[] = { + ASCIIToUTF16("3734 Elvis Presley Blvd."), + ASCIIToUTF16("123 Apple St."), + base::string16(), + base::string16() + }; + base::string16 expected_icons[] = { base::string16(), base::string16(), + base::string16(), base::string16()}; + int expected_unique_ids[] = {1, 2, 0, 0}; + external_delegate_->CheckSuggestions( + 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; + test::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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Charles") + }; + base::string16 expected_labels[] = {base::string16(), base::string16()}; + base::string16 expected_icons[] = {base::string16(), base::string16()}; + int expected_unique_ids[] = {1, 2}; + external_delegate_->CheckSuggestions( + 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; + test::CreateTestAddressFormData(&form); + FormFieldData field; + 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<base::string16> suggestions; + suggestions.push_back(ASCIIToUTF16("one")); + suggestions.push_back(ASCIIToUTF16("two")); + AutocompleteSuggestionsReturned(suggestions); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("one"), + ASCIIToUTF16("two") + }; + base::string16 expected_labels[] = {base::string16(), base::string16()}; + base::string16 expected_icons[] = {base::string16(), base::string16()}; + int expected_unique_ids[] = {0, 0}; + external_delegate_->CheckSuggestions( + 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; + test::CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // |profile| will be owned by the mock PersonalDataManager. + AutofillProfile* profile = new AutofillProfile; + 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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { ASCIIToUTF16("Elvis") }; + base::string16 expected_labels[] = { base::string16() }; + base::string16 expected_icons[] = { base::string16() }; + int expected_unique_ids[] = { 1 }; + external_delegate_->CheckSuggestions( + 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; + test::CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + // |profile| will be owned by the mock PersonalDataManager. + AutofillProfile* profile = new AutofillProfile; + test::SetProfileInfo(profile, "Elvis", "", "Presley", "me@x.com", "", + "", "", "", "", "", "", ""); + profile->set_guid("00000000-0000-0000-0000-000000000101"); + std::vector<base::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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("Elvis"), + ASCIIToUTF16("Elena") + }; + base::string16 expected_labels[] = { + ASCIIToUTF16("me@x.com"), + ASCIIToUTF16("me@x.com") + }; + base::string16 expected_icons[] = { base::string16(), base::string16() }; + int expected_unique_ids[] = { 1, 2 }; + external_delegate_->CheckSuggestions( + 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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { ASCIIToUTF16("Elena") }; + base::string16 expected_labels[] = { ASCIIToUTF16("me@x.com") }; + base::string16 expected_icons[] = { base::string16() }; + int expected_unique_ids[] = { 2 }; + external_delegate_->CheckSuggestions( + 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; + test::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<base::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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::string16 expected_values[] = { + ASCIIToUTF16("Travis"), + ASCIIToUTF16("Cynthia"), + ASCIIToUTF16("Zac") + }; + base::string16 expected_labels[] = { base::string16(), base::string16(), + base::string16() }; + base::string16 expected_icons[] = { base::string16(), base::string16(), + base::string16() }; + int expected_unique_ids[] = { 1, 2, 3 }; + external_delegate_->CheckSuggestions( + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +TEST_F(AutofillManagerTest, GetProfileSuggestionsFancyPhone) { + // Set up our form data. + FormData form; + test::CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + AutofillProfile* profile = new AutofillProfile; + profile->set_guid("00000000-0000-0000-0000-000000000103"); + std::vector<base::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<base::string16>()); + + // Test that we sent the right values to the external delegate. + base::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. + base::string16 expected_labels[] = { + ASCIIToUTF16("Elvis Aaron Presley"), + ASCIIToUTF16("Charles Hardin Holley"), + ASCIIToUTF16("Natty Bumppo"), + }; + base::string16 expected_icons[] = { base::string16(), base::string16(), + base::string16()}; + int expected_unique_ids[] = {1, 2, 3}; + external_delegate_->CheckSuggestions( + 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; + test::CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid), &response_page_id, &response_data); + ExpectFilledAddressFormElvis( + response_page_id, response_data, kDefaultPageID, false); +} + +// Test that we correctly fill an address form from an auxiliary profile. +TEST_F(AutofillManagerTest, FillAddressFormFromAuxiliaryProfile) { + personal_data_.ClearAutofillProfiles(); + PrefService* prefs = user_prefs::UserPrefs::Get(profile()); + prefs->SetBoolean(::autofill::prefs::kAutofillAuxiliaryProfilesEnabled, true); + personal_data_.CreateTestAuxiliaryProfiles(); + + // Set up our form data. + FormData form; + test::CreateTestAddressFormData(&form); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + + GUIDPair guid("00000000-0000-0000-0000-000000000001", 0); + GUIDPair empty(std::string(), 0); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid), &response_page_id, &response_data); + ExpectFilledAddressFormElvis( + response_page_id, response_data, 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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(guid, empty), &response_page_id, &response_data); + ExpectFilledCreditCardFormElvis( + response_page_id, response_data, 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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(guid, empty), &response_page_id, &response_data); + ExpectFilledCreditCardYearMonthWithYearMonth(response_page_id, response_data, + 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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(guid, empty), &response_page_id, &response_data); + ExpectFilledCreditCardYearMonthWithYearMonth(response_page_id, response_data, + 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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(guid, empty), &response_page_id, &response_data); + ExpectFilledCreditCardYearMonthWithYearMonth(response_page_id, response_data, + 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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(guid, empty), &response_page_id, &response_data); + ExpectFilledCreditCardYearMonthWithYearMonth(response_page_id, response_data, + 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; + test::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); + int response_page_id = 0; + FormData response_data; + { + SCOPED_TRACE("Address"); + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid), &response_page_id, &response_data); + ExpectFilledAddressFormElvis( + response_page_id, response_data, kDefaultPageID, true); + } + + // Now fill the credit card data. + const int kPageID2 = 2; + GUIDPair guid2("00000000-0000-0000-0000-000000000004", 0); + response_page_id = 0; + { + FillAutofillFormDataAndSaveResults(kPageID2, form, form.fields.back(), + PackGUIDs(guid2, empty), &response_page_id, &response_data); + SCOPED_TRACE("Credit card"); + ExpectFilledCreditCardFormElvis( + response_page_id, response_data, 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; + test::CreateTestAddressFormData(&form); + const size_t kAddressFormSize = form.fields.size(); + test::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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid), &response_page_id, &response_data); + { + SCOPED_TRACE("Address 1"); + // The second address section should be empty. + ASSERT_EQ(response_data.fields.size(), 2*kAddressFormSize); + for (size_t i = kAddressFormSize; i < form.fields.size(); ++i) { + EXPECT_EQ(base::string16(), response_data.fields[i].value); + } + + // The first address section should be filled with Elvis's data. + response_data.fields.resize(kAddressFormSize); + ExpectFilledAddressFormElvis( + response_page_id, response_data, 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); + response_page_id = 0; + FillAutofillFormDataAndSaveResults( + kPageID2, form, form.fields[kAddressFormSize + 9], + PackGUIDs(empty, guid2), &response_page_id, &response_data); + { + SCOPED_TRACE("Address 2"); + ASSERT_EQ(response_data.fields.size(), form.fields.size()); + + // The first address section should be empty. + ASSERT_EQ(response_data.fields.size(), 2*kAddressFormSize); + for (size_t i = 0; i < kAddressFormSize; ++i) { + EXPECT_EQ(base::string16(), response_data.fields[i].value); + } + + // The second address section should be filled with Elvis's data. + FormData secondSection = response_data; + secondSection.fields.erase(secondSection.fields.begin(), + secondSection.fields.begin() + kAddressFormSize); + for (size_t i = 0; i < kAddressFormSize; ++i) { + // Restore the expected field names. + base::string16 name = secondSection.fields[i].name; + base::string16 original_name = name.substr(0, name.size() - 1); + secondSection.fields[i].name = original_name; + } + ExpectFilledAddressFormElvis( + response_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; + + test::CreateTestFormField("", "country", "", "text", &field); + field.autocomplete_attribute = "section-billing country"; + form.fields.push_back(field); + + test::CreateTestFormField("", "firstname", "", "text", &field); + field.autocomplete_attribute = "given-name"; + form.fields.push_back(field); + + test::CreateTestFormField("", "lastname", "", "text", &field); + field.autocomplete_attribute = "family-name"; + form.fields.push_back(field); + + test::CreateTestFormField("", "address", "", "text", &field); + field.autocomplete_attribute = "section-billing address-line1"; + form.fields.push_back(field); + + test::CreateTestFormField("", "city", "", "text", &field); + field.autocomplete_attribute = "section-billing locality"; + form.fields.push_back(field); + + test::CreateTestFormField("", "state", "", "text", &field); + field.autocomplete_attribute = "section-billing region"; + form.fields.push_back(field); + + test::CreateTestFormField("", "zip", "", "text", &field); + field.autocomplete_attribute = "section-billing postal-code"; + form.fields.push_back(field); + + test::CreateTestFormField("", "ccname", "", "text", &field); + field.autocomplete_attribute = "section-billing cc-name"; + form.fields.push_back(field); + + test::CreateTestFormField("", "ccnumber", "", "text", &field); + field.autocomplete_attribute = "section-billing cc-number"; + form.fields.push_back(field); + + test::CreateTestFormField("", "ccexp", "", "text", &field); + field.autocomplete_attribute = "section-billing cc-exp"; + form.fields.push_back(field); + + 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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[1], + PackGUIDs(empty, guid), &response_page_id, &response_data); + { + SCOPED_TRACE("Unnamed section"); + EXPECT_EQ(kDefaultPageID, response_page_id); + EXPECT_EQ(ASCIIToUTF16("MyForm"), response_data.name); + EXPECT_EQ(ASCIIToUTF16("POST"), response_data.method); + EXPECT_EQ(GURL("https://myform.com/form.html"), response_data.origin); + EXPECT_EQ(GURL("https://myform.com/submit.html"), response_data.action); + EXPECT_TRUE(response_data.user_submitted); + ASSERT_EQ(11U, response_data.fields.size()); + + ExpectFilledField("", "country", "", "text", response_data.fields[0]); + ExpectFilledField("", "firstname", "Elvis", "text", + response_data.fields[1]); + ExpectFilledField("", "lastname", "Presley", "text", + response_data.fields[2]); + ExpectFilledField("", "address", "", "text", response_data.fields[3]); + ExpectFilledField("", "city", "", "text", response_data.fields[4]); + ExpectFilledField("", "state", "", "text", response_data.fields[5]); + ExpectFilledField("", "zip", "", "text", response_data.fields[6]); + ExpectFilledField("", "ccname", "", "text", response_data.fields[7]); + ExpectFilledField("", "ccnumber", "", "text", response_data.fields[8]); + ExpectFilledField("", "ccexp", "", "text", response_data.fields[9]); + ExpectFilledField("", "email", "theking@gmail.com", "text", + response_data.fields[10]); + } + + // Fill the address portion of the billing section. + const int kPageID2 = 2; + GUIDPair guid2("00000000-0000-0000-0000-000000000001", 0); + response_page_id = 0; + FillAutofillFormDataAndSaveResults(kPageID2, form, form.fields[0], + PackGUIDs(empty, guid2), &response_page_id, &response_data); + { + SCOPED_TRACE("Billing address"); + EXPECT_EQ(kPageID2, response_page_id); + EXPECT_EQ(ASCIIToUTF16("MyForm"), response_data.name); + EXPECT_EQ(ASCIIToUTF16("POST"), response_data.method); + EXPECT_EQ(GURL("https://myform.com/form.html"), response_data.origin); + EXPECT_EQ(GURL("https://myform.com/submit.html"), response_data.action); + EXPECT_TRUE(response_data.user_submitted); + ASSERT_EQ(11U, response_data.fields.size()); + + ExpectFilledField("", "country", "US", "text", + response_data.fields[0]); + ExpectFilledField("", "firstname", "", "text", response_data.fields[1]); + ExpectFilledField("", "lastname", "", "text", response_data.fields[2]); + ExpectFilledField("", "address", "3734 Elvis Presley Blvd.", "text", + response_data.fields[3]); + ExpectFilledField("", "city", "Memphis", "text", response_data.fields[4]); + ExpectFilledField("", "state", "Tennessee", "text", + response_data.fields[5]); + ExpectFilledField("", "zip", "38116", "text", response_data.fields[6]); + ExpectFilledField("", "ccname", "", "text", response_data.fields[7]); + ExpectFilledField("", "ccnumber", "", "text", response_data.fields[8]); + ExpectFilledField("", "ccexp", "", "text", response_data.fields[9]); + ExpectFilledField("", "email", "", "text", response_data.fields[10]); + } + + // Fill the credit card portion of the billing section. + const int kPageID3 = 3; + GUIDPair guid3("00000000-0000-0000-0000-000000000004", 0); + response_page_id = 0; + FillAutofillFormDataAndSaveResults( + kPageID3, form, form.fields[form.fields.size() - 2], + PackGUIDs(guid3, empty), &response_page_id, &response_data); + { + SCOPED_TRACE("Credit card"); + EXPECT_EQ(kPageID3, response_page_id); + EXPECT_EQ(ASCIIToUTF16("MyForm"), response_data.name); + EXPECT_EQ(ASCIIToUTF16("POST"), response_data.method); + EXPECT_EQ(GURL("https://myform.com/form.html"), response_data.origin); + EXPECT_EQ(GURL("https://myform.com/submit.html"), response_data.action); + EXPECT_TRUE(response_data.user_submitted); + ASSERT_EQ(11U, response_data.fields.size()); + + ExpectFilledField("", "country", "", "text", response_data.fields[0]); + ExpectFilledField("", "firstname", "", "text", response_data.fields[1]); + ExpectFilledField("", "lastname", "", "text", response_data.fields[2]); + ExpectFilledField("", "address", "", "text", response_data.fields[3]); + ExpectFilledField("", "city", "", "text", response_data.fields[4]); + ExpectFilledField("", "state", "", "text", response_data.fields[5]); + ExpectFilledField("", "zip", "", "text", response_data.fields[6]); + ExpectFilledField("", "ccname", "Elvis Presley", "text", + response_data.fields[7]); + ExpectFilledField("", "ccnumber", "4234567890123456", "text", + response_data.fields[8]); + ExpectFilledField("", "ccexp", "04/2012", "text", response_data.fields[9]); + ExpectFilledField("", "email", "", "text", response_data.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; + test::CreateTestAddressFormData(&form); + FormFieldData field; + 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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid), &response_page_id, &response_data); + + // The second email address should be filled. + EXPECT_EQ(ASCIIToUTF16("theking@gmail.com"), + response_data.fields.back().value); + + // The remainder of the form should be filled as usual. + response_data.fields.pop_back(); + ExpectFilledAddressFormElvis( + response_page_id, response_data, kDefaultPageID, false); +} + +// Test that we correctly fill a previously auto-filled form. +TEST_F(AutofillManagerTest, FillAutofilledForm) { + // Set up our form data. + FormData form; + test::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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, *form.fields.begin(), + PackGUIDs(empty, guid), &response_page_id, &response_data); + { + SCOPED_TRACE("Address"); + ExpectFilledForm(response_page_id, response_data, kDefaultPageID, + "Elvis", "", "", "", "", "", "", "", "", "", "", "", "", + "", "", true, true, false); + } + + // Now fill the credit card data. + const int kPageID2 = 2; + GUIDPair guid2("00000000-0000-0000-0000-000000000004", 0); + response_page_id = 0; + FillAutofillFormDataAndSaveResults(kPageID2, form, form.fields.back(), + PackGUIDs(guid2, empty), &response_page_id, &response_data); + { + SCOPED_TRACE("Credit card 1"); + ExpectFilledCreditCardFormElvis( + response_page_id, response_data, 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; + response_page_id = 0; + FillAutofillFormDataAndSaveResults(kPageID3, form, *form.fields.rbegin(), + PackGUIDs(guid2, empty), &response_page_id, &response_data); + { + SCOPED_TRACE("Credit card 2"); + ExpectFilledForm(response_page_id, response_data, 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; + test::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 base::string16 elvis_name = profile->GetRawInfo(NAME_FULL); + + std::vector<base::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); + int response_page_id = 0; + FormData response_data1; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid), &response_page_id, &response_data1); + { + SCOPED_TRACE("Valid variant"); + ExpectFilledAddressFormElvis( + response_page_id, response_data1, 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); + response_page_id = 0; + FormData response_data2; + FillAutofillFormDataAndSaveResults(kPageID2, form, form.fields[0], + PackGUIDs(empty, guid2), &response_page_id, &response_data2); + { + SCOPED_TRACE("Invalid variant"); + ExpectFilledForm(response_page_id, response_data2, 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) { + 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; + int response_page_id = 0; + FormData response_data1; + FillAutofillFormDataAndSaveResults(page_id, form_with_maxlength, + *form_with_maxlength.fields.begin(), + PackGUIDs(empty, guid), &response_page_id, &response_data1); + EXPECT_EQ(1, response_page_id); + + ASSERT_EQ(5U, response_data1.fields.size()); + EXPECT_EQ(ASCIIToUTF16("1"), response_data1.fields[0].value); + EXPECT_EQ(ASCIIToUTF16("650"), response_data1.fields[1].value); + EXPECT_EQ(ASCIIToUTF16("555"), response_data1.fields[2].value); + EXPECT_EQ(ASCIIToUTF16("4567"), response_data1.fields[3].value); + EXPECT_EQ(base::string16(), response_data1.fields[4].value); + + page_id = 2; + response_page_id = 0; + FormData response_data2; + FillAutofillFormDataAndSaveResults(page_id, form_with_autocompletetype, + *form_with_autocompletetype.fields.begin(), + PackGUIDs(empty, guid), &response_page_id, &response_data2); + EXPECT_EQ(2, response_page_id); + + ASSERT_EQ(5U, response_data2.fields.size()); + EXPECT_EQ(ASCIIToUTF16("1"), response_data2.fields[0].value); + EXPECT_EQ(ASCIIToUTF16("650"), response_data2.fields[1].value); + EXPECT_EQ(ASCIIToUTF16("555"), response_data2.fields[2].value); + EXPECT_EQ(ASCIIToUTF16("4567"), response_data2.fields[3].value); + EXPECT_EQ(base::string16(), response_data2.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; + response_page_id = 0; + FormData response_data3; + FillAutofillFormDataAndSaveResults(page_id, form_with_maxlength, + *form_with_maxlength.fields.begin(), + PackGUIDs(empty, guid), &response_page_id, &response_data3); + EXPECT_EQ(3, response_page_id); + + ASSERT_EQ(5U, response_data3.fields.size()); + EXPECT_EQ(ASCIIToUTF16("44"), response_data3.fields[0].value); + EXPECT_EQ(ASCIIToUTF16("7700"), response_data3.fields[1].value); + EXPECT_EQ(ASCIIToUTF16("954321"), response_data3.fields[2].value); + EXPECT_EQ(ASCIIToUTF16("954321"), response_data3.fields[3].value); + EXPECT_EQ(base::string16(), response_data3.fields[4].value); + + page_id = 4; + response_page_id = 0; + FormData response_data4; + FillAutofillFormDataAndSaveResults(page_id, form_with_autocompletetype, + *form_with_autocompletetype.fields.begin(), + PackGUIDs(empty, guid), &response_page_id, &response_data4); + EXPECT_EQ(4, response_page_id); + + ASSERT_EQ(5U, response_data4.fields.size()); + EXPECT_EQ(ASCIIToUTF16("44"), response_data4.fields[0].value); + EXPECT_EQ(ASCIIToUTF16("7700"), response_data4.fields[1].value); + EXPECT_EQ(ASCIIToUTF16("954321"), response_data4.fields[2].value); + EXPECT_EQ(ASCIIToUTF16("954321"), response_data4.fields[3].value); + EXPECT_EQ(base::string16(), response_data4.fields[4].value); + + // We should fill all phone fields with the same phone number variant. + std::vector<base::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; + response_page_id = 0; + FormData response_data5; + GUIDPair variant_guid(work_profile->guid(), 1); + FillAutofillFormDataAndSaveResults(page_id, form_with_maxlength, + *form_with_maxlength.fields.begin(), + PackGUIDs(empty, variant_guid), &response_page_id, &response_data5); + EXPECT_EQ(5, response_page_id); + + ASSERT_EQ(5U, response_data5.fields.size()); + EXPECT_EQ(ASCIIToUTF16("1"), response_data5.fields[0].value); + EXPECT_EQ(ASCIIToUTF16("888"), response_data5.fields[1].value); + EXPECT_EQ(ASCIIToUTF16("777"), response_data5.fields[2].value); + EXPECT_EQ(ASCIIToUTF16("1234"), response_data5.fields[3].value); + EXPECT_EQ(base::string16(), response_data5.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; + test::CreateTestAddressFormData(&form); + + // Add a field -- we'll remove it again later. + FormFieldData field; + 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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid), &response_page_id, &response_data); + ExpectFilledAddressFormElvis( + response_page_id, response_data, 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; + test::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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid), &response_page_id, &response_data); + ExpectFilledAddressFormElvis( + response_page_id, response_data, 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; + test::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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid), &response_page_id, &response_data); + ExpectFilledAddressFormElvis( + response_page_id, response_data, 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(response_data); +} + +// Test that when Autocomplete is enabled and Autofill is disabled, +// form submissions are still received by AutocompleteHistoryManager. +TEST_F(AutofillManagerTest, FormSubmittedAutocompleteEnabled) { + TestAutofillManagerDelegate delegate; + autofill_manager_.reset(new TestAutofillManager( + autofill_driver_.get(), + &delegate, + NULL)); + autofill_manager_->set_autofill_enabled(false); + scoped_ptr<MockAutocompleteHistoryManager> autocomplete_history_manager; + autocomplete_history_manager.reset( + new MockAutocompleteHistoryManager(autofill_driver_.get(), &delegate)); + autofill_manager_->autocomplete_history_manager_ = + autocomplete_history_manager.Pass(); + + // Set up our form data. + FormData form; + test::CreateTestAddressFormData(&form); + form.method = ASCIIToUTF16("GET"); + MockAutocompleteHistoryManager* m = static_cast< + MockAutocompleteHistoryManager*>( + autofill_manager_->autocomplete_history_manager_.get()); + EXPECT_CALL(*m, + OnFormSubmitted(_)).Times(1); + FormSubmitted(form); +} + +// Test that when Autocomplete is enabled and Autofill is disabled, +// Autocomplete suggestions are still received. +TEST_F(AutofillManagerTest, AutocompleteSuggestionsWhenAutofillDisabled) { + TestAutofillManagerDelegate delegate; + autofill_manager_.reset(new TestAutofillManager( + autofill_driver_.get(), + &delegate, + NULL)); + autofill_manager_->set_autofill_enabled(false); + autofill_manager_->SetExternalDelegate(external_delegate_.get()); + + // Set up our form data. + FormData form; + test::CreateTestAddressFormData(&form); + form.method = ASCIIToUTF16("GET"); + std::vector<FormData> forms(1, form); + FormsSeen(forms); + const FormFieldData& field = form.fields[0]; + GetAutofillSuggestions(form, field); + + // Add some Autocomplete suggestions. We should return the autocomplete + // suggestions, these will be culled by the renderer. + std::vector<base::string16> suggestions; + suggestions.push_back(ASCIIToUTF16("Jay")); + suggestions.push_back(ASCIIToUTF16("Jason")); + AutocompleteSuggestionsReturned(suggestions); + + base::string16 expected_values[] = { + ASCIIToUTF16("Jay"), + ASCIIToUTF16("Jason") + }; + base::string16 expected_labels[] = { base::string16(), base::string16()}; + base::string16 expected_icons[] = { base::string16(), base::string16()}; + int expected_unique_ids[] = {0, 0}; + external_delegate_->CheckSuggestions( + kDefaultPageID, arraysize(expected_values), expected_values, + expected_labels, expected_icons, expected_unique_ids); +} + +// 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; + test::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<ServerFieldType> 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)->heuristic_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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[0], + PackGUIDs(empty, guid), &response_page_id, &response_data); + ExpectFilledAddressFormElvis( + response_page_id, response_data, 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(response_data); +} + +// 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; + test::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; + test::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); + int response_page_id = 0; + FormData response_data; + FillAutofillFormDataAndSaveResults(kDefaultPageID, form, form.fields[3], + PackGUIDs(empty, guid), &response_page_id, &response_data); + + // 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(response_data); + + // Set the address field's value back to the default value. + response_data.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(response_data); +} + +// Checks that resetting the auxiliary profile enabled preference does the right +// thing on all platforms. +TEST_F(AutofillManagerTest, AuxiliaryProfilesReset) { + PrefService* prefs = user_prefs::UserPrefs::Get(profile()); +#if defined(OS_MACOSX) || defined(OS_ANDROID) + // Auxiliary profiles is implemented on Mac and Android only. + // OSX: enables Mac Address Book integration. + // Android: enables integration with user's contact profile. + ASSERT_TRUE( + prefs->GetBoolean(::autofill::prefs::kAutofillAuxiliaryProfilesEnabled)); + prefs->SetBoolean( + ::autofill::prefs::kAutofillAuxiliaryProfilesEnabled, false); + prefs->ClearPref(::autofill::prefs::kAutofillAuxiliaryProfilesEnabled); + ASSERT_TRUE( + prefs->GetBoolean(::autofill::prefs::kAutofillAuxiliaryProfilesEnabled)); +#else + ASSERT_FALSE( + prefs->GetBoolean(::autofill::prefs::kAutofillAuxiliaryProfilesEnabled)); + prefs->SetBoolean(::autofill::prefs::kAutofillAuxiliaryProfilesEnabled, true); + prefs->ClearPref(::autofill::prefs::kAutofillAuxiliaryProfilesEnabled); + ASSERT_FALSE( + prefs->GetBoolean(::autofill::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<ServerFieldTypeSet> expected_types; + + // These fields should all match. + FormFieldData field; + ServerFieldTypeSet types; + test::CreateTestFormField("", "1", "Elvis", "text", &field); + types.clear(); + types.insert(NAME_FIRST); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "2", "Aaron", "text", &field); + types.clear(); + types.insert(NAME_MIDDLE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "3", "A", "text", &field); + types.clear(); + types.insert(NAME_MIDDLE_INITIAL); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "4", "Presley", "text", &field); + types.clear(); + types.insert(NAME_LAST); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "5", "Elvis Presley", "text", &field); + types.clear(); + types.insert(CREDIT_CARD_NAME); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "6", "Elvis Aaron Presley", "text", + &field); + types.clear(); + types.insert(NAME_FULL); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "7", "theking@gmail.com", "email", + &field); + types.clear(); + types.insert(EMAIL_ADDRESS); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "8", "RCA", "text", &field); + types.clear(); + types.insert(COMPANY_NAME); + form.fields.push_back(field); + expected_types.push_back(types); + + 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); + + test::CreateTestFormField("", "10", "Apt. 10", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_LINE2); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "11", "Memphis", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_CITY); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "12", "Tennessee", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_STATE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "13", "38116", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_ZIP); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "14", "USA", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_COUNTRY); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "15", "United States", "text", &field); + types.clear(); + types.insert(ADDRESS_HOME_COUNTRY); + form.fields.push_back(field); + expected_types.push_back(types); + + 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); + + 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); + + test::CreateTestFormField("", "18", "1", "text", &field); + types.clear(); + types.insert(PHONE_HOME_COUNTRY_CODE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "19", "234", "text", &field); + types.clear(); + types.insert(PHONE_HOME_CITY_CODE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "20", "5678901", "text", &field); + types.clear(); + types.insert(PHONE_HOME_NUMBER); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "21", "567", "text", &field); + types.clear(); + types.insert(PHONE_HOME_NUMBER); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "22", "8901", "text", &field); + types.clear(); + types.insert(PHONE_HOME_NUMBER); + form.fields.push_back(field); + expected_types.push_back(types); + + 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); + + test::CreateTestFormField("", "24", "04", "text", &field); + types.clear(); + types.insert(CREDIT_CARD_EXP_MONTH); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "25", "April", "text", &field); + types.clear(); + types.insert(CREDIT_CARD_EXP_MONTH); + form.fields.push_back(field); + expected_types.push_back(types); + + 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); + + 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); + + 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. + test::CreateTestFormField("", "29", "", "text", &field); + types.clear(); + types.insert(EMPTY_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "30", " ", "text", &field); + types.clear(); + types.insert(EMPTY_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "31", " Elvis", "text", &field); + types.clear(); + types.insert(NAME_FIRST); + form.fields.push_back(field); + expected_types.push_back(types); + + 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. + test::CreateTestFormField("", "33", "elvis", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + 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. + test::CreateTestFormField("", "35", "Elvis Aaron", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "36", "Mr. Presley", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "37", "3734 Elvis Presley", "text", + &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "38", "TN", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "39", "38116-1023", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "20", "5", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + test::CreateTestFormField("", "20", "56", "text", &field); + types.clear(); + types.insert(UNKNOWN_TYPE); + form.fields.push_back(field); + expected_types.push_back(types); + + 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, 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()); + + EXPECT_EQ(1U, autofill_manager_->request_autocomplete_results().size()); + EXPECT_EQ(WebFormElement::AutocompleteResultErrorDisabled, + autofill_manager_->request_autocomplete_results()[0].first); +} + +namespace { + +class MockAutofillManagerDelegate : public TestAutofillManagerDelegate { + public: + MockAutofillManagerDelegate() + : autocheckout_bubble_shown_(false) {} + + virtual ~MockAutofillManagerDelegate() {} + + virtual bool ShowAutocheckoutBubble( + const gfx::RectF& bounds, + bool is_google_user, + const base::Callback<void(AutocheckoutBubbleState)>& callback) OVERRIDE { + autocheckout_bubble_shown_ = true; + callback.Run(AUTOCHECKOUT_BUBBLE_ACCEPTED); + return true; + } + + virtual void ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + DialogType dialog_type, + const base::Callback<void(const FormStructure*, + const std::string&)>& callback) OVERRIDE { + callback.Run(user_supplied_data_.get(), "google_transaction_id"); + } + + 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_; + } + + private: + bool autocheckout_bubble_shown_; + scoped_ptr<FormStructure> user_supplied_data_; + + DISALLOW_COPY_AND_ASSIGN(MockAutofillManagerDelegate); +}; + +} // namespace + +// Test that Autocheckout bubble is offered when server specifies field types. +TEST_F(AutofillManagerTest, TestBubbleShown) { + MockAutofillManagerDelegate delegate; + autofill_manager_.reset(new TestAutofillManager( + autofill_driver_.get(), &delegate, &personal_data_)); + autofill_manager_->set_autofill_enabled(true); + autofill_manager_->MarkAsFirstPageInAutocheckoutFlow(); + + FormData form; + test::CreateTestAddressFormData(&form); + + TestFormStructure* form_structure = new TestFormStructure(form); + AutofillMetrics metrics_logger; // ignored + form_structure->DetermineHeuristicTypes(metrics_logger); + + // Build and add form structure with server data. + std::vector<ServerFieldType> 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)->heuristic_type()); + } + form_structure->SetFieldTypes(heuristic_types, server_types); + autofill_manager_->AddSeenForm(form_structure); + + autofill_manager_->OnMaybeShowAutocheckoutBubble(form, gfx::RectF()); + + EXPECT_TRUE(delegate.autocheckout_bubble_shown()); +} + +// Test that Autocheckout bubble is not offered when server doesn't have data +// for the form. +TEST_F(AutofillManagerTest, TestAutocheckoutBubbleNotShown) { + MockAutofillManagerDelegate delegate; + autofill_manager_.reset(new TestAutofillManager( + autofill_driver_.get(), &delegate, &personal_data_)); + autofill_manager_->set_autofill_enabled(true); + autofill_manager_->MarkAsFirstPageInAutocheckoutFlow(); + + FormData form; + test::CreateTestAddressFormData(&form); + + TestFormStructure* form_structure = new TestFormStructure(form); + AutofillMetrics metrics_logger; // ignored + form_structure->DetermineHeuristicTypes(metrics_logger); + + // Build form structure without server data. + std::vector<ServerFieldType> heuristic_types, server_types; + for (size_t i = 0; i < form.fields.size(); ++i) { + heuristic_types.push_back(form_structure->field(i)->heuristic_type()); + server_types.push_back(NO_SERVER_DATA); + } + form_structure->SetFieldTypes(heuristic_types, server_types); + autofill_manager_->AddSeenForm(form_structure); + + autofill_manager_->OnMaybeShowAutocheckoutBubble(form, gfx::RectF()); + + EXPECT_FALSE(delegate.autocheckout_bubble_shown()); +} + +// Test our external delegate is called at the right time. +TEST_F(AutofillManagerTest, TestExternalDelegate) { + FormData form; + test::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() + + EXPECT_TRUE(external_delegate_->on_query_seen()); +} + +// Test that in the case of Autocheckout, forms seen are in order supplied. +TEST_F(AutofillManagerTest, DynamicFormsSeenAndIgnored) { + MockAutofillManagerDelegate delegate; + autofill_manager_.reset(new TestAutofillManager( + autofill_driver_.get(), &delegate, &personal_data_)); + FormData shipping_options; + CreateTestShippingOptionsFormData(&shipping_options); + FormData user_supplied; + CreateTestFormWithAutocompleteAttribute(&user_supplied); + FormData address; + test::CreateTestAddressFormData(&address); + + autofill_manager_->set_autocheckout_url_prefix("test-prefix"); + // Push address only + std::vector<FormData> forms; + forms.push_back(address); + + // Build and add form structure with server data. + scoped_ptr<TestFormStructure> form_structure(new TestFormStructure(address)); + std::vector<ServerFieldType> heuristic_types, server_types; + for (size_t i = 0; i < address.fields.size(); ++i) { + heuristic_types.push_back(UNKNOWN_TYPE); + server_types.push_back(form_structure->field(i)->heuristic_type()); + } + form_structure->SetFieldTypes(heuristic_types, server_types); + autofill_manager_->AddSeenForm(form_structure.release()); + + // Make sure normal form is handled correctly. + autofill_manager_->MarkAsFirstPageInAutocheckoutFlowIgnoringAjax(); + std::vector<FormStructure*> form_structures; + form_structures = autofill_manager_->GetFormStructures(); + ASSERT_EQ(1U, form_structures.size()); + EXPECT_EQ("/form.html", form_structures[0]->source_url().path()); + + scoped_ptr<FormStructure> filled_form(new TestFormStructure(address)); + delegate.SetUserSuppliedData(filled_form.Pass()); + autofill_manager_->OnMaybeShowAutocheckoutBubble(address, gfx::RectF()); + + // Push other forms + forms.push_back(shipping_options); + forms.push_back(user_supplied); + + // FormStructure should contain the same forms as before. + DynamicFormsSeen(forms); + form_structures = autofill_manager_->GetFormStructures(); + ASSERT_EQ(1U, form_structures.size()); + EXPECT_EQ("/form.html", form_structures[0]->source_url().path()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_merge_unittest.cc b/chromium/components/autofill/core/browser/autofill_merge_unittest.cc new file mode 100644 index 00000000000..e007a59d905 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_merge_unittest.cc @@ -0,0 +1,248 @@ +// 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 <map> +#include <vector> + +#include "base/basictypes.h" +#include "base/files/file_path.h" +#include "base/path_service.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_common_test.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/data_driven_test.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/browser/personal_data_manager.h" +#include "components/autofill/core/common/form_data.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/web/WebInputElement.h" +#include "url/gurl.h" + +namespace autofill { + +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 ServerFieldType 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 +}; + +const base::FilePath& GetTestDataDir() { + CR_DEFINE_STATIC_LOCAL(base::FilePath, dir, ()); + if (dir.empty()) { + PathService::Get(base::DIR_SOURCE_ROOT, &dir); + dir = dir.AppendASCII("components"); + dir = dir.AppendASCII("test"); + dir = dir.AppendASCII("data"); + } + return dir; +} + +// 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) { + ServerFieldType type = kProfileFieldTypes[j]; + std::vector<base::string16> values; + profiles[i]->GetRawMultiInfo(type, &values); + for (size_t k = 0; k < values.size(); ++k) { + result += AutofillType(type).ToString(); + 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("en-US") { +} + +PersonalDataManagerMock::~PersonalDataManagerMock() { +} + +void PersonalDataManagerMock::Reset() { + profiles_.clear(); +} + +void PersonalDataManagerMock::SaveImportedProfile( + const AutofillProfile& profile) { + std::vector<AutofillProfile> profiles; + if (!MergeProfile(profile, profiles_.get(), "en-US", &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); + + // Deserializes |str| into a field type. + ServerFieldType StringToFieldType(const std::string& str); + + PersonalDataManagerMock personal_data_; + + private: + std::map<std::string, ServerFieldType> string_to_field_type_map_; + + DISALLOW_COPY_AND_ASSIGN(AutofillMergeTest); +}; + +AutofillMergeTest::AutofillMergeTest() : DataDrivenTest(GetTestDataDir()) { + for (size_t i = NO_SERVER_DATA; i < MAX_VALID_FIELD_TYPE; ++i) { + ServerFieldType field_type = static_cast<ServerFieldType>(i); + string_to_field_type_map_[AutofillType(field_type).ToString()] = field_type; + } +} + +AutofillMergeTest::~AutofillMergeTest() { +} + +void AutofillMergeTest::SetUp() { + 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); + base::string16 field_type = UTF8ToUTF16(line.substr(0, separator_pos)); + base::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)); + ServerFieldType type = 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()); +} + +ServerFieldType AutofillMergeTest::StringToFieldType(const std::string& str) { + return string_to_field_type_map_[str]; +} + +TEST_F(AutofillMergeTest, DataDrivenMergeProfiles) { + RunDataDrivenTest(GetInputDirectory(kTestName), GetOutputDirectory(kTestName), + kFileNamePattern); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_metrics.cc b/chromium/components/autofill/core/browser/autofill_metrics.cc new file mode 100644 index 00000000000..57743c3b7fd --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_metrics.cc @@ -0,0 +1,598 @@ +// 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/core/browser/autofill_metrics.h" + +#include "base/logging.h" +#include "base/metrics/histogram.h" +#include "base/time/time.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/common/form_data.h" + +namespace autofill { + +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 ServerFieldType field_type, + const int metric, + const int num_possible_metrics) { + DCHECK_LT(metric, num_possible_metrics); + + FieldTypeGroupForMetrics group; + switch (AutofillType(field_type).group()) { + case ::autofill::NO_GROUP: + group = AMBIGUOUS; + break; + + case ::autofill::NAME: + group = NAME; + break; + + case ::autofill::COMPANY: + group = COMPANY; + break; + + case ::autofill::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 ::autofill::EMAIL: + group = EMAIL; + break; + + case ::autofill::PHONE_HOME: + group = PHONE; + break; + + case ::autofill::CREDIT_CARD: + switch (field_type) { + case ::autofill::CREDIT_CARD_NAME: + group = CREDIT_CARD_NAME; + break; + case ::autofill::CREDIT_CARD_NUMBER: + group = CREDIT_CARD_NUMBER; + break; + case ::autofill::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; +} + +// Returns the histogram prefix to use for reporting metrics for |dialog_type|. +std::string GetPrefixForDialogType(autofill::DialogType dialog_type) { + switch (dialog_type) { + case autofill::DIALOG_TYPE_AUTOCHECKOUT: + return "Autocheckout"; + + case autofill::DIALOG_TYPE_REQUEST_AUTOCOMPLETE: + return "RequestAutocomplete"; + } + + NOTREACHED(); + return "UnknownDialogType"; +} + +std::string WalletApiMetricToString( + AutofillMetrics::WalletApiCallMetric metric) { + switch (metric) { + case AutofillMetrics::ACCEPT_LEGAL_DOCUMENTS: + return "AcceptLegalDocuments"; + case AutofillMetrics::AUTHENTICATE_INSTRUMENT: + return "AuthenticateInstrument"; + case AutofillMetrics::GET_FULL_WALLET: + return "GetFullWallet"; + case AutofillMetrics::GET_WALLET_ITEMS: + return "GetWalletItems"; + case AutofillMetrics::SAVE_TO_WALLET: + return "SaveToWallet"; + case AutofillMetrics::SEND_STATUS: + return "SendStatus"; + case AutofillMetrics::UNKNOWN_API_CALL: + NOTREACHED(); + return "UnknownApiCall"; + } + + NOTREACHED(); + return "UnknownApiCall"; +} + +// 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) { + DCHECK_LT(sample, 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_TIMES macro that allows the |name| +// to vary over the program's runtime. +void LogUMAHistogramTimes(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::FromSeconds(10), + 50, + base::HistogramBase::kUmaTargetedHistogramFlag); + histogram->AddTime(duration); +} + +// 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 ServerFieldType field_type, + const std::string& experiment_id) { + DCHECK_LT(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_LT(metric, NUM_SERVER_EXPERIMENTS); + LogUMAHistogramEnumeration(histogram_name, metric, NUM_SERVER_EXPERIMENTS); +} + +} // namespace + +AutofillMetrics::AutofillMetrics() { +} + +AutofillMetrics::~AutofillMetrics() { +} + +void AutofillMetrics::LogAutocheckoutBubbleMetric(BubbleMetric metric) const { + DCHECK_LT(metric, NUM_BUBBLE_METRICS); + + UMA_HISTOGRAM_ENUMERATION("Autocheckout.Bubble", metric, NUM_BUBBLE_METRICS); +} + +void AutofillMetrics::LogAutocheckoutBuyFlowMetric( + AutocheckoutBuyFlowMetric metric) const { + DCHECK_LT(metric, NUM_AUTOCHECKOUT_BUY_FLOW_METRICS); + + UMA_HISTOGRAM_ENUMERATION("Autocheckout.BuyFlow", metric, + NUM_AUTOCHECKOUT_BUY_FLOW_METRICS); +} + +void AutofillMetrics::LogCreditCardInfoBarMetric(InfoBarMetric metric) const { + DCHECK_LT(metric, NUM_INFO_BAR_METRICS); + + UMA_HISTOGRAM_ENUMERATION("Autofill.CreditCardInfoBar", metric, + NUM_INFO_BAR_METRICS); +} + +void AutofillMetrics::LogDialogDismissalState( + autofill::DialogType dialog_type, + DialogDismissalState state) const { + std::string name = GetPrefixForDialogType(dialog_type) + ".DismissalState"; + LogUMAHistogramEnumeration(name, state, NUM_DIALOG_DISMISSAL_STATES); +} + +void AutofillMetrics::LogDialogInitialUserState( + autofill::DialogType dialog_type, + DialogInitialUserStateMetric user_type) const { + std::string name = GetPrefixForDialogType(dialog_type) + ".InitialUserState"; + LogUMAHistogramEnumeration( + name, user_type, NUM_DIALOG_INITIAL_USER_STATE_METRICS); +} + +void AutofillMetrics::LogDialogLatencyToShow( + autofill::DialogType dialog_type, + const base::TimeDelta& duration) const { + std::string name = + GetPrefixForDialogType(dialog_type) + ".UiLatencyToShow"; + LogUMAHistogramTimes(name, duration); +} + +void AutofillMetrics::LogDialogPopupEvent(autofill::DialogType dialog_type, + DialogPopupEvent event) const { + std::string name = GetPrefixForDialogType(dialog_type) + ".PopupInDialog"; + LogUMAHistogramEnumeration(name, event, NUM_DIALOG_POPUP_EVENTS); +} + +void AutofillMetrics::LogDialogSecurityMetric( + autofill::DialogType dialog_type, + DialogSecurityMetric metric) const { + std::string name = GetPrefixForDialogType(dialog_type) + ".Security"; + LogUMAHistogramEnumeration(name, metric, NUM_DIALOG_SECURITY_METRICS); +} + +void AutofillMetrics::LogDialogUiDuration( + const base::TimeDelta& duration, + autofill::DialogType dialog_type, + DialogDismissalAction dismissal_action) const { + std::string prefix = GetPrefixForDialogType(dialog_type); + + 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::LogDialogUiEvent(autofill::DialogType dialog_type, + DialogUiEvent event) const { + std::string name = GetPrefixForDialogType(dialog_type) + ".UiEvents"; + LogUMAHistogramEnumeration(name, event, NUM_DIALOG_UI_EVENTS); +} + +void AutofillMetrics::LogWalletErrorMetric(autofill::DialogType dialog_type, + WalletErrorMetric metric) const { + std::string name = GetPrefixForDialogType(dialog_type) + ".WalletErrors"; + LogUMAHistogramEnumeration(name, metric, NUM_WALLET_ERROR_METRICS); +} + +void AutofillMetrics::LogWalletApiCallDuration( + WalletApiCallMetric metric, + const base::TimeDelta& duration) const { + LogUMAHistogramTimes("Wallet.ApiCallDuration." + + WalletApiMetricToString(metric), duration); +} + +void AutofillMetrics::LogWalletRequiredActionMetric( + autofill::DialogType dialog_type, + WalletRequiredActionMetric required_action) const { + std::string name = + GetPrefixForDialogType(dialog_type) + ".WalletRequiredActions"; + LogUMAHistogramEnumeration( + name, required_action, NUM_WALLET_REQUIRED_ACTIONS); +} + +void AutofillMetrics::LogAutocheckoutDuration( + const base::TimeDelta& duration, + AutocheckoutCompletionStatus status) const { + std::string suffix; + switch (status) { + case AUTOCHECKOUT_CANCELLED: + suffix = "Cancelled"; + break; + + case AUTOCHECKOUT_FAILED: + suffix = "Failed"; + break; + + case AUTOCHECKOUT_SUCCEEDED: + suffix = "Succeeded"; + break; + } + + LogUMAHistogramLongTimes("Autocheckout.FlowDuration", duration); + LogUMAHistogramLongTimes("Autocheckout.FlowDuration." + suffix, duration); +} + +void AutofillMetrics::LogAutocheckoutWhitelistDownloadDuration( + const base::TimeDelta& duration, + AutocheckoutWhitelistDownloadStatus status) const { + std::string suffix; + switch (status) { + case AUTOCHECKOUT_WHITELIST_DOWNLOAD_FAILED: + suffix = "Failed"; + break; + + case AUTOCHECKOUT_WHITELIST_DOWNLOAD_SUCCEEDED: + suffix = "Succeeded"; + break; + } + + LogUMAHistogramTimes("Autocheckout.WhitelistDownloadDuration", duration); + LogUMAHistogramTimes( + "Autocheckout.WhitelistDownloadDuration." + suffix, duration); +} + +void AutofillMetrics::LogDeveloperEngagementMetric( + DeveloperEngagementMetric metric) const { + DCHECK_LT(metric, NUM_DEVELOPER_ENGAGEMENT_METRICS); + + UMA_HISTOGRAM_ENUMERATION("Autofill.DeveloperEngagement", metric, + NUM_DEVELOPER_ENGAGEMENT_METRICS); +} + +void AutofillMetrics::LogHeuristicTypePrediction( + FieldTypeQualityMetric metric, + ServerFieldType 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, + ServerFieldType 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, + ServerFieldType 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_LT(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_LT(metric, NUM_SERVER_QUERY_METRICS); + + UMA_HISTOGRAM_ENUMERATION("Autofill.ServerQueryResponse", metric, + NUM_SERVER_QUERY_METRICS); +} + +void AutofillMetrics::LogUserHappinessMetric(UserHappinessMetric metric) const { + DCHECK_LT(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); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_metrics.h b/chromium/components/autofill/core/browser/autofill_metrics.h new file mode 100644 index 00000000000..d6ae790b392 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_metrics.h @@ -0,0 +1,516 @@ +// 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_CORE_BROWSER_AUTOFILL_METRICS_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_METRICS_H_ + +#include <stddef.h> +#include <string> + +#include "base/basictypes.h" +#include "components/autofill/core/browser/autofill_manager_delegate.h" +#include "components/autofill/core/browser/field_types.h" + +namespace base { +class TimeDelta; +} + +namespace autofill { + +class AutofillMetrics { + public: + // The possible results of an Autocheckout flow. + enum AutocheckoutBuyFlowMetric { + // The user has initated Autocheckout. The baseline metric. + AUTOCHECKOUT_BUY_FLOW_STARTED, + // Autocheckout completed successfully. + AUTOCHECKOUT_BUY_FLOW_SUCCESS, + // Autocheckout failed due to missing server side data. + AUTOCHECKOUT_BUY_FLOW_MISSING_FIELDMAPPING, + // Autocheckout failed due to a missing proceed element. + AUTOCHECKOUT_BUY_FLOW_MISSING_ADVANCE_ELEMENT, + // Autocheckout failed for any number of other reasons, e.g, the proceed + // element click failed, the page numbers were not increasing, etc. + AUTOCHECKOUT_BUY_FLOW_CANNOT_PROCEED, + // Autocheckout failed due to a missing click element before form filling. + AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING, + // Autocheckout failed due to a missing click element after form filling. + AUTOCHECKOUT_BUY_FLOW_MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING, + NUM_AUTOCHECKOUT_BUY_FLOW_METRICS + }; + + // The success or failure of Autocheckout. + enum AutocheckoutCompletionStatus { + AUTOCHECKOUT_CANCELLED, // The user canceled Autocheckout while it was in + // progress. + 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. + BUBBLE_COULD_BE_DISPLAYED, // The bubble could be displayed. + 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. submitted, the dialog. + DIALOG_CANCELED, // The user canceled out of the dialog. + }; + + // The state of the Autofill dialog when it was dismissed. + enum DialogDismissalState { + // The user submitted with no data available to save. + DIALOG_ACCEPTED_EXISTING_DATA, + // The saved details to Online Wallet on submit. + DIALOG_ACCEPTED_SAVE_TO_WALLET, + // The saved details to the local Autofill database on submit. + DIALOG_ACCEPTED_SAVE_TO_AUTOFILL, + // The user submitted without saving any edited sections. + DIALOG_ACCEPTED_NO_SAVE, + // The user canceled with no edit UI showing. + DIALOG_CANCELED_NO_EDITS, + // The user canceled with edit UI showing, but no invalid fields. + DIALOG_CANCELED_NO_INVALID_FIELDS, + // The user canceled with at least one invalid field. + DIALOG_CANCELED_WITH_INVALID_FIELDS, + // The user canceled while the sign-in form was showing. + DIALOG_CANCELED_DURING_SIGNIN, + NUM_DIALOG_DISMISSAL_STATES + }; + + // The initial state of user that's interacting with a freshly shown Autofill + // dialog. + enum DialogInitialUserStateMetric { + // Could not determine the user's state due to failure to communicate with + // the Wallet server. + DIALOG_USER_STATE_UNKNOWN = 0, + // Not signed in, no verified Autofill profiles. + DIALOG_USER_NOT_SIGNED_IN_NO_AUTOFILL, + // Not signed in, has verified Autofill profiles. + DIALOG_USER_NOT_SIGNED_IN_HAS_AUTOFILL, + // Signed in, no Wallet items, no verified Autofill profiles. + DIALOG_USER_SIGNED_IN_NO_WALLET_NO_AUTOFILL, + // Signed in, no Wallet items, has verified Autofill profiles. + DIALOG_USER_SIGNED_IN_NO_WALLET_HAS_AUTOFILL, + // Signed in, has Wallet items, no verified Autofill profiles. + DIALOG_USER_SIGNED_IN_HAS_WALLET_NO_AUTOFILL, + // Signed in, has Wallet items, has verified Autofill profiles. + DIALOG_USER_SIGNED_IN_HAS_WALLET_HAS_AUTOFILL, + NUM_DIALOG_INITIAL_USER_STATE_METRICS + }; + + // Events related to the Autofill popup shown in a requestAutocomplete or + // Autocheckout dialog. + enum DialogPopupEvent { + // An Autofill popup was shown. + DIALOG_POPUP_SHOWN = 0, + // The user chose to fill the form with a suggestion from the popup. + DIALOG_POPUP_FORM_FILLED, + NUM_DIALOG_POPUP_EVENTS + }; + + // For measuring the frequency of security warnings or errors that can come + // up as part of the requestAutocomplete flow. + enum DialogSecurityMetric { + // Baseline metric: The dialog was shown. + SECURITY_METRIC_DIALOG_SHOWN = 0, + // Credit card requested over non-secure protocol. + SECURITY_METRIC_CREDIT_CARD_OVER_HTTP, + // Autocomplete data requested from a frame hosted on an origin not matching + // the main frame's origin. + SECURITY_METRIC_CROSS_ORIGIN_FRAME, + NUM_DIALOG_SECURITY_METRICS + }; + + // For measuring how users are interacting with the Autofill dialog UI. + enum DialogUiEvent { + // Baseline metric: The dialog was shown. + DIALOG_UI_SHOWN = 0, + + // Dialog dismissal actions: + DIALOG_UI_ACCEPTED, + DIALOG_UI_CANCELED, + + // Selections within the account switcher: + // Switched from a Wallet account to local Autofill data. + DIALOG_UI_ACCOUNT_CHOOSER_SWITCHED_TO_AUTOFILL, + // Switched from local Autofill data to a Wallet account. + DIALOG_UI_ACCOUNT_CHOOSER_SWITCHED_TO_WALLET, + // Switched from one Wallet account to another one. + DIALOG_UI_ACCOUNT_CHOOSER_SWITCHED_WALLET_ACCOUNT, + + // The sign-in UI was shown. + DIALOG_UI_SIGNIN_SHOWN, + + // Selecting a different item from a suggestion menu dropdown: + DIALOG_UI_EMAIL_SELECTED_SUGGESTION_CHANGED, + DIALOG_UI_BILLING_SELECTED_SUGGESTION_CHANGED, + DIALOG_UI_CC_BILLING_SELECTED_SUGGESTION_CHANGED, + DIALOG_UI_SHIPPING_SELECTED_SUGGESTION_CHANGED, + DIALOG_UI_CC_SELECTED_SUGGESTION_CHANGED, + + // Showing the editing UI for a section of the dialog: + DIALOG_UI_EMAIL_EDIT_UI_SHOWN, + DIALOG_UI_BILLING_EDIT_UI_SHOWN, + DIALOG_UI_CC_BILLING_EDIT_UI_SHOWN, + DIALOG_UI_SHIPPING_EDIT_UI_SHOWN, + DIALOG_UI_CC_EDIT_UI_SHOWN, + + // Adding a new item in a section of the dialog: + DIALOG_UI_EMAIL_ITEM_ADDED, + DIALOG_UI_BILLING_ITEM_ADDED, + DIALOG_UI_CC_BILLING_ITEM_ADDED, + DIALOG_UI_SHIPPING_ITEM_ADDED, + DIALOG_UI_CC_ITEM_ADDED, + + NUM_DIALOG_UI_EVENTS + }; + + 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, + }; + + // For measuring the network request time of various Wallet API calls. See + // WalletClient::RequestType. + enum WalletApiCallMetric { + UNKNOWN_API_CALL, // Catch all. Should never be used. + ACCEPT_LEGAL_DOCUMENTS, + AUTHENTICATE_INSTRUMENT, + GET_FULL_WALLET, + GET_WALLET_ITEMS, + SEND_STATUS, + SAVE_TO_WALLET, + }; + + // For measuring the frequency of errors while communicating with the Wallet + // server. + enum WalletErrorMetric { + // Baseline metric: Issued a request to the Wallet server. + WALLET_ERROR_BASELINE_ISSUED_REQUEST = 0, + // A fatal error occured while communicating with the Wallet server. This + // value has been deprecated. + WALLET_FATAL_ERROR_DEPRECATED, + // Received a malformed response from the Wallet server. + WALLET_MALFORMED_RESPONSE, + // A network error occured while communicating with the Wallet server. + WALLET_NETWORK_ERROR, + // The request was malformed. + WALLET_BAD_REQUEST, + // Risk deny, unsupported country, or account closed. + WALLET_BUYER_ACCOUNT_ERROR, + // Unknown server side error. + WALLET_INTERNAL_ERROR, + // API call had missing or invalid parameters. + WALLET_INVALID_PARAMS, + // Online Wallet is down. + WALLET_SERVICE_UNAVAILABLE, + // User needs make a cheaper transaction or not use Online Wallet. This + // value has been deprecated. + WALLET_SPENDING_LIMIT_EXCEEDED_DEPRECATED, + // The server API version of the request is no longer supported. + WALLET_UNSUPPORTED_API_VERSION, + // Catch all error type. + WALLET_UNKNOWN_ERROR, + // The merchant has been blacklisted for Online Wallet due to some manner + // of compliance violation. + WALLET_UNSUPPORTED_MERCHANT, + // Buyer Legal Address has a country which is unsupported by Wallet. + WALLET_BUYER_LEGAL_ADDRESS_NOT_SUPPORTED, + // Wallet's Know Your Customer(KYC) action is pending/failed for this user. + WALLET_UNVERIFIED_KNOW_YOUR_CUSTOMER_STATUS, + NUM_WALLET_ERROR_METRICS + }; + + // For measuring the frequency of "required actions" returned by the Wallet + // server. This is similar to the autofill::wallet::RequiredAction enum; + // but unlike that enum, the values in this one must remain constant over + // time, so that the metrics can be consistently interpreted on the + // server-side. + enum WalletRequiredActionMetric { + // Baseline metric: Issued a request to the Wallet server. + WALLET_REQUIRED_ACTION_BASELINE_ISSUED_REQUEST = 0, + // Values from the autofill::wallet::RequiredAction enum: + UNKNOWN_REQUIRED_ACTION, // Catch all type. + GAIA_AUTH, + PASSIVE_GAIA_AUTH, + SETUP_WALLET, + ACCEPT_TOS, + UPDATE_EXPIRATION_DATE, + UPGRADE_MIN_ADDRESS, + CHOOSE_ANOTHER_INSTRUMENT_OR_ADDRESS, + VERIFY_CVV, + INVALID_FORM_FIELD, + REQUIRE_PHONE_NUMBER, + NUM_WALLET_REQUIRED_ACTIONS + }; + + // The success or failure of downloading Autocheckout whitelist file. + enum AutocheckoutWhitelistDownloadStatus { + AUTOCHECKOUT_WHITELIST_DOWNLOAD_FAILED, + AUTOCHECKOUT_WHITELIST_DOWNLOAD_SUCCEEDED, + }; + + AutofillMetrics(); + virtual ~AutofillMetrics(); + + // Logs how the user interacted with the Autocheckout bubble. + virtual void LogAutocheckoutBubbleMetric(BubbleMetric metric) const; + + // Logs the result of an Autocheckout buy flow. + virtual void LogAutocheckoutBuyFlowMetric( + AutocheckoutBuyFlowMetric metric) const; + + virtual void LogCreditCardInfoBarMetric(InfoBarMetric metric) const; + + virtual void LogDeveloperEngagementMetric( + DeveloperEngagementMetric metric) const; + + virtual void LogHeuristicTypePrediction( + FieldTypeQualityMetric metric, + ServerFieldType field_type, + const std::string& experiment_id) const; + virtual void LogOverallTypePrediction( + FieldTypeQualityMetric metric, + ServerFieldType field_type, + const std::string& experiment_id) const; + virtual void LogServerTypePrediction(FieldTypeQualityMetric metric, + ServerFieldType 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; + + // Logs |state| to the dismissal states histogram for |dialog_type|. + virtual void LogDialogDismissalState(autofill::DialogType dialog_type, + DialogDismissalState state) const; + + // This should be called as soon as the user's signed-in status and Wallet + // item count is known. Records that a user starting out in |user_state| is + // interacting with a dialog of |dialog_type|. + virtual void LogDialogInitialUserState( + autofill::DialogType dialog_type, + DialogInitialUserStateMetric user_type) const; + + // Logs the time elapsed between the dialog being shown for |dialog_type| and + // when it is ready for user interaction. + virtual void LogDialogLatencyToShow(autofill::DialogType dialog_type, + const base::TimeDelta& duration) const; + + // Logs |event| to the popup events histogram for |dialog_type|. + virtual void LogDialogPopupEvent(autofill::DialogType dialog_type, + DialogPopupEvent event) const; + + // Logs |metric| to the security metrics histogram for |dialog_type|. + virtual void LogDialogSecurityMetric(autofill::DialogType dialog_type, + DialogSecurityMetric metric) const; + + // This should be called when the Autofill dialog, invoked by a dialog of type + // |dialog_type|, 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 canceling. + virtual void LogDialogUiDuration( + const base::TimeDelta& duration, + autofill::DialogType dialog_type, + DialogDismissalAction dismissal_action) const; + + // Logs |event| to the UI events histogram for |dialog_type|. + virtual void LogDialogUiEvent(autofill::DialogType dialog_type, + DialogUiEvent event) const; + + // Logs |metric| to the Wallet errors histogram for |dialog_type|. + virtual void LogWalletErrorMetric(autofill::DialogType dialog_type, + WalletErrorMetric metric) const; + + // Logs the network request time of Wallet API calls. + virtual void LogWalletApiCallDuration( + WalletApiCallMetric metric, + const base::TimeDelta& duration) const; + + // Logs |required_action| to the required actions histogram for |dialog_type|. + virtual void LogWalletRequiredActionMetric( + autofill::DialogType dialog_type, + WalletRequiredActionMetric required_action) const; + + virtual void LogAutocheckoutDuration( + const base::TimeDelta& duration, + AutocheckoutCompletionStatus status) const; + + // Logs the time taken to download Autocheckout whitelist file. + virtual void LogAutocheckoutWhitelistDownloadDuration( + const base::TimeDelta& duration, + AutocheckoutWhitelistDownloadStatus 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); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_METRICS_H_ diff --git a/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc b/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc new file mode 100644 index 00000000000..ece18d8dfd3 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_metrics_unittest.cc @@ -0,0 +1,1564 @@ +// 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/core/browser/autofill_metrics.h" + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.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/test/base/chrome_render_view_host_test_harness.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/content/browser/autocheckout_page_meta_data.h" +#include "components/autofill/core/browser/autofill_common_test.h" +#include "components/autofill/core/browser/autofill_external_delegate.h" +#include "components/autofill/core/browser/autofill_manager.h" +#include "components/autofill/core/browser/autofill_manager_delegate.h" +#include "components/autofill/core/browser/personal_data_manager.h" +#include "components/autofill/core/browser/test_autofill_driver.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/forms_seen_state.h" +#include "components/webdata/common/web_data_results.h" +#include "content/public/test/test_utils.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/rect.h" +#include "url/gurl.h" + +using base::TimeDelta; +using base::TimeTicks; +using testing::_; +using testing::AnyNumber; +using testing::Mock; + +namespace autofill { + +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, + ServerFieldType field_type, + const std::string& experiment_id)); + MOCK_CONST_METHOD3(LogOverallTypePrediction, + void(FieldTypeQualityMetric metric, + ServerFieldType field_type, + const std::string& experiment_id)); + MOCK_CONST_METHOD3(LogServerTypePrediction, + void(FieldTypeQualityMetric metric, + ServerFieldType 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() + : PersonalDataManager("en-US"), + autofill_enabled_(true) { + set_metric_logger(new testing::NiceMock<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; + 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; + 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<ServerFieldType>& heuristic_types, + const std::vector<ServerFieldType>& 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(AutofillDriver* driver, + AutofillManagerDelegate* manager_delegate, + TestPersonalDataManager* personal_manager) + : AutofillManager(driver, manager_delegate, personal_manager), + autofill_enabled_(true) { + set_metric_logger(new testing::NiceMock<MockAutofillMetrics>); + } + virtual ~TestAutofillManager() {} + + virtual std::string GetAutocheckoutURLPrefix() const OVERRIDE { + return std::string(); + } + + 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<ServerFieldType>& heuristic_types, + const std::vector<ServerFieldType>& 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 = base::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) { + message_loop_runner_ = new content::MessageLoopRunner(); + if (!OnFormSubmitted(form, timestamp)) + return; + + // Wait for the asynchronous FormSubmitted() call to complete. + message_loop_runner_->Run(); + } + + virtual void UploadFormDataAsyncCallback( + const FormStructure* submitted_form, + const base::TimeTicks& load_time, + const base::TimeTicks& interaction_time, + const base::TimeTicks& submission_time) OVERRIDE { + message_loop_runner_->Quit(); + + AutofillManager::UploadFormDataAsyncCallback(submitted_form, + load_time, + interaction_time, + submission_time); + } + + private: + bool autofill_enabled_; + scoped_refptr<content::MessageLoopRunner> message_loop_runner_; + + DISALLOW_COPY_AND_ASSIGN(TestAutofillManager); +}; + +} // namespace + +class AutofillMetricsTest : public ChromeRenderViewHostTestHarness { + public: + virtual ~AutofillMetricsTest(); + + virtual void SetUp() OVERRIDE; + virtual void TearDown() OVERRIDE; + + protected: + scoped_ptr<ConfirmInfoBarDelegate> CreateDelegate( + MockAutofillMetrics* metric_logger); + + scoped_ptr<TestAutofillDriver> autofill_driver_; + scoped_ptr<TestAutofillManager> autofill_manager_; + scoped_ptr<TestPersonalDataManager> personal_data_; + scoped_ptr<AutofillExternalDelegate> external_delegate_; +}; + +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() { + ChromeRenderViewHostTestHarness::SetUp(); + + // Ensure Mac OS X does not pop up a modal dialog for the Address Book. + autofill::test::DisableSystemServices(profile()); + + PersonalDataManagerFactory::GetInstance()->SetTestingFactory(profile(), NULL); + + TabAutofillManagerDelegate::CreateForWebContents(web_contents()); + + personal_data_.reset(new TestPersonalDataManager()); + personal_data_->SetBrowserContext(profile()); + autofill_driver_.reset(new TestAutofillDriver(web_contents())); + autofill_manager_.reset(new TestAutofillManager( + autofill_driver_.get(), + TabAutofillManagerDelegate::FromWebContents(web_contents()), + personal_data_.get())); + + external_delegate_.reset(new AutofillExternalDelegate( + web_contents(), + autofill_manager_.get(), + autofill_driver_.get())); + autofill_manager_->SetExternalDelegate(external_delegate_.get()); +} + +void AutofillMetricsTest::TearDown() { + // 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(); + autofill_driver_.reset(); + personal_data_.reset(); + ChromeRenderViewHostTestHarness::TearDown(); +} + +scoped_ptr<ConfirmInfoBarDelegate> AutofillMetricsTest::CreateDelegate( + MockAutofillMetrics* metric_logger) { + EXPECT_CALL(*metric_logger, + LogCreditCardInfoBarMetric(AutofillMetrics::INFOBAR_SHOWN)); + + CreditCard credit_card; + return AutofillCCInfoBarDelegate::Create( + metric_logger, + base::Bind(&TestPersonalDataManager::SaveImportedCreditCard, + base::Unretained(personal_data_.get()), 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<ServerFieldType> heuristic_types, server_types; + FormFieldData field; + + 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); + + 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); + + 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); + + 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); + + 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); + + 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; + ServerFieldType heuristic_type; + ServerFieldType 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<ServerFieldType> heuristic_types, server_types; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(failure_cases); ++i) { + FormFieldData field; + 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<ServerFieldType> heuristic_types, server_types; + + FormFieldData field; + 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); + 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); + 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); + 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(); + 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; + test::CreateTestFormField("Name", "name", "", "text", &field); + form.fields.push_back(field); + 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::NO_SPECIAL_FORMS_SEEN); + autofill_manager_->Reset(); + Mock::VerifyAndClearExpectations(autofill_manager_->metric_logger()); + } + + // Add another field to the form, so that it becomes fillable. + 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::NO_SPECIAL_FORMS_SEEN); + 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. + test::CreateTestFormField("", "", "", "text", &field); + field.autocomplete_attribute = "given-name"; + forms.back().fields.push_back(field); + test::CreateTestFormField("", "", "", "text", &field); + field.autocomplete_attribute = "email"; + forms.back().fields.push_back(field); + test::CreateTestFormField("", "", "", "text", &field); + field.autocomplete_attribute = "address-line1"; + 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::NO_SPECIAL_FORMS_SEEN); + 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; + test::CreateTestFormField( + "Autofilled", "autofilled", "Elvis Presley", "text", &field); + field.is_autofilled = true; + form.fields.push_back(field); + 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"); + 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<ServerFieldType> heuristic_types, server_types; + FormFieldData field; + + 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); + + 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); + + 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); + + 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); + + 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<ServerFieldType> field_types; + test::CreateTestFormField("Name", "name", "", "text", &field); + form.fields.push_back(field); + field_types.push_back(NAME_FULL); + test::CreateTestFormField("Email", "email", "", "email", &field); + form.fields.push_back(field); + field_types.push_back(EMAIL_ADDRESS); + 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. + 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(), + autofill::NO_SPECIAL_FORMS_SEEN); + + // 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(), + autofill::NO_SPECIAL_FORMS_SEEN); +} + +// Test that credit card infobar metrics are logged correctly. +TEST_F(AutofillMetricsTest, CreditCardInfoBar) { + testing::NiceMock<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) { + testing::NiceMock<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)); + 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; + test::CreateTestFormField("Name", "name", "", "text", &field); + form.fields.push_back(field); + 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(), + autofill::NO_SPECIAL_FORMS_SEEN); + } + + + // 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. + test::CreateTestFormField("Phone", "phone", "", "text", &field); + form.fields.push_back(field); + 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(), + autofill::NO_SPECIAL_FORMS_SEEN); + } + + // 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 = base::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; + test::CreateTestFormField("Name", "name", "", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Email", "email", "", "text", &field); + form.fields.push_back(field); + 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(), + autofill::NO_SPECIAL_FORMS_SEEN); + } + + // 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; + test::CreateTestFormField("Name", "name", "", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Email", "email", "", "text", &field); + form.fields.push_back(field); + 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::NO_SPECIAL_FORMS_SEEN); + 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::NO_SPECIAL_FORMS_SEEN); + 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::NO_SPECIAL_FORMS_SEEN); + 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::NO_SPECIAL_FORMS_SEEN); + 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()); + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_popup_delegate.h b/chromium/components/autofill/core/browser/autofill_popup_delegate.h new file mode 100644 index 00000000000..ec9a2207bc6 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_popup_delegate.h @@ -0,0 +1,46 @@ +// 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_CORE_BROWSER_AUTOFILL_POPUP_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_POPUP_DELEGATE_H_ + +#include "base/strings/string16.h" + +namespace content { +class KeyboardListener; +} + +namespace autofill { + +// 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 base::string16& value, + int identifier) = 0; + + // Delete the described suggestion. + virtual void RemoveSuggestion(const base::string16& value, + int identifier) = 0; + + // Informs the delegate that the Autofill previewed form should be cleared. + virtual void ClearPreviewedForm() = 0; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_POPUP_DELEGATE_H_ diff --git a/chromium/components/autofill/core/browser/autofill_profile.cc b/chromium/components/autofill/core/browser/autofill_profile.cc new file mode 100644 index 00000000000..6e18011e5c4 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_profile.cc @@ -0,0 +1,906 @@ +// 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/core/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/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/address.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/contact_info.h" +#include "components/autofill/core/browser/phone_number.h" +#include "components/autofill/core/browser/phone_number_i18n.h" +#include "components/autofill/core/browser/validation.h" +#include "components/autofill/core/common/form_field_data.h" +#include "grit/component_strings.h" +#include "ui/base/l10n/l10n_util.h" + +namespace autofill { +namespace { + +// Like |AutofillType::GetStorableType()|, but also returns |NAME_FULL| for +// first, middle, and last name field types. +ServerFieldType GetStorableTypeCollapsingNames(ServerFieldType type) { + ServerFieldType storable_type = AutofillType(type).GetStorableType(); + if (AutofillType(storable_type).group() == NAME) + return NAME_FULL; + + return storable_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<ServerFieldType>* suggested_fields, + ServerFieldType excluded_field, + std::vector<ServerFieldType>* distinguishing_fields) { + static const ServerFieldType 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<ServerFieldType> seen_fields; + seen_fields.insert(UNKNOWN_TYPE); + seen_fields.insert(GetStorableTypeCollapsingNames(excluded_field)); + + distinguishing_fields->clear(); + for (std::vector<ServerFieldType>::const_iterator it = + suggested_fields->begin(); + it != suggested_fields->end(); ++it) { + ServerFieldType suggested_type = GetStorableTypeCollapsingNames(*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 && + GetStorableTypeCollapsingNames(excluded_field) == NAME_FULL) { + for (std::vector<ServerFieldType>::const_iterator it = + suggested_fields->begin(); + it != suggested_fields->end(); ++it) { + if (*it != excluded_field && + GetStorableTypeCollapsingNames(*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 base::string16 MultiString(const AutofillProfile& p, + ServerFieldType type) { + std::vector<base::string16> values; + p.GetRawMultiInfo(type, &values); + base::string16 accumulate; + for (size_t i = 0; i < values.size(); ++i) { + if (i > 0) + accumulate += ASCIIToUTF16(" "); + accumulate += values[i]; + } + return accumulate; +} + +base::string16 GetFormGroupInfo(const FormGroup& form_group, + const AutofillType& type, + const std::string& app_locale) { + return app_locale.empty() ? + form_group.GetRawInfo(type.GetStorableType()) : + form_group.GetInfo(type, app_locale); +} + +template <class T> +void CopyValuesToItems(ServerFieldType type, + const std::vector<base::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(const AutofillType& type, + const std::vector<T>& form_group_items, + const std::string& app_locale, + std::vector<base::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(ServerFieldTypeSet* type_set) { + ServerFieldTypeSet collapsed_set; + for (ServerFieldTypeSet::iterator it = type_set->begin(); + it != type_set->end(); ++it) { + switch (*it) { + 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(*it); + } + } + std::swap(*type_set, collapsed_set); +} + +class FindByPhone { + public: + FindByPhone(const base::string16& phone, + const std::string& country_code, + const std::string& app_locale) + : phone_(phone), + country_code_(country_code), + app_locale_(app_locale) { + } + + bool operator()(const base::string16& phone) { + return i18n::PhoneNumbersMatch(phone, phone_, country_code_, app_locale_); + } + + bool operator()(const base::string16* phone) { + return i18n::PhoneNumbersMatch(*phone, phone_, country_code_, app_locale_); + } + + private: + base::string16 phone_; + std::string country_code_; + std::string app_locale_; +}; + +// Functor used to check for case-insensitive equality of two strings. +struct CaseInsensitiveStringEquals + : public std::binary_function<base::string16, base::string16, bool> +{ + bool operator()(const base::string16& x, const base::string16& y) const { + return + x.size() == y.size() && StringToLowerASCII(x) == StringToLowerASCII(y); + } +}; + +} // namespace + +AutofillProfile::AutofillProfile(const std::string& guid, + const std::string& origin) + : AutofillDataModel(guid, origin), + name_(1), + email_(1), + phone_number_(1, PhoneNumber(this)) { +} + +AutofillProfile::AutofillProfile() + : AutofillDataModel(base::GenerateGUID(), std::string()), + name_(1), + email_(1), + phone_number_(1, PhoneNumber(this)) { +} + +AutofillProfile::AutofillProfile(const AutofillProfile& profile) + : AutofillDataModel(std::string(), std::string()) { + operator=(profile); +} + +AutofillProfile::~AutofillProfile() { +} + +AutofillProfile& AutofillProfile::operator=(const AutofillProfile& profile) { + if (this == &profile) + return *this; + + set_guid(profile.guid()); + set_origin(profile.origin()); + + label_ = profile.label_; + name_ = profile.name_; + email_ = profile.email_; + company_ = profile.company_; + phone_number_ = profile.phone_number_; + + for (size_t i = 0; i < phone_number_.size(); ++i) + phone_number_[i].set_profile(this); + + address_ = profile.address_; + + return *this; +} + +void AutofillProfile::GetMatchingTypes( + const base::string16& text, + const std::string& app_locale, + ServerFieldTypeSet* matching_types) const { + FormGroupList info = FormGroups(); + for (FormGroupList::const_iterator it = info.begin(); it != info.end(); ++it) + (*it)->GetMatchingTypes(text, app_locale, matching_types); +} + +base::string16 AutofillProfile::GetRawInfo(ServerFieldType type) const { + const FormGroup* form_group = FormGroupForType(AutofillType(type)); + if (!form_group) + return base::string16(); + + return form_group->GetRawInfo(type); +} + +void AutofillProfile::SetRawInfo(ServerFieldType type, + const base::string16& value) { + FormGroup* form_group = MutableFormGroupForType(AutofillType(type)); + if (form_group) + form_group->SetRawInfo(type, CollapseWhitespace(value, false)); +} + +base::string16 AutofillProfile::GetInfo(const AutofillType& type, + const std::string& app_locale) const { + const FormGroup* form_group = FormGroupForType(type); + if (!form_group) + return base::string16(); + + return form_group->GetInfo(type, app_locale); +} + +bool AutofillProfile::SetInfo(const AutofillType& type, + const base::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( + ServerFieldType type, + const std::vector<base::string16>& values) { + switch (AutofillType(type).group()) { + case NAME: + case NAME_BILLING: + CopyValuesToItems(type, values, &name_, NameInfo()); + break; + case EMAIL: + CopyValuesToItems(type, values, &email_, EmailInfo()); + break; + case PHONE_HOME: + case PHONE_BILLING: + CopyValuesToItems(type, + values, + &phone_number_, + PhoneNumber(this)); + break; + default: + if (values.size() == 1) { + SetRawInfo(type, values[0]); + } else if (values.size() == 0) { + SetRawInfo(type, base::string16()); + } else { + // Shouldn't attempt to set multiple values on single-valued field. + NOTREACHED(); + } + break; + } +} + +void AutofillProfile::GetRawMultiInfo( + ServerFieldType type, + std::vector<base::string16>* values) const { + GetMultiInfoImpl(AutofillType(type), std::string(), values); +} + +void AutofillProfile::GetMultiInfo(const AutofillType& type, + const std::string& app_locale, + std::vector<base::string16>* values) const { + GetMultiInfoImpl(type, app_locale, values); +} + +void AutofillProfile::FillFormField(const AutofillField& field, + size_t variant, + const std::string& app_locale, + FormFieldData* field_data) const { + AutofillType type = field.Type(); + DCHECK_NE(CREDIT_CARD, type.group()); + DCHECK(field_data); + + if (type.GetStorableType() == PHONE_HOME_NUMBER) { + FillPhoneNumberField(field, variant, app_locale, field_data); + } else if (field_data->form_control_type == "select-one") { + FillSelectControl(type, app_locale, field_data); + } else { + std::vector<base::string16> values; + GetMultiInfo(type, app_locale, &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, + const std::string& app_locale, + FormFieldData* field_data) const { + std::vector<base::string16> values; + GetMultiInfo(field.Type(), app_locale, &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. + base::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 base::string16 AutofillProfile::Label() const { + return label_; +} + +bool AutofillProfile::IsEmpty(const std::string& app_locale) const { + ServerFieldTypeSet types; + GetNonEmptyTypes(app_locale, &types); + return types.empty(); +} + +bool AutofillProfile::IsPresentButInvalid(ServerFieldType type) const { + std::string country = UTF16ToUTF8(GetRawInfo(ADDRESS_HOME_COUNTRY)); + base::string16 data = GetRawInfo(type); + switch (type) { + case ADDRESS_HOME_STATE: + if (!data.empty() && country == "US" && !autofill::IsValidState(data)) + return true; + break; + + case ADDRESS_HOME_ZIP: + if (!data.empty() && country == "US" && !autofill::IsValidZip(data)) + return true; + break; + + case PHONE_HOME_WHOLE_NUMBER: { + if (!data.empty() && !i18n::PhoneObject(data, country).IsValidNumber()) + return true; + break; + } + + default: + NOTREACHED(); + break; + } + + return false; +} + + +int AutofillProfile::Compare(const AutofillProfile& profile) const { + const ServerFieldType 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 ServerFieldType 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<base::string16> values_a; + std::vector<base::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() && + origin() == profile.origin() && + Compare(profile) == 0; +} + +bool AutofillProfile::operator!=(const AutofillProfile& profile) const { + return !operator==(profile); +} + +const base::string16 AutofillProfile::PrimaryValue() const { + return GetRawInfo(ADDRESS_HOME_LINE1) + GetRawInfo(ADDRESS_HOME_CITY); +} + +bool AutofillProfile::IsSubsetOf(const AutofillProfile& profile, + const std::string& app_locale) const { + ServerFieldTypeSet types; + GetNonEmptyTypes(app_locale, &types); + + for (ServerFieldTypeSet::const_iterator it = types.begin(); it != types.end(); + ++it) { + if (*it == 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(*it).group() == PHONE_HOME) { + // Phone numbers should be canonicalized prior to being compared. + if (*it != PHONE_HOME_WHOLE_NUMBER) { + continue; + } else if (!i18n::PhoneNumbersMatch( + GetRawInfo(*it), + profile.GetRawInfo(*it), + UTF16ToASCII(GetRawInfo(ADDRESS_HOME_COUNTRY)), + app_locale)) { + return false; + } + } else if (StringToLowerASCII(GetRawInfo(*it)) != + StringToLowerASCII(profile.GetRawInfo(*it))) { + return false; + } + } + + return true; +} + +void AutofillProfile::OverwriteWithOrAddTo(const AutofillProfile& profile, + const std::string& app_locale) { + // Verified profiles should never be overwritten with unverified data. + DCHECK(!IsVerified() || profile.IsVerified()); + set_origin(profile.origin()); + + ServerFieldTypeSet field_types; + profile.GetNonEmptyTypes(app_locale, &field_types); + + // Only transfer "full" types (e.g. full name) and not fragments (e.g. + // first name, last name). + CollapseCompoundFieldTypes(&field_types); + + for (ServerFieldTypeSet::const_iterator iter = field_types.begin(); + iter != field_types.end(); ++iter) { + if (AutofillProfile::SupportsMultiValue(*iter)) { + std::vector<base::string16> new_values; + profile.GetRawMultiInfo(*iter, &new_values); + std::vector<base::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<base::string16>::iterator value_iter = + new_values.begin(); + value_iter != new_values.end(); ++value_iter) { + // Don't add duplicates. + if (group == PHONE_HOME) { + AddPhoneIfUnique(*value_iter, app_locale, &existing_values); + } else { + std::vector<base::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 { + base::string16 new_value = profile.GetRawInfo(*iter); + if (StringToLowerASCII(GetRawInfo(*iter)) != + StringToLowerASCII(new_value)) { + SetRawInfo(*iter, new_value); + } + } + } +} + +// static +bool AutofillProfile::SupportsMultiValue(ServerFieldType type) { + FieldTypeGroup group = AutofillType(type).group(); + return group == NAME || + group == NAME_BILLING || + group == EMAIL || + group == PHONE_HOME || + group == PHONE_BILLING; +} + +// static +bool AutofillProfile::AdjustInferredLabels( + std::vector<AutofillProfile*>* profiles) { + const size_t kMinimalFieldsShown = 2; + + std::vector<base::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<ServerFieldType>* suggested_fields, + ServerFieldType excluded_field, + size_t minimal_fields_shown, + std::vector<base::string16>* created_labels) { + DCHECK(profiles); + DCHECK(created_labels); + + std::vector<ServerFieldType> 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<base::string16, std::list<size_t> > labels; + for (size_t i = 0; i < profiles->size(); ++i) { + base::string16 label = + (*profiles)[i]->ConstructInferredLabel(fields_to_use, + minimal_fields_shown); + labels[label].push_back(i); + } + + created_labels->resize(profiles->size()); + for (std::map<base::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. + base::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( + ServerFieldTypeSet* supported_types) const { + FormGroupList info = FormGroups(); + for (FormGroupList::const_iterator it = info.begin(); it != info.end(); ++it) + (*it)->GetSupportedTypes(supported_types); +} + +bool AutofillProfile::FillCountrySelectControl( + const std::string& app_locale, + FormFieldData* field_data) const { + std::string country_code = UTF16ToASCII(GetRawInfo(ADDRESS_HOME_COUNTRY)); + + 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. + base::string16 value = field_data->option_values[i]; + base::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( + const AutofillType& type, + const std::string& app_locale, + std::vector<base::string16>* values) const { + switch (type.group()) { + case NAME: + case NAME_BILLING: + CopyItemsToValues(type, name_, app_locale, values); + break; + case EMAIL: + CopyItemsToValues(type, email_, app_locale, values); + break; + case PHONE_HOME: + case PHONE_BILLING: + CopyItemsToValues(type, phone_number_, app_locale, values); + break; + default: + values->resize(1); + (*values)[0] = GetFormGroupInfo(*this, type, app_locale); + } +} + +void AutofillProfile::AddPhoneIfUnique( + const base::string16& phone, + const std::string& app_locale, + std::vector<base::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. + std::string country_code = UTF16ToASCII(GetRawInfo(ADDRESS_HOME_COUNTRY)); + if (std::find_if(existing_phones->begin(), existing_phones->end(), + FindByPhone(phone, country_code, app_locale)) == + existing_phones->end()) { + existing_phones->push_back(phone); + } +} + +base::string16 AutofillProfile::ConstructInferredLabel( + const std::vector<ServerFieldType>& included_fields, + size_t num_fields_to_use) const { + const base::string16 separator = + l10n_util::GetStringUTF16(IDS_AUTOFILL_ADDRESS_SUMMARY_SEPARATOR); + + base::string16 label; + size_t num_fields_used = 0; + for (std::vector<ServerFieldType>::const_iterator it = + included_fields.begin(); + it != included_fields.end() && num_fields_used < num_fields_to_use; + ++it) { + base::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<ServerFieldType>& fields, + size_t num_fields_to_include, + std::vector<base::string16>* created_labels) { + // For efficiency, we first construct a map of fields to their text values and + // each value's frequency. + std::map<ServerFieldType, + std::map<base::string16, size_t> > field_text_frequencies_by_field; + for (std::vector<ServerFieldType>::const_iterator field = fields.begin(); + field != fields.end(); ++field) { + std::map<base::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]; + base::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<ServerFieldType> label_fields; + bool found_differentiating_field = false; + for (std::vector<ServerFieldType>::const_iterator field = fields.begin(); + field != fields.end(); ++field) { + // Skip over empty fields. + base::string16 field_text = profile->GetRawInfo(*field); + if (field_text.empty()) + continue; + + std::map<base::string16, size_t>& field_text_frequencies = + field_text_frequencies_by_field[*field]; + found_differentiating_field |= + !field_text_frequencies.count(base::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] = &phone_number_[0]; + v[4] = &address_; + return v; +} + +const FormGroup* AutofillProfile::FormGroupForType( + const AutofillType& type) const { + return const_cast<AutofillProfile*>(this)->MutableFormGroupForType(type); +} + +FormGroup* AutofillProfile::MutableFormGroupForType(const AutofillType& type) { + switch (type.group()) { + case NAME: + case NAME_BILLING: + return &name_[0]; + + case EMAIL: + return &email_[0]; + + case COMPANY: + return &company_; + + case PHONE_HOME: + case PHONE_BILLING: + return &phone_number_[0]; + + case ADDRESS_HOME: + case ADDRESS_BILLING: + return &address_; + + case NO_GROUP: + case CREDIT_CARD: + return NULL; + } + + NOTREACHED(); + return NULL; +} + +// So we can compare AutofillProfiles with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const AutofillProfile& profile) { + return os + << UTF16ToUTF8(profile.Label()) + << " " + << profile.guid() + << " " + << profile.origin() + << " " + << 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)); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_profile.h b/chromium/components/autofill/core/browser/autofill_profile.h new file mode 100644 index 00000000000..5a3f65c756a --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_profile.h @@ -0,0 +1,217 @@ +// 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_CORE_BROWSER_AUTOFILL_PROFILE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_PROFILE_H_ + +#include <stddef.h> + +#include <iosfwd> +#include <list> +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/address.h" +#include "components/autofill/core/browser/autofill_data_model.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/contact_info.h" +#include "components/autofill/core/browser/phone_number.h" + +namespace autofill { + +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 AutofillDataModel { + public: + AutofillProfile(const std::string& guid, const std::string& origin); + + // For use in STL containers. + AutofillProfile(); + AutofillProfile(const AutofillProfile& profile); + virtual ~AutofillProfile(); + + AutofillProfile& operator=(const AutofillProfile& profile); + + // FormGroup: + virtual void GetMatchingTypes( + const base::string16& text, + const std::string& app_locale, + ServerFieldTypeSet* matching_types) const OVERRIDE; + virtual base::string16 GetRawInfo(ServerFieldType type) const OVERRIDE; + virtual void SetRawInfo(ServerFieldType type, + const base::string16& value) OVERRIDE; + virtual base::string16 GetInfo(const AutofillType& type, + const std::string& app_locale) const OVERRIDE; + virtual bool SetInfo(const AutofillType& type, + const base::string16& value, + const std::string& app_locale) OVERRIDE; + + // AutofillDataModel: + virtual void FillFormField(const AutofillField& field, + size_t variant, + const std::string& app_locale, + FormFieldData* field_data) const OVERRIDE; + + // Multi-value equivalents to |GetInfo| and |SetInfo|. + void SetRawMultiInfo(ServerFieldType type, + const std::vector<base::string16>& values); + void GetRawMultiInfo(ServerFieldType type, + std::vector<base::string16>* values) const; + void GetMultiInfo(const AutofillType& type, + const std::string& app_locale, + std::vector<base::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, + const std::string& app_locale, + 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 base::string16 Label() const; + + // Returns true if there are no values (field types) set. + bool IsEmpty(const std::string& app_locale) const; + + // Returns true if the |type| of data in this profile is present, but invalid. + // Otherwise returns false. + bool IsPresentButInvalid(ServerFieldType type) 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 and origins 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, origins, 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 base::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 std::string& app_locale) 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, + const std::string& app_locale); + + // Returns |true| if |type| accepts multi-values. + static bool SupportsMultiValue(ServerFieldType 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<ServerFieldType>* suggested_fields, + ServerFieldType excluded_field, + size_t minimal_fields_shown, + std::vector<base::string16>* created_labels); + + private: + typedef std::vector<const FormGroup*> FormGroupList; + + // FormGroup: + virtual bool FillCountrySelectControl(const std::string& app_locale, + FormFieldData* field) const OVERRIDE; + virtual void GetSupportedTypes( + ServerFieldTypeSet* 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(const AutofillType& type, + const std::string& app_locale, + std::vector<base::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 base::string16& phone, + const std::string& app_locale, + std::vector<base::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. + base::string16 ConstructInferredLabel( + const std::vector<ServerFieldType>& 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<ServerFieldType>& fields, + size_t num_fields_to_include, + std::vector<base::string16>* created_labels); + + // Utilities for listing and lookup of the data members that constitute + // user-visible profile information. + FormGroupList FormGroups() const; + const FormGroup* FormGroupForType(const AutofillType& type) const; + FormGroup* MutableFormGroupForType(const AutofillType& type); + + // The label presented to the user when selecting a profile. + base::string16 label_; + + // Personal information for this profile. + std::vector<NameInfo> name_; + std::vector<EmailInfo> email_; + CompanyInfo company_; + std::vector<PhoneNumber> phone_number_; + Address address_; +}; + +// So we can compare AutofillProfiles with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const AutofillProfile& profile); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_PROFILE_H_ diff --git a/chromium/components/autofill/core/browser/autofill_profile_unittest.cc b/chromium/components/autofill/core/browser/autofill_profile_unittest.cc new file mode 100644 index 00000000000..f67c536324d --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_profile_unittest.cc @@ -0,0 +1,931 @@ +// 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 "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/strings/string16.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_common_test.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/common/form_field_data.h" +#include "grit/component_strings.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +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(base::GenerateGUID(), "https://www.example.com/"); + // Empty profile - nothing to update. + EXPECT_FALSE(UpdateProfileLabel(&profile0)); + base::string16 summary0 = profile0.Label(); + EXPECT_EQ(base::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(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&profile00, "", "", "", + "johnwayne@me.xyz", "Fox", "", "", "Hollywood", "CA", "91601", "US", + "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile00)); + base::string16 summary00 = profile00.Label(); + EXPECT_EQ(ASCIIToUTF16("Hollywood, CA"), summary00); + + // Case 1: "<address>" without line 2. + AutofillProfile profile1(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&profile1, "", "", "", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "", "Hollywood", "CA", + "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile1)); + base::string16 summary1 = profile1.Label(); + EXPECT_EQ(ASCIIToUTF16("123 Zoo St., Hollywood"), summary1); + + // Case 1a: "<address>" with line 2. + AutofillProfile profile1a(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&profile1a, "", "", "", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile1a)); + base::string16 summary1a = profile1a.Label(); + EXPECT_EQ(ASCIIToUTF16("123 Zoo St., unit 5"), summary1a); + + // Case 2: "<lastname>" + AutofillProfile profile2(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&profile2, "", "Mitchell", + "Morrison", "johnwayne@me.xyz", "Fox", "", "", "Hollywood", "CA", + "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile2)); + base::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(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&profile3, "", "Mitchell", + "Morrison", "johnwayne@me.xyz", "Fox", "123 Zoo St.", "", + "Hollywood", "CA", "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile3)); + base::string16 summary3 = profile3.Label(); + EXPECT_EQ(ASCIIToUTF16("Mitchell Morrison, 123 Zoo St."), summary3); + + // Case 4: "<firstname>" + AutofillProfile profile4(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&profile4, "Marion", "Mitchell", "", + "johnwayne@me.xyz", "Fox", "", "", "Hollywood", "CA", "91601", "US", + "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile4)); + base::string16 summary4 = profile4.Label(); + EXPECT_EQ(ASCIIToUTF16("Marion Mitchell, Hollywood"), summary4); + + // Case 5: "<firstname>, <address>" + AutofillProfile profile5(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&profile5, "Marion", "Mitchell", "", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile5)); + base::string16 summary5 = profile5.Label(); + EXPECT_EQ(ASCIIToUTF16("Marion Mitchell, 123 Zoo St."), summary5); + + // Case 6: "<firstname> <lastname>" + AutofillProfile profile6(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&profile6, "Marion", "Mitchell", + "Morrison", "johnwayne@me.xyz", "Fox", "", "", "Hollywood", "CA", + "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile6)); + base::string16 summary6 = profile6.Label(); + EXPECT_EQ(ASCIIToUTF16("Marion Mitchell Morrison, Hollywood"), + summary6); + + // Case 7: "<firstname> <lastname>, <address>" + AutofillProfile profile7(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&profile7, "Marion", "Mitchell", + "Morrison", "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", + "Hollywood", "CA", "91601", "US", "16505678910"); + EXPECT_TRUE(UpdateProfileLabel(&profile7)); + base::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(base::GenerateGUID(), "https://www.example.com/"); + 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(); + base::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) { + ScopedVector<AutofillProfile> profiles; + profiles.push_back( + new AutofillProfile(base::GenerateGUID(), "https://www.example.com/")); + test::SetProfileInfo( + profiles[0], + "John", + "", + "Doe", + "johndoe@hades.com", + "Underworld", + "666 Erebus St.", + "", + "Elysium", "CA", + "91111", + "US", + "16502111111"); + profiles.push_back( + new AutofillProfile(base::GenerateGUID(), "http://www.example.com/")); + 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.get())); + // No need to adjust them anymore. + EXPECT_FALSE(AutofillProfile::AdjustInferredLabels(&profiles.get())); + 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(base::GenerateGUID(), "Chrome settings")); + test::SetProfileInfo( + profiles[2], + "John", + "", + "Doe", + "johndoe@tertium.com", + "Underworld", + "666 Erebus St.", + "", + "Elysium", "CA", + "91111", + "US", + "16502111111"); + EXPECT_TRUE(AutofillProfile::AdjustInferredLabels(&profiles.get())); + + // 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()); + + profiles.resize(2); + + profiles.push_back( + new AutofillProfile(base::GenerateGUID(), std::string())); + 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.get())); + + // 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(base::GenerateGUID(), "https://www.example.com/")); + 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.get())); + + 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(base::GenerateGUID(), "https://www.example.com/")); + 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.get())); + + 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.get())); +} + +TEST(AutofillProfileTest, CreateInferredLabels) { + ScopedVector<AutofillProfile> profiles; + profiles.push_back( + new AutofillProfile(base::GenerateGUID(), "https://www.example.com/")); + test::SetProfileInfo(profiles[0], + "John", + "", + "Doe", + "johndoe@hades.com", + "Underworld", + "666 Erebus St.", + "", + "Elysium", "CA", + "91111", + "US", + "16502111111"); + profiles.push_back( + new AutofillProfile(base::GenerateGUID(), "https://www.example.com/")); + test::SetProfileInfo(profiles[1], + "Jane", + "", + "Doe", + "janedoe@tertium.com", + "Pluto Inc.", + "123 Letha Shore.", + "", + "Dis", "CA", + "91222", + "US", + "12345678910"); + std::vector<base::string16> labels; + // Two fields at least - no filter. + AutofillProfile::CreateInferredLabels(&profiles.get(), 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.get(), 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<ServerFieldType> 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.get(), &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.get(), &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.get(), &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.get(), &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.get(), &suggested_fields, + NAME_MIDDLE, 1, &labels); + EXPECT_EQ(base::string16(), labels[0]); + EXPECT_EQ(base::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.get(), &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.get(), &suggested_fields, + NAME_FULL, 1, &labels); + EXPECT_EQ(base::string16(ASCIIToUTF16("666 Erebus St.")), labels[0]); + EXPECT_EQ(base::string16(ASCIIToUTF16("123 Letha Shore.")), labels[1]); +} + +// 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(base::GenerateGUID(), "https://www.example.com/")); + test::SetProfileInfo(profiles[0], + "John", "", "Doe", "doe@example.com", "", + "88 Nowhere Ave.", "", "", "", "", "", ""); + profiles.push_back( + new AutofillProfile(base::GenerateGUID(), "https://www.example.com/")); + 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<ServerFieldType> suggested_fields; + suggested_fields.push_back(NAME_LAST); + suggested_fields.push_back(ADDRESS_HOME_LINE1); + suggested_fields.push_back(EMAIL_ADDRESS); + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/")); + test::SetProfileInfo(profiles[0], + "John", "", "Doe", "doe@example.com", "", + "88 Nowhere Ave.", "", "", "", "", "", ""); + profiles.push_back( + new AutofillProfile(base::GenerateGUID(), "https://www.example.com/")); + 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<ServerFieldType> suggested_fields; + suggested_fields.push_back(ADDRESS_HOME_LINE1); + suggested_fields.push_back(ADDRESS_BILLING_LINE1); + suggested_fields.push_back(EMAIL_ADDRESS); + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/")); + test::SetProfileInfo(profiles[0], + "John", "", "Doe", "doe@example.com", + "Gogole", "", "", "", "", "", "", ""); + profiles.push_back( + new AutofillProfile(base::GenerateGUID(), "https://www.example.com/")); + test::SetProfileInfo(profiles[1], + "John", "", "Doe", "doe@example.com", + "Ggoole", "", "", "", "", "", "", ""); + profiles.push_back( + new AutofillProfile(base::GenerateGUID(), "https://www.example.com/")); + test::SetProfileInfo(profiles[2], + "John", "", "Doe", "john.doe@example.com", + "Goolge", "", "", "", "", "", "", ""); + + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/")); + b.reset( + new AutofillProfile(base::GenerateGUID(), "https://www.example.com/")); + test::SetProfileInfo(a.get(), "Thomas", NULL, "Jefferson", + "declaration_guy@gmail.com", NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL); + 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, "en-US")); + + // |b| is not a subset of |a|. + EXPECT_FALSE(b->IsSubsetOf(*a, "en-US")); + + // |a| is a subset of |a|. + EXPECT_TRUE(a->IsSubsetOf(*a, "en-US")); + + // One field in |b| is different. + a.reset( + new AutofillProfile(base::GenerateGUID(), "https://www.example.com/")); + b.reset( + new AutofillProfile(base::GenerateGUID(), "https://www.example.com/")); + test::SetProfileInfo(a.get(), "Thomas", NULL, "Jefferson", + "declaration_guy@gmail.com", NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL); + test::SetProfileInfo(a.get(), "Thomas", NULL, "Adams", + "declaration_guy@gmail.com", NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL); + EXPECT_FALSE(a->IsSubsetOf(*b, "en-US")); +} + +TEST(AutofillProfileTest, OverwriteWithOrAddTo) { + AutofillProfile a(base::GenerateGUID(), "https://www.example.com"); + test::SetProfileInfo(&a, "Marion", "Mitchell", "Morrison", + "marion@me.xyz", "Fox", "123 Zoo St.", "unit 5", + "Hollywood", "CA", "91601", "US", + "12345678910"); + std::vector<base::string16> names; + a.GetRawMultiInfo(NAME_FULL, &names); + names.push_back(ASCIIToUTF16("Marion Morrison")); + a.SetRawMultiInfo(NAME_FULL, names); + + // Create an identical profile except that the new profile: + // (1) Has a different origin, + // (2) Has a different address line 2, + // (3) Lacks a company name, and + // (4) Has a different full name variant. + AutofillProfile b = a; + b.set_guid(base::GenerateGUID()); + b.set_origin("Chrome settings"); + b.SetRawInfo(ADDRESS_HOME_LINE2, ASCIIToUTF16("area 51")); + b.SetRawInfo(COMPANY_NAME, base::string16()); + b.GetRawMultiInfo(NAME_FULL, &names); + names.push_back(ASCIIToUTF16("Marion M. Morrison")); + b.SetRawMultiInfo(NAME_FULL, names); + + a.OverwriteWithOrAddTo(b, "en-US"); + EXPECT_EQ("Chrome settings", a.origin()); + EXPECT_EQ(ASCIIToUTF16("area 51"), a.GetRawInfo(ADDRESS_HOME_LINE2)); + EXPECT_EQ(ASCIIToUTF16("Fox"), a.GetRawInfo(COMPANY_NAME)); + a.GetRawMultiInfo(NAME_FULL, &names); + ASSERT_EQ(3U, names.size()); + EXPECT_EQ(ASCIIToUTF16("Marion Mitchell Morrison"), names[0]); + EXPECT_EQ(ASCIIToUTF16("Marion Morrison"), names[1]); + EXPECT_EQ(ASCIIToUTF16("Marion M. Morrison"), names[2]); +} + +TEST(AutofillProfileTest, AssignmentOperator) { + AutofillProfile a(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&a, "Marion", "Mitchell", "Morrison", + "marion@me.xyz", "Fox", "123 Zoo St.", "unit 5", + "Hollywood", "CA", "91601", "US", + "12345678910"); + + // Result of assignment should be logically equal to the original profile. + AutofillProfile b(base::GenerateGUID(), "http://www.example.com/"); + 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(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&a, "Marion", "Mitchell", "Morrison", + "marion@me.xyz", "Fox", "123 Zoo St.", "unit 5", + "Hollywood", "CA", "91601", "US", + "12345678910"); + + // Clone should be logically equal to the original. + AutofillProfile b(a); + EXPECT_TRUE(a == b); +} + +TEST(AutofillProfileTest, Compare) { + AutofillProfile a(base::GenerateGUID(), std::string()); + AutofillProfile b(base::GenerateGUID(), std::string()); + + // 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)); + + // Origins don't count. + a.set_origin("apple"); + b.set_origin("banana"); + EXPECT_EQ(0, a.Compare(b)); + + // Different values produce non-zero results. + test::SetProfileInfo(&a, "Jimmy", NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL); + 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 + test::SetProfileInfo(&a, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, "650.555.4321"); + 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, MultiValueNames) { + AutofillProfile p(base::GenerateGUID(), "https://www.example.com/"); + const base::string16 kJohnDoe(ASCIIToUTF16("John Doe")); + const base::string16 kJohnPDoe(ASCIIToUTF16("John P. Doe")); + std::vector<base::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<base::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 base::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(base::string16(), get_values[0]); + + // Expect regular |GetInfo| returns empty value. + EXPECT_EQ(base::string16(), p.GetRawInfo(NAME_FULL)); +} + +TEST(AutofillProfileTest, MultiValueEmails) { + AutofillProfile p(base::GenerateGUID(), "https://www.example.com/"); + const base::string16 kJohnDoe(ASCIIToUTF16("john@doe.com")); + const base::string16 kJohnPDoe(ASCIIToUTF16("john_p@doe.com")); + std::vector<base::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<base::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 base::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(base::string16(), get_values[0]); + + // Expect regular |GetInfo| returns empty value. + EXPECT_EQ(base::string16(), p.GetRawInfo(EMAIL_ADDRESS)); +} + +TEST(AutofillProfileTest, MultiValuePhone) { + AutofillProfile p(base::GenerateGUID(), "https://www.example.com/"); + const base::string16 kJohnDoe(ASCIIToUTF16("4151112222")); + const base::string16 kJohnPDoe(ASCIIToUTF16("4151113333")); + std::vector<base::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<base::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 base::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(base::string16(), get_values[0]); + + // Expect regular |GetInfo| returns empty value. + EXPECT_EQ(base::string16(), p.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); +} + +TEST(AutofillProfileTest, AddressCountryFull) { + const char* const kCountries[] = { + "Albania", "Canada" + }; + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/"); + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("CA")); + profile.FillSelectControl( + AutofillType(ADDRESS_HOME_COUNTRY), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("Canada"), field.value); +} + +TEST(AutofillProfileTest, AddressCountryAbbrev) { + const char* const kCountries[] = { + "AL", "CA" + }; + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/"); + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("CA")); + profile.FillSelectControl( + AutofillType(ADDRESS_HOME_COUNTRY), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("CA"), field.value); +} + +TEST(AutofillProfileTest, AddressStateFull) { + const char* const kStates[] = { + "Alabama", "California" + }; + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/"); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("CA")); + profile.FillSelectControl(AutofillType(ADDRESS_HOME_STATE), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("California"), field.value); +} + +TEST(AutofillProfileTest, AddressStateAbbrev) { + const char* const kStates[] = { + "AL", "CA" + }; + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/"); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("California")); + profile.FillSelectControl(AutofillType(ADDRESS_HOME_STATE), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("CA"), field.value); +} + +TEST(AutofillProfileTest, FillByValue) { + const char* const kStates[] = { + "Alabama", "California" + }; + std::vector<base::string16> values(arraysize(kStates)); + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/"); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("California")); + profile.FillSelectControl(AutofillType(ADDRESS_HOME_STATE), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("California"), field.value); +} + +TEST(AutofillProfileTest, FillByContents) { + const char* const kStates[] = { + "Alabama", "California" + }; + std::vector<base::string16> values(arraysize(kStates)); + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/"); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("California")); + profile.FillSelectControl(AutofillType(ADDRESS_HOME_STATE), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("2"), field.value); +} + +TEST(AutofillProfileTest, IsPresentButInvalid) { + AutofillProfile profile(base::GenerateGUID(), "https://www.example.com/"); + EXPECT_FALSE(profile.IsPresentButInvalid(ADDRESS_HOME_STATE)); + EXPECT_FALSE(profile.IsPresentButInvalid(ADDRESS_HOME_ZIP)); + EXPECT_FALSE(profile.IsPresentButInvalid(PHONE_HOME_WHOLE_NUMBER)); + + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + EXPECT_FALSE(profile.IsPresentButInvalid(ADDRESS_HOME_STATE)); + EXPECT_FALSE(profile.IsPresentButInvalid(ADDRESS_HOME_ZIP)); + EXPECT_FALSE(profile.IsPresentButInvalid(PHONE_HOME_WHOLE_NUMBER)); + + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("C")); + EXPECT_TRUE(profile.IsPresentButInvalid(ADDRESS_HOME_STATE)); + + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("CA")); + EXPECT_FALSE(profile.IsPresentButInvalid(ADDRESS_HOME_STATE)); + + profile.SetRawInfo(ADDRESS_HOME_ZIP, ASCIIToUTF16("90")); + EXPECT_TRUE(profile.IsPresentButInvalid(ADDRESS_HOME_ZIP)); + + profile.SetRawInfo(ADDRESS_HOME_ZIP, ASCIIToUTF16("90210")); + EXPECT_FALSE(profile.IsPresentButInvalid(ADDRESS_HOME_ZIP)); + + profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("310")); + EXPECT_TRUE(profile.IsPresentButInvalid(PHONE_HOME_WHOLE_NUMBER)); + + profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("(310) 310-6000")); + EXPECT_FALSE(profile.IsPresentButInvalid(PHONE_HOME_WHOLE_NUMBER)); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_regex_constants.cc.utf8 b/chromium/components/autofill/core/browser/autofill_regex_constants.cc.utf8 new file mode 100644 index 00000000000..960f43269db --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_regex_constants.cc.utf8 @@ -0,0 +1,294 @@ +// 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. + +// 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/core/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 kAddressLinesExtraRe[] = + "address.*line[3-9]|address[3-9]|addr[3-9]|street|line[3-9]" + "|municipio" // es + "|batiment|residence" // fr-FR + "|indirizzo[3-9]"; // 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" + "|courriel" // fr + "|メールアドレス" // 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/chromium/components/autofill/core/browser/autofill_regex_constants.h b/chromium/components/autofill/core/browser/autofill_regex_constants.h new file mode 100644 index 00000000000..1c97dec282e --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_regex_constants.h @@ -0,0 +1,59 @@ +// 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_CORE_BROWSER_AUTOFILL_REGEX_CONSTANTS_H_ +#define COMPONENTS_AUTOFILL_CORE_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 kAddressLinesExtraRe[]; +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_CORE_BROWSER_AUTOFILL_REGEX_CONSTANTS_H_ diff --git a/chromium/components/autofill/core/browser/autofill_regexes.cc b/chromium/components/autofill/core/browser/autofill_regexes.cc new file mode 100644 index 00000000000..c8b2ba487a8 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_regexes.cc @@ -0,0 +1,84 @@ +// 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/core/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/strings/string16.h" +#include "third_party/icu/source/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 base::string16& pattern); + + private: + AutofillRegexes(); + ~AutofillRegexes(); + friend struct DefaultSingletonTraits<AutofillRegexes>; + + // Maps patterns to their corresponding regex matchers. + std::map<base::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 base::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 base::string16& input, + const base::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/chromium/components/autofill/core/browser/autofill_regexes.h b/chromium/components/autofill/core/browser/autofill_regexes.h new file mode 100644 index 00000000000..f4b57759e03 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_regexes.h @@ -0,0 +1,20 @@ +// 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_CORE_BROWSER_AUTOFILL_REGEXES_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_REGEXES_H_ + +#include "base/strings/string16.h" + +// Parsing utilities. +namespace autofill { + +// Case-insensitive regular expression matching. +// Returns true if |pattern| is found in |input|. +bool MatchesPattern(const base::string16& input, + const base::string16& pattern); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_REGEXES_H_ diff --git a/chromium/components/autofill/core/browser/autofill_regexes_unittest.cc b/chromium/components/autofill/core/browser/autofill_regexes_unittest.cc new file mode 100644 index 00000000000..453941567ff --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_regexes_unittest.cc @@ -0,0 +1,64 @@ +// 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/core/browser/autofill_regexes.h" + +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_regex_constants.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +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))); + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_scanner.cc b/chromium/components/autofill/core/browser/autofill_scanner.cc new file mode 100644 index 00000000000..418362e4c8c --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_scanner.cc @@ -0,0 +1,58 @@ +// 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/core/browser/autofill_scanner.h" + +#include "base/logging.h" +#include "components/autofill/core/browser/autofill_field.h" + +namespace autofill { + +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_); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_scanner.h b/chromium/components/autofill/core/browser/autofill_scanner.h new file mode 100644 index 00000000000..2a634e6d8aa --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_scanner.h @@ -0,0 +1,61 @@ +// 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_CORE_BROWSER_AUTOFILL_SCANNER_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_SCANNER_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/strings/string16.h" + +namespace autofill { + +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); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_SCANNER_H_ diff --git a/chromium/components/autofill/core/browser/autofill_server_field_info.h b/chromium/components/autofill/core/browser/autofill_server_field_info.h new file mode 100644 index 00000000000..e62cba7d095 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_server_field_info.h @@ -0,0 +1,24 @@ +// 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_CORE_BROWSER_AUTOFILL_SERVER_FIELD_INFO_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_SERVER_FIELD_INFO_H_ + +#include <string> + +#include "components/autofill/core/browser/field_types.h" + +namespace autofill { + +struct AutofillServerFieldInfo { + // The predicted type returned by the Autofill server for this field. + ServerFieldType field_type; + // Default value to be used for the field (only applies to + // FIELD_WITH_DEFAULT_TYPE field type) + std::string default_value; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_SERVER_FIELD_INFO_H_ diff --git a/chromium/components/autofill/core/browser/autofill_type.cc b/chromium/components/autofill/core/browser/autofill_type.cc new file mode 100644 index 00000000000..f1e280ba9d8 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_type.cc @@ -0,0 +1,612 @@ +// 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/core/browser/autofill_type.h" + +#include "base/logging.h" + +namespace autofill { + +AutofillType::AutofillType(ServerFieldType field_type) + : html_type_(HTML_TYPE_UNKNOWN), + html_mode_(HTML_MODE_NONE) { + 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)) { + server_type_ = UNKNOWN_TYPE; + } else { + server_type_ = field_type; + } +} + +AutofillType::AutofillType(HtmlFieldType field_type, HtmlFieldMode mode) + : server_type_(UNKNOWN_TYPE), + html_type_(field_type), + html_mode_(mode) {} + + +AutofillType::AutofillType(const AutofillType& autofill_type) { + *this = autofill_type; +} + +AutofillType& AutofillType::operator=(const AutofillType& autofill_type) { + if (this != &autofill_type) { + this->server_type_ = autofill_type.server_type_; + this->html_type_ = autofill_type.html_type_; + this->html_mode_ = autofill_type.html_mode_; + } + + return *this; +} + +FieldTypeGroup AutofillType::group() const { + switch (server_type_) { + case NAME_FIRST: + case NAME_MIDDLE: + case NAME_LAST: + case NAME_MIDDLE_INITIAL: + case NAME_FULL: + case NAME_SUFFIX: + return NAME; + + case NAME_BILLING_FIRST: + case NAME_BILLING_MIDDLE: + case NAME_BILLING_LAST: + case NAME_BILLING_MIDDLE_INITIAL: + case NAME_BILLING_FULL: + case NAME_BILLING_SUFFIX: + return NAME_BILLING; + + 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_HOME; + + case PHONE_BILLING_NUMBER: + case PHONE_BILLING_CITY_CODE: + case PHONE_BILLING_COUNTRY_CODE: + case PHONE_BILLING_CITY_AND_NUMBER: + case PHONE_BILLING_WHOLE_NUMBER: + return PHONE_BILLING; + + 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; + + case NO_SERVER_DATA: + case EMPTY_TYPE: + case PHONE_FAX_NUMBER: + case PHONE_FAX_CITY_CODE: + case PHONE_FAX_COUNTRY_CODE: + case PHONE_FAX_CITY_AND_NUMBER: + case PHONE_FAX_WHOLE_NUMBER: + case FIELD_WITH_DEFAULT_VALUE: + return NO_GROUP; + + case MAX_VALID_FIELD_TYPE: + NOTREACHED(); + return NO_GROUP; + + case UNKNOWN_TYPE: + break; + } + + switch (html_type_) { + case HTML_TYPE_NAME: + case HTML_TYPE_GIVEN_NAME: + case HTML_TYPE_ADDITIONAL_NAME: + case HTML_TYPE_ADDITIONAL_NAME_INITIAL: + case HTML_TYPE_FAMILY_NAME: + return html_mode_ == HTML_MODE_BILLING ? NAME_BILLING : NAME; + + case HTML_TYPE_ORGANIZATION: + return COMPANY; + + case HTML_TYPE_STREET_ADDRESS: + case HTML_TYPE_ADDRESS_LINE1: + case HTML_TYPE_ADDRESS_LINE2: + case HTML_TYPE_LOCALITY: + case HTML_TYPE_REGION: + case HTML_TYPE_COUNTRY_CODE: + case HTML_TYPE_COUNTRY_NAME: + case HTML_TYPE_POSTAL_CODE: + return html_mode_ == HTML_MODE_BILLING ? ADDRESS_BILLING : ADDRESS_HOME; + + case HTML_TYPE_CREDIT_CARD_NAME: + case HTML_TYPE_CREDIT_CARD_NUMBER: + case HTML_TYPE_CREDIT_CARD_EXP: + case HTML_TYPE_CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: + case HTML_TYPE_CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: + case HTML_TYPE_CREDIT_CARD_EXP_MONTH: + case HTML_TYPE_CREDIT_CARD_EXP_YEAR: + case HTML_TYPE_CREDIT_CARD_EXP_2_DIGIT_YEAR: + case HTML_TYPE_CREDIT_CARD_EXP_4_DIGIT_YEAR: + case HTML_TYPE_CREDIT_CARD_VERIFICATION_CODE: + case HTML_TYPE_CREDIT_CARD_TYPE: + return CREDIT_CARD; + + case HTML_TYPE_TEL: + case HTML_TYPE_TEL_COUNTRY_CODE: + case HTML_TYPE_TEL_NATIONAL: + case HTML_TYPE_TEL_AREA_CODE: + case HTML_TYPE_TEL_LOCAL: + case HTML_TYPE_TEL_LOCAL_PREFIX: + case HTML_TYPE_TEL_LOCAL_SUFFIX: + return html_mode_ == HTML_MODE_BILLING ? PHONE_BILLING : PHONE_HOME; + + case HTML_TYPE_EMAIL: + return EMAIL; + + case HTML_TYPE_UNKNOWN: + break; + } + + return NO_GROUP; +} + +bool AutofillType::IsUnknown() const { + return server_type_ == UNKNOWN_TYPE && html_type_ == HTML_TYPE_UNKNOWN; +} + +ServerFieldType AutofillType::GetStorableType() const { + // Map billing types to the equivalent non-billing types. + switch (server_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; + + case PHONE_BILLING_WHOLE_NUMBER: + return PHONE_HOME_WHOLE_NUMBER; + + case PHONE_BILLING_NUMBER: + return PHONE_HOME_NUMBER; + + case PHONE_BILLING_CITY_CODE: + return PHONE_HOME_CITY_CODE; + + case PHONE_BILLING_COUNTRY_CODE: + return PHONE_HOME_COUNTRY_CODE; + + case PHONE_BILLING_CITY_AND_NUMBER: + return PHONE_HOME_CITY_AND_NUMBER; + + case NAME_BILLING_FIRST: + return NAME_FIRST; + + case NAME_BILLING_MIDDLE: + return NAME_MIDDLE; + + case NAME_BILLING_LAST: + return NAME_LAST; + + case NAME_BILLING_MIDDLE_INITIAL: + return NAME_MIDDLE_INITIAL; + + case NAME_BILLING_FULL: + return NAME_FULL; + + case NAME_BILLING_SUFFIX: + return NAME_SUFFIX; + + case UNKNOWN_TYPE: + break; // Try to parse HTML types instead. + + default: + return server_type_; + } + + switch (html_type_) { + case HTML_TYPE_UNKNOWN: + return UNKNOWN_TYPE; + + case HTML_TYPE_NAME: + return NAME_FULL; + + case HTML_TYPE_GIVEN_NAME: + return NAME_FIRST; + + case HTML_TYPE_ADDITIONAL_NAME: + return NAME_MIDDLE; + + case HTML_TYPE_FAMILY_NAME: + return NAME_LAST; + + case HTML_TYPE_ORGANIZATION: + return COMPANY_NAME; + + case HTML_TYPE_STREET_ADDRESS: + return ADDRESS_HOME_LINE1; + + case HTML_TYPE_ADDRESS_LINE1: + return ADDRESS_HOME_LINE1; + + case HTML_TYPE_ADDRESS_LINE2: + return ADDRESS_HOME_LINE2; + + case HTML_TYPE_LOCALITY: + return ADDRESS_HOME_CITY; + + case HTML_TYPE_REGION: + return ADDRESS_HOME_STATE; + + case HTML_TYPE_COUNTRY_CODE: + case HTML_TYPE_COUNTRY_NAME: + return ADDRESS_HOME_COUNTRY; + + case HTML_TYPE_POSTAL_CODE: + return ADDRESS_HOME_ZIP; + + case HTML_TYPE_CREDIT_CARD_NAME: + return CREDIT_CARD_NAME; + + case HTML_TYPE_CREDIT_CARD_NUMBER: + return CREDIT_CARD_NUMBER; + + case HTML_TYPE_CREDIT_CARD_EXP: + return CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR; + + case HTML_TYPE_CREDIT_CARD_EXP_MONTH: + return CREDIT_CARD_EXP_MONTH; + + case HTML_TYPE_CREDIT_CARD_EXP_YEAR: + return CREDIT_CARD_EXP_4_DIGIT_YEAR; + + case HTML_TYPE_CREDIT_CARD_VERIFICATION_CODE: + return CREDIT_CARD_VERIFICATION_CODE; + + case HTML_TYPE_CREDIT_CARD_TYPE: + return CREDIT_CARD_TYPE; + + case HTML_TYPE_TEL: + return PHONE_HOME_WHOLE_NUMBER; + + case HTML_TYPE_TEL_COUNTRY_CODE: + return PHONE_HOME_COUNTRY_CODE; + + case HTML_TYPE_TEL_NATIONAL: + return PHONE_HOME_CITY_AND_NUMBER; + + case HTML_TYPE_TEL_AREA_CODE: + return PHONE_HOME_CITY_CODE; + + case HTML_TYPE_TEL_LOCAL: + case HTML_TYPE_TEL_LOCAL_PREFIX: + case HTML_TYPE_TEL_LOCAL_SUFFIX: + return PHONE_HOME_NUMBER; + + case HTML_TYPE_EMAIL: + return EMAIL_ADDRESS; + + case HTML_TYPE_ADDITIONAL_NAME_INITIAL: + return NAME_MIDDLE_INITIAL; + + case HTML_TYPE_CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: + return CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR; + + case HTML_TYPE_CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: + return CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR; + + case HTML_TYPE_CREDIT_CARD_EXP_2_DIGIT_YEAR: + return CREDIT_CARD_EXP_2_DIGIT_YEAR; + + case HTML_TYPE_CREDIT_CARD_EXP_4_DIGIT_YEAR: + return CREDIT_CARD_EXP_4_DIGIT_YEAR; + } + + NOTREACHED(); + return UNKNOWN_TYPE; +} + +// static +ServerFieldType AutofillType::GetEquivalentBillingFieldType( + ServerFieldType field_type) { + switch (field_type) { + case ADDRESS_HOME_LINE1: + return ADDRESS_BILLING_LINE1; + + case ADDRESS_HOME_LINE2: + return ADDRESS_BILLING_LINE2; + + case ADDRESS_HOME_APT_NUM: + return ADDRESS_BILLING_APT_NUM; + + case ADDRESS_HOME_CITY: + return ADDRESS_BILLING_CITY; + + case ADDRESS_HOME_STATE: + return ADDRESS_BILLING_STATE; + + case ADDRESS_HOME_ZIP: + return ADDRESS_BILLING_ZIP; + + case ADDRESS_HOME_COUNTRY: + return ADDRESS_BILLING_COUNTRY; + + case PHONE_HOME_WHOLE_NUMBER: + return PHONE_BILLING_WHOLE_NUMBER; + + case PHONE_HOME_NUMBER: + return PHONE_BILLING_NUMBER; + + case PHONE_HOME_CITY_CODE: + return PHONE_BILLING_CITY_CODE; + + case PHONE_HOME_COUNTRY_CODE: + return PHONE_BILLING_COUNTRY_CODE; + + case PHONE_HOME_CITY_AND_NUMBER: + return PHONE_BILLING_CITY_AND_NUMBER; + + case NAME_FIRST: + return NAME_BILLING_FIRST; + + case NAME_MIDDLE: + return NAME_BILLING_MIDDLE; + + case NAME_LAST: + return NAME_BILLING_LAST; + + case NAME_MIDDLE_INITIAL: + return NAME_BILLING_MIDDLE_INITIAL; + + case NAME_FULL: + return NAME_BILLING_FULL; + + case NAME_SUFFIX: + return NAME_BILLING_SUFFIX; + + default: + return field_type; + } +} + +std::string AutofillType::ToString() const { + if (IsUnknown()) + return "UNKNOWN_TYPE"; + + switch (server_type_) { + case NO_SERVER_DATA: + return "NO_SERVER_DATA"; + case UNKNOWN_TYPE: + break; // Should be handled in the HTML type handling code below. + 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 NAME_BILLING_FIRST: + return "NAME_BILLING_FIRST"; + case NAME_BILLING_MIDDLE: + return "NAME_BILLING_MIDDLE"; + case NAME_BILLING_LAST: + return "NAME_BILLING_LAST"; + case NAME_BILLING_MIDDLE_INITIAL: + return "NAME_BILLING_MIDDLE_INITIAL"; + case NAME_BILLING_FULL: + return "NAME_BILLING_FULL"; + case NAME_BILLING_SUFFIX: + return "NAME_BILLING_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 PHONE_FAX_NUMBER: + return "PHONE_FAX_NUMBER"; + case PHONE_FAX_CITY_CODE: + return "PHONE_FAX_CITY_CODE"; + case PHONE_FAX_COUNTRY_CODE: + return "PHONE_FAX_COUNTRY_CODE"; + case PHONE_FAX_CITY_AND_NUMBER: + return "PHONE_FAX_CITY_AND_NUMBER"; + case PHONE_FAX_WHOLE_NUMBER: + return "PHONE_FAX_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"; + case FIELD_WITH_DEFAULT_VALUE: + return "FIELD_WITH_DEFAULT_VALUE"; + case PHONE_BILLING_NUMBER: + return "PHONE_BILLING_NUMBER"; + case PHONE_BILLING_CITY_CODE: + return "PHONE_BILLING_CITY_CODE"; + case PHONE_BILLING_COUNTRY_CODE: + return "PHONE_BILLING_COUNTRY_CODE"; + case PHONE_BILLING_CITY_AND_NUMBER: + return "PHONE_BILLING_CITY_AND_NUMBER"; + case PHONE_BILLING_WHOLE_NUMBER: + return "PHONE_BILLING_WHOLE_NUMBER"; + case MAX_VALID_FIELD_TYPE: + return std::string(); + } + + switch (html_type_) { + case HTML_TYPE_UNKNOWN: + NOTREACHED(); + break; + case HTML_TYPE_NAME: + return "HTML_TYPE_NAME"; + case HTML_TYPE_GIVEN_NAME: + return "HTML_TYPE_GIVEN_NAME"; + case HTML_TYPE_ADDITIONAL_NAME: + return "HTML_TYPE_ADDITIONAL_NAME"; + case HTML_TYPE_FAMILY_NAME: + return "HTML_TYPE_FAMILY_NAME"; + case HTML_TYPE_ORGANIZATION: + return "HTML_TYPE_ORGANIZATION"; + case HTML_TYPE_STREET_ADDRESS: + return "HTML_TYPE_STREET_ADDRESS"; + case HTML_TYPE_ADDRESS_LINE1: + return "HTML_TYPE_ADDRESS_LINE1"; + case HTML_TYPE_ADDRESS_LINE2: + return "HTML_TYPE_ADDRESS_LINE2"; + case HTML_TYPE_LOCALITY: + return "HTML_TYPE_LOCALITY"; + case HTML_TYPE_REGION: + return "HTML_TYPE_REGION"; + case HTML_TYPE_COUNTRY_CODE: + return "HTML_TYPE_COUNTRY_CODE"; + case HTML_TYPE_COUNTRY_NAME: + return "HTML_TYPE_COUNTRY_NAME"; + case HTML_TYPE_POSTAL_CODE: + return "HTML_TYPE_POSTAL_CODE"; + case HTML_TYPE_CREDIT_CARD_NAME: + return "HTML_TYPE_CREDIT_CARD_NAME"; + case HTML_TYPE_CREDIT_CARD_NUMBER: + return "HTML_TYPE_CREDIT_CARD_NUMBER"; + case HTML_TYPE_CREDIT_CARD_EXP: + return "HTML_TYPE_CREDIT_CARD_EXP"; + case HTML_TYPE_CREDIT_CARD_EXP_MONTH: + return "HTML_TYPE_CREDIT_CARD_EXP_MONTH"; + case HTML_TYPE_CREDIT_CARD_EXP_YEAR: + return "HTML_TYPE_CREDIT_CARD_EXP_YEAR"; + case HTML_TYPE_CREDIT_CARD_VERIFICATION_CODE: + return "HTML_TYPE_CREDIT_CARD_VERIFICATION_CODE"; + case HTML_TYPE_CREDIT_CARD_TYPE: + return "HTML_TYPE_CREDIT_CARD_TYPE"; + case HTML_TYPE_TEL: + return "HTML_TYPE_TEL"; + case HTML_TYPE_TEL_COUNTRY_CODE: + return "HTML_TYPE_TEL_COUNTRY_CODE"; + case HTML_TYPE_TEL_NATIONAL: + return "HTML_TYPE_TEL_NATIONAL"; + case HTML_TYPE_TEL_AREA_CODE: + return "HTML_TYPE_TEL_AREA_CODE"; + case HTML_TYPE_TEL_LOCAL: + return "HTML_TYPE_TEL_LOCAL"; + case HTML_TYPE_TEL_LOCAL_PREFIX: + return "HTML_TYPE_TEL_LOCAL_PREFIX"; + case HTML_TYPE_TEL_LOCAL_SUFFIX: + return "HTML_TYPE_TEL_LOCAL_SUFFIX"; + case HTML_TYPE_EMAIL: + return "HTML_TYPE_EMAIL"; + case HTML_TYPE_ADDITIONAL_NAME_INITIAL: + return "HTML_TYPE_ADDITIONAL_NAME_INITIAL"; + case HTML_TYPE_CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR: + return "HTML_TYPE_CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR"; + case HTML_TYPE_CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: + return "HTML_TYPE_CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR"; + case HTML_TYPE_CREDIT_CARD_EXP_2_DIGIT_YEAR: + return "HTML_TYPE_CREDIT_CARD_EXP_2_DIGIT_YEAR"; + case HTML_TYPE_CREDIT_CARD_EXP_4_DIGIT_YEAR: + return "HTML_TYPE_CREDIT_CARD_EXP_4_DIGIT_YEAR"; + } + + NOTREACHED(); + return std::string(); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_type.h b/chromium/components/autofill/core/browser/autofill_type.h new file mode 100644 index 00000000000..41fa613b767 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_type.h @@ -0,0 +1,59 @@ +// 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_CORE_BROWSER_AUTOFILL_TYPE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_TYPE_H_ + +#include <string> + +#include "components/autofill/core/browser/field_types.h" + +namespace autofill { + +// 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: + explicit AutofillType(ServerFieldType field_type); + AutofillType(HtmlFieldType field_type, HtmlFieldMode mode); + AutofillType(const AutofillType& autofill_type); + AutofillType& operator=(const AutofillType& autofill_type); + + HtmlFieldType html_type() const { return html_type_; } + + FieldTypeGroup group() const; + + // Returns true if both the |server_type_| and the |html_type_| are set to + // their respective enum's unknown value. + bool IsUnknown() const; + + // Maps |this| type to a field type that can be directly stored in an Autofill + // data model (in the sense that it makes sense to call + // |AutofillDataModel::SetRawInfo()| with the returned field type as the first + // parameter). Note that the returned type might not be exactly equivalent to + // |this| type. For example, there is no exact analogue to the + // 'street-address' HTML type hint among the storable field types. + ServerFieldType GetStorableType() const; + + // Serializes |this| type to a string. + std::string ToString() const; + + // Maps |field_type| to the corresponding billing field type if the field type + // is an address, name, or phone number type. + static ServerFieldType GetEquivalentBillingFieldType( + ServerFieldType field_type); + + private: + // The server-native field type, or UNKNOWN_TYPE if unset. + ServerFieldType server_type_; + + // The HTML autocomplete field type and mode hints, or HTML_TYPE_UNKNOWN and + // HTML_MODE_NONE if unset. + HtmlFieldType html_type_; + HtmlFieldMode html_mode_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_TYPE_H_ diff --git a/chromium/components/autofill/core/browser/autofill_type_unittest.cc b/chromium/components/autofill/core/browser/autofill_type_unittest.cc new file mode 100644 index 00000000000..71e98b4996d --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_type_unittest.cc @@ -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. + +#include "components/autofill/core/browser/autofill_type.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { +namespace { + +TEST(AutofillTypeTest, ServerFieldTypes) { + // No server data. + AutofillType none(NO_SERVER_DATA); + EXPECT_EQ(NO_SERVER_DATA, none.GetStorableType()); + EXPECT_EQ(NO_GROUP, none.group()); + + // Unknown type. + AutofillType unknown(UNKNOWN_TYPE); + EXPECT_EQ(UNKNOWN_TYPE, unknown.GetStorableType()); + EXPECT_EQ(NO_GROUP, unknown.group()); + + // Type with group but no subgroup. + AutofillType first(NAME_FIRST); + EXPECT_EQ(NAME_FIRST, first.GetStorableType()); + EXPECT_EQ(NAME, first.group()); + + // Type with group and subgroup. + AutofillType phone(PHONE_HOME_NUMBER); + EXPECT_EQ(PHONE_HOME_NUMBER, phone.GetStorableType()); + EXPECT_EQ(PHONE_HOME, phone.group()); + + // Billing type. + AutofillType billing_address(ADDRESS_BILLING_LINE1); + EXPECT_EQ(ADDRESS_HOME_LINE1, billing_address.GetStorableType()); + EXPECT_EQ(ADDRESS_BILLING, billing_address.group()); + + // Last value, to check any offset errors. + AutofillType last(NAME_BILLING_SUFFIX); + EXPECT_EQ(NAME_SUFFIX, last.GetStorableType()); + EXPECT_EQ(NAME_BILLING, last.group()); + + // Boundary (error) condition. + AutofillType boundary(MAX_VALID_FIELD_TYPE); + EXPECT_EQ(UNKNOWN_TYPE, boundary.GetStorableType()); + EXPECT_EQ(NO_GROUP, boundary.group()); + + // Beyond the boundary (error) condition. + AutofillType beyond(static_cast<ServerFieldType>(MAX_VALID_FIELD_TYPE + 10)); + EXPECT_EQ(UNKNOWN_TYPE, beyond.GetStorableType()); + EXPECT_EQ(NO_GROUP, beyond.group()); + + // In-between value. Missing from enum but within range. Error condition. + AutofillType between(static_cast<ServerFieldType>(16)); + EXPECT_EQ(UNKNOWN_TYPE, between.GetStorableType()); + EXPECT_EQ(NO_GROUP, between.group()); +} + +TEST(AutofillTypeTest, HtmlFieldTypes) { + // Unknown type. + AutofillType unknown(HTML_TYPE_UNKNOWN, HTML_MODE_NONE); + EXPECT_EQ(UNKNOWN_TYPE, unknown.GetStorableType()); + EXPECT_EQ(NO_GROUP, unknown.group()); + + // Type with group but no subgroup. + AutofillType first(HTML_TYPE_GIVEN_NAME, HTML_MODE_NONE); + EXPECT_EQ(NAME_FIRST, first.GetStorableType()); + EXPECT_EQ(NAME, first.group()); + + // Type with group and subgroup. + AutofillType phone(HTML_TYPE_TEL, HTML_MODE_NONE); + EXPECT_EQ(PHONE_HOME_WHOLE_NUMBER, phone.GetStorableType()); + EXPECT_EQ(PHONE_HOME, phone.group()); + + // Last value, to check any offset errors. + AutofillType last(HTML_TYPE_CREDIT_CARD_EXP_4_DIGIT_YEAR, HTML_MODE_NONE); + EXPECT_EQ(CREDIT_CARD_EXP_4_DIGIT_YEAR, last.GetStorableType()); + EXPECT_EQ(CREDIT_CARD, last.group()); + + // Shipping mode. + AutofillType shipping_first(HTML_TYPE_GIVEN_NAME, HTML_MODE_SHIPPING); + EXPECT_EQ(NAME_FIRST, shipping_first.GetStorableType()); + EXPECT_EQ(NAME, shipping_first.group()); + + // Billing mode. + AutofillType billing_first(HTML_TYPE_GIVEN_NAME, HTML_MODE_BILLING); + EXPECT_EQ(NAME_FIRST, billing_first.GetStorableType()); + EXPECT_EQ(NAME_BILLING, billing_first.group()); +} + +} // namespace +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_xml_parser.cc b/chromium/components/autofill/core/browser/autofill_xml_parser.cc new file mode 100644 index 00000000000..c1acbe90f7f --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_xml_parser.cc @@ -0,0 +1,260 @@ +// 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/core/browser/autofill_xml_parser.h" + +#include <stdlib.h> +#include <string.h> + +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "components/autofill/content/browser/autocheckout_page_meta_data.h" +#include "components/autofill/core/browser/autofill_server_field_info.h" +#include "third_party/libjingle/source/talk/xmllite/qname.h" + +namespace autofill { + +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, + AutocheckoutPageMetaData* page_meta_data) + : field_infos_(field_infos), + upload_required_(upload_required), + experiment_id_(experiment_id), + page_meta_data_(page_meta_data), + current_click_element_(NULL), + current_page_number_for_page_types_(0), + is_in_type_section_(false) { + DCHECK(upload_required_); + DCHECK(experiment_id_); + DCHECK(page_meta_data_); +} + +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<ServerFieldType>(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) + page_meta_data_->current_page_number = GetIntValue(context, *attrs); + else if (attribute_name.compare("total_pages") == 0) + page_meta_data_->total_pages = GetIntValue(context, *attrs); + else if (attribute_name.compare("ignore_ajax") == 0) + page_meta_data_->ignore_ajax = strcmp(*attrs, "false") != 0; + ++attrs; + } + } else if (element.compare("page_advance_button") == 0) { + page_meta_data_->proceed_element_descriptor = WebElementDescriptor(); + ParseElementDescriptor(context, + attrs, + &page_meta_data_->proceed_element_descriptor); + } else if (element.compare("click_elements_before_formfill") == 0) { + page_meta_data_->click_elements_before_form_fill.push_back( + WebElementDescriptor()); + current_click_element_ = &page_meta_data_->click_elements_before_form_fill. + back(); + } else if (element.compare("click_elements_after_formfill") == 0) { + page_meta_data_->click_elements_after_form_fill.push_back( + WebElementDescriptor()); + current_click_element_ = &page_meta_data_->click_elements_after_form_fill. + back(); + } else if (element.compare("web_element") == 0) { + ParseElementDescriptor(context, attrs, current_click_element_); + } else if (element.compare("flow_page") == 0) { + 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_for_page_types_ = GetIntValue(context, *attrs); + ++attrs; + } + } else if (element.compare("type") == 0) { + is_in_type_section_ = true; + } +} + +void AutofillQueryXmlParser::ParseElementDescriptor( + buzz::XmlParseContext* context, + const char* const* attrs, + WebElementDescriptor* element_descriptor) { + // If both id and css_selector are set, the first one to appear will take + // precedence. + // |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(); + 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()) { + element_descriptor->retrieval_method = autofill::WebElementDescriptor::ID; + element_descriptor->descriptor = attribute_value; + break; + } else if (attribute_name.compare("css_selector") == 0 && + !attribute_value.empty()) { + element_descriptor->retrieval_method = + autofill::WebElementDescriptor::CSS_SELECTOR; + element_descriptor->descriptor = attribute_value; + break; + } + } +} + +void AutofillQueryXmlParser::EndElement(buzz::XmlParseContext* context, + const char* name) { + is_in_type_section_ = false; +} + +void AutofillQueryXmlParser::CharacterData( + buzz::XmlParseContext* context, const char* text, int len) { + if (!is_in_type_section_) + return; + + int type = -1; + base::StringToInt(std::string(text, len), &type); + if (type >= AUTOCHECKOUT_STEP_MIN_VALUE && + type <= AUTOCHECKOUT_STEP_MAX_VALUE) { + AutocheckoutStepType step_type = + static_cast<AutocheckoutStepType>(type); + page_meta_data_->page_types[current_page_number_for_page_types_] + .push_back(step_type); + } +} + +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; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/autofill_xml_parser.h b/chromium/components/autofill/core/browser/autofill_xml_parser.h new file mode 100644 index 00000000000..c01f13759c9 --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_xml_parser.h @@ -0,0 +1,189 @@ +// 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_CORE_BROWSER_AUTOFILL_XML_PARSER_H_ +#define COMPONENTS_AUTOFILL_CORE_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/core/browser/autofill_server_field_info.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/form_structure.h" +#include "third_party/libjingle/source/talk/xmllite/xmlparser.h" + +namespace autofill { + +struct AutocheckoutPageMetaData; + +// 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/core/browser/field_types.h. +class AutofillQueryXmlParser : public AutofillXmlParser { + public: + AutofillQueryXmlParser(std::vector<AutofillServerFieldInfo>* field_infos, + UploadRequired* upload_required, + std::string* experiment_id, + AutocheckoutPageMetaData* page_meta_data); + virtual ~AutofillQueryXmlParser(); + + 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 parse a |WebElementDescriptor|. + // |context| is the current parsing context. + // |attrs| is the list of attributes (names and values) for the element. + // |element_descriptor| will be populated by this function. + void ParseElementDescriptor(buzz::XmlParseContext* context, + const char* const* attrs, + WebElementDescriptor* element_descriptor); + + // 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; + + // 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_; + + // The server experiment to which this query response belongs. + // For the default server implementation, this is empty. + std::string* experiment_id_; + + // Page metadata for multipage autofill flow. + AutocheckoutPageMetaData* page_meta_data_; + + // The click element the parser is currently processing. + WebElementDescriptor* current_click_element_; + + // Number of page whose type is currently being parsed. + int current_page_number_for_page_types_; + + // Whether the instance is currently parsing inside 'type' tags. + bool is_in_type_section_; + + 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); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_AUTOFILL_XML_PARSER_H_ diff --git a/chromium/components/autofill/core/browser/autofill_xml_parser_unittest.cc b/chromium/components/autofill/core/browser/autofill_xml_parser_unittest.cc new file mode 100644 index 00000000000..0c6b0f2154b --- /dev/null +++ b/chromium/components/autofill/core/browser/autofill_xml_parser_unittest.cc @@ -0,0 +1,442 @@ +// 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 <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_number_conversions.h" +#include "components/autofill/content/browser/autocheckout_page_meta_data.h" +#include "components/autofill/core/browser/autofill_xml_parser.h" +#include "components/autofill/core/browser/field_types.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/libjingle/source/talk/xmllite/xmlparser.h" + +namespace autofill { +namespace { + +class AutofillQueryXmlParserTest : public testing::Test { + public: + AutofillQueryXmlParserTest(): upload_required_(USE_UPLOAD_RATES) {}; + virtual ~AutofillQueryXmlParserTest() {}; + + protected: + void ParseQueryXML(const std::string& xml, bool should_succeed) { + // Create a parser. + AutofillQueryXmlParser parse_handler(&field_infos_, + &upload_required_, + &experiment_id_, + &page_meta_data_); + buzz::XmlParser parser(&parse_handler); + parser.Parse(xml.c_str(), xml.length(), true); + EXPECT_EQ(should_succeed, parse_handler.succeeded()); + } + + std::vector<AutofillServerFieldInfo> field_infos_; + UploadRequired upload_required_; + std::string experiment_id_; + autofill::AutocheckoutPageMetaData page_meta_data_; +}; + +class AutofillUploadXmlParserTest : public testing::Test { + public: + AutofillUploadXmlParserTest(): positive_(0), negative_(0) {}; + virtual ~AutofillUploadXmlParserTest() {}; + + protected: + void ParseUploadXML(const std::string& xml, bool should_succeed) { + // Create a parser. + AutofillUploadXmlParser parse_handler(&positive_, &negative_); + buzz::XmlParser parser(&parse_handler); + parser.Parse(xml.c_str(), xml.length(), true); + + EXPECT_EQ(should_succeed, parse_handler.succeeded()); + } + + double positive_; + double negative_; +}; + +TEST_F(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>"; + ParseQueryXML(xml, true); + + 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_TRUE(field_infos_[3].default_value.empty()); + EXPECT_EQ(FIELD_WITH_DEFAULT_VALUE, field_infos_[4].field_type); + EXPECT_EQ("default", field_infos_[4].default_value); + EXPECT_TRUE(experiment_id_.empty()); +} + +// Test parsing the upload required attribute. +TEST_F(AutofillQueryXmlParserTest, TestUploadRequired) { + std::string xml = "<autofillqueryresponse uploadrequired=\"true\">" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(upload_required_, upload_required_); + ASSERT_EQ(1U, field_infos_.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos_[0].field_type); + EXPECT_TRUE(experiment_id_.empty()); + + field_infos_.clear(); + xml = "<autofillqueryresponse uploadrequired=\"false\">" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(UPLOAD_NOT_REQUIRED, upload_required_); + ASSERT_EQ(1U, field_infos_.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos_[0].field_type); + EXPECT_TRUE(experiment_id_.empty()); + + field_infos_.clear(); + xml = "<autofillqueryresponse uploadrequired=\"bad_value\">" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(USE_UPLOAD_RATES, upload_required_); + ASSERT_EQ(1U, field_infos_.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos_[0].field_type); + EXPECT_TRUE(experiment_id_.empty()); +} + +// Test parsing the experiment id attribute +TEST_F(AutofillQueryXmlParserTest, ParseExperimentId) { + // When the attribute is missing, we should get back the default value -- the + // empty string. + std::string xml = "<autofillqueryresponse>" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(USE_UPLOAD_RATES, upload_required_); + ASSERT_EQ(1U, field_infos_.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos_[0].field_type); + EXPECT_TRUE(experiment_id_.empty()); + + field_infos_.clear(); + + // When the attribute is present, make sure we parse it. + xml = "<autofillqueryresponse experimentid=\"FancyNewAlgorithm\">" + "<field autofilltype=\"0\" />" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + 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>"; + + ParseQueryXML(xml, true); + + 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("ServerSmartyPants", experiment_id_); +} + +// Fails on ASAN bot. http://crbug.com/253797 +#if defined(ADDRESS_SANITIZER) +#define MAYBE_ParseAutofillFlow DISABLED_ParseAutofillFlow +#else +#define MAYBE_ParseAutofillFlow ParseAutofillFlow +#endif + +// Test XML response with autofill_flow information. +TEST_F(AutofillQueryXmlParserTest, MAYBE_ParseAutofillFlow) { + std::string xml = "<autofillqueryresponse>" + "<field autofilltype=\"55\"/>" + "<autofill_flow page_no=\"1\" total_pages=\"10\">" + "<page_advance_button id=\"foo\"/>" + "<flow_page page_no=\"0\">" + "<type>1</type>" + "<type>2</type>" + "</flow_page>" + "<flow_page page_no=\"1\">" + "<type>3</type>" + "</flow_page>" + "</autofill_flow>" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(1U, field_infos_.size()); + EXPECT_EQ(1, page_meta_data_.current_page_number); + EXPECT_EQ(10, page_meta_data_.total_pages); + EXPECT_TRUE(page_meta_data_.ignore_ajax); + EXPECT_EQ("foo", page_meta_data_.proceed_element_descriptor.descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::ID, + page_meta_data_.proceed_element_descriptor.retrieval_method); + EXPECT_EQ(2U, page_meta_data_.page_types.size()); + EXPECT_EQ(2U, page_meta_data_.page_types[0].size()); + EXPECT_EQ(1U, page_meta_data_.page_types[1].size()); + EXPECT_EQ(AUTOCHECKOUT_STEP_SHIPPING, page_meta_data_.page_types[0][0]); + EXPECT_EQ(AUTOCHECKOUT_STEP_DELIVERY, page_meta_data_.page_types[0][1]); + EXPECT_EQ(AUTOCHECKOUT_STEP_BILLING, page_meta_data_.page_types[1][0]); + + // 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>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(1U, field_infos_.size()); + EXPECT_EQ(1, page_meta_data_.current_page_number); + EXPECT_EQ(10, page_meta_data_.total_pages); + EXPECT_EQ("[name=\"foo\"]", + page_meta_data_.proceed_element_descriptor.descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, + page_meta_data_.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>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(1U, field_infos_.size()); + EXPECT_EQ(1, page_meta_data_.current_page_number); + EXPECT_EQ(10, page_meta_data_.total_pages); + EXPECT_EQ("[name=\"foo\"]", + page_meta_data_.proceed_element_descriptor.descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, + page_meta_data_.proceed_element_descriptor.retrieval_method); + + // Clear |field_infos_| for the next test; + field_infos_.clear(); + + // Test parsing click_elements_before_formfill correctly. + xml = "<autofillqueryresponse>" + "<field autofilltype=\"55\"/>" + "<autofill_flow page_no=\"1\" total_pages=\"10\">" + "<click_elements_before_formfill>" + "<web_element id=\"btn1\" /></click_elements_before_formfill>" + "<click_elements_before_formfill>" + "<web_element css_selector=\"[name="btn2"]\"/>" + "</click_elements_before_formfill>" + "</autofill_flow>" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(1U, field_infos_.size()); + EXPECT_EQ(1, page_meta_data_.current_page_number); + EXPECT_EQ(10, page_meta_data_.total_pages); + ASSERT_EQ(2U, page_meta_data_.click_elements_before_form_fill.size()); + autofill::WebElementDescriptor& click_elment = + page_meta_data_.click_elements_before_form_fill[0]; + EXPECT_EQ("btn1", click_elment.descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::ID, click_elment.retrieval_method); + click_elment = page_meta_data_.click_elements_before_form_fill[1]; + EXPECT_EQ("[name=\"btn2\"]", click_elment.descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, + click_elment.retrieval_method); + + // Clear |field_infos_| for the next test; + field_infos_.clear(); + + // Test parsing click_elements_after_formfill correctly. + xml = "<autofillqueryresponse>" + "<field autofilltype=\"55\"/>" + "<autofill_flow page_no=\"1\" total_pages=\"10\">" + "<click_elements_after_formfill>" + "<web_element id=\"btn1\" /></click_elements_after_formfill>" + "</autofill_flow>" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(1U, field_infos_.size()); + EXPECT_EQ(1, page_meta_data_.current_page_number); + EXPECT_EQ(10, page_meta_data_.total_pages); + ASSERT_EQ(1U, page_meta_data_.click_elements_after_form_fill.size()); + click_elment = page_meta_data_.click_elements_after_form_fill[0]; + EXPECT_EQ("btn1", click_elment.descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::ID, click_elment.retrieval_method); + + // Clear |field_infos_| for the next test. + field_infos_.clear(); + + // Test setting of ignore_ajax attribute. + xml = "<autofillqueryresponse>" + "<field autofilltype=\"55\"/>" + "<autofill_flow page_no=\"1\" total_pages=\"10\" ignore_ajax=\"true\">" + "<page_advance_button css_selector=\"[name="foo"]\"" + " id=\"foo\"/>" + "</autofill_flow>" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(1U, field_infos_.size()); + EXPECT_EQ(1, page_meta_data_.current_page_number); + EXPECT_EQ(10, page_meta_data_.total_pages); + EXPECT_TRUE(page_meta_data_.ignore_ajax); + EXPECT_EQ("[name=\"foo\"]", + page_meta_data_.proceed_element_descriptor.descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, + page_meta_data_.proceed_element_descriptor.retrieval_method); + + // Clear |field_infos_| for the next test. + field_infos_.clear(); + + // Test redundant setting to false of ignore_ajax attribute. + xml = "<autofillqueryresponse>" + "<field autofilltype=\"55\"/>" + "<autofill_flow page_no=\"1\" total_pages=\"10\" ignore_ajax=\"false\">" + "<page_advance_button css_selector=\"[name="foo"]\"" + " id=\"foo\"/>" + "</autofill_flow>" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + EXPECT_EQ(1U, field_infos_.size()); + EXPECT_EQ(1, page_meta_data_.current_page_number); + EXPECT_EQ(10, page_meta_data_.total_pages); + EXPECT_FALSE(page_meta_data_.ignore_ajax); + EXPECT_EQ("[name=\"foo\"]", + page_meta_data_.proceed_element_descriptor.descriptor); + EXPECT_EQ(autofill::WebElementDescriptor::CSS_SELECTOR, + page_meta_data_.proceed_element_descriptor.retrieval_method); +} + +// Test badly formed XML queries. +TEST_F(AutofillQueryXmlParserTest, ParseErrors) { + // Test no Autofill type. + std::string xml = "<autofillqueryresponse>" + "<field/>" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, false); + + EXPECT_EQ(USE_UPLOAD_RATES, upload_required_); + EXPECT_EQ(0U, field_infos_.size()); + EXPECT_TRUE(experiment_id_.empty()); + + // Test an incorrect Autofill type. + xml = "<autofillqueryresponse>" + "<field autofilltype=\"-1\"/>" + "</autofillqueryresponse>"; + + ParseQueryXML(xml, true); + + 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_TRUE(experiment_id_.empty()); + + // 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>"; + + ParseQueryXML(xml, true); + + 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_TRUE(experiment_id_.empty()); + + // 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_. + ParseQueryXML(xml, false); + + EXPECT_EQ(USE_UPLOAD_RATES, upload_required_); + ASSERT_EQ(1U, field_infos_.size()); + EXPECT_EQ(NO_SERVER_DATA, field_infos_[0].field_type); + EXPECT_TRUE(experiment_id_.empty()); +} + +// Test successfull upload response. +TEST_F(AutofillUploadXmlParserTest, TestSuccessfulResponse) { + ParseUploadXML("<autofilluploadresponse positiveuploadrate=\"0.5\" " + "negativeuploadrate=\"0.3\"/>", + true); + + EXPECT_DOUBLE_EQ(0.5, positive_); + EXPECT_DOUBLE_EQ(0.3, negative_); +} + +// Test failed upload response. +TEST_F(AutofillUploadXmlParserTest, TestFailedResponse) { + ParseUploadXML("<autofilluploadresponse positiveuploadrate=\"\" " + "negativeuploadrate=\"0.3\"/>", + false); + + EXPECT_DOUBLE_EQ(0, positive_); + EXPECT_DOUBLE_EQ(0.3, negative_); // Partially parsed. + negative_ = 0; + + ParseUploadXML("<autofilluploadresponse positiveuploadrate=\"0.5\" " + "negativeuploadrate=\"0.3\"", + false); + + EXPECT_DOUBLE_EQ(0, positive_); + EXPECT_DOUBLE_EQ(0, negative_); + + ParseUploadXML("bad data", false); + + EXPECT_DOUBLE_EQ(0, positive_); + EXPECT_DOUBLE_EQ(0, negative_); + + ParseUploadXML(std::string(), false); + + EXPECT_DOUBLE_EQ(0, positive_); + EXPECT_DOUBLE_EQ(0, negative_); +} + +} // namespace +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/contact_info.cc b/chromium/components/autofill/core/browser/contact_info.cc new file mode 100644 index 00000000000..603c4fa3b54 --- /dev/null +++ b/chromium/components/autofill/core/browser/contact_info.cc @@ -0,0 +1,217 @@ +// 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/core/browser/contact_info.h" + +#include <stddef.h> +#include <ostream> +#include <string> + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_type.h" + +namespace autofill { + +static const ServerFieldType 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(ServerFieldTypeSet* 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); +} + +base::string16 NameInfo::GetRawInfo(ServerFieldType type) const { + // TODO(isherman): Is GetStorableType even necessary? + switch (AutofillType(type).GetStorableType()) { + case NAME_FIRST: + return first(); + + case NAME_MIDDLE: + return middle(); + + case NAME_LAST: + return last(); + + case NAME_MIDDLE_INITIAL: + return MiddleInitial(); + + case NAME_FULL: + return FullName(); + + default: + return base::string16(); + } +} + +void NameInfo::SetRawInfo(ServerFieldType type, const base::string16& value) { + // TODO(isherman): Is GetStorableType even necessary? + ServerFieldType storable_type = AutofillType(type).GetStorableType(); + DCHECK_EQ(NAME, AutofillType(storable_type).group()); + switch (storable_type) { + case NAME_FIRST: + first_ = value; + break; + + case NAME_MIDDLE: + case NAME_MIDDLE_INITIAL: + middle_ = value; + break; + + case NAME_LAST: + last_ = value; + break; + + case NAME_FULL: + SetFullName(value); + break; + + default: + NOTREACHED(); + } +} + +base::string16 NameInfo::FullName() const { + std::vector<base::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, ' '); +} + +base::string16 NameInfo::MiddleInitial() const { + if (middle_.empty()) + return base::string16(); + + base::string16 middle_name(middle()); + base::string16 initial; + initial.push_back(middle_name[0]); + return initial; +} + +void NameInfo::SetFullName(const base::string16& full) { + // Clear the names. + first_ = base::string16(); + middle_ = base::string16(); + last_ = base::string16(); + + std::vector<base::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(ServerFieldTypeSet* supported_types) const { + supported_types->insert(EMAIL_ADDRESS); +} + +base::string16 EmailInfo::GetRawInfo(ServerFieldType type) const { + if (type == EMAIL_ADDRESS) + return email_; + + return base::string16(); +} + +void EmailInfo::SetRawInfo(ServerFieldType type, const base::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(ServerFieldTypeSet* supported_types) const { + supported_types->insert(COMPANY_NAME); +} + +base::string16 CompanyInfo::GetRawInfo(ServerFieldType type) const { + if (type == COMPANY_NAME) + return company_name_; + + return base::string16(); +} + +void CompanyInfo::SetRawInfo(ServerFieldType type, + const base::string16& value) { + DCHECK_EQ(COMPANY_NAME, type); + company_name_ = value; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/contact_info.h b/chromium/components/autofill/core/browser/contact_info.h new file mode 100644 index 00000000000..007be1be81f --- /dev/null +++ b/chromium/components/autofill/core/browser/contact_info.h @@ -0,0 +1,101 @@ +// 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_CORE_BROWSER_CONTACT_INFO_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_CONTACT_INFO_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/form_group.h" + +namespace autofill { + +// 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 base::string16 GetRawInfo(ServerFieldType type) const OVERRIDE; + virtual void SetRawInfo(ServerFieldType type, + const base::string16& value) OVERRIDE; + + private: + // FormGroup: + virtual void GetSupportedTypes( + ServerFieldTypeSet* supported_types) const OVERRIDE; + + // Returns the full name, which can include up to the first, middle, and last + // name. + base::string16 FullName() const; + + // Returns the middle initial if |middle_| is non-empty. Returns an empty + // string otherwise. + base::string16 MiddleInitial() const; + + const base::string16& first() const { return first_; } + const base::string16& middle() const { return middle_; } + const base::string16& last() const { return last_; } + + // Sets |first_|, |middle_|, and |last_| to the tokenized |full|. + // It is tokenized on a space only. + void SetFullName(const base::string16& full); + + base::string16 first_; + base::string16 middle_; + base::string16 last_; +}; + +class EmailInfo : public FormGroup { + public: + EmailInfo(); + EmailInfo(const EmailInfo& info); + virtual ~EmailInfo(); + + EmailInfo& operator=(const EmailInfo& info); + + // FormGroup: + virtual base::string16 GetRawInfo(ServerFieldType type) const OVERRIDE; + virtual void SetRawInfo(ServerFieldType type, + const base::string16& value) OVERRIDE; + + private: + // FormGroup: + virtual void GetSupportedTypes( + ServerFieldTypeSet* supported_types) const OVERRIDE; + + base::string16 email_; +}; + +class CompanyInfo : public FormGroup { + public: + CompanyInfo(); + CompanyInfo(const CompanyInfo& info); + virtual ~CompanyInfo(); + + CompanyInfo& operator=(const CompanyInfo& info); + + // FormGroup: + virtual base::string16 GetRawInfo(ServerFieldType type) const OVERRIDE; + virtual void SetRawInfo(ServerFieldType type, + const base::string16& value) OVERRIDE; + + private: + // FormGroup: + virtual void GetSupportedTypes( + ServerFieldTypeSet* supported_types) const OVERRIDE; + + base::string16 company_name_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_CONTACT_INFO_H_ diff --git a/chromium/components/autofill/core/browser/contact_info_unittest.cc b/chromium/components/autofill/core/browser/contact_info_unittest.cc new file mode 100644 index 00000000000..87e96ada779 --- /dev/null +++ b/chromium/components/autofill/core/browser/contact_info_unittest.cc @@ -0,0 +1,105 @@ +// 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/core/browser/contact_info.h" + +#include "base/basictypes.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/field_types.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +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), base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), base::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), base::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, base::string16()); + name.SetRawInfo(NAME_LAST, base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("First")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("First")); + + name.SetRawInfo(NAME_FIRST, base::string16()); + name.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Middle")); + name.SetRawInfo(NAME_LAST, base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), ASCIIToUTF16("Middle")); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("Middle")); + + name.SetRawInfo(NAME_FIRST, base::string16()); + name.SetRawInfo(NAME_MIDDLE, base::string16()); + name.SetRawInfo(NAME_LAST, ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), base::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, base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("First")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), ASCIIToUTF16("Middle")); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("First Middle")); + + name.SetRawInfo(NAME_FIRST, ASCIIToUTF16("First")); + name.SetRawInfo(NAME_MIDDLE, base::string16()); + name.SetRawInfo(NAME_LAST, ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), ASCIIToUTF16("First")); + EXPECT_EQ(name.GetRawInfo(NAME_MIDDLE), base::string16()); + EXPECT_EQ(name.GetRawInfo(NAME_LAST), ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FULL), ASCIIToUTF16("First Last")); + + name.SetRawInfo(NAME_FIRST, base::string16()); + name.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Middle")); + name.SetRawInfo(NAME_LAST, ASCIIToUTF16("Last")); + EXPECT_EQ(name.GetRawInfo(NAME_FIRST), base::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")); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/credit_card.cc b/chromium/components/autofill/core/browser/credit_card.cc new file mode 100644 index 00000000000..f3ac6b112ab --- /dev/null +++ b/chromium/components/autofill/core/browser/credit_card.cc @@ -0,0 +1,714 @@ +// 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/core/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/strings/string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_regexes.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/validation.h" +#include "components/autofill/core/common/form_field_data.h" +#include "grit/component_strings.h" +#include "grit/webkit_resources.h" +#include "third_party/icu/source/common/unicode/uloc.h" +#include "third_party/icu/source/i18n/unicode/dtfmtsym.h" +#include "ui/base/l10n/l10n_util.h" + +namespace autofill { + +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; + +bool ConvertYear(const base::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 base::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". + base::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 base::string16 icu_month = base::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 base::string16 icu_month = base::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, const std::string& origin) + : AutofillDataModel(guid, origin), + type_(kGenericCard), + expiration_month_(0), + expiration_year_(0) { +} + +CreditCard::CreditCard() + : AutofillDataModel(base::GenerateGUID(), std::string()), + type_(kGenericCard), + expiration_month_(0), + expiration_year_(0) { +} + +CreditCard::CreditCard(const CreditCard& credit_card) + : AutofillDataModel(std::string(), std::string()) { + operator=(credit_card); +} + +CreditCard::~CreditCard() {} + +// static +const base::string16 CreditCard::StripSeparators(const base::string16& number) { + const char16 kSeparators[] = {'-', ' ', '\0'}; + base::string16 stripped; + RemoveChars(number, kSeparators, &stripped); + return stripped; +} + +// static +base::string16 CreditCard::TypeForDisplay(const std::string& type) { + 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 == kUnionPay) + return l10n_util::GetStringUTF16(IDS_AUTOFILL_CC_UNION_PAY); + 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 base::string16(); +} + +// static +int CreditCard::IconResourceId(const std::string& type) { + 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 == kUnionPay) + return IDR_AUTOFILL_CC_GENERIC; // Needs resource: http://crbug.com/259211 + 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; +} + +// static +std::string CreditCard::GetCreditCardType(const base::string16& number) { + // Credit card number specifications taken from: + // http://en.wikipedia.org/wiki/Credit_card_numbers, + // http://en.wikipedia.org/wiki/List_of_Issuer_Identification_Numbers, + // http://www.discovernetwork.com/merchants/images/Merchant_Marketing_PDF.pdf, + // http://www.regular-expressions.info/creditcard.html, + // http://developer.ean.com/general_info/Valid_Credit_Card_Types, + // http://www.bincodes.com/, + // http://www.fraudpractice.com/FL-binCC.html, and + // http://www.beachnet.com/~hstiles/cardtype.html + // + // The last site is currently unavailable, but a cached version remains at + // http://web.archive.org/web/20120923111349/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,3095,36,38-39 14 + // Discover Card 6011,644-649,65 16 + // JCB 3528-3589 16 + // MasterCard 51-55 16 + // UnionPay 62 16-19 + + // Check for prefixes of length 1. + if (number.empty()) + return kGenericCard; + + if (number[0] == '4') + return kVisaCard; + + // Check for prefixes of length 2. + if (number.size() < 2) + return kGenericCard; + + int first_two_digits = 0; + if (!base::StringToInt(number.substr(0, 2), &first_two_digits)) + return kGenericCard; + + if (first_two_digits == 34 || first_two_digits == 37) + return kAmericanExpressCard; + + if (first_two_digits == 36 || + first_two_digits == 38 || + first_two_digits == 39) + return kDinersCard; + + if (first_two_digits >= 51 && first_two_digits <= 55) + return kMasterCard; + + if (first_two_digits == 62) + return kUnionPay; + + if (first_two_digits == 65) + return kDiscoverCard; + + // Check for prefixes of length 3. + if (number.size() < 3) + return kGenericCard; + + int first_three_digits = 0; + if (!base::StringToInt(number.substr(0, 3), &first_three_digits)) + return kGenericCard; + + if (first_three_digits >= 300 && first_three_digits <= 305) + return kDinersCard; + + if (first_three_digits >= 644 && first_three_digits <= 649) + return kDiscoverCard; + + // Check for prefixes of length 4. + if (number.size() < 4) + return kGenericCard; + + int first_four_digits = 0; + if (!base::StringToInt(number.substr(0, 4), &first_four_digits)) + return kGenericCard; + + if (first_four_digits == 3095) + return kDinersCard; + + if (first_four_digits >= 3528 && first_four_digits <= 3589) + return kJCBCard; + + if (first_four_digits == 6011) + return kDiscoverCard; + + return kGenericCard; +} + +base::string16 CreditCard::GetRawInfo(ServerFieldType 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: { + base::string16 month = ExpirationMonthAsString(); + base::string16 year = Expiration2DigitYearAsString(); + if (!month.empty() && !year.empty()) + return month + ASCIIToUTF16("/") + year; + return base::string16(); + } + + case CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR: { + base::string16 month = ExpirationMonthAsString(); + base::string16 year = Expiration4DigitYearAsString(); + if (!month.empty() && !year.empty()) + return month + ASCIIToUTF16("/") + year; + return base::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 base::string16(); + + default: + // ComputeDataPresentForArray will hit this repeatedly. + return base::string16(); + } +} + +void CreditCard::SetRawInfo(ServerFieldType type, + const base::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; + } +} + +base::string16 CreditCard::GetInfo(const AutofillType& type, + const std::string& app_locale) const { + ServerFieldType storable_type = type.GetStorableType(); + if (storable_type == CREDIT_CARD_NUMBER) + return StripSeparators(number_); + + return GetRawInfo(storable_type); +} + +bool CreditCard::SetInfo(const AutofillType& type, + const base::string16& value, + const std::string& app_locale) { + ServerFieldType storable_type = type.GetStorableType(); + if (storable_type == CREDIT_CARD_NUMBER) + SetRawInfo(storable_type, StripSeparators(value)); + else if (storable_type == CREDIT_CARD_EXP_MONTH) + SetExpirationMonthFromString(value, app_locale); + else + SetRawInfo(storable_type, value); + + return true; +} + +void CreditCard::GetMatchingTypes(const base::string16& text, + const std::string& app_locale, + ServerFieldTypeSet* matching_types) const { + FormGroup::GetMatchingTypes(text, app_locale, matching_types); + + base::string16 card_number = + GetInfo(AutofillType(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 base::string16 CreditCard::Label() const { + base::string16 label; + if (number().empty()) + return name_on_card_; // No CC number, return name only. + + base::string16 obfuscated_cc_number = ObfuscatedNumber(); + if (!expiration_month_ || !expiration_year_) + return obfuscated_cc_number; // No expiration date set. + + // TODO(georgey): Internationalize date. + base::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 base::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<base::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); +} + +base::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_; + + base::string16 number = StripSeparators(number_); + + // Avoid making very long obfuscated numbers. + size_t obfuscated_digits = std::min(kMaxObfuscationSize, number.size() - 4); + base::string16 result(obfuscated_digits, kCreditCardObfuscationSymbol); + return result.append(LastFourDigits()); +} + +base::string16 CreditCard::LastFourDigits() const { + static const size_t kNumLastDigits = 4; + + base::string16 number = StripSeparators(number_); + if (number.size() < kNumLastDigits) + return base::string16(); + + return number.substr(number.size() - kNumLastDigits, kNumLastDigits); +} + +base::string16 CreditCard::TypeForDisplay() const { + return CreditCard::TypeForDisplay(type_); +} + +base::string16 CreditCard::TypeAndLastFourDigits() const { + base::string16 type = TypeForDisplay(); + // TODO(estade): type may be empty, we probably want to return + // "Card - 1234" or something in that case. + + base::string16 digits = LastFourDigits(); + if (digits.empty()) + return type; + + // TODO(estade): i18n. + return type + ASCIIToUTF16(" - ") + digits; +} + +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_; + + set_guid(credit_card.guid()); + set_origin(credit_card.origin()); +} + +bool CreditCard::UpdateFromImportedCard(const CreditCard& imported_card, + const std::string& app_locale) { + if (this->GetInfo(AutofillType(CREDIT_CARD_NUMBER), app_locale) != + imported_card.GetInfo(AutofillType(CREDIT_CARD_NUMBER), app_locale)) { + return false; + } + + // Heuristically aggregated data should never overwrite verified data. + // Instead, discard any heuristically aggregated credit cards that disagree + // with explicitly entered data, so that the UI is not cluttered with + // duplicate cards. + if (this->IsVerified() && !imported_card.IsVerified()) + return true; + + set_origin(imported_card.origin()); + + // 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*/, + const std::string& app_locale, + FormFieldData* field_data) const { + DCHECK_EQ(CREDIT_CARD, field.Type().group()); + DCHECK(field_data); + + if (field_data->form_control_type == "select-one") { + FillSelectControl(field.Type(), app_locale, field_data); + } else if (field_data->form_control_type == "month") { + // HTML5 input="month" consists of year-month. + base::string16 year = + GetInfo(AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), app_locale); + base::string16 month = + GetInfo(AutofillType(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 ServerFieldType types[] = { CREDIT_CARD_NAME, + CREDIT_CARD_NUMBER, + CREDIT_CARD_EXP_MONTH, + CREDIT_CARD_EXP_4_DIGIT_YEAR }; + for (size_t i = 0; i < arraysize(types); ++i) { + int comparison = + GetRawInfo(types[i]).compare(credit_card.GetRawInfo(types[i])); + if (comparison != 0) + return comparison; + } + + return 0; +} + +bool CreditCard::operator==(const CreditCard& credit_card) const { + return guid() == credit_card.guid() && + origin() == credit_card.origin() && + Compare(credit_card) == 0; +} + +bool CreditCard::operator!=(const CreditCard& credit_card) const { + return !operator==(credit_card); +} + +bool CreditCard::IsEmpty(const std::string& app_locale) const { + ServerFieldTypeSet types; + GetNonEmptyTypes(app_locale, &types); + return types.empty(); +} + +bool CreditCard::IsComplete() const { + return + autofill::IsValidCreditCardNumber(number_) && + expiration_month_ != 0 && + expiration_year_ != 0; +} + +bool CreditCard::IsValid() const { + return autofill::IsValidCreditCardNumber(number_) && + autofill::IsValidCreditCardExpirationDate( + expiration_year_, expiration_month_, base::Time::Now()); +} + +void CreditCard::GetSupportedTypes(ServerFieldTypeSet* 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); +} + +base::string16 CreditCard::ExpirationMonthAsString() const { + if (expiration_month_ == 0) + return base::string16(); + + base::string16 month = base::IntToString16(expiration_month_); + if (expiration_month_ >= 10) + return month; + + base::string16 zero = ASCIIToUTF16("0"); + zero.append(month); + return zero; +} + +base::string16 CreditCard::Expiration4DigitYearAsString() const { + if (expiration_year_ == 0) + return base::string16(); + + return base::IntToString16(Expiration4DigitYear()); +} + +base::string16 CreditCard::Expiration2DigitYearAsString() const { + if (expiration_year_ == 0) + return base::string16(); + + return base::IntToString16(Expiration2DigitYear()); +} + +void CreditCard::SetExpirationMonthFromString(const base::string16& text, + const std::string& app_locale) { + int month; + if (!ConvertMonth(text, app_locale, &month)) + return; + + SetExpirationMonth(month); +} + +void CreditCard::SetExpirationYearFromString(const base::string16& text) { + int year; + if (!ConvertYear(text, &year)) + return; + + SetExpirationYear(year); +} + +void CreditCard::SetNumber(const base::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() + << " " + << credit_card.origin() + << " " + << 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 kUnionPay = "unionPayCC"; +const char* const kVisaCard = "visaCC"; + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/credit_card.h b/chromium/components/autofill/core/browser/credit_card.h new file mode 100644 index 00000000000..d5bf1a0f7d7 --- /dev/null +++ b/chromium/components/autofill/core/browser/credit_card.h @@ -0,0 +1,174 @@ +// 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_CORE_BROWSER_CREDIT_CARD_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_CREDIT_CARD_H_ + +#include <iosfwd> +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/autofill_data_model.h" + +namespace autofill { + +struct FormFieldData; + +// A form group that stores credit card information. +class CreditCard : public AutofillDataModel { + public: + CreditCard(const std::string& guid, const std::string& origin); + + // 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 base::string16 StripSeparators(const base::string16& number); + + // The user-visible type of the card, e.g. 'Mastercard'. + static base::string16 TypeForDisplay(const std::string& type); + + // The ResourceBundle ID for the appropriate credit card image. + static int IconResourceId(const std::string& type); + + // Returns the internal representation of credit card type corresponding to + // the given |number|. The credit card type is determined purely according to + // the Issuer Identification Number (IIN), a.k.a. the "Bank Identification + // Number (BIN)", which is parsed from the relevant prefix of the |number|. + // This function performs no additional validation checks on the |number|. + // Hence, the returned type for both the valid card "4111-1111-1111-1111" and + // the invalid card "4garbage" will be Visa, which has an IIN of 4. + static std::string GetCreditCardType(const base::string16& number); + + // FormGroup: + virtual void GetMatchingTypes( + const base::string16& text, + const std::string& app_locale, + ServerFieldTypeSet* matching_types) const OVERRIDE; + virtual base::string16 GetRawInfo(ServerFieldType type) const OVERRIDE; + virtual void SetRawInfo(ServerFieldType type, + const base::string16& value) OVERRIDE; + virtual base::string16 GetInfo(const AutofillType& type, + const std::string& app_locale) const OVERRIDE; + virtual bool SetInfo(const AutofillType& type, + const base::string16& value, + const std::string& app_locale) OVERRIDE; + + // AutofillDataModel: + virtual void FillFormField(const AutofillField& field, + size_t variant, + const std::string& app_locale, + FormFieldData* field_data) const OVERRIDE; + + // Credit card preview summary, for example: ******1234, Exp: 01/2020 + const base::string16 Label() const; + + // Special method to set value for HTML5 month input type. + void SetInfoForMonthInputType(const base::string16& value); + + // The number altered for display, for example: ******1234 + base::string16 ObfuscatedNumber() const; + // The last four digits of the credit card number. + base::string16 LastFourDigits() const; + // The user-visible type of the card, e.g. 'Mastercard'. + base::string16 TypeForDisplay() const; + // A label for this credit card formatted as 'Cardname - 2345'. + base::string16 TypeAndLastFourDigits() const; + + const std::string& type() const { return type_; } + + int expiration_month() const { return expiration_month_; } + int expiration_year() const { return expiration_year_; } + + // For use in STL containers. + void operator=(const CreditCard& credit_card); + + // If the card numbers for |this| and |imported_card| match, and merging the + // two wouldn't result in unverified data overwriting verified data, + // overwrites |this| card's data with the data in |credit_card|. + // Returns true if the card numbers match, false otherwise. + 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, origins, 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 std::string& app_locale) const; + + // Returns true if all field types have valid values set. + bool IsComplete() const; + + // Returns true if all field types have valid values set and the card is not + // expired. + bool IsValid() const; + + // Returns the credit card number. + const base::string16& number() const { return number_; } + + private: + // FormGroup: + virtual void GetSupportedTypes( + ServerFieldTypeSet* 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; } + base::string16 ExpirationMonthAsString() const; + base::string16 Expiration4DigitYearAsString() const; + base::string16 Expiration2DigitYearAsString() const; + + // Sets |expiration_month_| to the integer conversion of |text|. + void SetExpirationMonthFromString(const base::string16& text, + const std::string& app_locale); + + // Sets |expiration_year_| to the integer conversion of |text|. + void SetExpirationYearFromString(const base::string16& text); + + // Sets |number_| to |number| and computes the appropriate card |type_|. + void SetNumber(const base::string16& number); + + // These setters verify that the month and year are within appropriate + // ranges. + void SetExpirationMonth(int expiration_month); + void SetExpirationYear(int expiration_year); + + base::string16 number_; // The credit card number. + base::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_; +}; + +// 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 kUnionPay; +extern const char* const kVisaCard; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_CREDIT_CARD_H_ diff --git a/chromium/components/autofill/core/browser/credit_card_field.cc b/chromium/components/autofill/core/browser/credit_card_field.cc new file mode 100644 index 00000000000..bba804c9586 --- /dev/null +++ b/chromium/components/autofill/core/browser/credit_card_field.cc @@ -0,0 +1,233 @@ +// 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/core/browser/credit_card_field.h" + +#include <stddef.h> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_regex_constants.h" +#include "components/autofill/core/browser/autofill_scanner.h" +#include "components/autofill/core/browser/field_types.h" +#include "ui/base/l10n/l10n_util.h" + +namespace autofill { + +// 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) { + base::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. + base::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. + base::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(ServerFieldTypeMap* 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; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/credit_card_field.h b/chromium/components/autofill/core/browser/credit_card_field.h new file mode 100644 index 00000000000..119e1ab79dd --- /dev/null +++ b/chromium/components/autofill/core/browser/credit_card_field.h @@ -0,0 +1,75 @@ +// 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_CORE_BROWSER_CREDIT_CARD_FIELD_H_ +#define COMPONENTS_AUTOFILL_CORE_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/core/browser/autofill_type.h" +#include "components/autofill/core/browser/form_field.h" + +namespace autofill { + +class AutofillField; +class AutofillScanner; + +class CreditCardField : public FormField { + public: + static FormField* Parse(AutofillScanner* scanner); + + protected: + // FormField: + virtual bool ClassifyField(ServerFieldTypeMap* 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); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_CREDIT_CARD_FIELD_H_ diff --git a/chromium/components/autofill/core/browser/credit_card_field_unittest.cc b/chromium/components/autofill/core/browser/credit_card_field_unittest.cc new file mode 100644 index 00000000000..9f60a1d6bda --- /dev/null +++ b/chromium/components/autofill/core/browser/credit_card_field_unittest.cc @@ -0,0 +1,322 @@ +// 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 "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_scanner.h" +#include "components/autofill/core/browser/credit_card_field.h" +#include "components/autofill/core/common/form_field_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +class CreditCardFieldTest : public testing::Test { + public: + CreditCardFieldTest() {} + + protected: + ScopedVector<const AutofillField> list_; + scoped_ptr<CreditCardField> field_; + ServerFieldTypeMap 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")]); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/credit_card_unittest.cc b/chromium/components/autofill/core/browser/credit_card_unittest.cc new file mode 100644 index 00000000000..837d729f979 --- /dev/null +++ b/chromium/components/autofill/core/browser/credit_card_unittest.cc @@ -0,0 +1,693 @@ +// 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 "base/basictypes.h" +#include "base/guid.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_common_test.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/validation.h" +#include "components/autofill/core/common/form_field_data.h" +#include "grit/webkit_resources.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { +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", + + // A UnionPay card that doesn't pass the Luhn checksum + "6200000000000000", +}; +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(base::GenerateGUID(), "https://www.example.com/"); + base::string16 summary0 = credit_card0.Label(); + EXPECT_EQ(base::string16(), summary0); + base::string16 obfuscated0 = credit_card0.ObfuscatedNumber(); + EXPECT_EQ(base::string16(), obfuscated0); + + // Case 00: Empty credit card with empty strings. + CreditCard credit_card00(base::GenerateGUID(), "https://www.example.com/"); + test::SetCreditCardInfo(&credit_card00,"John Dillinger", "", "", ""); + base::string16 summary00 = credit_card00.Label(); + EXPECT_EQ(base::string16(ASCIIToUTF16("John Dillinger")), summary00); + base::string16 obfuscated00 = credit_card00.ObfuscatedNumber(); + EXPECT_EQ(base::string16(), obfuscated00); + + // Case 1: No credit card number. + CreditCard credit_card1(base::GenerateGUID(), "https://www.example.com/"); + test::SetCreditCardInfo(&credit_card1,"John Dillinger", "", "01", "2010"); + base::string16 summary1 = credit_card1.Label(); + EXPECT_EQ(base::string16(ASCIIToUTF16("John Dillinger")), summary1); + base::string16 obfuscated1 = credit_card1.ObfuscatedNumber(); + EXPECT_EQ(base::string16(), obfuscated1); + + // Case 2: No month. + CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com/"); + test::SetCreditCardInfo( + &credit_card2, "John Dillinger", "5105 1051 0510 5100", "", "2010"); + base::string16 summary2 = credit_card2.Label(); + EXPECT_EQ(ASCIIToUTF16("************5100"), summary2); + base::string16 obfuscated2 = credit_card2.ObfuscatedNumber(); + EXPECT_EQ(ASCIIToUTF16("************5100"), obfuscated2); + + // Case 3: No year. + CreditCard credit_card3(base::GenerateGUID(), "https://www.example.com/"); + test::SetCreditCardInfo( + &credit_card3, "John Dillinger", "5105 1051 0510 5100", "01", ""); + base::string16 summary3 = credit_card3.Label(); + EXPECT_EQ(ASCIIToUTF16("************5100"), summary3); + base::string16 obfuscated3 = credit_card3.ObfuscatedNumber(); + EXPECT_EQ(ASCIIToUTF16("************5100"), obfuscated3); + + // Case 4: Have everything. + CreditCard credit_card4(base::GenerateGUID(), "https://www.example.com/"); + test::SetCreditCardInfo( + &credit_card4, "John Dillinger", "5105 1051 0510 5100", "01", "2010"); + base::string16 summary4 = credit_card4.Label(); + EXPECT_EQ(ASCIIToUTF16("************5100, Exp: 01/2010"), summary4); + base::string16 obfuscated4 = credit_card4.ObfuscatedNumber(); + EXPECT_EQ(ASCIIToUTF16("************5100"), obfuscated4); + + // Case 5: Very long credit card + CreditCard credit_card5(base::GenerateGUID(), "https://www.example.com/"); + test::SetCreditCardInfo( + &credit_card5, + "John Dillinger", + "0123456789 0123456789 0123456789 5105 1051 0510 5100", "01", "2010"); + base::string16 summary5 = credit_card5.Label(); + EXPECT_EQ(ASCIIToUTF16("********************5100, Exp: 01/2010"), summary5); + base::string16 obfuscated5 = credit_card5.ObfuscatedNumber(); + EXPECT_EQ(ASCIIToUTF16("********************5100"), obfuscated5); +} + +TEST(CreditCardTest, AssignmentOperator) { + CreditCard a(base::GenerateGUID(), "some origin"); + test::SetCreditCardInfo(&a, "John Dillinger", "123456789012", "01", "2010"); + + // Result of assignment should be logically equal to the original profile. + CreditCard b(base::GenerateGUID(), "some other origin"); + b = a; + EXPECT_TRUE(a == b); + + // Assignment to self should not change the profile value. + a = a; + EXPECT_TRUE(a == b); +} + +TEST(CreditCardTest, Copy) { + CreditCard a(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&a, "John Dillinger", "123456789012", "01", "2010"); + + // Clone should be logically equal to the original. + CreditCard b(a); + EXPECT_TRUE(a == b); +} + +TEST(CreditCardTest, Compare) { + CreditCard a(base::GenerateGUID(), std::string()); + CreditCard b(base::GenerateGUID(), std::string()); + + // Empty cards 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)); + + // Origins don't count. + a.set_origin("apple"); + b.set_origin("banana"); + EXPECT_EQ(0, a.Compare(b)); + + // Different values produce non-zero results. + test::SetCreditCardInfo(&a, "Jimmy", NULL, NULL, NULL); + test::SetCreditCardInfo(&b, "Ringo", NULL, NULL, NULL); + EXPECT_GT(0, a.Compare(b)); + EXPECT_LT(0, b.Compare(a)); +} + +// Test we get the correct icon for each card type. +TEST(CreditCardTest, IconResourceId) { + EXPECT_EQ(IDR_AUTOFILL_CC_AMEX, + CreditCard::IconResourceId(kAmericanExpressCard)); + EXPECT_EQ(IDR_AUTOFILL_CC_DINERS, + CreditCard::IconResourceId(kDinersCard)); + EXPECT_EQ(IDR_AUTOFILL_CC_DISCOVER, + CreditCard::IconResourceId(kDiscoverCard)); + EXPECT_EQ(IDR_AUTOFILL_CC_JCB, + CreditCard::IconResourceId(kJCBCard)); + EXPECT_EQ(IDR_AUTOFILL_CC_MASTERCARD, + CreditCard::IconResourceId(kMasterCard)); + EXPECT_EQ(IDR_AUTOFILL_CC_VISA, + CreditCard::IconResourceId(kVisaCard)); +} + +TEST(CreditCardTest, UpdateFromImportedCard) { + CreditCard original_card(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo( + &original_card, "John Dillinger", "123456789012", "09", "2017"); + + CreditCard a = original_card; + + // The new card has a different name, expiration date, and origin. + CreditCard b = a; + b.set_guid(base::GenerateGUID()); + b.set_origin("https://www.example.org"); + b.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("J. Dillinger")); + b.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("08")); + b.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("2019")); + + EXPECT_TRUE(a.UpdateFromImportedCard(b, "en-US")); + EXPECT_EQ("https://www.example.org", a.origin()); + EXPECT_EQ(ASCIIToUTF16("J. Dillinger"), a.GetRawInfo(CREDIT_CARD_NAME)); + EXPECT_EQ(ASCIIToUTF16("08"), a.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + EXPECT_EQ(ASCIIToUTF16("2019"), a.GetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR)); + + // Try again, but with no name set for |b|. + a = original_card; + b.SetRawInfo(CREDIT_CARD_NAME, base::string16()); + + EXPECT_TRUE(a.UpdateFromImportedCard(b, "en-US")); + EXPECT_EQ("https://www.example.org", a.origin()); + EXPECT_EQ(ASCIIToUTF16("John Dillinger"), a.GetRawInfo(CREDIT_CARD_NAME)); + EXPECT_EQ(ASCIIToUTF16("08"), a.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + EXPECT_EQ(ASCIIToUTF16("2019"), a.GetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR)); + + // Try again, but with only the original card having a verified origin. + // |a| should be unchanged. + a = original_card; + a.set_origin("Chrome settings"); + b.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("J. Dillinger")); + + EXPECT_TRUE(a.UpdateFromImportedCard(b, "en-US")); + EXPECT_EQ("Chrome settings", a.origin()); + EXPECT_EQ(ASCIIToUTF16("John Dillinger"), a.GetRawInfo(CREDIT_CARD_NAME)); + EXPECT_EQ(ASCIIToUTF16("09"), a.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + EXPECT_EQ(ASCIIToUTF16("2017"), a.GetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR)); + + // Try again, but with only the new card having a verified origin. + a = original_card; + b.set_origin("Chrome settings"); + + EXPECT_TRUE(a.UpdateFromImportedCard(b, "en-US")); + EXPECT_EQ("Chrome settings", a.origin()); + EXPECT_EQ(ASCIIToUTF16("J. Dillinger"), a.GetRawInfo(CREDIT_CARD_NAME)); + EXPECT_EQ(ASCIIToUTF16("08"), a.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + EXPECT_EQ(ASCIIToUTF16("2019"), a.GetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR)); + + // Try again, with both cards having a verified origin. + a = original_card; + a.set_origin("Chrome Autofill dialog"); + b.set_origin("Chrome settings"); + + EXPECT_TRUE(a.UpdateFromImportedCard(b, "en-US")); + EXPECT_EQ("Chrome settings", a.origin()); + EXPECT_EQ(ASCIIToUTF16("J. Dillinger"), a.GetRawInfo(CREDIT_CARD_NAME)); + EXPECT_EQ(ASCIIToUTF16("08"), a.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + EXPECT_EQ(ASCIIToUTF16("2019"), a.GetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR)); + + // Try again, but with |b| having a different card number. + // |a| should be unchanged. + a = original_card; + b.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16("4111111111111111")); + + EXPECT_FALSE(a.UpdateFromImportedCard(b, "en-US")); + EXPECT_EQ(original_card, a); +} + +TEST(CreditCardTest, IsComplete) { + CreditCard card(base::GenerateGUID(), "https://www.example.com/"); + 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, IsValid) { + CreditCard card; + // Invalid because expired + card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("1")); + card.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("2010")); + card.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16("4111111111111111")); + EXPECT_FALSE(card.IsValid()); + + // Invalid because card number is not complete + card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("12")); + card.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("9999")); + card.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16("41111")); + EXPECT_FALSE(card.IsValid()); + + // Valid + card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("12")); + card.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("9999")); + card.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16("4111111111111111")); + EXPECT_TRUE(card.IsValid()); +} + +TEST(CreditCardTest, InvalidMastercardNumber) { + CreditCard card(base::GenerateGUID(), "https://www.example.com/"); + + test::SetCreditCardInfo(&card, "Baby Face Nelson", + "5200000000000004", "01", "2010"); + EXPECT_EQ(kMasterCard, card.type()); + EXPECT_FALSE(card.IsComplete()); +} + +// Verify that we preserve exactly what the user typed for credit card numbers. +TEST(CreditCardTest, SetRawInfoCreditCardNumber) { + CreditCard card(base::GenerateGUID(), "https://www.example.com/"); + + 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(base::GenerateGUID(), "https://www.example.com/"); + + card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("05")); + EXPECT_EQ(ASCIIToUTF16("05"), card.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + EXPECT_EQ(5, card.expiration_month()); + + card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("7")); + EXPECT_EQ(ASCIIToUTF16("07"), card.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + EXPECT_EQ(7, card.expiration_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)); + EXPECT_EQ(7, card.expiration_month()); + + card.SetInfo( + AutofillType(CREDIT_CARD_EXP_MONTH), ASCIIToUTF16("January"), "en-US"); + EXPECT_EQ(ASCIIToUTF16("01"), card.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + EXPECT_EQ(1, card.expiration_month()); + + card.SetInfo( + AutofillType(CREDIT_CARD_EXP_MONTH), ASCIIToUTF16("Apr"), "en-US"); + EXPECT_EQ(ASCIIToUTF16("04"), card.GetRawInfo(CREDIT_CARD_EXP_MONTH)); + EXPECT_EQ(4, card.expiration_month()); +} + +TEST(CreditCardTest, CreditCardType) { + CreditCard card(base::GenerateGUID(), "https://www.example.com/"); + + // The card type cannot be set directly. + card.SetRawInfo(CREDIT_CARD_TYPE, ASCIIToUTF16("Visa")); + EXPECT_EQ(base::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(base::GenerateGUID(), "https://www.example.com/"); + + // The verification code cannot be set, as Chrome does not store this data. + card.SetRawInfo(CREDIT_CARD_VERIFICATION_CODE, ASCIIToUTF16("999")); + EXPECT_EQ(base::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<base::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(base::GenerateGUID(), "https://www.example.com/"); + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("01")); + credit_card.FillSelectControl( + AutofillType(CREDIT_CARD_EXP_MONTH), "en-US", &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<base::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(base::GenerateGUID(), "https://www.example.com/"); + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("01")); + credit_card.FillSelectControl( + AutofillType(CREDIT_CARD_EXP_MONTH), "en-US", &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<base::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(base::GenerateGUID(), "https://www.example.com/"); + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("01")); + credit_card.FillSelectControl( + AutofillType(CREDIT_CARD_EXP_MONTH), "en-US", &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<base::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(base::GenerateGUID(), "https://www.example.com/"); + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("01")); + credit_card.FillSelectControl( + AutofillType(CREDIT_CARD_EXP_MONTH), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("1"), field.value); +} + +TEST(CreditCardTest, CreditCardTwoDigitYear) { + const char* const kYears[] = { + "12", "13", "14", "15", "16", "17", "18", "19" + }; + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/"); + credit_card.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("2017")); + credit_card.FillSelectControl( + AutofillType(CREDIT_CARD_EXP_4_DIGIT_YEAR), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("17"), field.value); + EXPECT_EQ(2017, credit_card.expiration_year()); +} + +TEST(CreditCardTest, CreditCardTypeSelectControl) { + const char* const kCreditCardTypes[] = { + "Visa", "Master Card", "AmEx", "discover" + }; + std::vector<base::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(base::GenerateGUID(), "https://www.example.com/"); + credit_card.SetRawInfo(CREDIT_CARD_NUMBER, + ASCIIToUTF16("4111111111111111")); + credit_card.FillSelectControl( + AutofillType(CREDIT_CARD_TYPE), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("Visa"), field.value); + } + + { + // Filling should be able to handle intervening whitespace: + CreditCard credit_card(base::GenerateGUID(), "https://www.example.com/"); + credit_card.SetRawInfo(CREDIT_CARD_NUMBER, + ASCIIToUTF16("5105105105105100")); + credit_card.FillSelectControl( + AutofillType(CREDIT_CARD_TYPE), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("Master Card"), field.value); + } + + { + // American Express is sometimes abbreviated as AmEx: + CreditCard credit_card(base::GenerateGUID(), "https://www.example.com/"); + credit_card.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16("371449635398431")); + credit_card.FillSelectControl( + AutofillType(CREDIT_CARD_TYPE), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("AmEx"), field.value); + } + + { + // Case insensitivity: + CreditCard credit_card(base::GenerateGUID(), "https://www.example.com/"); + credit_card.SetRawInfo(CREDIT_CARD_NUMBER, + ASCIIToUTF16("6011111111111117")); + credit_card.FillSelectControl( + AutofillType(CREDIT_CARD_TYPE), "en-US", &field); + EXPECT_EQ(ASCIIToUTF16("discover"), field.value); + } +} + +TEST(CreditCardTest, GetCreditCardType) { + struct { + std::string card_number; + std::string type; + bool is_valid; + } test_cases[] = { + // The relevant sample numbers from + // http://www.paypalobjects.com/en_US/vhelp/paypalmanager_help/credit_card_numbers.htm + { "378282246310005", kAmericanExpressCard, true }, + { "371449635398431", kAmericanExpressCard, true }, + { "378734493671000", kAmericanExpressCard, true }, + { "30569309025904", kDinersCard, true }, + { "38520000023237", kDinersCard, true }, + { "6011111111111117", kDiscoverCard, true }, + { "6011000990139424", kDiscoverCard, true }, + { "3530111333300000", kJCBCard, true }, + { "3566002020360505", kJCBCard, true }, + { "5555555555554444", kMasterCard, true }, + { "5105105105105100", kMasterCard, true }, + { "4111111111111111", kVisaCard, true }, + { "4012888888881881", kVisaCard, true }, + { "4222222222222", kVisaCard, true }, + + // The relevant sample numbers from + // http://auricsystems.com/support-center/sample-credit-card-numbers/ + { "343434343434343", kAmericanExpressCard, true }, + { "371144371144376", kAmericanExpressCard, true }, + { "341134113411347", kAmericanExpressCard, true }, + { "36438936438936", kDinersCard, true }, + { "36110361103612", kDinersCard, true }, + { "36111111111111", kDinersCard, true }, + { "6011016011016011", kDiscoverCard, true }, + { "6011000990139424", kDiscoverCard, true }, + { "6011000000000004", kDiscoverCard, true }, + { "6011000995500000", kDiscoverCard, true }, + { "6500000000000002", kDiscoverCard, true }, + { "3566002020360505", kJCBCard, true }, + { "3528000000000007", kJCBCard, true }, + { "5500005555555559", kMasterCard, true }, + { "5555555555555557", kMasterCard, true }, + { "5454545454545454", kMasterCard, true }, + { "5555515555555551", kMasterCard, true }, + { "5405222222222226", kMasterCard, true }, + { "5478050000000007", kMasterCard, true }, + { "5111005111051128", kMasterCard, true }, + { "5112345112345114", kMasterCard, true }, + { "5115915115915118", kMasterCard, true }, + + // A UnionPay card that doesn't pass the Luhn checksum + { "6200000000000000", kUnionPay, true }, + + // Empty string + { std::string(), kGenericCard, false }, + + // Non-numeric + { "garbage", kGenericCard, false }, + { "4garbage", kVisaCard, false }, + + // Fails Luhn check. + { "4111111111111112", kVisaCard, false }, + + // Invalid length. + { "3434343434343434", kAmericanExpressCard, false }, + { "411111111111116", kVisaCard, false }, + + // Issuer Identification Numbers (IINs) that Chrome recognizes. + { "4", kVisaCard, false }, + { "34", kAmericanExpressCard, false }, + { "37", kAmericanExpressCard, false }, + { "300", kDinersCard, false }, + { "301", kDinersCard, false }, + { "302", kDinersCard, false }, + { "303", kDinersCard, false }, + { "304", kDinersCard, false }, + { "305", kDinersCard, false }, + { "3095", kDinersCard, false }, + { "36", kDinersCard, false }, + { "38", kDinersCard, false }, + { "39", kDinersCard, false }, + { "6011", kDiscoverCard, false }, + { "644", kDiscoverCard, false }, + { "645", kDiscoverCard, false }, + { "646", kDiscoverCard, false }, + { "647", kDiscoverCard, false }, + { "648", kDiscoverCard, false }, + { "649", kDiscoverCard, false }, + { "65", kDiscoverCard, false }, + { "3528", kJCBCard, false }, + { "3531", kJCBCard, false }, + { "3589", kJCBCard, false }, + { "51", kMasterCard, false }, + { "52", kMasterCard, false }, + { "53", kMasterCard, false }, + { "54", kMasterCard, false }, + { "55", kMasterCard, false }, + { "62", kUnionPay, false }, + + // Not enough data to determine an IIN uniquely. + { "3", kGenericCard, false }, + { "30", kGenericCard, false }, + { "309", kGenericCard, false }, + { "35", kGenericCard, false }, + { "5", kGenericCard, false }, + { "6", kGenericCard, false }, + { "60", kGenericCard, false }, + { "601", kGenericCard, false }, + { "64", kGenericCard, false }, + + // Unknown IINs. + { "0", kGenericCard, false }, + { "1", kGenericCard, false }, + { "2", kGenericCard, false }, + { "306", kGenericCard, false }, + { "307", kGenericCard, false }, + { "308", kGenericCard, false }, + { "3091", kGenericCard, false }, + { "3094", kGenericCard, false }, + { "3096", kGenericCard, false }, + { "31", kGenericCard, false }, + { "32", kGenericCard, false }, + { "33", kGenericCard, false }, + { "351", kGenericCard, false }, + { "3527", kGenericCard, false }, + { "359", kGenericCard, false }, + { "50", kGenericCard, false }, + { "56", kGenericCard, false }, + { "57", kGenericCard, false }, + { "58", kGenericCard, false }, + { "59", kGenericCard, false }, + { "600", kGenericCard, false }, + { "602", kGenericCard, false }, + { "603", kGenericCard, false }, + { "604", kGenericCard, false }, + { "605", kGenericCard, false }, + { "606", kGenericCard, false }, + { "607", kGenericCard, false }, + { "608", kGenericCard, false }, + { "609", kGenericCard, false }, + { "61", kGenericCard, false }, + { "63", kGenericCard, false }, + { "640", kGenericCard, false }, + { "641", kGenericCard, false }, + { "642", kGenericCard, false }, + { "643", kGenericCard, false }, + { "66", kGenericCard, false }, + { "67", kGenericCard, false }, + { "68", kGenericCard, false }, + { "69", kGenericCard, false }, + { "7", kGenericCard, false }, + { "8", kGenericCard, false }, + { "9", kGenericCard, false }, + + // Oddball case: Unknown issuer, but valid Luhn check and plausible length. + { "7000700070007000", kGenericCard, true }, + }; + + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(test_cases); ++i) { + base::string16 card_number = ASCIIToUTF16(test_cases[i].card_number); + SCOPED_TRACE(card_number); + EXPECT_EQ(test_cases[i].type, CreditCard::GetCreditCardType(card_number)); + EXPECT_EQ(test_cases[i].is_valid, IsValidCreditCardNumber(card_number)); + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/crypto/rc4_decryptor.h b/chromium/components/autofill/core/browser/crypto/rc4_decryptor.h new file mode 100644 index 00000000000..095413a6b3a --- /dev/null +++ b/chromium/components/autofill/core/browser/crypto/rc4_decryptor.h @@ -0,0 +1,110 @@ +// 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_CORE_BROWSER_CRYPTO_RC4_DECRYPTOR_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_CRYPTO_RC4_DECRYPTOR_H_ + +#include <string> +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace autofill { + +// 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/core/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_ptr<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_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_CRYPTO_RC4_DECRYPTOR_H_ diff --git a/chromium/components/autofill/core/browser/data_driven_test.cc b/chromium/components/autofill/core/browser/data_driven_test.cc new file mode 100644 index 00000000000..18f461ece0e --- /dev/null +++ b/chromium/components/autofill/core/browser/data_driven_test.cc @@ -0,0 +1,93 @@ +// 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/core/browser/data_driven_test.h" + +#include "base/file_util.h" +#include "base/files/file_enumerator.h" +#include "base/strings/string_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { +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(), + static_cast<int>(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) { + ASSERT_TRUE(base::DirectoryExists(input_directory)); + ASSERT_TRUE(base::DirectoryExists(output_directory)); + base::FileEnumerator input_files(input_directory, + false, + base::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 dir; + dir = test_data_directory_.AppendASCII("autofill") + .Append(test_name) + .AppendASCII("input"); + return dir; +} + +base::FilePath DataDrivenTest::GetOutputDirectory( + const base::FilePath::StringType& test_name) { + base::FilePath dir; + dir = test_data_directory_.AppendASCII("autofill") + .Append(test_name) + .AppendASCII("output"); + return dir; +} + +DataDrivenTest::DataDrivenTest(const base::FilePath& test_data_directory) + : test_data_directory_(test_data_directory) { +} + +DataDrivenTest::~DataDrivenTest() { +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/data_driven_test.h b/chromium/components/autofill/core/browser/data_driven_test.h new file mode 100644 index 00000000000..aff2f319cd8 --- /dev/null +++ b/chromium/components/autofill/core/browser/data_driven_test.h @@ -0,0 +1,56 @@ +// 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_CORE_BROWSER_DATA_DRIVEN_TEST_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_DATA_DRIVEN_TEST_H_ + +#include <string> + +#include "base/files/file_path.h" +#include "base/strings/string16.h" + +namespace autofill { + +// 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(const base::FilePath& test_data_directory); + virtual ~DataDrivenTest(); + + private: + base::FilePath test_data_directory_; + + DISALLOW_COPY_AND_ASSIGN(DataDrivenTest); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_DATA_DRIVEN_TEST_H_ diff --git a/chromium/components/autofill/core/browser/email_field.cc b/chromium/components/autofill/core/browser/email_field.cc new file mode 100644 index 00000000000..053ecec3c83 --- /dev/null +++ b/chromium/components/autofill/core/browser/email_field.cc @@ -0,0 +1,32 @@ +// 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/core/browser/email_field.h" + +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_regex_constants.h" +#include "components/autofill/core/browser/autofill_scanner.h" +#include "ui/base/l10n/l10n_util.h" + +namespace autofill { + +// 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(ServerFieldTypeMap* map) const { + return AddClassification(field_, EMAIL_ADDRESS, map); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/email_field.h b/chromium/components/autofill/core/browser/email_field.h new file mode 100644 index 00000000000..4e270a18bc6 --- /dev/null +++ b/chromium/components/autofill/core/browser/email_field.h @@ -0,0 +1,32 @@ +// 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_CORE_BROWSER_EMAIL_FIELD_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_EMAIL_FIELD_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "components/autofill/core/browser/form_field.h" + +namespace autofill { + +class EmailField : public FormField { + public: + static FormField* Parse(AutofillScanner* scanner); + + protected: + // FormField: + virtual bool ClassifyField(ServerFieldTypeMap* map) const OVERRIDE; + + private: + explicit EmailField(const AutofillField* field); + + const AutofillField* field_; + + DISALLOW_COPY_AND_ASSIGN(EmailField); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_EMAIL_FIELD_H_ diff --git a/chromium/components/autofill/core/browser/field_types.h b/chromium/components/autofill/core/browser/field_types.h new file mode 100644 index 00000000000..081a7a75c42 --- /dev/null +++ b/chromium/components/autofill/core/browser/field_types.h @@ -0,0 +1,191 @@ +// 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_CORE_BROWSER_FIELD_TYPES_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_FIELD_TYPES_H_ + +#include <map> +#include <set> + +#include "base/strings/string16.h" + +namespace autofill { + +// 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. +// +// The list of all field types natively understood by the Autofill server. A +// subset of these types is used to store Autofill data in the user's profile. +enum ServerFieldType { + // 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, + + PHONE_BILLING_NUMBER = 62, + PHONE_BILLING_CITY_CODE = 63, + PHONE_BILLING_COUNTRY_CODE = 64, + PHONE_BILLING_CITY_AND_NUMBER = 65, + PHONE_BILLING_WHOLE_NUMBER = 66, + + NAME_BILLING_FIRST = 67, + NAME_BILLING_MIDDLE = 68, + NAME_BILLING_LAST = 69, + NAME_BILLING_MIDDLE_INITIAL = 70, + NAME_BILLING_FULL = 71, + NAME_BILLING_SUFFIX = 72, + + // No new types can be added without a corresponding change to the Autofill + // server. + + MAX_VALID_FIELD_TYPE = 73, +}; + +// The list of all HTML autocomplete field type hints supported by Chrome. +// See [ http://is.gd/whatwg_autocomplete ] for the full list of specced hints. +enum HtmlFieldType { + // Default type. + HTML_TYPE_UNKNOWN, + + // Name types. + HTML_TYPE_NAME, + HTML_TYPE_GIVEN_NAME, + HTML_TYPE_ADDITIONAL_NAME, + HTML_TYPE_FAMILY_NAME, + + // Business types. + HTML_TYPE_ORGANIZATION, + + // Address types. + HTML_TYPE_STREET_ADDRESS, + HTML_TYPE_ADDRESS_LINE1, + HTML_TYPE_ADDRESS_LINE2, + HTML_TYPE_LOCALITY, // For U.S. addresses, corresponds to the city. + HTML_TYPE_REGION, // For U.S. addresses, corresponds to the state. + HTML_TYPE_COUNTRY_CODE, // The ISO 3166-1-alpha-2 country code. + HTML_TYPE_COUNTRY_NAME, // The localized country name. + HTML_TYPE_POSTAL_CODE, + + // Credit card types. + HTML_TYPE_CREDIT_CARD_NAME, + HTML_TYPE_CREDIT_CARD_NUMBER, + HTML_TYPE_CREDIT_CARD_EXP, + HTML_TYPE_CREDIT_CARD_EXP_MONTH, + HTML_TYPE_CREDIT_CARD_EXP_YEAR, + HTML_TYPE_CREDIT_CARD_VERIFICATION_CODE, + HTML_TYPE_CREDIT_CARD_TYPE, + + // Phone number types. + HTML_TYPE_TEL, + HTML_TYPE_TEL_COUNTRY_CODE, + HTML_TYPE_TEL_NATIONAL, + HTML_TYPE_TEL_AREA_CODE, + HTML_TYPE_TEL_LOCAL, + HTML_TYPE_TEL_LOCAL_PREFIX, + HTML_TYPE_TEL_LOCAL_SUFFIX, + + // Email. + HTML_TYPE_EMAIL, + + // Variants of type hints specified in the HTML specification that are + // inferred based on a field's 'maxlength' attribute. + // TODO(isherman): Remove these types, in favor of understanding maxlength + // when filling fields. See also: AutofillField::phone_part_. + HTML_TYPE_ADDITIONAL_NAME_INITIAL, + HTML_TYPE_CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR, + HTML_TYPE_CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR, + HTML_TYPE_CREDIT_CARD_EXP_2_DIGIT_YEAR, + HTML_TYPE_CREDIT_CARD_EXP_4_DIGIT_YEAR, +}; + +// The list of all HTML autocomplete field mode hints supported by Chrome. +// See [ http://is.gd/whatwg_autocomplete ] for the full list of specced hints. +enum HtmlFieldMode { + HTML_MODE_NONE, + HTML_MODE_BILLING, + HTML_MODE_SHIPPING, +}; + +enum FieldTypeGroup { + NO_GROUP, + NAME, + NAME_BILLING, + EMAIL, + COMPANY, + ADDRESS_HOME, + ADDRESS_BILLING, + PHONE_HOME, + PHONE_BILLING, + CREDIT_CARD, +}; + +typedef std::set<ServerFieldType> ServerFieldTypeSet; +typedef std::map<base::string16, ServerFieldType> ServerFieldTypeMap; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_FIELD_TYPES_H_ diff --git a/chromium/components/autofill/core/browser/form_field.cc b/chromium/components/autofill/core/browser/form_field.cc new file mode 100644 index 00000000000..2e001127ada --- /dev/null +++ b/chromium/components/autofill/core/browser/form_field.cc @@ -0,0 +1,199 @@ +// 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/core/browser/form_field.h" + +#include <stddef.h> +#include <string> +#include <utility> + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/address_field.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_regexes.h" +#include "components/autofill/core/browser/autofill_scanner.h" +#include "components/autofill/core/browser/credit_card_field.h" +#include "components/autofill/core/browser/email_field.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/browser/name_field.h" +#include "components/autofill/core/browser/phone_field.h" +#include "ui/base/l10n/l10n_util.h" + +namespace autofill { +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, + ServerFieldTypeMap* 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 base::string16& pattern, + const AutofillField** match) { + return ParseFieldSpecifics(scanner, pattern, MATCH_DEFAULT, match); +} + +// static +bool FormField::ParseFieldSpecifics(AutofillScanner* scanner, + const base::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, + ServerFieldType type, + ServerFieldTypeMap* 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 base::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 base::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, + ServerFieldTypeMap* 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); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/form_field.h b/chromium/components/autofill/core/browser/form_field.h new file mode 100644 index 00000000000..dbb937f7b61 --- /dev/null +++ b/chromium/components/autofill/core/browser/form_field.h @@ -0,0 +1,124 @@ +// 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_CORE_BROWSER_FORM_FIELD_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_FIELD_H_ + +#include <vector> + +#include "base/basictypes.h" +#include "base/gtest_prod_util.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/field_types.h" + +namespace autofill { + +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, + ServerFieldTypeMap* 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 base::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 base::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, + ServerFieldType type, + ServerFieldTypeMap* 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(ServerFieldTypeMap* 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 base::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 base::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, + ServerFieldTypeMap* map); + + DISALLOW_COPY_AND_ASSIGN(FormField); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_FIELD_H_ diff --git a/chromium/components/autofill/core/browser/form_field_unittest.cc b/chromium/components/autofill/core/browser/form_field_unittest.cc new file mode 100644 index 00000000000..5014b713648 --- /dev/null +++ b/chromium/components/autofill/core/browser/form_field_unittest.cc @@ -0,0 +1,152 @@ +// 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 "base/memory/scoped_vector.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/form_field.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +TEST(FormFieldTest, Match) { + AutofillField field; + + // Empty strings match. + EXPECT_TRUE(FormField::Match(&field, base::string16(), + FormField::MATCH_LABEL)); + + // Empty pattern matches non-empty string. + field.label = ASCIIToUTF16("a"); + EXPECT_TRUE(FormField::Match(&field, base::string16(), + FormField::MATCH_LABEL)); + + // Strictly empty pattern matches empty string. + field.label = base::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 = base::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)); + + ServerFieldTypeMap 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); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/form_group.cc b/chromium/components/autofill/core/browser/form_group.cc new file mode 100644 index 00000000000..17eb036aadc --- /dev/null +++ b/chromium/components/autofill/core/browser/form_group.cc @@ -0,0 +1,51 @@ +// 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/core/browser/form_group.h" + +#include "components/autofill/core/browser/autofill_type.h" + +namespace autofill { + +void FormGroup::GetMatchingTypes(const base::string16& text, + const std::string& app_locale, + ServerFieldTypeSet* matching_types) const { + if (text.empty()) { + matching_types->insert(EMPTY_TYPE); + return; + } + + ServerFieldTypeSet types; + GetSupportedTypes(&types); + for (ServerFieldTypeSet::const_iterator type = types.begin(); + type != types.end(); ++type) { + if (GetInfo(AutofillType(*type), app_locale) == text) + matching_types->insert(*type); + } +} + +void FormGroup::GetNonEmptyTypes(const std::string& app_locale, + ServerFieldTypeSet* non_empty_types) const { + ServerFieldTypeSet types; + GetSupportedTypes(&types); + for (ServerFieldTypeSet::const_iterator type = types.begin(); + type != types.end(); ++type) { + if (!GetInfo(AutofillType(*type), app_locale).empty()) + non_empty_types->insert(*type); + } +} + +base::string16 FormGroup::GetInfo(const AutofillType& type, + const std::string& app_locale) const { + return GetRawInfo(type.GetStorableType()); +} + +bool FormGroup::SetInfo(const AutofillType& type, + const base::string16& value, + const std::string& app_locale) { + SetRawInfo(type.GetStorableType(), value); + return true; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/form_group.h b/chromium/components/autofill/core/browser/form_group.h new file mode 100644 index 00000000000..86bd4531bb1 --- /dev/null +++ b/chromium/components/autofill/core/browser/form_group.h @@ -0,0 +1,68 @@ +// 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_CORE_BROWSER_FORM_GROUP_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_GROUP_H_ + +#include <string> + +#include "base/strings/string16.h" +#include "components/autofill/core/browser/field_types.h" + +namespace autofill { + +class AutofillType; + +// This class is an interface for collections of form fields, grouped by type. +class FormGroup { + public: + virtual ~FormGroup() {} + + // 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 base::string16& text, + const std::string& app_locale, + ServerFieldTypeSet* matching_types) const; + + // Returns a set of server field types for which this FormGroup has non-empty + // data. This method is additive on |non_empty_types|. + virtual void GetNonEmptyTypes(const std::string& app_locale, + ServerFieldTypeSet* non_empty_types) const; + + // Returns the string associated with |type|, without canonicalizing the + // returned value. For user-visible strings, use GetInfo() instead. + virtual base::string16 GetRawInfo(ServerFieldType 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(ServerFieldType type, + const base::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 base::string16 GetInfo(const AutofillType& 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(const AutofillType& type, + const base::string16& value, + const std::string& app_locale); + + 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 server field types for which this FormGroup can store + // data. This method is additive on |supported_types|. + virtual void GetSupportedTypes(ServerFieldTypeSet* supported_types) const = 0; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_GROUP_H_ diff --git a/chromium/components/autofill/core/browser/form_structure.cc b/chromium/components/autofill/core/browser/form_structure.cc new file mode 100644 index 00000000000..0a534f8d847 --- /dev/null +++ b/chromium/components/autofill/core/browser/form_structure.cc @@ -0,0 +1,1259 @@ +// 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/core/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/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "components/autofill/content/browser/autocheckout_page_meta_data.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/autofill_xml_parser.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/form_field.h" +#include "components/autofill/core/common/autofill_constants.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_data_predictions.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/form_field_data_predictions.h" +#include "third_party/icu/source/i18n/unicode/regex.h" +#include "third_party/libjingle/source/talk/xmllite/xmlelement.h" + +namespace autofill { +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 kAttributeFieldID[] = "fieldid"; +const char kAttributeFieldType[] = "fieldtype"; +const char kAttributeFormSignature[] = "formsignature"; +const char kAttributeName[] = "name"; +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 kXMLElementFieldAssignments[] = "fieldassignments"; +const char kXMLElementField[] = "field"; +const char kXMLElementFields[] = "fields"; +const char kXMLElementForm[] = "form"; +const char kBillingMode[] = "billing"; +const char kShippingMode[] = "shipping"; + +// Stip away >= 5 consecutive digits. +const char kIgnorePatternInFieldName[] = "\\d{5,}+"; + +// 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 ServerFieldTypeSet& 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 (ServerFieldTypeSet::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; +} + +// Helper for |EncodeFormRequest()| that creates XmlElements for the given field +// in upload xml, and also add them to the parent XmlElement. +void EncodeFieldForUpload(const AutofillField& field, + buzz::XmlElement* parent) { + // Don't upload checkable fields. + if (field.is_checkable) + return; + + ServerFieldTypeSet types = field.possible_types(); + // |types| could be empty in unit-tests only. + for (ServerFieldTypeSet::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)); + parent->AddElement(field_element); + } +} + +// Helper for |EncodeFormRequest()| that creates XmlElement for the given field +// in query xml, and also add it to the parent XmlElement. +void EncodeFieldForQuery(const AutofillField& field, + buzz::XmlElement* parent) { + buzz::XmlElement *field_element = new buzz::XmlElement( + buzz::QName(kXMLElementField)); + field_element->SetAttr(buzz::QName(kAttributeSignature), + field.FieldSignature()); + parent->AddElement(field_element); +} + +// Helper for |EncodeFormRequest()| that creates XmlElements for the given field +// in field assignments xml, and also add them to the parent XmlElement. +void EncodeFieldForFieldAssignments(const AutofillField& field, + buzz::XmlElement* parent) { + ServerFieldTypeSet types = field.possible_types(); + for (ServerFieldTypeSet::iterator field_type = types.begin(); + field_type != types.end(); ++field_type) { + buzz::XmlElement *field_element = new buzz::XmlElement( + buzz::QName(kXMLElementFields)); + + field_element->SetAttr(buzz::QName(kAttributeFieldID), + field.FieldSignature()); + field_element->SetAttr(buzz::QName(kAttributeFieldType), + base::IntToString(*field_type)); + field_element->SetAttr(buzz::QName(kAttributeName), + UTF16ToUTF8(field.name)); + parent->AddElement(field_element); + } +} + +// 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, + HtmlFieldType 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 == HTML_TYPE_EMAIL || + (field_type >= HTML_TYPE_TEL && + field_type <= HTML_TYPE_TEL_LOCAL_SUFFIX); + } + + // 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 >= HTML_TYPE_TEL && + field_type <= HTML_TYPE_TEL_LOCAL_SUFFIX; + } + + return false; +} + +// Returns the Chrome Autofill-supported field type corresponding to the given +// |autocomplete_attribute_value|, 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 +HtmlFieldType FieldTypeFromAutocompleteAttributeValue( + const std::string& autocomplete_attribute_value, + const AutofillField& field) { + if (autocomplete_attribute_value == "name") + return HTML_TYPE_NAME; + + if (autocomplete_attribute_value == "given-name") + return HTML_TYPE_GIVEN_NAME; + + if (autocomplete_attribute_value == "additional-name") { + if (field.max_length == 1) + return HTML_TYPE_ADDITIONAL_NAME_INITIAL; + else + return HTML_TYPE_ADDITIONAL_NAME; + } + + if (autocomplete_attribute_value == "family-name") + return HTML_TYPE_FAMILY_NAME; + + if (autocomplete_attribute_value == "organization") + return HTML_TYPE_ORGANIZATION; + + if (autocomplete_attribute_value == "street-address") + return HTML_TYPE_STREET_ADDRESS; + + if (autocomplete_attribute_value == "address-line1") + return HTML_TYPE_ADDRESS_LINE1; + + if (autocomplete_attribute_value == "address-line2") + return HTML_TYPE_ADDRESS_LINE2; + + if (autocomplete_attribute_value == "locality") + return HTML_TYPE_LOCALITY; + + if (autocomplete_attribute_value == "region") + return HTML_TYPE_REGION; + + if (autocomplete_attribute_value == "country") + return HTML_TYPE_COUNTRY_CODE; + + if (autocomplete_attribute_value == "country-name") + return HTML_TYPE_COUNTRY_NAME; + + if (autocomplete_attribute_value == "postal-code") + return HTML_TYPE_POSTAL_CODE; + + if (autocomplete_attribute_value == "cc-name") + return HTML_TYPE_CREDIT_CARD_NAME; + + if (autocomplete_attribute_value == "cc-number") + return HTML_TYPE_CREDIT_CARD_NUMBER; + + if (autocomplete_attribute_value == "cc-exp") { + if (field.max_length == 5) + return HTML_TYPE_CREDIT_CARD_EXP_DATE_2_DIGIT_YEAR; + else if (field.max_length == 7) + return HTML_TYPE_CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR; + else + return HTML_TYPE_CREDIT_CARD_EXP; + } + + if (autocomplete_attribute_value == "cc-exp-month") + return HTML_TYPE_CREDIT_CARD_EXP_MONTH; + + if (autocomplete_attribute_value == "cc-exp-year") { + if (field.max_length == 2) + return HTML_TYPE_CREDIT_CARD_EXP_2_DIGIT_YEAR; + else if (field.max_length == 4) + return HTML_TYPE_CREDIT_CARD_EXP_4_DIGIT_YEAR; + else + return HTML_TYPE_CREDIT_CARD_EXP_YEAR; + } + + if (autocomplete_attribute_value == "cc-csc") + return HTML_TYPE_CREDIT_CARD_VERIFICATION_CODE; + + if (autocomplete_attribute_value == "cc-type") + return HTML_TYPE_CREDIT_CARD_TYPE; + + if (autocomplete_attribute_value == "tel") + return HTML_TYPE_TEL; + + if (autocomplete_attribute_value == "tel-country-code") + return HTML_TYPE_TEL_COUNTRY_CODE; + + if (autocomplete_attribute_value == "tel-national") + return HTML_TYPE_TEL_NATIONAL; + + if (autocomplete_attribute_value == "tel-area-code") + return HTML_TYPE_TEL_AREA_CODE; + + if (autocomplete_attribute_value == "tel-local") + return HTML_TYPE_TEL_LOCAL; + + if (autocomplete_attribute_value == "tel-local-prefix") + return HTML_TYPE_TEL_LOCAL_PREFIX; + + if (autocomplete_attribute_value == "tel-local-suffix") + return HTML_TYPE_TEL_LOCAL_SUFFIX; + + if (autocomplete_attribute_value == "email") + return HTML_TYPE_EMAIL; + + return HTML_TYPE_UNKNOWN; +} + +std::string StripDigitsIfRequired(const base::string16& input) { + UErrorCode status = U_ZERO_ERROR; + CR_DEFINE_STATIC_LOCAL(icu::UnicodeString, icu_pattern, + (kIgnorePatternInFieldName)); + CR_DEFINE_STATIC_LOCAL(icu::RegexMatcher, matcher, + (icu_pattern, UREGEX_CASE_INSENSITIVE, status)); + DCHECK_EQ(status, U_ZERO_ERROR); + + icu::UnicodeString icu_input(input.data(), input.length()); + matcher.reset(icu_input); + + icu::UnicodeString replaced_string = matcher.replaceAll("", status); + + std::string return_string; + status = U_ZERO_ERROR; + UTF16ToUTF8(replaced_string.getBuffer(), + static_cast<size_t>(replaced_string.length()), + &return_string); + if (status != U_ZERO_ERROR) { + DVLOG(1) << "Couldn't strip digits in " << UTF16ToUTF8(input); + return UTF16ToUTF8(input); + } + + return return_string; +} + +} // 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), + active_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), + filled_by_autocheckout_(false) { + // Copy the form fields. + std::map<base::string16, size_t> unique_names; + for (std::vector<FormFieldData>::const_iterator field = + form.fields.begin(); + field != form.fields.end(); field++) { + + if (!ShouldSkipField(*field)) { + // 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(StripDigitsIfRequired(field->name)); + + ++active_field_count_; + } + + // 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]; + base::string16 unique_name = field->name + ASCIIToUTF16("_") + + base::IntToString16(unique_names[field->name]); + fields_.push_back(new AutofillField(*field, unique_name)); + } + + 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_) { + ServerFieldTypeMap field_type_map; + FormField::ParseFormFields(fields_.get(), &field_type_map); + for (size_t i = 0; i < field_count(); ++i) { + AutofillField* field = fields_[i]; + ServerFieldTypeMap::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 ServerFieldTypeSet& available_field_types, + bool form_was_autofilled, + std::string* encoded_xml) const { + DCHECK(ShouldBeCrowdsourced()); + + // 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 (ServerFieldTypeSet::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 = 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; +} + +bool FormStructure::EncodeFieldAssignments( + const ServerFieldTypeSet& available_field_types, + std::string* encoded_xml) const { + DCHECK(ShouldBeCrowdsourced()); + + // Set up the <fieldassignments> element and its attributes. + buzz::XmlElement autofill_request_xml( + (buzz::QName(kXMLElementFieldAssignments))); + autofill_request_xml.SetAttr(buzz::QName(kAttributeFormSignature), + FormSignature()); + + if (!EncodeFormRequest(FormStructure::FIELD_ASSIGNMENTS, + &autofill_request_xml)) + return false; // Malformed form, skip it. + + // Obtain the XML structure as a string. + *encoded_xml = kXMLDeclaration; + *encoded_xml += autofill_request_xml.Str().c_str(); + + 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, + page_meta_data); + buzz::XmlParser parser(&parse_handler); + parser.Parse(response_xml.c_str(), response_xml.length(), true); + if (!parse_handler.succeeded()) + return; + + 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) { + if (form->ShouldSkipField(**field)) + continue; + + // 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); + + ServerFieldType heuristic_type = (*field)->heuristic_type(); + if (heuristic_type != UNKNOWN_TYPE) + heuristics_detected_fillable_field = true; + + (*field)->set_server_type(current_info->field_type); + if (heuristic_type != (*field)->Type().GetStorableType()) + 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); + + ++current_info; + } + + 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((*field)->heuristic_type()).ToString(); + annotated_field.server_type = + AutofillType((*field)->server_type()).ToString(); + annotated_field.overall_type = (*field)->Type().ToString(); + 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(); +} + +bool FormStructure::ShouldSkipField(const FormFieldData& field) const { + return (field.is_checkable || field.form_control_type == "password") && + !IsAutocheckoutEnabled(); +} + +size_t FormStructure::RequiredFillableFields() const { + return IsAutocheckoutEnabled() ? 0 : kRequiredAutofillFields; +} + +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 { + if (active_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 { + // Allow all forms in Autocheckout flow to be crowdsourced. + return (!has_author_specified_types_ && ShouldBeParsed(true)) || + IsAutocheckoutEnabled(); +} + +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 = base::string16(); + } + + field->set_heuristic_type(cached_field->second->heuristic_type()); + field->set_server_type(cached_field->second->server_type()); + } + } + + UpdateAutofillCount(); + + filled_by_autocheckout_ = cached_form.filled_by_autocheckout(); + 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 ServerFieldTypeSet& 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. + ServerFieldTypeSet collapsed_field_types; + for (ServerFieldTypeSet::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(*it).GetStorableType()); + } + + // Capture the field's type, if it is unambiguous. + ServerFieldType field_type = UNKNOWN_TYPE; + if (collapsed_field_types.size() == 1) + field_type = *collapsed_field_types.begin(); + + ServerFieldType heuristic_type = + AutofillType(field->heuristic_type()).GetStorableType(); + ServerFieldType server_type = + AutofillType(field->server_type()).GetStorableType(); + ServerFieldType predicted_type = field->Type().GetStorableType(); + + // 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::active_field_count() const { + return active_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]; + switch (request_type) { + case FormStructure::UPLOAD: + EncodeFieldForUpload(*field, encompassing_xml_element); + break; + case FormStructure::QUERY: + if (ShouldSkipField(*field)) + continue; + EncodeFieldForQuery(*field, encompassing_xml_element); + break; + case FormStructure::FIELD_ASSIGNMENTS: + EncodeFieldForFieldAssignments(*field, encompassing_xml_element); + break; + } + } + 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(); + HtmlFieldType field_type = + FieldTypeFromAutocompleteAttributeValue(field_type_token, *field); + if (field_type == HTML_TYPE_UNKNOWN) + 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(); + HtmlFieldMode mode = HTML_MODE_NONE; + if (!tokens.empty()) { + if (tokens.back() == kShippingMode) + mode = HTML_MODE_SHIPPING; + else if (tokens.back() == kBillingMode) + mode = HTML_MODE_BILLING; + } + + if (mode != HTML_MODE_NONE) { + 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->SetHtmlType(field_type, mode); + } +} + +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. + base::string16 current_section = fields_.front()->unique_name(); + + // Keep track of the types we've seen in this section. + std::set<ServerFieldType> seen_types; + ServerFieldType previous_type = UNKNOWN_TYPE; + + for (std::vector<AutofillField*>::iterator field = fields_.begin(); + field != fields_.end(); ++field) { + const ServerFieldType current_type = (*field)->Type().GetStorableType(); + + bool already_saw_current_type = seen_types.count(current_type) > 0; + + // Forms often ask for multiple phone numbers -- e.g. both a daytime and + // evening phone number. Our phone number detection is also generally a + // little off. Hence, ignore this field type as a signal here. + if (AutofillType(current_type).group() == PHONE_HOME) + already_saw_current_type = false; + + // 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) { + FieldTypeGroup field_type_group = (*field)->Type().group(); + if (field_type_group == CREDIT_CARD) + (*field)->set_section((*field)->section() + "-cc"); + else + (*field)->set_section((*field)->section() + "-default"); + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/form_structure.h b/chromium/components/autofill/core/browser/form_structure.h new file mode 100644 index 00000000000..1af765057c2 --- /dev/null +++ b/chromium/components/autofill/core/browser/form_structure.h @@ -0,0 +1,262 @@ +// 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_CORE_BROWSER_FORM_STRUCTURE_H_ +#define COMPONENTS_AUTOFILL_CORE_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/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/common/web_element_descriptor.h" +#include "url/gurl.h" + +enum RequestMethod { + GET, + POST +}; + +enum UploadRequired { + UPLOAD_NOT_REQUIRED, + UPLOAD_REQUIRED, + USE_UPLOAD_RATES +}; + +namespace base { +class TimeTicks; +} + +namespace buzz { +class XmlElement; +} + +namespace autofill { + +class AutofillMetrics; + +struct AutocheckoutPageMetaData; +struct FormData; +struct FormDataPredictions; + +// 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 ServerFieldTypeSet& available_field_types, + bool form_was_autofilled, + std::string* encoded_xml) const; + + // Encodes a XML block contains autofill field type from this FormStructure. + // This XML will be written VLOG only, never be sent to server. It will + // help make FieldAssignments and feed back to autofill server as + // experiment data. + bool EncodeFieldAssignments(const ServerFieldTypeSet& available_field_types, + 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; + + // 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 filled_by_autocheckout() const { return filled_by_autocheckout_; } + void set_filled_by_autocheckout(bool filled_by_autocheckout) { + filled_by_autocheckout_ = filled_by_autocheckout; + } + + 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, + FIELD_ASSIGNMENTS, + }; + + // 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 true if field should be skipped when talking to Autofill server. + bool ShouldSkipField(const FormFieldData& field) const; + + // Returns the minimal number of fillable fields required to start autofill. + size_t RequiredFillableFields() const; + size_t active_field_count() const; + + // The name of the form. + base::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 counted towards form signature and request to Autofill + // server. + size_t active_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_; + + // Whether or not this form was filled by Autocheckout. + bool filled_by_autocheckout_; + + DISALLOW_COPY_AND_ASSIGN(FormStructure); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_FORM_STRUCTURE_H_ diff --git a/chromium/components/autofill/core/browser/form_structure_unittest.cc b/chromium/components/autofill/core/browser/form_structure_unittest.cc new file mode 100644 index 00000000000..7d38165b4d1 --- /dev/null +++ b/chromium/components/autofill/core/browser/form_structure_unittest.cc @@ -0,0 +1,2466 @@ +// 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/core/browser/form_structure.h" + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/content/browser/autocheckout_page_meta_data.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_field_data.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/web/WebInputElement.h" +#include "url/gurl.h" + +using WebKit::WebInputElement; + +namespace autofill { +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 = base::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 = base::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 = base::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 = base::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 = base::string16(); + field.name = ASCIIToUTF16("field1"); + field.autocomplete_attribute = "given-name"; + form.fields.push_back(field); + + field.label = base::string16(); + field.name = ASCIIToUTF16("field2"); + field.autocomplete_attribute = "family-name"; + form.fields.push_back(field); + + field.label = base::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(HTML_TYPE_GIVEN_NAME, form_structure->field(0)->html_type()); + EXPECT_EQ(HTML_TYPE_FAMILY_NAME, form_structure->field(1)->html_type()); + EXPECT_EQ(HTML_TYPE_EMAIL, form_structure->field(2)->html_type()); + 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 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 = base::string16(); + field.name = ASCIIToUTF16("field1"); + field.autocomplete_attribute = "tel-local"; + form.fields.push_back(field); + + field.label = base::string16(); + field.name = ASCIIToUTF16("field2"); + field.autocomplete_attribute = "tel-local-prefix"; + form.fields.push_back(field); + + field.label = base::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(HTML_TYPE_TEL_LOCAL, form_structure->field(0)->html_type()); + EXPECT_EQ(AutofillField::IGNORED, form_structure->field(0)->phone_part()); + EXPECT_EQ(HTML_TYPE_TEL_LOCAL_PREFIX, form_structure->field(1)->html_type()); + EXPECT_EQ(AutofillField::PHONE_PREFIX, + form_structure->field(1)->phone_part()); + EXPECT_EQ(HTML_TYPE_TEL_LOCAL_SUFFIX, form_structure->field(2)->html_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()); + + // When Autocheckout is enabled, we should ignore 'autocomplete' attribute + // when deciding to crowdsource. + form_structure.reset(new FormStructure(form, "http://fake.url")); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + EXPECT_TRUE(form_structure->IsAutofillable(true)); + EXPECT_TRUE(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 address-line1"; + 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 = "address-line1"; + form.fields.push_back(field); + field.name = base::string16(); + field.autocomplete_attribute = "section-foo email"; + form.fields.push_back(field); + field.name = base::string16(); + field.autocomplete_attribute = "name"; + form.fields.push_back(field); + field.name = ASCIIToUTF16("two"); + field.autocomplete_attribute = "address-line1"; + 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 = base::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 = base::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 = base::string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Last Name"); + field.name = base::string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Email"); + field.name = base::string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Phone"); + field.name = base::string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address"); + field.name = base::string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address"); + field.name = base::string16(); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Zip code"); + field.name = base::string16(); + form.fields.push_back(field); + + field.label = base::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 = base::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 = base::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()); +} + +// Numbered address lines after line two are ignored. +TEST(FormStructureTest, SurplusAddressLinesIgnored) { + 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 Line3"); + field.name = ASCIIToUTF16("billing.address.addressLine3"); + form.fields.push_back(field); + + field.label = ASCIIToUTF16("Address Line4"); + field.name = ASCIIToUTF16("billing.address.addressLine4"); + form.fields.push_back(field); + + form_structure.reset(new FormStructure(form, std::string())); + form_structure->DetermineHeuristicTypes(TestAutofillMetrics()); + ASSERT_EQ(4U, form_structure->field_count()); + ASSERT_EQ(2U, 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 (ignored). + EXPECT_EQ(UNKNOWN_TYPE, form_structure->field(2)->heuristic_type()); + // Address Line 4 (ignored). + EXPECT_EQ(UNKNOWN_TYPE, 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 = base::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<ServerFieldTypeSet> 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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]); + + ServerFieldTypeSet 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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)); +} + +TEST(FormStructureTest, EncodeFieldAssignments) { + scoped_ptr<FormStructure> form_structure; + std::vector<ServerFieldTypeSet> 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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]); + + ServerFieldTypeSet 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->EncodeFieldAssignments( + available_field_types, &encoded_xml)); + EXPECT_EQ( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<fieldassignments formsignature=\"8736493185895608956\">" + "<fields fieldid=\"3763331450\" fieldtype=\"3\" name=\"firstname\"/>" + "<fields fieldid=\"3494530716\" fieldtype=\"5\" name=\"lastname\"/>" + "<fields fieldid=\"1029417091\" fieldtype=\"9\" name=\"email\"/>" + "<fields fieldid=\"466116101\" fieldtype=\"14\" name=\"phone\"/>" + "<fields fieldid=\"2799270304\" fieldtype=\"36\" name=\"country\"/>" + "<fields fieldid=\"3410250678\" fieldtype=\"36\" name=\"Checkable1\"/>" + "</fieldassignments>", + 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(ServerFieldTypeSet()); + 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->EncodeFieldAssignments( + available_field_types, &encoded_xml)); + EXPECT_EQ( + "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<fieldassignments formsignature=\"7816485729218079147\">" + "<fields fieldid=\"3763331450\" fieldtype=\"3\" name=\"firstname\"/>" + "<fields fieldid=\"3494530716\" fieldtype=\"5\" name=\"lastname\"/>" + "<fields fieldid=\"1029417091\" fieldtype=\"9\" name=\"email\"/>" + "<fields fieldid=\"466116101\" fieldtype=\"14\" name=\"phone\"/>" + "<fields fieldid=\"2799270304\" fieldtype=\"36\" name=\"country\"/>" + "<fields fieldid=\"3410250678\" fieldtype=\"36\" name=\"Checkable1\"/>" + "<fields fieldid=\"509334676\" fieldtype=\"30\" name=\"address\"/>" + "<fields fieldid=\"509334676\" fieldtype=\"31\" name=\"address\"/>" + "<fields fieldid=\"509334676\" fieldtype=\"37\" name=\"address\"/>" + "<fields fieldid=\"509334676\" fieldtype=\"38\" name=\"address\"/>" + "<fields fieldid=\"509334676\" fieldtype=\"30\" name=\"address\"/>" + "<fields fieldid=\"509334676\" fieldtype=\"31\" name=\"address\"/>" + "<fields fieldid=\"509334676\" fieldtype=\"37\" name=\"address\"/>" + "<fields fieldid=\"509334676\" fieldtype=\"38\" name=\"address\"/>" + "</fieldassignments>", + 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()); + + ServerFieldTypeSet 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 + ServerFieldTypeSet 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 + ServerFieldTypeSet 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<ServerFieldTypeSet> 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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(ServerFieldTypeSet()); + 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); + + // Password fields shouldn't affect the signature. + field.label = ASCIIToUTF16("Password"); + field.name = ASCIIToUTF16("password"); + field.form_control_type = "password"; + 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()); + + field.label = ASCIIToUTF16("Random Field label"); + field.name = ASCIIToUTF16("random1234"); + field.form_control_type = "text"; + form.fields.push_back(field); + field.label = ASCIIToUTF16("Random Field label2"); + field.name = ASCIIToUTF16("random12345"); + form.fields.push_back(field); + field.label = ASCIIToUTF16("Random Field label3"); + field.name = ASCIIToUTF16("1random12345678"); + form.fields.push_back(field); + field.label = ASCIIToUTF16("Random Field label3"); + field.name = ASCIIToUTF16("12345random"); + form.fields.push_back(field); + form_structure.reset(new FormStructure(form, std::string())); + EXPECT_EQ(FormStructureTest::Hash64Bit( + std::string("https://login.facebook.com&login_form&email&first&" + "random1234&random&1random&random")), + 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 = base::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()); +} + +TEST(FormStructureTest, SkipFieldTest) { + 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 = base::string16(); + field.name = ASCIIToUTF16("email"); + field.form_control_type = "text"; + form.fields.push_back(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 kSignature = "18006745212084723782"; + const char * const kResponse = + "<\?xml version=\"1.0\" encoding=\"UTF-8\"?><autofillquery " + "clientversion=\"6.1.1715.1442/en (GGLL)\" accepts=\"e\"><form " + "signature=\"18006745212084723782\"><field signature=\"239111655\"/>" + "<field signature=\"420638584\"/></form></autofillquery>"; + ASSERT_TRUE(FormStructure::EncodeQueryRequest(forms.get(), + &encoded_signatures, + &encoded_xml)); + ASSERT_EQ(1U, encoded_signatures.size()); + EXPECT_EQ(kSignature, encoded_signatures[0]); + EXPECT_EQ(kResponse, encoded_xml); + + AutocheckoutPageMetaData page_meta_data; + const char * const kServerResponse = + "<autofillqueryresponse><field autofilltype=\"3\" />" + "<field autofilltype=\"9\" /></autofillqueryresponse>"; + FormStructure::ParseQueryResponse(kServerResponse, forms.get(), + &page_meta_data, TestAutofillMetrics()); + ASSERT_EQ(NAME_FIRST, forms[0]->field(0)->server_type()); + ASSERT_EQ(NO_SERVER_DATA, forms[0]->field(1)->server_type()); + ASSERT_EQ(EMAIL_ADDRESS, forms[0]->field(2)->server_type()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/name_field.cc b/chromium/components/autofill/core/browser/name_field.cc new file mode 100644 index 00000000000..de74ba1a2d6 --- /dev/null +++ b/chromium/components/autofill/core/browser/name_field.cc @@ -0,0 +1,217 @@ +// 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/core/browser/name_field.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_regex_constants.h" +#include "components/autofill/core/browser/autofill_scanner.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "ui/base/l10n/l10n_util.h" + +namespace autofill { +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(ServerFieldTypeMap* 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(ServerFieldTypeMap* 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(ServerFieldTypeMap* 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(ServerFieldTypeMap* 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(ServerFieldTypeMap* map) const { + bool ok = AddClassification(first_name_, NAME_FIRST, map); + ok = ok && AddClassification(last_name_, NAME_LAST, map); + ServerFieldType type = middle_initial_ ? NAME_MIDDLE_INITIAL : NAME_MIDDLE; + ok = ok && AddClassification(middle_name_, type, map); + return ok; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/name_field.h b/chromium/components/autofill/core/browser/name_field.h new file mode 100644 index 00000000000..e15e7e3d5f7 --- /dev/null +++ b/chromium/components/autofill/core/browser/name_field.h @@ -0,0 +1,46 @@ +// 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_CORE_BROWSER_NAME_FIELD_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_NAME_FIELD_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/form_field.h" + +namespace autofill { + +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(ServerFieldTypeMap* 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); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_NAME_FIELD_H_ diff --git a/chromium/components/autofill/core/browser/name_field_unittest.cc b/chromium/components/autofill/core/browser/name_field_unittest.cc new file mode 100644 index 00000000000..df6a29bf70b --- /dev/null +++ b/chromium/components/autofill/core/browser/name_field_unittest.cc @@ -0,0 +1,311 @@ +// 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 "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_scanner.h" +#include "components/autofill/core/browser/name_field.h" +#include "components/autofill/core/common/form_field_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +class NameFieldTest : public testing::Test { + public: + NameFieldTest() {} + + protected: + ScopedVector<const AutofillField> list_; + scoped_ptr<NameField> field_; + ServerFieldTypeMap 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 = base::string16(); + field.name = ASCIIToUTF16("firstName"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = base::string16(); + field.name = ASCIIToUTF16("middleName"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + field.label = base::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 = base::string16(); + field.name = ASCIIToUTF16("first_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = base::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 = base::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 = base::string16(); + field.name = ASCIIToUTF16("middle_name"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + field.label = base::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 = base::string16(); + field.name = ASCIIToUTF16("XXXnameXXXfirst"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name1"))); + + field.label = base::string16(); + field.name = ASCIIToUTF16("XXXnameXXXmi"); + list_.push_back(new AutofillField(field, ASCIIToUTF16("name2"))); + + field.label = base::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")]); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/password_autofill_manager.cc b/chromium/components/autofill/core/browser/password_autofill_manager.cc new file mode 100644 index 00000000000..b568f8d36ed --- /dev/null +++ b/chromium/components/autofill/core/browser/password_autofill_manager.cc @@ -0,0 +1,96 @@ +// 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/core/browser/password_autofill_manager.h" +#include "components/autofill/core/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" + +namespace autofill { + +//////////////////////////////////////////////////////////////////////////////// +// PasswordAutofillManager, public: + +PasswordAutofillManager::PasswordAutofillManager( + content::WebContents* web_contents) : web_contents_(web_contents) { +} + +PasswordAutofillManager::~PasswordAutofillManager() { +} + +bool PasswordAutofillManager::DidAcceptAutofillSuggestion( + const FormFieldData& field, + const base::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 base::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; + + // Scan additional logins for a match. + for (PasswordFormFillData::LoginCollection::const_iterator iter = + fill_data.additional_logins.begin(); + iter != fill_data.additional_logins.end(); ++iter) { + if (iter->first == current_username) + return true; + } + + for (PasswordFormFillData::UsernamesCollection::const_iterator usernames_iter + = fill_data.other_possible_usernames.begin(); + usernames_iter != fill_data.other_possible_usernames.end(); + ++usernames_iter) { + for (size_t i = 0; i < usernames_iter->second.size(); ++i) { + if (usernames_iter->second[i] == 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; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/password_autofill_manager.h b/chromium/components/autofill/core/browser/password_autofill_manager.h new file mode 100644 index 00000000000..97af87f865e --- /dev/null +++ b/chromium/components/autofill/core/browser/password_autofill_manager.h @@ -0,0 +1,75 @@ +// 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_CORE_BROWSER_PASSWORD_AUTOFILL_MANAGER_H_ +#define COMPONENTS_AUTOFILL_CORE_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/core/common/password_form_fill_data.h" + +namespace content { +class WebContents; +} // namespace content + +namespace autofill { + +// 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 base::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 base::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); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_PASSWORD_AUTOFILL_MANAGER_H_ diff --git a/chromium/components/autofill/core/browser/password_autofill_manager_unittest.cc b/chromium/components/autofill/core/browser/password_autofill_manager_unittest.cc new file mode 100644 index 00000000000..d7c5cfdd679 --- /dev/null +++ b/chromium/components/autofill/core/browser/password_autofill_manager_unittest.cc @@ -0,0 +1,78 @@ +// 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 "base/compiler_specific.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/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 + +namespace autofill { + +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. + base::string16 username1 = ASCIIToUTF16(kAliceUsername); + base::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))); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/password_generator.cc b/chromium/components/autofill/core/browser/password_generator.cc new file mode 100644 index 00000000000..6f09b6c7029 --- /dev/null +++ b/chromium/components/autofill/core/browser/password_generator.cc @@ -0,0 +1,125 @@ +// 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/core/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/chromium/components/autofill/core/browser/password_generator.h b/chromium/components/autofill/core/browser/password_generator.h new file mode 100644 index 00000000000..a57695c6d4f --- /dev/null +++ b/chromium/components/autofill/core/browser/password_generator.h @@ -0,0 +1,48 @@ +// 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_CORE_BROWSER_PASSWORD_GENERATOR_H_ +#define COMPONENTS_AUTOFILL_CORE_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_CORE_BROWSER_PASSWORD_GENERATOR_H_ diff --git a/chromium/components/autofill/core/browser/password_generator_unittest.cc b/chromium/components/autofill/core/browser/password_generator_unittest.cc new file mode 100644 index 00000000000..6d9118943c9 --- /dev/null +++ b/chromium/components/autofill/core/browser/password_generator_unittest.cc @@ -0,0 +1,58 @@ +// 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 <locale> + +#include "components/autofill/core/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/chromium/components/autofill/core/browser/personal_data_manager.cc b/chromium/components/autofill/core/browser/personal_data_manager.cc new file mode 100644 index 00000000000..9ddff255d5d --- /dev/null +++ b/chromium/components/autofill/core/browser/personal_data_manager.cc @@ -0,0 +1,1033 @@ +// 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/core/browser/personal_data_manager.h" + +#include <algorithm> +#include <functional> +#include <iterator> + +#include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/prefs/pref_service.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill-inl.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/browser/personal_data_manager_observer.h" +#include "components/autofill/core/browser/phone_number.h" +#include "components/autofill/core/browser/phone_number_i18n.h" +#include "components/autofill/core/browser/validation.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service.h" +#include "components/autofill/core/common/autofill_pref_names.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" + +using content::BrowserContext; + +namespace autofill { +namespace { + +const base::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 the fields +// required as determined by its country code. +// No verification of validity of the contents is preformed. This is an +// existence check only. +bool IsMinimumAddress(const AutofillProfile& profile, + const std::string& app_locale) { + // All countries require at least one address line. + if (profile.GetRawInfo(ADDRESS_HOME_LINE1).empty()) + return false; + std::string country_code = + UTF16ToASCII(profile.GetRawInfo(ADDRESS_HOME_COUNTRY)); + + if (country_code.empty()) + country_code = AutofillCountry::CountryCodeForLocale(app_locale); + + AutofillCountry country(country_code, app_locale); + + if (country.requires_city() && profile.GetRawInfo(ADDRESS_HOME_CITY).empty()) + return false; + + if (country.requires_state() && + profile.GetRawInfo(ADDRESS_HOME_STATE).empty()) + return false; + + if (country.requires_zip() && profile.GetRawInfo(ADDRESS_HOME_ZIP).empty()) + return false; + + return true; +} + +// Return true if the |field_type| and |value| are valid within the context +// of importing a form. +bool IsValidFieldTypeAndValue(const std::set<ServerFieldType>& types_seen, + ServerFieldType field_type, + const base::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, and for EMAIL_ADDRESS because it is + // common to see second 'confirm email address' fields on forms. + if (types_seen.count(field_type) && + field_type != PHONE_HOME_NUMBER && + field_type != EMAIL_ADDRESS) + 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 && IsValidEmailAddress(value)) + return false; + + return true; +} + +} // namespace + +PersonalDataManager::PersonalDataManager(const std::string& app_locale) + : browser_context_(NULL), + is_data_loaded_(false), + pending_profiles_query_(0), + pending_creditcards_query_(0), + app_locale_(app_locale), + metric_logger_(new AutofillMetrics), + has_logged_profile_count_(false) {} + +void PersonalDataManager::Init(BrowserContext* browser_context) { + browser_context_ = browser_context; + + if (!browser_context_->IsOffTheRecord()) + metric_logger_->LogIsAutofillEnabledAtStartup(IsAutofillEnabled()); + + scoped_refptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + + // WebDataService may not be available in tests. + if (!autofill_data.get()) + return; + + LoadProfiles(); + LoadCreditCards(); + + autofill_data->AddObserver(this); +} + +PersonalDataManager::~PersonalDataManager() { + CancelPendingQuery(&pending_profiles_query_); + CancelPendingQuery(&pending_creditcards_query_); + + if (!browser_context_) + return; + + scoped_refptr<AutofillWebDataService> autofill_data( + AutofillWebDataService::FromBrowserContext(browser_context_)); + if (autofill_data.get()) + autofill_data->RemoveObserver(this); +} + +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::AutofillMultipleChanged() { + Refresh(); +} + +void PersonalDataManager::AddObserver(PersonalDataManagerObserver* observer) { + observers_.AddObserver(observer); +} + +void PersonalDataManager::RemoveObserver( + PersonalDataManagerObserver* observer) { + observers_.RemoveObserver(observer); +} + +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); + + const std::string origin = form.source_url().spec(); + imported_profile->set_origin(origin); + local_imported_credit_card->set_origin(origin); + + // 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. + // TODO(isherman): Some types are overlapping but not equal, e.g. phone number + // parts, address parts. + std::set<ServerFieldType> types_seen; + + // We only set complete phone, so aggregate phone parts in these vars and set + // complete at the end. + PhoneNumber::PhoneCombineHelper home; + + for (size_t i = 0; i < form.field_count(); ++i) { + const AutofillField* field = form.field(i); + base::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; + + AutofillType field_type = field->Type(); + ServerFieldType server_field_type = field_type.GetStorableType(); + FieldTypeGroup group(field_type.group()); + + // There can be multiple email fields (e.g. in the case of 'confirm email' + // fields) but they must all contain the same value, else the profile is + // invalid. + if (server_field_type == EMAIL_ADDRESS) { + if (types_seen.count(server_field_type) && + imported_profile->GetRawInfo(EMAIL_ADDRESS) != value) { + imported_profile.reset(); + break; + } + } + + // If the |field_type| and |value| don't pass basic validity checks then + // abandon the import. + if (!IsValidFieldTypeAndValue(types_seen, server_field_type, value)) { + imported_profile.reset(); + local_imported_credit_card.reset(); + break; + } + + types_seen.insert(server_field_type); + + if (group == CREDIT_CARD) { + if (LowerCaseEqualsASCII(field->form_control_type, "month")) { + DCHECK_EQ(CREDIT_CARD_EXP_MONTH, server_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 (server_field_type == ADDRESS_HOME_COUNTRY && + !value.empty() && + imported_profile->GetRawInfo(ADDRESS_HOME_COUNTRY).empty()) { + imported_profile.reset(); + break; + } + } + } + + // Construct the phone number. Reject the profile if the number is invalid. + if (imported_profile.get() && !home.IsEmpty()) { + base::string16 constructed_number; + if (!home.ParseNumber(*imported_profile, app_locale_, + &constructed_number) || + !imported_profile->SetInfo(AutofillType(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, app_locale_)) + 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) { + // Make a local copy so that the data in |credit_cards_| isn't modified + // directly by the UpdateFromImportedCard() call. + CreditCard card = **iter; + if (card.UpdateFromImportedCard(*local_imported_credit_card.get(), + app_locale_)) { + merged_credit_card = true; + UpdateCreditCard(card); + 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(app_locale_)) + return; + + // Don't add an existing profile. + if (FindByGUID<AutofillProfile>(web_profiles_, profile.guid())) + return; + + scoped_refptr<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; + + AutofillProfile* existing_profile = GetProfileByGUID(profile.guid()); + if (!existing_profile) + return; + + // Don't overwrite the origin for a profile that is already stored. + if (existing_profile->Compare(profile) == 0) + return; + + if (profile.IsEmpty(app_locale_)) { + RemoveByGUID(profile.guid()); + return; + } + + scoped_refptr<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(app_locale_)) + return; + + if (FindByGUID<CreditCard>(credit_cards_, credit_card.guid())) + return; + + scoped_refptr<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; + + CreditCard* existing_credit_card = GetCreditCardByGUID(credit_card.guid()); + if (!existing_credit_card) + return; + + // Don't overwrite the origin for a credit card that is already stored. + if (existing_credit_card->Compare(credit_card) == 0) + return; + + if (credit_card.IsEmpty(app_locale_)) { + RemoveByGUID(credit_card.guid()); + return; + } + + scoped_refptr<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_refptr<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) { + const std::vector<CreditCard*>& credit_cards = GetCreditCards(); + for (std::vector<CreditCard*>::const_iterator iter = credit_cards.begin(); + iter != credit_cards.end(); ++iter) { + if ((*iter)->guid() == guid) + return *iter; + } + return NULL; +} + +void PersonalDataManager::GetNonEmptyTypes( + ServerFieldTypeSet* non_empty_types) { + 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 (!user_prefs::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::GetCreditCards() const { + return credit_cards_.get(); +} + +void PersonalDataManager::Refresh() { + LoadProfiles(); + LoadCreditCards(); +} + +void PersonalDataManager::GetProfileSuggestions( + const AutofillType& type, + const base::string16& field_contents, + bool field_is_autofilled, + std::vector<ServerFieldType> other_field_types, + std::vector<base::string16>* values, + std::vector<base::string16>* labels, + std::vector<base::string16>* icons, + std::vector<GUIDPair>* guid_pairs) { + values->clear(); + labels->clear(); + icons->clear(); + guid_pairs->clear(); + + const std::vector<AutofillProfile*>& profiles = GetProfiles(); + 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<base::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; + + base::string16 profile_value_lower_case( + StringToLowerASCII(multi_values[i])); + base::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.GetStorableType() == PHONE_HOME_NUMBER && + !field_value_lower_case.empty() && + profile_value_lower_case.find(field_value_lower_case) != + base::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.GetStorableType(), 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( + const AutofillType& type, + const base::string16& field_contents, + std::vector<base::string16>* values, + std::vector<base::string16>* labels, + std::vector<base::string16>* icons, + std::vector<GUIDPair>* guid_pairs) { + values->clear(); + labels->clear(); + icons->clear(); + guid_pairs->clear(); + + const std::vector<CreditCard*>& credit_cards = GetCreditCards(); + 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|. + base::string16 creditcard_field_value = + credit_card->GetInfo(type, app_locale_); + if (!creditcard_field_value.empty() && + StartsWith(creditcard_field_value, field_contents, false)) { + if (type.GetStorableType() == CREDIT_CARD_NUMBER) + creditcard_field_value = credit_card->ObfuscatedNumber(); + + base::string16 label; + if (credit_card->number().empty()) { + // If there is no CC number, return name to show something. + label = + credit_card->GetInfo(AutofillType(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 user_prefs::UserPrefs::Get(browser_context_)->GetBoolean( + prefs::kAutofillEnabled); +} + +// static +bool PersonalDataManager::IsValidLearnableProfile( + const AutofillProfile& profile, + const std::string& app_locale) { + if (!IsMinimumAddress(profile, app_locale)) + return false; + + base::string16 email = profile.GetRawInfo(EMAIL_ADDRESS); + if (!email.empty() && !IsValidEmailAddress(email)) + return false; + + // Reject profiles with invalid US state information. + if (profile.IsPresentButInvalid(ADDRESS_HOME_STATE)) + return false; + + // Reject profiles with invalid US zip information. + if (profile.IsPresentButInvalid(ADDRESS_HOME_ZIP)) + return false; + + return true; +} + +// static +bool PersonalDataManager::MergeProfile( + const AutofillProfile& new_profile, + const std::vector<AutofillProfile*>& existing_profiles, + const std::string& app_locale, + std::vector<AutofillProfile>* merged_profiles) { + merged_profiles->clear(); + + // Set to true if |existing_profiles| already contains an equivalent profile. + bool matching_profile_found = 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) { + AutofillProfile* existing_profile = *iter; + if (!matching_profile_found && + !new_profile.PrimaryValue().empty() && + StringToLowerASCII(existing_profile->PrimaryValue()) == + StringToLowerASCII(new_profile.PrimaryValue())) { + // Unverified profiles should always be updated with the newer data, + // whereas verified profiles should only ever be overwritten by verified + // data. If an automatically aggregated profile would overwrite a + // verified profile, just drop it. + matching_profile_found = true; + if (!existing_profile->IsVerified() || new_profile.IsVerified()) + existing_profile->OverwriteWithOrAddTo(new_profile, app_locale); + } + merged_profiles->push_back(*existing_profile); + } + + // If the new profile was not merged with an existing one, add it to the list. + if (!matching_profile_found) + merged_profiles->push_back(new_profile); + + return matching_profile_found; +} + +void PersonalDataManager::SetProfiles(std::vector<AutofillProfile>* profiles) { + if (browser_context_->IsOffTheRecord()) + return; + + // Remove empty profiles from input. + for (std::vector<AutofillProfile>::iterator it = profiles->begin(); + it != profiles->end();) { + if (it->IsEmpty(app_locale_)) + profiles->erase(it); + else + it++; + } + + // 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_refptr<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. + for (std::vector<CreditCard>::iterator it = credit_cards->begin(); + it != credit_cards->end();) { + if (it->IsEmpty(app_locale_)) + credit_cards->erase(it); + else + it++; + } + + scoped_refptr<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_refptr<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 and Android implementations +// fill in the contents of |auxiliary_profiles_|. +#if !defined(OS_MACOSX) && !defined(OS_ANDROID) +void PersonalDataManager::LoadAuxiliaryProfiles() { +} +#endif + +void PersonalDataManager::LoadCreditCards() { + scoped_refptr<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_refptr<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()) + 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, app_locale_)) + return; + } + + std::vector<AutofillProfile> profiles; + MergeProfile(imported_profile, web_profiles_.get(), app_locale_, &profiles); + SetProfiles(&profiles); +} + + +void PersonalDataManager::SaveImportedCreditCard( + const CreditCard& imported_card) { + DCHECK(!imported_card.number().empty()); + if (browser_context_->IsOffTheRecord()) + return; + + // Set to true if |imported_card| is merged into the credit card list. + bool merged = false; + + 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; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/personal_data_manager.h b/chromium/components/autofill/core/browser/personal_data_manager.h new file mode 100644 index 00000000000..9b71aa55c58 --- /dev/null +++ b/chromium/components/autofill/core/browser/personal_data_manager.h @@ -0,0 +1,293 @@ +// 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_CORE_BROWSER_PERSONAL_DATA_MANAGER_H_ +#define COMPONENTS_AUTOFILL_CORE_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/strings/string16.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service_observer.h" +#include "components/webdata/common/web_data_service_consumer.h" + +class RemoveAutofillTester; + +namespace content { +class BrowserContext; +} + +namespace autofill { +class AutofillInteractiveTest; +class AutofillMetrics; +class AutofillTest; +class FormStructure; +class PersonalDataManagerObserver; +class PersonalDataManagerFactory; +} // namespace autofill + +namespace autofill_helper { +void SetProfiles(int, std::vector<autofill::AutofillProfile>*); +void SetCreditCards(int, std::vector<autofill::CreditCard>*); +} // namespace autofill_helper + +namespace autofill { + +// 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 AutofillWebDataServiceObserverOnUIThread { + 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; + + explicit PersonalDataManager(const std::string& app_locale); + 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; + + // AutofillWebDataServiceObserverOnUIThread: + virtual void AutofillMultipleChanged() 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); + + // 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(ServerFieldTypeSet* 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. |GetProfiles()| 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*>& GetCreditCards() 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( + const AutofillType& type, + const base::string16& field_contents, + bool field_is_autofilled, + std::vector<ServerFieldType> other_field_types, + std::vector<base::string16>* values, + std::vector<base::string16>* labels, + std::vector<base::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( + const AutofillType& type, + const base::string16& field_contents, + std::vector<base::string16>* values, + std::vector<base::string16>* labels, + std::vector<base::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(); + + const std::string& app_locale() const { return app_locale_; } + + // Checks suitability of |profile| for adding to the user's set of profiles. + static bool IsValidLearnableProfile(const AutofillProfile& profile, + const std::string& app_locale); + + // Merges |new_profile| into one of the |existing_profiles| if possible; + // otherwise appends |new_profile| to the end of that list. Fills + // |merged_profiles| with the result. + static bool MergeProfile( + const AutofillProfile& new_profile, + const std::vector<AutofillProfile*>& existing_profiles, + const std::string& app_locale, + 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 autofill::AutofillInteractiveTest; + friend class autofill::AutofillTest; + friend class autofill::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<autofill::AutofillProfile>*); + friend void autofill_helper::SetCreditCards( + int, std::vector<autofill::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 and Android 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: + std::string app_locale_; + + // 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_; + + DISALLOW_COPY_AND_ASSIGN(PersonalDataManager); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_PERSONAL_DATA_MANAGER_H_ diff --git a/chromium/components/autofill/core/browser/personal_data_manager_mac.mm b/chromium/components/autofill/core/browser/personal_data_manager_mac.mm new file mode 100644 index 00000000000..409fdc11da3 --- /dev/null +++ b/chromium/components/autofill/core/browser/personal_data_manager_mac.mm @@ -0,0 +1,273 @@ +// 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/core/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/strings/stringprintf.h" +#include "base/strings/sys_string_conversions.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/phone_number.h" +#include "grit/component_strings.h" +#include "ui/base/l10n/l10n_util_mac.h" + +namespace autofill { +namespace { + +const char kAddressBookOrigin[] = "OS X Address Book"; + +// 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(const std::string& app_locale); + + private: + void GetAddressBookNames(ABPerson* me, + NSString* addressLabelRaw, + AutofillProfile* profile); + void GetAddressBookAddress(const std::string& app_locale, + 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( + const std::string& app_locale) { + 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, kAddressBookOrigin)); + DCHECK(base::IsValidGUID(profile->guid())); + + // Fill in name and company information. + GetAddressBookNames(me, addressLabelRaw, profile.get()); + + // Fill in address information. + GetAddressBookAddress(app_locale, 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(const std::string& app_locale, + 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(AutofillType(ADDRESS_HOME_COUNTRY), + base::SysNSStringToUTF16(country), + app_locale); + } +} + +// 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]) { + base::string16 homePhone = base::SysNSStringToUTF16( + [phoneNumbers valueAtIndex:reverseK]); + profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, homePhone); + } else if ([addressLabelRaw isEqualToString:kABAddressWorkLabel] && + [phoneLabelRaw isEqualToString:kABPhoneWorkLabel]) { + base::string16 workPhone = base::SysNSStringToUTF16( + [phoneNumbers valueAtIndex:reverseK]); + profile->SetRawInfo(PHONE_HOME_WHOLE_NUMBER, workPhone); + } else if ([phoneLabelRaw isEqualToString:kABPhoneMobileLabel] || + [phoneLabelRaw isEqualToString:kABPhoneMainLabel]) { + base::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(app_locale_); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/personal_data_manager_observer.h b/chromium/components/autofill/core/browser/personal_data_manager_observer.h new file mode 100644 index 00000000000..d4a24cabb1c --- /dev/null +++ b/chromium/components/autofill/core/browser/personal_data_manager_observer.h @@ -0,0 +1,25 @@ +// 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_CORE_BROWSER_PERSONAL_DATA_MANAGER_OBSERVER_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_PERSONAL_DATA_MANAGER_OBSERVER_H_ + +namespace autofill { + +// 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() {} +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_PERSONAL_DATA_MANAGER_OBSERVER_H_ diff --git a/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc b/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc new file mode 100644 index 00000000000..7c08e5f8983 --- /dev/null +++ b/chromium/components/autofill/core/browser/personal_data_manager_unittest.cc @@ -0,0 +1,2418 @@ +// 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 <string> + +#include "base/basictypes.h" +#include "base/guid.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/waitable_event.h" +#include "chrome/test/base/testing_profile.h" +#include "components/autofill/core/browser/autofill_common_test.h" +#include "components/autofill/core/browser/autofill_metrics.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/form_structure.h" +#include "components/autofill/core/browser/personal_data_manager.h" +#include "components/autofill/core/browser/personal_data_manager_observer.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service.h" +#include "components/autofill/core/common/form_data.h" +#include "components/webdata/encryptor/encryptor.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 autofill { +namespace { + +ACTION(QuitUIMessageLoop) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + base::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_->CreateWebDataService(); + + test::DisableSystemServices(profile_.get()); + ResetPersonalDataManager(); + } + + virtual void TearDown() { + // Destruction order is imposed explicitly here. + personal_data_.reset(NULL); + profile_.reset(NULL); + + // Schedule another task on the DB thread to notify us that it's safe to + // stop the thread. + base::WaitableEvent done(false, false); + BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, + base::Bind(&base::WaitableEvent::Signal, base::Unretained(&done))); + done.Wait(); + base::MessageLoop::current()->PostTask(FROM_HERE, + base::MessageLoop::QuitClosure()); + base::MessageLoop::current()->Run(); + db_thread_.Stop(); + } + + void ResetPersonalDataManager() { + personal_data_.reset(new PersonalDataManager("en-US")); + 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()); + base::MessageLoop::current()->Run(); + } + + void MakeProfileIncognito() { + profile_->set_incognito(true); + } + + base::MessageLoopForUI message_loop_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread db_thread_; + scoped_ptr<TestingProfile> profile_; + scoped_ptr<PersonalDataManager> personal_data_; + PersonalDataLoadedObserverMock personal_data_observer_; +}; + +TEST_F(PersonalDataManagerTest, AddProfile) { + // Add profile0 to the database. + AutofillProfile profile0(autofill::test::GetFullProfile()); + profile0.SetRawInfo(EMAIL_ADDRESS, ASCIIToUTF16("j@s.com")); + 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(base::GenerateGUID(), "https://www.example.com"); + test::SetProfileInfo(&profile0, + "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910"); + + AutofillProfile profile1(base::GenerateGUID(), "https://www.example.com"); + test::SetProfileInfo(&profile1, + "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "903 Apple Ct.", NULL, "Orlando", "FL", "32801", + "US", "19482937549"); + + AutofillProfile profile2(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::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()); + base::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(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&credit_card0, + "John Dillinger", "423456789012" /* Visa */, "01", "2010"); + + CreditCard credit_card1(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&credit_card1, + "Bonnie Parker", "518765432109" /* Mastercard */, "12", "2012"); + + CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::MessageLoop::current()->Run(); + + const std::vector<CreditCard*>& results1 = personal_data_->GetCreditCards(); + 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()); + base::MessageLoop::current()->Run(); + + const std::vector<CreditCard*>& results2 = personal_data_->GetCreditCards(); + 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_->GetCreditCards(); + ASSERT_EQ(2U, results3.size()); + EXPECT_EQ(credit_card0, *results3[0]); + EXPECT_EQ(credit_card2, *results3[1]); +} + +TEST_F(PersonalDataManagerTest, UpdateUnverifiedProfilesAndCreditCards) { + // Start with unverified data. + AutofillProfile profile(base::GenerateGUID(), "https://www.example.com/"); + test::SetProfileInfo(&profile, + "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910"); + EXPECT_FALSE(profile.IsVerified()); + + CreditCard credit_card(base::GenerateGUID(), "https://www.example.com/"); + test::SetCreditCardInfo(&credit_card, + "John Dillinger", "423456789012" /* Visa */, "01", "2010"); + EXPECT_FALSE(credit_card.IsVerified()); + + // Add the data to the database. + personal_data_->AddProfile(profile); + 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()); + base::MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& profiles1 = + personal_data_->GetProfiles(); + const std::vector<CreditCard*>& cards1 = personal_data_->GetCreditCards(); + ASSERT_EQ(1U, profiles1.size()); + ASSERT_EQ(1U, cards1.size()); + EXPECT_EQ(0, profile.Compare(*profiles1[0])); + EXPECT_EQ(0, credit_card.Compare(*cards1[0])); + + // Try to update with just the origin changed. + AutofillProfile original_profile(profile); + CreditCard original_credit_card(credit_card); + profile.set_origin("Chrome settings"); + credit_card.set_origin("Chrome settings"); + + EXPECT_TRUE(profile.IsVerified()); + EXPECT_TRUE(credit_card.IsVerified()); + + personal_data_->UpdateProfile(profile); + personal_data_->UpdateCreditCard(credit_card); + + // Note: No refresh, as no update is expected. + + const std::vector<AutofillProfile*>& profiles2 = + personal_data_->GetProfiles(); + const std::vector<CreditCard*>& cards2 = personal_data_->GetCreditCards(); + ASSERT_EQ(1U, profiles2.size()); + ASSERT_EQ(1U, cards2.size()); + EXPECT_NE(profile.origin(), profiles2[0]->origin()); + EXPECT_NE(credit_card.origin(), cards2[0]->origin()); + EXPECT_EQ(original_profile.origin(), profiles2[0]->origin()); + EXPECT_EQ(original_credit_card.origin(), cards2[0]->origin()); + + // Try to update with data changed as well. + profile.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John")); + credit_card.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Joe")); + + personal_data_->UpdateProfile(profile); + personal_data_->UpdateCreditCard(credit_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + base::MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& profiles3 = + personal_data_->GetProfiles(); + const std::vector<CreditCard*>& cards3 = personal_data_->GetCreditCards(); + ASSERT_EQ(1U, profiles3.size()); + ASSERT_EQ(1U, cards3.size()); + EXPECT_EQ(0, profile.Compare(*profiles3[0])); + EXPECT_EQ(0, credit_card.Compare(*cards3[0])); + EXPECT_EQ(profile.origin(), profiles3[0]->origin()); + EXPECT_EQ(credit_card.origin(), cards3[0]->origin()); +} + +TEST_F(PersonalDataManagerTest, AddProfilesAndCreditCards) { + AutofillProfile profile0(base::GenerateGUID(), "https://www.example.com"); + test::SetProfileInfo(&profile0, + "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910"); + + AutofillProfile profile1(base::GenerateGUID(), "https://www.example.com"); + test::SetProfileInfo(&profile1, + "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "903 Apple Ct.", NULL, "Orlando", "FL", "32801", + "US", "19482937549"); + + CreditCard credit_card0(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&credit_card0, + "John Dillinger", "423456789012" /* Visa */, "01", "2010"); + + CreditCard credit_card1(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::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()); + base::MessageLoop::current()->Run(); + + const std::vector<CreditCard*>& results2 = personal_data_->GetCreditCards(); + 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(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::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(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::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(base::GenerateGUID(), "https://www.example.com"); + 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(base::GenerateGUID(), "https://www.example.com"); + 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_->GetCreditCards(); + ASSERT_EQ(0U, results2.size()); +} + +TEST_F(PersonalDataManagerTest, Refresh) { + AutofillProfile profile0(base::GenerateGUID(), "https://www.example.com"); + test::SetProfileInfo(&profile0, + "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910"); + + AutofillProfile profile1(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::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(base::GenerateGUID(), "https://www.example.com"); + 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<AutofillWebDataService> wds = + AutofillWebDataService::FromBrowserContext(profile_.get()); + 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()); + base::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 deleted + // 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). + profile0.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Mar")); + 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()); + base::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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + AutofillProfile expected(base::GenerateGUID(), "https://www.example.com"); + 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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Email:", "email", "bogus", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form.fields.push_back(field); + 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()); +} + +// Tests that a 'confirm email' field does not block profile import. +TEST_F(PersonalDataManagerTest, ImportFormDataTwoEmails) { + FormData form; + FormFieldData field; + test::CreateTestFormField( + "Name:", "name", "George Washington", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "example@example.com", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Confirm email:", "confirm_email", "example@example.com", "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)); + const std::vector<AutofillProfile*>& results = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results.size()); +} + +// Tests two email fields containing different values blocks provile import. +TEST_F(PersonalDataManagerTest, ImportFormDataTwoDifferentEmails) { + FormData form; + FormFieldData field; + test::CreateTestFormField( + "Name:", "name", "George Washington", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "example@example.com", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email2", "example2@example.com", "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)); + const std::vector<AutofillProfile*>& results = personal_data_->GetProfiles(); + ASSERT_EQ(0U, results.size()); +} + +TEST_F(PersonalDataManagerTest, ImportFormDataNotEnoughFilledFields) { + FormData form; + FormFieldData field; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form.fields.push_back(field); + 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*>& cards = personal_data_->GetCreditCards(); + ASSERT_EQ(0U, cards.size()); +} + +TEST_F(PersonalDataManagerTest, ImportFormMinimumAddressUSA) { + // United States addresses must specifiy one address line, a city, state and + // zip code. + FormData form; + FormFieldData field; + test::CreateTestFormField("Name:", "name", "Barack Obama", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address", "1600 Pennsylvania Avenue", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("City:", "city", "Washington", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("State:", "state", "DC", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Zip:", "zip", "20500", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Country:", "country", "USA", "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)); + const std::vector<AutofillProfile*>& profiles = personal_data_->GetProfiles(); + ASSERT_EQ(1U, profiles.size()); +} + +TEST_F(PersonalDataManagerTest, ImportFormMinimumAddressGB) { + // British addresses do not require a state/province as the county is usually + // not requested on forms. + FormData form; + FormFieldData field; + test::CreateTestFormField("Name:", "name", "David Cameron", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address", "10 Downing Street", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("City:", "city", "London", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Postcode:", "postcode", "SW1A 2AA", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Country:", "country", "United Kingdom", "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)); + const std::vector<AutofillProfile*>& profiles = personal_data_->GetProfiles(); + ASSERT_EQ(1U, profiles.size()); +} + +TEST_F(PersonalDataManagerTest, ImportFormMinimumAddressGI) { + // Gibraltar has the most minimal set of requirements for a valid address. + // There are no cities or provinces and no postal/zip code system. + FormData form; + FormFieldData field; + test::CreateTestFormField( + "Name:", "name", "Sir Adrian Johns", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address", "The Convent, Main Street", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Country:", "country", "Gibraltar", "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)); + const std::vector<AutofillProfile*>& profiles = personal_data_->GetProfiles(); + ASSERT_EQ(1U, profiles.size()); +} + +TEST_F(PersonalDataManagerTest, ImportPhoneNumberSplitAcrossMultipleFields) { + FormData form; + FormFieldData field; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Phone #:", "home_phone_area_code", "650", "text", &field); + field.max_length = 3; + form.fields.push_back(field); + test::CreateTestFormField( + "Phone #:", "home_phone_prefix", "555", "text", &field); + field.max_length = 3; + form.fields.push_back(field); + test::CreateTestFormField( + "Phone #:", "home_phone_suffix", "0000", "text", &field); + field.max_length = 4; + form.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + AutofillProfile expected(base::GenerateGUID(), "https://www.example.com"); + 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(base::GenerateGUID(), "https://www.example.com"); + credit_card0.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("John")); + CreditCard credit_card1(base::GenerateGUID(), "https://www.example.com"); + credit_card1.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Paul")); + CreditCard credit_card2(base::GenerateGUID(), "https://www.example.com"); + credit_card2.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Ringo")); + CreditCard credit_card3(base::GenerateGUID(), "https://www.example.com"); + credit_card3.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Other")); + CreditCard credit_card4(base::GenerateGUID(), "https://www.example.com"); + credit_card4.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Ozzy")); + CreditCard credit_card5(base::GenerateGUID(), "https://www.example.com"); + 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_->GetCreditCards(); + 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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + AutofillProfile expected(base::GenerateGUID(), "https://www.example.com"); + 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; + test::CreateTestFormField( + "First name:", "first_name", "John", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Adams", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "second@gmail.com", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "22 Laussat St", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form2.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + AutofillProfile expected2(base::GenerateGUID(), "https://www.example.com"); + 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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + AutofillProfile expected(base::GenerateGUID(), "https://www.example.com"); + 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; + test::CreateTestFormField( + "First name:", "first_name", "John", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("Last name:", "last_name", "Adams", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "second@gmail.com", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form2.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + // Modify expected to include multi-valued fields. + std::vector<base::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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address", "1600 Pennsylvania Avenue", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Address Line 2:", "address2", "Suite A", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + AutofillProfile expected(base::GenerateGUID(), "https://www.example.com"); + 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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address", "1600 Pennsylvania Avenue", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Address Line 2:", "address2", "Suite A", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form2.fields.push_back(field); + // Country gets added. + test::CreateTestFormField("Country:", "country", "USA", "text", &field); + form2.fields.push_back(field); + // Phone gets updated. + 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()); + base::MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + // Add multi-valued phone number to expectation. Also, country gets added. + std::vector<base::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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Address Line 1:", "address", "190 High Street", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("City:", "city", "Philadelphia", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("State:", "state", "Pennsylvania", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + AutofillProfile expected(base::GenerateGUID(), "https://www.example.com"); + 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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Address Line 1:", "address", "190 High Street", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("City:", "city", "Philadelphia", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("State:", "state", "Pennsylvania", "text", &field); + form2.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + AutofillProfile expected2(base::GenerateGUID(), "https://www.example.com"); + 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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Company:", "company", "Government", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Address Line 1:", "address", "190 High Street", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("City:", "city", "Philadelphia", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("State:", "state", "Pennsylvania", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + AutofillProfile expected(base::GenerateGUID(), "https://www.example.com"); + 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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form2.fields.push_back(field); + // Note missing Company field. + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Address Line 1:", "address", "190 High Street", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("City:", "city", "Philadelphia", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("State:", "state", "Pennsylvania", "text", &field); + form2.fields.push_back(field); + 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()); + base::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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Company:", "company", "Government", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Address Line 1:", "address", "190 High Street", "text", &field); + form1.fields.push_back(field); + 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); + + // Since no refresh is expected, reload the data from the database to make + // sure no changes were written out. + ResetPersonalDataManager(); + + const std::vector<AutofillProfile*>& profiles = personal_data_->GetProfiles(); + ASSERT_EQ(0U, profiles.size()); + const std::vector<CreditCard*>& cards = personal_data_->GetCreditCards(); + ASSERT_EQ(0U, 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(base::GenerateGUID(), "https://www.example.com"); + 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; + test::CreateTestFormField( + "First name:", "first_name", "Tester", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "McAddressBookTesterson", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "tester@example.com", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Address:", "address1", "1 Main", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("State:", "state", "CA", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); + form.fields.push_back(field); + 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; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + CreditCard expected(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results = personal_data_->GetCreditCards(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); + + // Add a second different valid credit card. + FormData form2; + test::CreateTestFormField( + "Name on card:", "name_on_card", "", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "5500 0000 0000 0004", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "02", "text", &field); + form2.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + CreditCard expected2(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected2,"", "5500000000000004", "02", "2012"); + const std::vector<CreditCard*>& results2 = personal_data_->GetCreditCards(); + 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; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + CreditCard expected(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results = personal_data_->GetCreditCards(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); + + // Add a second different invalid credit card. + FormData form2; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Jim Johansen", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "1000000000000000", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "02", "text", &field); + form2.fields.push_back(field); + 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); + + // Since no refresh is expected, reload the data from the database to make + // sure no changes were written out. + ResetPersonalDataManager(); + + const std::vector<CreditCard*>& results2 = personal_data_->GetCreditCards(); + 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; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + CreditCard expected(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results = personal_data_->GetCreditCards(); + 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; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "4111 1111 1111 1111", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form2.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + // Expect that the newer information is saved. In this case the year is + // updated to "2012". + CreditCard expected2(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected2, + "Biggie Smalls", "4111111111111111", "01", "2012"); + const std::vector<CreditCard*>& results2 = personal_data_->GetCreditCards(); + 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; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + CreditCard expected(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results = personal_data_->GetCreditCards(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, expected.Compare(*results[0])); + + // Add a second credit card with no number. + FormData form2; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form2.fields.push_back(field); + 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); + + // Since no refresh is expected, reload the data from the database to make + // sure no changes were written out. + ResetPersonalDataManager(); + + // No change is expected. + CreditCard expected2(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected2, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results2 = personal_data_->GetCreditCards(); + 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; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + CreditCard expected(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results = personal_data_->GetCreditCards(); + 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. + test::CreateTestFormField( + "Card Number:", "card_number", "4111111111111111", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form2.fields.push_back(field); + 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); + + // Since no refresh is expected, reload the data from the database to make + // sure no changes were written out. + ResetPersonalDataManager(); + + // No change is expected. + CreditCard expected2(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected2, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results2 = personal_data_->GetCreditCards(); + 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; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Johnny McEnroe", "text", &field); + form3.fields.push_back(field); + 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); + + // Since no refresh is expected, reload the data from the database to make + // sure no changes were written out. + ResetPersonalDataManager(); + + // No change is expected. + CreditCard expected3(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected3, + "Biggie Smalls", "4111111111111111", "01", "2011"); + const std::vector<CreditCard*>& results3 = personal_data_->GetCreditCards(); + ASSERT_EQ(1U, results3.size()); + EXPECT_EQ(0, expected3.Compare(*results3[0])); +} + +TEST_F(PersonalDataManagerTest, AggregateCreditCardWithMissingInfoInOld) { + // Start with a single valid credit card stored via the preferences. + // Note the empty name. + CreditCard saved_credit_card(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::MessageLoop::current()->Run(); + + const std::vector<CreditCard*>& results1 = personal_data_->GetCreditCards(); + 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; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + // Expect that the newer information is saved. In this case the year is + // added to the existing credit card. + CreditCard expected2(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo(&expected2, + "Biggie Smalls", "4111111111111111", "01", "2012"); + const std::vector<CreditCard*>& results2 = personal_data_->GetCreditCards(); + 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(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::MessageLoop::current()->Run(); + + const std::vector<CreditCard*>& results1 = personal_data_->GetCreditCards(); + 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; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "4111-1111-1111-1111", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form.fields.push_back(field); + 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); + + // Since no refresh is expected, reload the data from the database to make + // sure no changes were written out. + ResetPersonalDataManager(); + + // Expect that no new card is saved. + const std::vector<CreditCard*>& results2 = personal_data_->GetCreditCards(); + ASSERT_EQ(1U, results2.size()); + EXPECT_EQ(0, saved_credit_card.Compare(*results2[0])); +} + +// Ensure that if a verified profile already exists, aggregated profiles cannot +// modify it in any way. +TEST_F(PersonalDataManagerTest, AggregateExistingVerifiedProfileWithConflict) { + // Start with a verified profile. + AutofillProfile profile(base::GenerateGUID(), "Chrome settings"); + test::SetProfileInfo(&profile, + "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910"); + EXPECT_TRUE(profile.IsVerified()); + + // Add the profile to the database. + personal_data_->AddProfile(profile); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + base::MessageLoop::current()->Run(); + + // Simulate a form submission with conflicting info. + FormData form; + FormFieldData field; + test::CreateTestFormField( + "First name:", "first_name", "Marion", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Morrison", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "other.email@example.com", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "123 Zoo St.", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("City:", "city", "Hollywood", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("State:", "state", "CA", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Zip:", "zip", "91601", "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()); + base::MessageLoop::current()->Run(); + + // Expect that no new profile is saved. + const std::vector<AutofillProfile*>& results = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, profile.Compare(*results[0])); +} + +// Ensure that if a verified credit card already exists, aggregated credit cards +// cannot modify it in any way. +TEST_F(PersonalDataManagerTest, + AggregateExistingVerifiedCreditCardWithConflict) { + // Start with a verified credit card. + CreditCard credit_card(base::GenerateGUID(), "Chrome settings"); + test::SetCreditCardInfo(&credit_card, + "Biggie Smalls", "4111 1111 1111 1111" /* Visa */, "01", "2011"); + EXPECT_TRUE(credit_card.IsVerified()); + + // Add the credit card to the database. + 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()); + base::MessageLoop::current()->Run(); + + // Simulate a form submission with conflicting expiration year. + FormData form; + FormFieldData field; + test::CreateTestFormField( + "Name on card:", "name_on_card", "Biggie Smalls", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField( + "Card Number:", "card_number", "4111 1111 1111 1111", "text", &field); + form.fields.push_back(field); + test::CreateTestFormField("Exp Month:", "exp_month", "01", "text", &field); + form.fields.push_back(field); + 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)); + ASSERT_FALSE(imported_credit_card); + + // Since no refresh is expected, reload the data from the database to make + // sure no changes were written out. + ResetPersonalDataManager(); + + // Expect that the saved credit card is not modified. + const std::vector<CreditCard*>& results = personal_data_->GetCreditCards(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, credit_card.Compare(*results[0])); +} + +// Ensure that verified profiles can be saved via SaveImportedProfile, +// overwriting existing unverified profiles. +TEST_F(PersonalDataManagerTest, SaveImportedProfileWithVerifiedData) { + // Start with an unverified profile. + AutofillProfile profile(base::GenerateGUID(), "https://www.example.com"); + test::SetProfileInfo(&profile, + "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910"); + EXPECT_FALSE(profile.IsVerified()); + + // Add the profile to the database. + personal_data_->AddProfile(profile); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + base::MessageLoop::current()->Run(); + + AutofillProfile new_verified_profile = profile; + new_verified_profile.set_guid(base::GenerateGUID()); + new_verified_profile.set_origin("Chrome settings"); + new_verified_profile.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Fizzbang, Inc.")); + EXPECT_TRUE(new_verified_profile.IsVerified()); + + personal_data_->SaveImportedProfile(new_verified_profile); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + base::MessageLoop::current()->Run(); + + // Expect that the existing profile is not modified, and instead the new + // profile is added. + const std::vector<AutofillProfile*>& results = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(0, new_verified_profile.Compare(*results[0])); +} + +// Ensure that verified profiles can be saved via SaveImportedProfile, +// overwriting existing verified profiles as well. +TEST_F(PersonalDataManagerTest, SaveImportedProfileWithExistingVerifiedData) { + // Start with a verified profile. + AutofillProfile profile(base::GenerateGUID(), "Chrome settings"); + test::SetProfileInfo(&profile, + "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910"); + EXPECT_TRUE(profile.IsVerified()); + + // Add the profile to the database. + personal_data_->AddProfile(profile); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + base::MessageLoop::current()->Run(); + + AutofillProfile new_verified_profile = profile; + new_verified_profile.set_guid(base::GenerateGUID()); + new_verified_profile.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Fizzbang, Inc.")); + new_verified_profile.SetRawInfo(NAME_MIDDLE, base::string16()); + EXPECT_TRUE(new_verified_profile.IsVerified()); + + personal_data_->SaveImportedProfile(new_verified_profile); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + base::MessageLoop::current()->Run(); + + // The new profile should be merged into the existing one. + AutofillProfile expected_profile = new_verified_profile; + expected_profile.set_guid(profile.guid()); + std::vector<base::string16> names; + expected_profile.GetRawMultiInfo(NAME_FULL, &names); + names.insert(names.begin(), ASCIIToUTF16("Marion Mitchell Morrison")); + expected_profile.SetRawMultiInfo(NAME_FULL, names); + + const std::vector<AutofillProfile*>& results = personal_data_->GetProfiles(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(expected_profile, *results[0]); +} + +// Ensure that verified credit cards can be saved via SaveImportedCreditCard. +TEST_F(PersonalDataManagerTest, SaveImportedCreditCardWithVerifiedData) { + // Start with a verified credit card. + CreditCard credit_card(base::GenerateGUID(), "Chrome settings"); + test::SetCreditCardInfo(&credit_card, + "Biggie Smalls", "4111 1111 1111 1111" /* Visa */, "01", "2011"); + EXPECT_TRUE(credit_card.IsVerified()); + + // Add the credit card to the database. + 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()); + base::MessageLoop::current()->Run(); + + CreditCard new_verified_card = credit_card; + new_verified_card.set_guid(base::GenerateGUID()); + new_verified_card.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("B. Small")); + EXPECT_TRUE(new_verified_card.IsVerified()); + + personal_data_->SaveImportedCreditCard(new_verified_card); + + // Verify that the web database has been updated and the notification sent. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataChanged()).WillOnce(QuitUIMessageLoop()); + base::MessageLoop::current()->Run(); + + // Expect that the saved credit card is updated. + const std::vector<CreditCard*>& results = personal_data_->GetCreditCards(); + ASSERT_EQ(1U, results.size()); + EXPECT_EQ(ASCIIToUTF16("B. Small"), results[0]->GetRawInfo(CREDIT_CARD_NAME)); +} + +TEST_F(PersonalDataManagerTest, GetNonEmptyTypes) { + // Check that there are no available types with no profiles stored. + ServerFieldTypeSet non_empty_types; + personal_data_->GetNonEmptyTypes(&non_empty_types); + EXPECT_EQ(0U, non_empty_types.size()); + + // Test with one profile stored. + AutofillProfile profile0(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::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(base::GenerateGUID(), "https://www.example.com"); + test::SetProfileInfo(&profile1, + "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "903 Apple Ct.", NULL, "Orlando", "FL", "32801", + "US", "16502937549"); + + AutofillProfile profile2(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::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(base::GenerateGUID(), "https://www.example.com"); + 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()); + base::MessageLoop::current()->Run(); + + personal_data_->GetNonEmptyTypes(&non_empty_types); + EXPECT_EQ(26U, 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_TYPE)); + 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; + test::CreateTestFormField( + "First name:", "first_name", "George", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "City:", "city", "San Francisco", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form1.fields.push_back(field); + test::CreateTestFormField( + "Zip:", "zip", "94102", "text", &field); + form1.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + AutofillProfile expected(base::GenerateGUID(), "https://www.example.com"); + 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; + test::CreateTestFormField( + "First name:", "first_name", "GEORGE", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Last name:", "last_name", "Washington", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Email:", "email", "theprez@gmail.com", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField( + "Address:", "address1", "21 Laussat St", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("City:", "city", "San Francisco", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("State:", "state", "California", "text", &field); + form2.fields.push_back(field); + test::CreateTestFormField("Zip:", "zip", "94102", "text", &field); + form2.fields.push_back(field); + 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()); + base::MessageLoop::current()->Run(); + + const std::vector<AutofillProfile*>& results2 = personal_data_->GetProfiles(); + + // Modify expected to include multi-valued fields. + std::vector<base::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])); +} + +TEST_F(PersonalDataManagerTest, IncognitoReadOnly) { + ASSERT_TRUE(personal_data_->GetProfiles().empty()); + ASSERT_TRUE(personal_data_->GetCreditCards().empty()); + + AutofillProfile steve_jobs(base::GenerateGUID(), "https://www.example.com"); + test::SetProfileInfo(&steve_jobs, "Steven", "Paul", "Jobs", "sjobs@apple.com", + "Apple Computer, Inc.", "1 Infinite Loop", "", "Cupertino", "CA", "95014", + "US", "(800) 275-2273"); + personal_data_->AddProfile(steve_jobs); + + CreditCard bill_gates(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo( + &bill_gates, "William H. Gates", "5555555555554444", "1", "2020"); + personal_data_->AddCreditCard(bill_gates); + + ResetPersonalDataManager(); + ASSERT_EQ(1U, personal_data_->GetProfiles().size()); + ASSERT_EQ(1U, personal_data_->GetCreditCards().size()); + + // After this point no adds, saves, or updates should take effect. + MakeProfileIncognito(); + EXPECT_CALL(personal_data_observer_, OnPersonalDataChanged()).Times(0); + + // Add profiles or credit card shouldn't work. + personal_data_->AddProfile(test::GetFullProfile()); + + CreditCard larry_page(base::GenerateGUID(), "https://www.example.com"); + test::SetCreditCardInfo( + &larry_page, "Lawrence Page", "4111111111111111", "10", "2025"); + personal_data_->AddCreditCard(larry_page); + + ResetPersonalDataManager(); + EXPECT_EQ(1U, personal_data_->GetProfiles().size()); + EXPECT_EQ(1U, personal_data_->GetCreditCards().size()); + + // Saving or creating profiles from imported profiles shouldn't work. + steve_jobs.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Steve")); + personal_data_->SaveImportedProfile(steve_jobs); + + bill_gates.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Bill Gates")); + personal_data_->SaveImportedCreditCard(bill_gates); + + ResetPersonalDataManager(); + EXPECT_EQ(ASCIIToUTF16("Steven"), + personal_data_->GetProfiles()[0]->GetRawInfo(NAME_FIRST)); + EXPECT_EQ(ASCIIToUTF16("William H. Gates"), + personal_data_->GetCreditCards()[0]->GetRawInfo(CREDIT_CARD_NAME)); + + // Updating existing profiles shouldn't work. + steve_jobs.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Steve")); + personal_data_->UpdateProfile(steve_jobs); + + bill_gates.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Bill Gates")); + personal_data_->UpdateCreditCard(bill_gates); + + ResetPersonalDataManager(); + EXPECT_EQ(ASCIIToUTF16("Steven"), + personal_data_->GetProfiles()[0]->GetRawInfo(NAME_FIRST)); + EXPECT_EQ(ASCIIToUTF16("William H. Gates"), + personal_data_->GetCreditCards()[0]->GetRawInfo(CREDIT_CARD_NAME)); + + // Removing shouldn't work. + personal_data_->RemoveByGUID(steve_jobs.guid()); + personal_data_->RemoveByGUID(bill_gates.guid()); + + ResetPersonalDataManager(); + EXPECT_EQ(1U, personal_data_->GetProfiles().size()); + EXPECT_EQ(1U, personal_data_->GetCreditCards().size()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/phone_field.cc b/chromium/components/autofill/core/browser/phone_field.cc new file mode 100644 index 00000000000..6d156e909c3 --- /dev/null +++ b/chromium/components/autofill/core/browser/phone_field.cc @@ -0,0 +1,276 @@ +// 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/core/browser/phone_field.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_regex_constants.h" +#include "components/autofill/core/browser/autofill_scanner.h" +#include "ui/base/l10n/l10n_util.h" + +namespace autofill { +namespace { + +// This string includes all area code separators, including NoText. +base::string16 GetAreaRegex() { + base::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(ServerFieldTypeMap* 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); + } + + ServerFieldType 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 +base::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 base::string16(); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/phone_field.h b/chromium/components/autofill/core/browser/phone_field.h new file mode 100644 index 00000000000..f163b9c89ae --- /dev/null +++ b/chromium/components/autofill/core/browser/phone_field.h @@ -0,0 +1,93 @@ +// 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_CORE_BROWSER_PHONE_FIELD_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_PHONE_FIELD_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/form_field.h" +#include "components/autofill/core/browser/phone_number.h" + +namespace autofill { + +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(ServerFieldTypeMap* 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 base::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); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_PHONE_FIELD_H_ diff --git a/chromium/components/autofill/core/browser/phone_field_unittest.cc b/chromium/components/autofill/core/browser/phone_field_unittest.cc new file mode 100644 index 00000000000..44c3df66b8c --- /dev/null +++ b/chromium/components/autofill/core/browser/phone_field_unittest.cc @@ -0,0 +1,230 @@ +// 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 "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_field.h" +#include "components/autofill/core/browser/autofill_scanner.h" +#include "components/autofill/core/browser/phone_field.h" +#include "components/autofill/core/common/form_field_data.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +class PhoneFieldTest : public testing::Test { + public: + PhoneFieldTest() {} + + protected: + ScopedVector<const AutofillField> list_; + scoped_ptr<PhoneField> field_; + ServerFieldTypeMap 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")]); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/phone_number.cc b/chromium/components/autofill/core/browser/phone_number.cc new file mode 100644 index 00000000000..2ee67f98b72 --- /dev/null +++ b/chromium/components/autofill/core/browser/phone_number.cc @@ -0,0 +1,247 @@ +// 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/core/browser/phone_number.h" + +#include "base/basictypes.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/phone_number_i18n.h" + +namespace autofill { +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(base::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) { + base::string16 country_code = profile.GetRawInfo(ADDRESS_HOME_COUNTRY); + if (!country_code.empty()) + return UTF16ToASCII(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(ServerFieldTypeSet* 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); +} + +base::string16 PhoneNumber::GetRawInfo(ServerFieldType type) const { + // TODO(isherman): Is GetStorableType even necessary? + if (AutofillType(type).GetStorableType() == 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 base::string16(); +} + +void PhoneNumber::SetRawInfo(ServerFieldType type, + const base::string16& value) { + // TODO(isherman): Is GetStorableType even necessary? + type = AutofillType(type).GetStorableType(); + 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_ = 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. +base::string16 PhoneNumber::GetInfo(const AutofillType& type, + const std::string& app_locale) const { + ServerFieldType storable_type = type.GetStorableType(); + 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 (storable_type != PHONE_HOME_WHOLE_NUMBER && + !cached_parsed_phone_.IsValidNumber()) + return base::string16(); + + switch (storable_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 base::string16(); + } +} + +bool PhoneNumber::SetInfo(const AutofillType& type, + const base::string16& value, + const std::string& app_locale) { + SetRawInfo(type.GetStorableType(), 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 base::string16& text, + const std::string& app_locale, + ServerFieldTypeSet* matching_types) const { + base::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. + base::string16 number = GetInfo(AutofillType(PHONE_HOME_NUMBER), app_locale); + if (GetRegion(*profile_, app_locale) == "US" && + number.size() == (kPrefixLength + kSuffixLength)) { + base::string16 prefix = number.substr(kPrefixOffset, kPrefixLength); + base::string16 suffix = number.substr(kSuffixOffset, kSuffixLength); + if (text == prefix || text == suffix) + matching_types->insert(PHONE_HOME_NUMBER); + } + + base::string16 whole_number = + GetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), app_locale); + if (!whole_number.empty()) { + base::string16 normalized_number = + 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_ = i18n::PhoneObject(number_, region); +} + +PhoneNumber::PhoneCombineHelper::PhoneCombineHelper() { +} + +PhoneNumber::PhoneCombineHelper::~PhoneCombineHelper() { +} + +bool PhoneNumber::PhoneCombineHelper::SetInfo(const AutofillType& type, + const base::string16& value) { + ServerFieldType storable_type = type.GetStorableType(); + if (storable_type == PHONE_HOME_COUNTRY_CODE) { + country_ = value; + return true; + } + + if (storable_type == PHONE_HOME_CITY_CODE) { + city_ = value; + return true; + } + + if (storable_type == PHONE_HOME_CITY_AND_NUMBER) { + phone_ = value; + return true; + } + + if (storable_type == PHONE_HOME_WHOLE_NUMBER) { + whole_number_ = value; + return true; + } + + if (storable_type == PHONE_HOME_NUMBER) { + phone_.append(value); + return true; + } + + return false; +} + +bool PhoneNumber::PhoneCombineHelper::ParseNumber( + const AutofillProfile& profile, + const std::string& app_locale, + base::string16* value) { + if (IsEmpty()) + return false; + + if (!whole_number_.empty()) { + *value = whole_number_; + return true; + } + + return i18n::ConstructPhoneNumber( + country_, city_, phone_, GetRegion(profile, app_locale), value); +} + +bool PhoneNumber::PhoneCombineHelper::IsEmpty() const { + return phone_.empty() && whole_number_.empty(); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/phone_number.h b/chromium/components/autofill/core/browser/phone_number.h new file mode 100644 index 00000000000..19f9f091da4 --- /dev/null +++ b/chromium/components/autofill/core/browser/phone_number.h @@ -0,0 +1,99 @@ +// 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_CORE_BROWSER_PHONE_NUMBER_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_PHONE_NUMBER_H_ + +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/strings/string16.h" +#include "components/autofill/core/browser/form_group.h" +#include "components/autofill/core/browser/phone_number_i18n.h" + +namespace autofill { + +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 base::string16& text, + const std::string& app_locale, + ServerFieldTypeSet* matching_types) const OVERRIDE; + virtual base::string16 GetRawInfo(ServerFieldType type) const OVERRIDE; + virtual void SetRawInfo(ServerFieldType type, + const base::string16& value) OVERRIDE; + virtual base::string16 GetInfo(const AutofillType& type, + const std::string& app_locale) const OVERRIDE; + virtual bool SetInfo(const AutofillType& type, + const base::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(const AutofillType& type, const base::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, + base::string16* value); + + // Returns true if both |phone_| and |whole_number_| are empty. + bool IsEmpty() const; + + private: + base::string16 country_; + base::string16 city_; + base::string16 phone_; + base::string16 whole_number_; + }; + + private: + // FormGroup: + virtual void GetSupportedTypes( + ServerFieldTypeSet* 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. + base::string16 number_; + // Profile which stores the region used as hint when normalizing the number. + const AutofillProfile* profile_; // WEAK + + // Cached number. + mutable i18n::PhoneObject cached_parsed_phone_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_PHONE_NUMBER_H_ diff --git a/chromium/components/autofill/core/browser/phone_number_i18n.cc b/chromium/components/autofill/core/browser/phone_number_i18n.cc new file mode 100644 index 00000000000..1cb49ca20b3 --- /dev/null +++ b/chromium/components/autofill/core/browser/phone_number_i18n.cc @@ -0,0 +1,299 @@ +// 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/core/browser/phone_number_i18n.h" + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "third_party/libphonenumber/src/phonenumber_api.h" + +using i18n::phonenumbers::PhoneNumber; +using i18n::phonenumbers::PhoneNumberUtil; + +namespace autofill { + +namespace { + +std::string SanitizeRegion(const std::string& region, + const std::string& app_locale) { + if (region.length() == 2) + return region; + + return AutofillCountry::CountryCodeForLocale(app_locale); +} + +// 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 base::string16& country_code, + base::string16* formatted_number, + base::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 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 base::string16& value, + const std::string& region, + base::string16* country_code, + base::string16* city_code, + base::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 = base::string16(); + + phone_util->NormalizeDigitsOnly(&number_text); + base::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; +} + +base::string16 NormalizePhoneNumber(const base::string16& value, + const std::string& region) { + DCHECK_EQ(2u, region.size()); + base::string16 country_code; + base::string16 unused_city_code; + base::string16 unused_number; + PhoneNumber phone_number; + if (!ParsePhoneNumber(value, region, &country_code, &unused_city_code, + &unused_number, &phone_number)) { + return base::string16(); // Parsing failed - do not store phone. + } + + base::string16 normalized_number; + FormatValidatedNumber(phone_number, country_code, NULL, &normalized_number); + return normalized_number; +} + +bool ConstructPhoneNumber(const base::string16& country_code, + const base::string16& city_code, + const base::string16& number, + const std::string& region, + base::string16* whole_number) { + DCHECK_EQ(2u, region.size()); + whole_number->clear(); + + base::string16 unused_country_code; + base::string16 unused_city_code; + base::string16 unused_number; + PhoneNumber phone_number; + if (!ParsePhoneNumber(country_code + city_code + number, 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 base::string16& number_a, + const base::string16& number_b, + const std::string& raw_region, + const std::string& app_locale) { + // Sanitize the provided |raw_region| before trying to use it for parsing. + const std::string region = SanitizeRegion(raw_region, app_locale); + + 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 base::string16& number, + const std::string& region) + : region_(region) { + DCHECK_EQ(2u, region.size()); + // 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) { *this = other; } + +PhoneObject::PhoneObject() {} + +PhoneObject::~PhoneObject() { +} + +base::string16 PhoneObject::GetFormattedNumber() const { + if (i18n_number_ && formatted_number_.empty()) { + FormatValidatedNumber(*i18n_number_, country_code_, &formatted_number_, + &whole_number_); + } + + return formatted_number_; +} + +base::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 i18n +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/phone_number_i18n.h b/chromium/components/autofill/core/browser/phone_number_i18n.h new file mode 100644 index 00000000000..3fcc824280a --- /dev/null +++ b/chromium/components/autofill/core/browser/phone_number_i18n.h @@ -0,0 +1,112 @@ +// 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_CORE_BROWSER_PHONE_NUMBER_I18N_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_PHONE_NUMBER_I18N_H_ + +#include <string> +#include <vector> + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" + +namespace i18n { +namespace phonenumbers { +class PhoneNumber; +} +} + +namespace autofill { + +// Utilities to process, normalize and compare international phone numbers. +namespace 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 base::string16& value, + const std::string& region, + base::string16* country_code, + base::string16* city_code, + base::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. +base::string16 NormalizePhoneNumber(const base::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 base::string16& country_code, + const base::string16& city_code, + const base::string16& number, + const std::string& region, + base::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 base::string16& number_a, + const base::string16& number_b, + const std::string& region, + const std::string& app_locale); + +// The cached phone number, does parsing only once, improves performance. +class PhoneObject { + public: + PhoneObject(const base::string16& number, + const std::string& region); + PhoneObject(const PhoneObject&); + PhoneObject(); + ~PhoneObject(); + + std::string region() const { return region_; } + + base::string16 country_code() const { return country_code_; } + base::string16 city_code() const { return city_code_; } + base::string16 number() const { return number_; } + + base::string16 GetFormattedNumber() const; + base::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_; + base::string16 city_code_; + base::string16 country_code_; + base::string16 number_; + + // Pretty printed version of the whole number, or empty if parsing failed. + // Set on first request. + mutable base::string16 formatted_number_; + + // The whole number, normalized to contain only digits if possible. + // Set on first request. + mutable base::string16 whole_number_; +}; + +} // namespace i18n +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_PHONE_NUMBER_I18N_H_ diff --git a/chromium/components/autofill/core/browser/phone_number_i18n_unittest.cc b/chromium/components/autofill/core/browser/phone_number_i18n_unittest.cc new file mode 100644 index 00000000000..b10345b2166 --- /dev/null +++ b/chromium/components/autofill/core/browser/phone_number_i18n_unittest.cc @@ -0,0 +1,389 @@ +// 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 "base/message_loop/message_loop.h" +#include "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/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 content::BrowserThread; + +namespace autofill { + +using i18n::NormalizePhoneNumber; +using i18n::ParsePhoneNumber; +using i18n::ConstructPhoneNumber; +using i18n::PhoneNumbersMatch; + +TEST(PhoneNumberI18NTest, NormalizePhoneNumber) { + // "Large" digits. + base::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. + base::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")); + + base::string16 phone3(UTF8ToUTF16("16503334\xef\xbc\x92\x35\xd9\xa5")); + EXPECT_EQ(NormalizePhoneNumber(phone3, "US"), ASCIIToUTF16("16503334255")); + + base::string16 phone4(UTF8ToUTF16("+1(650)2346789")); + EXPECT_EQ(NormalizePhoneNumber(phone4, "US"), ASCIIToUTF16("16502346789")); + + base::string16 phone5(UTF8ToUTF16("6502346789")); + EXPECT_EQ(NormalizePhoneNumber(phone5, "US"), ASCIIToUTF16("6502346789")); +} + +TEST(PhoneNumberI18NTest, ParsePhoneNumber) { + base::string16 number; + base::string16 city_code; + base::string16 country_code; + ::i18n::phonenumbers::PhoneNumber unused_i18n_number; + + // Test for empty string. Should give back empty strings. + base::string16 phone0; + EXPECT_FALSE(ParsePhoneNumber(phone0, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(base::string16(), number); + EXPECT_EQ(base::string16(), city_code); + EXPECT_EQ(base::string16(), country_code); + + // Test for string with less than 7 digits. Should give back empty strings. + base::string16 phone1(ASCIIToUTF16("1234")); + EXPECT_FALSE(ParsePhoneNumber(phone1, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(base::string16(), number); + EXPECT_EQ(base::string16(), city_code); + EXPECT_EQ(base::string16(), country_code); + + // Test for string with exactly 7 digits. + // Not a valid number - starts with 1 + base::string16 phone2(ASCIIToUTF16("1234567")); + EXPECT_FALSE(ParsePhoneNumber(phone2, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(base::string16(), number); + EXPECT_EQ(base::string16(), city_code); + EXPECT_EQ(base::string16(), country_code); + + // Not a valid number - does not have area code. + base::string16 phone3(ASCIIToUTF16("2234567")); + EXPECT_FALSE(ParsePhoneNumber(phone3, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(base::string16(), number); + EXPECT_EQ(base::string16(), city_code); + EXPECT_EQ(base::string16(), country_code); + + // Test for string with greater than 7 digits but less than 10 digits. + // Should fail parsing in US. + base::string16 phone4(ASCIIToUTF16("123456789")); + EXPECT_FALSE(ParsePhoneNumber(phone4, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(base::string16(), number); + EXPECT_EQ(base::string16(), city_code); + EXPECT_EQ(base::string16(), country_code); + + // Test for string with greater than 7 digits but less than 10 digits and + // separators. + // Should fail parsing in US. + base::string16 phone_separator4(ASCIIToUTF16("12.345-6789")); + EXPECT_FALSE(ParsePhoneNumber(phone_separator4, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(base::string16(), number); + EXPECT_EQ(base::string16(), city_code); + EXPECT_EQ(base::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. + base::string16 phone5(ASCIIToUTF16("1234567890")); + EXPECT_FALSE(ParsePhoneNumber(phone5, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(base::string16(), number); + EXPECT_EQ(base::string16(), city_code); + EXPECT_EQ(base::string16(), country_code); + + base::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(base::string16(), number); + EXPECT_EQ(base::string16(), city_code); + EXPECT_EQ(base::string16(), country_code); + + base::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(base::string16(), country_code); + + // Test for string with exactly 10 digits and separators. + // Should give back phone number and city code. + base::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(base::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. + base::string16 phone8(ASCIIToUTF16("0116504567890")); + EXPECT_FALSE(ParsePhoneNumber(phone8, "US", + &country_code, + &city_code, + &number, + &unused_i18n_number)); + EXPECT_EQ(base::string16(), number); + EXPECT_EQ(base::string16(), city_code); + EXPECT_EQ(base::string16(), country_code); + + // 011 is a correct "dial out" prefix in the USA - the parsing should succeed. + base::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. + base::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. + base::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. + base::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); + + base::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); + + base::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. + base::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) { + base::string16 number; + EXPECT_TRUE(ConstructPhoneNumber(ASCIIToUTF16("1"), + ASCIIToUTF16("650"), + ASCIIToUTF16("2345678"), + "US", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("+1 650-234-5678")); + EXPECT_TRUE(ConstructPhoneNumber(base::string16(), + ASCIIToUTF16("650"), + ASCIIToUTF16("2345678"), + "US", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("(650) 234-5678")); + EXPECT_TRUE(ConstructPhoneNumber(ASCIIToUTF16("1"), + base::string16(), + ASCIIToUTF16("6502345678"), + "US", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("+1 650-234-5678")); + EXPECT_TRUE(ConstructPhoneNumber(base::string16(), + base::string16(), + ASCIIToUTF16("6502345678"), + "US", + &number)); + EXPECT_EQ(number, ASCIIToUTF16("(650) 234-5678")); + + EXPECT_FALSE(ConstructPhoneNumber(base::string16(), + ASCIIToUTF16("650"), + ASCIIToUTF16("234567890"), + "US", + &number)); + EXPECT_EQ(number, base::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(base::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(base::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", + "en-US")); + // Same numbers, undefined country code. + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("4158889999"), + std::string(), + "en-US")); + + // Numbers differ by country code only. + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("14158889999"), + ASCIIToUTF16("4158889999"), + "US", + "en-US")); + + // Same numbers, different formats. + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("415-888-9999"), + "US", + "en-US")); + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("(415)888-9999"), + "US", + "en-US")); + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("415 888 9999"), + "US", + "en-US")); + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("4158889999"), + ASCIIToUTF16("415 TUV WXYZ"), + "US", + "en-US")); + EXPECT_TRUE(PhoneNumbersMatch(ASCIIToUTF16("1(415)888-99-99"), + ASCIIToUTF16("+14158889999"), + "US", + "en-US")); + + // Partial matches don't count. + EXPECT_FALSE(PhoneNumbersMatch(ASCIIToUTF16("14158889999"), + ASCIIToUTF16("8889999"), + "US", + "en-US")); + + // Different numbers don't match. + EXPECT_FALSE(PhoneNumbersMatch(ASCIIToUTF16("14158889999"), + ASCIIToUTF16("1415888"), + "US", + "en-US")); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/phone_number_unittest.cc b/chromium/components/autofill/core/browser/phone_number_unittest.cc new file mode 100644 index 00000000000..fd654d06fa7 --- /dev/null +++ b/chromium/components/autofill/core/browser/phone_number_unittest.cc @@ -0,0 +1,212 @@ +// 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 "base/strings/string16.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/field_types.h" +#include "components/autofill/core/browser/phone_number.h" +#include "components/autofill/core/browser/phone_number_i18n.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +TEST(PhoneNumberTest, Matcher) { + AutofillProfile profile; + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + // Set phone number so country_code == 1, city_code = 650, number = 2345678. + base::string16 phone(ASCIIToUTF16("1 [650] 234-5678")); + PhoneNumber phone_number(&profile); + phone_number.SetInfo(AutofillType(PHONE_HOME_WHOLE_NUMBER), phone, "US"); + + ServerFieldTypeSet matching_types; + phone_number.GetMatchingTypes(base::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.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + + PhoneNumber phone(&profile); + EXPECT_EQ(base::string16(), phone.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); + + // Set the formatted info directly. + EXPECT_TRUE(phone.SetInfo(AutofillType(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(AutofillType(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(AutofillType(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(AutofillType(PHONE_HOME_WHOLE_NUMBER), + ASCIIToUTF16("650111111"), "US")); + EXPECT_EQ(base::string16(), phone.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); +} + +// Test that cached phone numbers are correctly invalidated and updated. +TEST(PhoneNumberTest, UpdateCachedPhoneNumber) { + AutofillProfile profile; + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + + PhoneNumber phone(&profile); + phone.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("6502345678")); + EXPECT_EQ(ASCIIToUTF16("650"), + phone.GetInfo(AutofillType(PHONE_HOME_CITY_CODE), "US")); + + // Update the area code. + phone.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("8322345678")); + EXPECT_EQ(ASCIIToUTF16("832"), + phone.GetInfo(AutofillType(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(base::string16(), + phone.GetInfo(AutofillType(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.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("GB")); + phone.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("07023456789")); + EXPECT_EQ(ASCIIToUTF16("70"), + phone.GetInfo(AutofillType(PHONE_HOME_CITY_CODE), "US")); +} + +TEST(PhoneNumberTest, PhoneCombineHelper) { + AutofillProfile profile; + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + + PhoneNumber::PhoneCombineHelper number1; + EXPECT_FALSE(number1.SetInfo(AutofillType(ADDRESS_BILLING_CITY), + ASCIIToUTF16("1"))); + EXPECT_TRUE(number1.SetInfo(AutofillType(PHONE_HOME_COUNTRY_CODE), + ASCIIToUTF16("1"))); + EXPECT_TRUE(number1.SetInfo(AutofillType(PHONE_HOME_CITY_CODE), + ASCIIToUTF16("650"))); + EXPECT_TRUE(number1.SetInfo(AutofillType(PHONE_HOME_NUMBER), + ASCIIToUTF16("2345678"))); + base::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(AutofillType(PHONE_HOME_CITY_CODE), + ASCIIToUTF16("650"))); + EXPECT_TRUE(number3.SetInfo(AutofillType(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(AutofillType(PHONE_HOME_CITY_CODE), + ASCIIToUTF16("123"))); // Incorrect city code. + EXPECT_TRUE(number4.SetInfo(AutofillType(PHONE_HOME_NUMBER), + ASCIIToUTF16("2345680"))); + EXPECT_FALSE(number4.ParseNumber(profile, "en-US", &parsed_phone)); + EXPECT_EQ(base::string16(), parsed_phone); + + PhoneNumber::PhoneCombineHelper number5; + EXPECT_TRUE(number5.SetInfo(AutofillType(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(AutofillType(PHONE_HOME_CITY_CODE), + ASCIIToUTF16("650"))); + EXPECT_TRUE(number6.SetInfo(AutofillType(PHONE_HOME_NUMBER), + ASCIIToUTF16("234"))); + EXPECT_TRUE(number6.SetInfo(AutofillType(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(AutofillType(PHONE_HOME_CITY_CODE), + ASCIIToUTF16("650"))); + EXPECT_TRUE(number7.SetInfo(AutofillType(PHONE_HOME_NUMBER), + ASCIIToUTF16("234"))); + EXPECT_TRUE(number7.SetInfo(AutofillType(PHONE_HOME_NUMBER), + ASCIIToUTF16("5682"))); + EXPECT_TRUE(number7.ParseNumber(AutofillProfile(), "en-US", &parsed_phone)); + EXPECT_EQ(ASCIIToUTF16("(650) 234-5682"), parsed_phone); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/state_names.cc b/chromium/components/autofill/core/browser/state_names.cc new file mode 100644 index 00000000000..0ad35250cf5 --- /dev/null +++ b/chromium/components/autofill/core/browser/state_names.cc @@ -0,0 +1,115 @@ +// 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/core/browser/state_names.h" + +#include "base/basictypes.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" + +namespace autofill { +namespace state_names { + +namespace { + +// TODO(jhawkins): Add more states/provinces. See http://crbug.com/45039. + +struct StateData { + const char* const name; + const char abbreviation[3]; +}; + +StateData kStateData[] = { + { "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", "mn" }, + { "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" }, +}; + +} // namespace + +base::string16 GetAbbreviationForName(const base::string16& name) { + for (size_t i = 0; i < arraysize(kStateData); ++i) { + const StateData& state = kStateData[i]; + if (LowerCaseEqualsASCII(name, state.name)) + return ASCIIToUTF16(state.abbreviation); + } + return base::string16(); +} + +base::string16 GetNameForAbbreviation(const base::string16& abbreviation) { + for (size_t i = 0; i < arraysize(kStateData); ++i) { + const StateData& state = kStateData[i]; + if (LowerCaseEqualsASCII(abbreviation, state.abbreviation)) + return ASCIIToUTF16(state.name); + } + return base::string16(); +} + +void GetNameAndAbbreviation(const base::string16& value, + base::string16* name, + base::string16* abbreviation) { + base::string16 full = GetNameForAbbreviation(value); + base::string16 abbr = value; + if (full.empty()) { + abbr = GetAbbreviationForName(value); + full = value; + } + + if (name) + name->swap(full); + if (abbreviation) + abbreviation->swap(abbr); +} + +} // namespace state_names +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/state_names.h b/chromium/components/autofill/core/browser/state_names.h new file mode 100644 index 00000000000..cd71539de6a --- /dev/null +++ b/chromium/components/autofill/core/browser/state_names.h @@ -0,0 +1,31 @@ +// 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 "base/strings/string16.h" + +#ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_STATE_NAMES_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_STATE_NAMES_H_ + +namespace autofill { +namespace state_names { + +// Returns the abbrevation corresponding to the state named |name|, or the empty +// string if there is no such state. +base::string16 GetAbbreviationForName(const base::string16& name); + +// Returns the full state name corresponding to the |abbrevation|, or the empty +// string if there is no such state. +base::string16 GetNameForAbbreviation(const base::string16& abbreviation); + +// |value| is either a state name or abbreviation. Detects which it is, and +// outputs both |name| and |abbreviation|. If it's neither, then |name| is +// set to |value| and |abbreviation| will be empty. +void GetNameAndAbbreviation(const base::string16& value, + base::string16* name, + base::string16* abbreviation); + +} // namespace state_names +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_STATE_NAMES_H_ diff --git a/chromium/components/autofill/core/browser/test_autofill_driver.cc b/chromium/components/autofill/core/browser/test_autofill_driver.cc new file mode 100644 index 00000000000..2b20c3d743c --- /dev/null +++ b/chromium/components/autofill/core/browser/test_autofill_driver.cc @@ -0,0 +1,40 @@ +// 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/core/browser/test_autofill_driver.h" + +namespace autofill { + +TestAutofillDriver::TestAutofillDriver(content::WebContents* web_contents) + : content::WebContentsObserver(web_contents) {} + +TestAutofillDriver::~TestAutofillDriver() {} + +content::WebContents* TestAutofillDriver::GetWebContents() { + return web_contents(); +} + +bool TestAutofillDriver::RendererIsAvailable() { + return true; +} + +void TestAutofillDriver::SetRendererActionOnFormDataReception( + RendererFormDataAction action) { +} + +void TestAutofillDriver::SendFormDataToRenderer(int query_id, + const FormData& form_data) { +} + +void TestAutofillDriver::SendAutofillTypePredictionsToRenderer( + const std::vector<FormStructure*>& forms) { +} + +void TestAutofillDriver::RendererShouldClearFilledForm() { +} + +void TestAutofillDriver::RendererShouldClearPreviewedForm() { +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/test_autofill_driver.h b/chromium/components/autofill/core/browser/test_autofill_driver.h new file mode 100644 index 00000000000..dbfe4ef43d4 --- /dev/null +++ b/chromium/components/autofill/core/browser/test_autofill_driver.h @@ -0,0 +1,43 @@ +// 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_CORE_BROWSER_TEST_AUTOFILL_DRIVER_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_DRIVER_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "components/autofill/core/browser/autofill_driver.h" +#include "content/public/browser/web_contents_observer.h" + +namespace autofill { + +// This class is only for easier writing of tests. All pure virtual functions +// have been given empty methods. +// TODO(blundell): Eliminate this class being a WebContentsObserver once +// autofill shared code no longer needs knowledge of WebContents. +class TestAutofillDriver : public AutofillDriver, + public content::WebContentsObserver { + public: + explicit TestAutofillDriver(content::WebContents* web_contents); + virtual ~TestAutofillDriver(); + + // AutofillDriver implementation. + virtual content::WebContents* GetWebContents() OVERRIDE; + virtual bool RendererIsAvailable() OVERRIDE; + virtual void SetRendererActionOnFormDataReception( + RendererFormDataAction action) OVERRIDE; + virtual void SendFormDataToRenderer(int query_id, + const FormData& data) OVERRIDE; + virtual void SendAutofillTypePredictionsToRenderer( + const std::vector<FormStructure*>& forms) OVERRIDE; + virtual void RendererShouldClearFilledForm() OVERRIDE; + virtual void RendererShouldClearPreviewedForm() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(TestAutofillDriver); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_DRIVER_H_ diff --git a/chromium/components/autofill/core/browser/test_autofill_external_delegate.cc b/chromium/components/autofill/core/browser/test_autofill_external_delegate.cc new file mode 100644 index 00000000000..94e6b9ec8bc --- /dev/null +++ b/chromium/components/autofill/core/browser/test_autofill_external_delegate.cc @@ -0,0 +1,29 @@ +// 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/core/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<base::string16> autofill_item; + autofill_item.push_back(base::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/chromium/components/autofill/core/browser/test_autofill_external_delegate.h b/chromium/components/autofill/core/browser/test_autofill_external_delegate.h new file mode 100644 index 00000000000..cc7bf73ee44 --- /dev/null +++ b/chromium/components/autofill/core/browser/test_autofill_external_delegate.h @@ -0,0 +1,21 @@ +// 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_CORE_BROWSER_TEST_AUTOFILL_EXTERNAL_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_EXTERNAL_DELEGATE_H_ + +#include "components/autofill/core/browser/autofill_external_delegate.h" + +namespace autofill { + +class AutofillManager; + +// 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_CORE_BROWSER_TEST_AUTOFILL_EXTERNAL_DELEGATE_H_ diff --git a/chromium/components/autofill/core/browser/test_autofill_manager_delegate.cc b/chromium/components/autofill/core/browser/test_autofill_manager_delegate.cc new file mode 100644 index 00000000000..dc9781d11e0 --- /dev/null +++ b/chromium/components/autofill/core/browser/test_autofill_manager_delegate.cc @@ -0,0 +1,80 @@ +// 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/core/browser/test_autofill_manager_delegate.h" + +namespace autofill { + +TestAutofillManagerDelegate::TestAutofillManagerDelegate() {} +TestAutofillManagerDelegate::~TestAutofillManagerDelegate() {} + +PersonalDataManager* TestAutofillManagerDelegate::GetPersonalDataManager() { + return NULL; +} + +autocheckout::WhitelistManager* +TestAutofillManagerDelegate::GetAutocheckoutWhitelistManager() const { + return NULL; +} + +PrefService* TestAutofillManagerDelegate::GetPrefs() { + return NULL; +} + +void TestAutofillManagerDelegate::HideRequestAutocompleteDialog() {} + +void TestAutofillManagerDelegate::OnAutocheckoutError() {} + +void TestAutofillManagerDelegate::OnAutocheckoutSuccess() {} + +void TestAutofillManagerDelegate::ShowAutofillSettings() {} + +void TestAutofillManagerDelegate::ConfirmSaveCreditCard( + const AutofillMetrics& metric_logger, + const CreditCard& credit_card, + const base::Closure& save_card_callback) {} + +bool TestAutofillManagerDelegate::ShowAutocheckoutBubble( + const gfx::RectF& bounding_box, + bool is_google_user, + const base::Callback<void(AutocheckoutBubbleState)>& callback) { + return true; +} + +void TestAutofillManagerDelegate::HideAutocheckoutBubble() {} + +void TestAutofillManagerDelegate::ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + DialogType dialog_type, + const base::Callback<void(const FormStructure*, + const std::string&)>& callback) {} + +void TestAutofillManagerDelegate::ShowAutofillPopup( + const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction, + const std::vector<base::string16>& values, + const std::vector<base::string16>& labels, + const std::vector<base::string16>& icons, + const std::vector<int>& identifiers, + base::WeakPtr<AutofillPopupDelegate> delegate) {} + +void TestAutofillManagerDelegate::UpdateAutofillPopupDataListValues( + const std::vector<base::string16>& values, + const std::vector<base::string16>& labels) {} + +void TestAutofillManagerDelegate::HideAutofillPopup() {} + +void TestAutofillManagerDelegate::AddAutocheckoutStep( + AutocheckoutStepType step_type) {} + +void TestAutofillManagerDelegate::UpdateAutocheckoutStep( + AutocheckoutStepType step_type, + AutocheckoutStepStatus step_status) {} + +bool TestAutofillManagerDelegate::IsAutocompleteEnabled() { + return true; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/test_autofill_manager_delegate.h b/chromium/components/autofill/core/browser/test_autofill_manager_delegate.h new file mode 100644 index 00000000000..1fee0ba24a9 --- /dev/null +++ b/chromium/components/autofill/core/browser/test_autofill_manager_delegate.h @@ -0,0 +1,71 @@ +// 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_CORE_BROWSER_TEST_AUTOFILL_MANAGER_DELEGATE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_MANAGER_DELEGATE_H_ + +#include "base/compiler_specific.h" +#include "base/i18n/rtl.h" +#include "components/autofill/core/browser/autocheckout_bubble_state.h" +#include "components/autofill/core/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 autocheckout::WhitelistManager* + GetAutocheckoutWhitelistManager() const OVERRIDE; + virtual void HideRequestAutocompleteDialog() OVERRIDE; + virtual void OnAutocheckoutError() OVERRIDE; + virtual void OnAutocheckoutSuccess() OVERRIDE; + virtual void ShowAutofillSettings() OVERRIDE; + virtual void ConfirmSaveCreditCard( + const AutofillMetrics& metric_logger, + const CreditCard& credit_card, + const base::Closure& save_card_callback) OVERRIDE; + virtual bool ShowAutocheckoutBubble( + const gfx::RectF& bounding_box, + bool is_google_user, + const base::Callback<void(AutocheckoutBubbleState)>& callback) OVERRIDE; + virtual void HideAutocheckoutBubble() OVERRIDE; + virtual void ShowRequestAutocompleteDialog( + const FormData& form, + const GURL& source_url, + DialogType dialog_type, + const base::Callback<void(const FormStructure*, + const std::string&)>& callback) OVERRIDE; + virtual void ShowAutofillPopup( + const gfx::RectF& element_bounds, + base::i18n::TextDirection text_direction, + const std::vector<base::string16>& values, + const std::vector<base::string16>& labels, + const std::vector<base::string16>& icons, + const std::vector<int>& identifiers, + base::WeakPtr<AutofillPopupDelegate> delegate) OVERRIDE; + virtual void UpdateAutofillPopupDataListValues( + const std::vector<base::string16>& values, + const std::vector<base::string16>& labels) OVERRIDE; + virtual void HideAutofillPopup() OVERRIDE; + virtual bool IsAutocompleteEnabled() OVERRIDE; + + virtual void AddAutocheckoutStep(AutocheckoutStepType step_type) OVERRIDE; + virtual void UpdateAutocheckoutStep( + AutocheckoutStepType step_type, + AutocheckoutStepStatus step_status) OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(TestAutofillManagerDelegate); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_TEST_AUTOFILL_MANAGER_DELEGATE_H_ diff --git a/chromium/components/autofill/core/browser/test_personal_data_manager.cc b/chromium/components/autofill/core/browser/test_personal_data_manager.cc new file mode 100644 index 00000000000..0a517d7655b --- /dev/null +++ b/chromium/components/autofill/core/browser/test_personal_data_manager.cc @@ -0,0 +1,42 @@ +// 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/core/browser/test_personal_data_manager.h" + +#include "components/autofill/core/browser/personal_data_manager_observer.h" + +namespace autofill { + +TestPersonalDataManager::TestPersonalDataManager() + : PersonalDataManager("en-US") {} + +TestPersonalDataManager::~TestPersonalDataManager() {} + +void TestPersonalDataManager::AddTestingProfile(AutofillProfile* profile) { + profiles_.push_back(profile); + FOR_EACH_OBSERVER(PersonalDataManagerObserver, observers_, + OnPersonalDataChanged()); +} + +void TestPersonalDataManager::AddTestingCreditCard(CreditCard* credit_card) { + credit_cards_.push_back(credit_card); + FOR_EACH_OBSERVER(PersonalDataManagerObserver, observers_, + OnPersonalDataChanged()); +} + +const std::vector<AutofillProfile*>& TestPersonalDataManager::GetProfiles() { + return profiles_; +} + +const std::vector<CreditCard*>& TestPersonalDataManager:: + GetCreditCards() const { + return credit_cards_; +} + +void TestPersonalDataManager::SaveImportedProfile( + const AutofillProfile& imported_profile) { + imported_profile_ = imported_profile; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/test_personal_data_manager.h b/chromium/components/autofill/core/browser/test_personal_data_manager.h new file mode 100644 index 00000000000..c9f9a43d4a4 --- /dev/null +++ b/chromium/components/autofill/core/browser/test_personal_data_manager.h @@ -0,0 +1,44 @@ +// 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_BROWSER_AUTOFILL_TEST_PERSONAL_DATA_MANAGER_H_ +#define COMPONENTS_BROWSER_AUTOFILL_TEST_PERSONAL_DATA_MANAGER_H_ + +#include <vector> + +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/personal_data_manager.h" + +namespace autofill { + +// A simplistic PersonalDataManager used for testing. +class TestPersonalDataManager : public PersonalDataManager { + public: + TestPersonalDataManager(); + virtual ~TestPersonalDataManager(); + + // Adds |profile| to |profiles_|. This does not take ownership of |profile|. + void AddTestingProfile(AutofillProfile* profile); + + // Adds |credit_card| to |credit_cards_|. This does not take ownership of + // |credit_card|. + void AddTestingCreditCard(CreditCard* credit_card); + + virtual const std::vector<AutofillProfile*>& GetProfiles() OVERRIDE; + virtual void SaveImportedProfile(const AutofillProfile& imported_profile) + OVERRIDE; + + const AutofillProfile& imported_profile() { return imported_profile_; } + virtual const std::vector<CreditCard*>& GetCreditCards() const OVERRIDE; + + private: + std::vector<AutofillProfile*> profiles_; + std::vector<CreditCard*> credit_cards_; + AutofillProfile imported_profile_; +}; + +} // namespace autofill + +#endif // COMPONENTS_BROWSER_AUTOFILL_TEST_PERSONAL_DATA_MANAGER_H_ diff --git a/chromium/components/autofill/core/browser/validation.cc b/chromium/components/autofill/core/browser/validation.cc new file mode 100644 index 00000000000..a8aef35f7ca --- /dev/null +++ b/chromium/components/autofill/core/browser/validation.cc @@ -0,0 +1,220 @@ +// 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/core/browser/validation.h" + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_piece.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "components/autofill/core/browser/autofill_regexes.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/state_names.h" + +using base::StringPiece16; + +namespace { + +// The separator characters for SSNs. +const char16 kSSNSeparators[] = {' ', '-', 0}; + +} // namespace + +namespace autofill { + +bool IsValidCreditCardExpirationDate(const base::string16& year, + const base::string16& month, + const base::Time& now) { + base::string16 year_cleaned, month_cleaned; + TrimWhitespace(year, TRIM_ALL, &year_cleaned); + TrimWhitespace(month, TRIM_ALL, &month_cleaned); + if (year_cleaned.length() != 4) + return false; + + int cc_year; + if (!base::StringToInt(year_cleaned, &cc_year)) + return false; + + int cc_month; + if (!base::StringToInt(month_cleaned, &cc_month)) + return false; + + return IsValidCreditCardExpirationDate(cc_year, cc_month, now); +} + +bool IsValidCreditCardExpirationDate(int year, + int month, + const base::Time& now) { + base::Time::Exploded now_exploded; + now.LocalExplode(&now_exploded); + + if (year < now_exploded.year) + return false; + + if (year == now_exploded.year && month < now_exploded.month) + return false; + + return true; +} + +bool IsValidCreditCardNumber(const base::string16& text) { + base::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]. Specific card issuers have more rigidly + // defined sizes. + // [1] http://www.merriampark.com/anatomycc.htm + // [2] http://en.wikipedia.org/wiki/Bank_card_number + const std::string type = CreditCard::GetCreditCardType(text); + if (type == kAmericanExpressCard && number.size() != 15) + return false; + if (type == kDinersCard && number.size() != 14) + return false; + if (type == kDiscoverCard && number.size() != 16) + return false; + if (type == kJCBCard && number.size() != 16) + return false; + if (type == kMasterCard && number.size() != 16) + return false; + if (type == kUnionPay && (number.size() < 16 || number.size() > 19)) + return false; + if (type == kVisaCard && number.size() != 13 && number.size() != 16) + return false; + if (type == kGenericCard && (number.size() < 12 || number.size() > 19)) + return false; + + // Unlike all the other supported types, UnionPay cards lack Luhn checksum + // validation. + if (type == kUnionPay) + return true; + + // Use the Luhn formula [3] to validate the number. + // [3] http://en.wikipedia.org/wiki/Luhn_algorithm + int sum = 0; + bool odd = false; + for (base::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 base::string16& text) { + if (text.size() < 3U || text.size() > 4U) + return false; + + for (base::string16::const_iterator iter = text.begin(); + iter != text.end(); + ++iter) { + if (!IsAsciiDigit(*iter)) + return false; + } + return true; +} + +bool IsValidCreditCardSecurityCode(const base::string16& code, + const base::string16& number) { + CreditCard card; + card.SetRawInfo(CREDIT_CARD_NUMBER, number); + size_t required_length = 3; + if (card.type() == kAmericanExpressCard) + required_length = 4; + + return code.length() == required_length; +} + +bool IsValidEmailAddress(const base::string16& text) { + // E-Mail pattern as defined by the WhatWG. (4.10.7.1.5 E-Mail state) + const base::string16 kEmailPattern = ASCIIToUTF16( + "^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@" + "[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*$"); + return MatchesPattern(text, kEmailPattern); +} + +bool IsValidState(const base::string16& text) { + return !state_names::GetAbbreviationForName(text).empty() || + !state_names::GetNameForAbbreviation(text).empty(); +} + +bool IsValidZip(const base::string16& text) { + const base::string16 kZipPattern = ASCIIToUTF16("^\\d{5}(-\\d{4})?$"); + return MatchesPattern(text, kZipPattern); +} + +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; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/validation.h b/chromium/components/autofill/core/browser/validation.h new file mode 100644 index 00000000000..bdb6574d4ce --- /dev/null +++ b/chromium/components/autofill/core/browser/validation.h @@ -0,0 +1,53 @@ +// 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_CORE_BROWSER_VALIDATION_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_VALIDATION_H_ + +#include "base/strings/string16.h" + +namespace base { +class Time; +} // namespace base; + +namespace autofill { + +// Returns true if |year| and |month| describe a date later than |now|. +// |year| must have 4 digits. +bool IsValidCreditCardExpirationDate(const base::string16& year, + const base::string16& month, + const base::Time& now); +bool IsValidCreditCardExpirationDate(int year, + int month, + const base::Time& now); + +// Returns true if |text| looks like a valid credit card number. +// Uses the Luhn formula to validate the number. +bool IsValidCreditCardNumber(const base::string16& text); + +// Returns true if |text| looks like a valid credit card security code. +bool IsValidCreditCardSecurityCode(const base::string16& text); + +// Returns true if |code| looks like a valid credit card security code +// for the type of credit card designated by |number|. +bool IsValidCreditCardSecurityCode(const base::string16& code, + const base::string16& number); + +// Returns true if |text| looks like a valid e-mail address. +bool IsValidEmailAddress(const base::string16& text); + +// Returns true if |text| is a valid US state name or abbreviation. It is +// case insensitive. Valid for US states only. +bool IsValidState(const base::string16& text); + +// Returns true if |text| looks like a valid zip code. +// Valid for US zip codes only. +bool IsValidZip(const base::string16& text); + +// Returns true if |text| looks like an SSN, with or without separators. +bool IsSSN(const string16& text); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_VALIDATION_H_ diff --git a/chromium/components/autofill/core/browser/validation_unittest.cc b/chromium/components/autofill/core/browser/validation_unittest.cc new file mode 100644 index 00000000000..171dc5fd087 --- /dev/null +++ b/chromium/components/autofill/core/browser/validation_unittest.cc @@ -0,0 +1,199 @@ +// 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 "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "components/autofill/core/browser/validation.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { +namespace { + +struct ExpirationDate { + const char* year; + const char* month; +}; + +struct IntExpirationDate { + const int year; + const int month; +}; + +// 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", + + // A UnionPay card that doesn't pass the Luhn checksum + "6200000000000000", +}; +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 kCurrentDate[]="1 May 2013"; +const ExpirationDate kValidCreditCardExpirationDate[] = { + { "2013", "5" }, // Valid month in current year. + { "2014", "1" }, // Any month in next year. + { "2014", " 1" }, // Whitespace in month. + { " 2014", "1" }, // Whitespace in year. +}; +const IntExpirationDate kValidCreditCardIntExpirationDate[] = { + { 2013, 5 }, // Valid month in current year. + { 2014, 1 }, // Any month in next year. +}; +const ExpirationDate kInvalidCreditCardExpirationDate[] = { + { "2013", "04" }, // Previous month in current year. + { "2012", "12" }, // Any month in previous year. +}; +const IntExpirationDate kInvalidCreditCardIntExpirationDate[] = { + { 2013, 4 }, // Previous month in current year. + { 2012, 12 }, // Any month in previous year. +}; +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" +}; +const char kAmericanExpressCard[] = "341111111111111"; +const char kVisaCard[] = "4111111111111111"; +const char kAmericanExpressCVC[] = "1234"; +const char kVisaCVC[] = "123"; +} // 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, IsValidCreditCardExpirationDate) +{ + base::Time now; + ASSERT_TRUE(base::Time::FromString(kCurrentDate, &now)); + + for (size_t i = 0; i < arraysize(kValidCreditCardExpirationDate); ++i) { + const ExpirationDate& data = kValidCreditCardExpirationDate[i]; + SCOPED_TRACE(data.year); + SCOPED_TRACE(data.month); + EXPECT_TRUE( + autofill::IsValidCreditCardExpirationDate(ASCIIToUTF16(data.year), + ASCIIToUTF16(data.month), + now)); + } + for (size_t i = 0; i < arraysize(kInvalidCreditCardExpirationDate); ++i) { + const ExpirationDate& data = kInvalidCreditCardExpirationDate[i]; + SCOPED_TRACE(data.year); + SCOPED_TRACE(data.month); + EXPECT_TRUE( + !autofill::IsValidCreditCardExpirationDate(ASCIIToUTF16(data.year), + ASCIIToUTF16(data.month), + now)); + } +} + +TEST(AutofillValidation, IsValidCreditCardIntExpirationDate) +{ + base::Time now; + ASSERT_TRUE(base::Time::FromString(kCurrentDate, &now)); + + for (size_t i = 0; i < arraysize(kValidCreditCardIntExpirationDate); ++i) { + const IntExpirationDate& data = kValidCreditCardIntExpirationDate[i]; + SCOPED_TRACE(data.year); + SCOPED_TRACE(data.month); + EXPECT_TRUE( + autofill::IsValidCreditCardExpirationDate(data.year, data.month, now)); + } + for (size_t i = 0; i < arraysize(kInvalidCreditCardIntExpirationDate); ++i) { + const IntExpirationDate& data = kInvalidCreditCardIntExpirationDate[i]; + SCOPED_TRACE(data.year); + SCOPED_TRACE(data.month); + EXPECT_TRUE( + !autofill::IsValidCreditCardExpirationDate(data.year, data.month, now)); + } +} +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]))); + } +} + +TEST(AutofillValidation, IsValidCreditCardSecurityCodeWithNumber) { + EXPECT_TRUE(autofill::IsValidCreditCardSecurityCode( + ASCIIToUTF16(kAmericanExpressCVC), ASCIIToUTF16(kAmericanExpressCard))); + EXPECT_TRUE(autofill::IsValidCreditCardSecurityCode( + ASCIIToUTF16(kVisaCVC), ASCIIToUTF16(kVisaCard))); + EXPECT_FALSE(autofill::IsValidCreditCardSecurityCode( + ASCIIToUTF16(kVisaCVC), ASCIIToUTF16(kAmericanExpressCard))); + EXPECT_FALSE(autofill::IsValidCreditCardSecurityCode( + ASCIIToUTF16(kAmericanExpressCVC), ASCIIToUTF16(kVisaCard))); + EXPECT_TRUE(autofill::IsValidCreditCardSecurityCode( + ASCIIToUTF16(kVisaCVC), ASCIIToUTF16(kInvalidNumbers[0]))); + EXPECT_FALSE(autofill::IsValidCreditCardSecurityCode( + ASCIIToUTF16(kAmericanExpressCVC), ASCIIToUTF16(kInvalidNumbers[0]))); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/webdata/autofill_change.cc b/chromium/components/autofill/core/browser/webdata/autofill_change.cc new file mode 100644 index 00000000000..c02bdd8464d --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_change.cc @@ -0,0 +1,39 @@ +// 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/core/browser/webdata/autofill_change.h" + +#include "base/logging.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" + +namespace autofill { + +AutofillChange::AutofillChange(Type type, const AutofillKey& key) + : GenericAutofillChange<AutofillKey>(type, key) { +} + +AutofillChange::~AutofillChange() { +} + +AutofillProfileChange::AutofillProfileChange( + Type type, const std::string& key, const AutofillProfile* profile) + : GenericAutofillChange<std::string>(type, key), + profile_(profile) { + DCHECK(type == ADD ? (profile && profile->guid() == key) : true); + DCHECK(type == UPDATE ? (profile && profile->guid() == key) : true); + DCHECK(type == REMOVE ? !profile : true); +} + +AutofillProfileChange::~AutofillProfileChange() { +} + +bool AutofillProfileChange::operator==( + const AutofillProfileChange& change) const { + return type() == change.type() && + key() == change.key() && + (type() != REMOVE) ? *profile() == *change.profile() : true; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/webdata/autofill_change.h b/chromium/components/autofill/core/browser/webdata/autofill_change.h new file mode 100644 index 00000000000..458ac6d2e42 --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_change.h @@ -0,0 +1,76 @@ +// 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_CORE_BROWSER_WEBDATA_AUTOFILL_CHANGE_H__ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_CHANGE_H__ + +#include <vector> + +#include "components/autofill/core/browser/webdata/autofill_entry.h" + +namespace autofill { + +class AutofillProfile; +class CreditCard; + +// For classic Autofill form fields, the KeyType is AutofillKey. +// Autofill++ types such as AutofillProfile and CreditCard simply use an int. +template <typename KeyType> +class GenericAutofillChange { + public: + typedef enum { + ADD, + UPDATE, + REMOVE + } Type; + + virtual ~GenericAutofillChange() {} + + Type type() const { return type_; } + const KeyType& key() const { return key_; } + + protected: + GenericAutofillChange(Type type, const KeyType& key) + : type_(type), + key_(key) {} + private: + Type type_; + KeyType key_; +}; + +class AutofillChange : public GenericAutofillChange<AutofillKey> { + public: + AutofillChange(Type type, const AutofillKey& key); + virtual ~AutofillChange(); + bool operator==(const AutofillChange& change) const { + return type() == change.type() && key() == change.key(); + } +}; + +typedef std::vector<AutofillChange> AutofillChangeList; + +// Change notification details for Autofill profile changes. +class AutofillProfileChange : public GenericAutofillChange<std::string> { + public: + // The |type| input specifies the change type. The |key| input is the key, + // which is expected to be the GUID identifying the |profile|. + // When |type| == ADD, |profile| should be non-NULL. + // When |type| == UPDATE, |profile| should be non-NULL. + // When |type| == REMOVE, |profile| should be NULL. + AutofillProfileChange(Type type, + const std::string& key, + const AutofillProfile* profile); + virtual ~AutofillProfileChange(); + + const AutofillProfile* profile() const { return profile_; } + bool operator==(const AutofillProfileChange& change) const; + + private: + // Weak reference, can be NULL. + const AutofillProfile* profile_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_CHANGE_H__ diff --git a/chromium/components/autofill/core/browser/webdata/autofill_entry.cc b/chromium/components/autofill/core/browser/webdata/autofill_entry.cc new file mode 100644 index 00000000000..5cdc237d88c --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_entry.cc @@ -0,0 +1,129 @@ +// 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/core/browser/webdata/autofill_entry.h" + +#include <algorithm> +#include <set> + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" + +namespace autofill { +namespace { + +// The period after which Autofill entries should expire in days. +const int64 kExpirationPeriodInDays = 60; + +} // namespace + +AutofillKey::AutofillKey() {} + +AutofillKey::AutofillKey(const base::string16& name, + const base::string16& value) + : name_(name), + value_(value) { +} + +AutofillKey::AutofillKey(const char* name, const char* value) + : name_(UTF8ToUTF16(name)), + value_(UTF8ToUTF16(value)) { +} + +AutofillKey::AutofillKey(const AutofillKey& key) + : name_(key.name()), + value_(key.value()) { +} + +AutofillKey::~AutofillKey() {} + +bool AutofillKey::operator==(const AutofillKey& key) const { + return name_ == key.name() && value_ == key.value(); +} + +bool AutofillKey::operator<(const AutofillKey& key) const { + int diff = name_.compare(key.name()); + if (diff < 0) { + return true; + } else if (diff == 0) { + return value_.compare(key.value()) < 0; + } else { + return false; + } +} + +AutofillEntry::AutofillEntry(const AutofillKey& key, + const std::vector<base::Time>& timestamps) + : key_(key) { + timestamps_culled_ = CullTimeStamps(timestamps, ×tamps_); +} + +AutofillEntry::~AutofillEntry() {} + +bool AutofillEntry::operator==(const AutofillEntry& entry) const { + if (!(key_ == entry.key())) + return false; + + if (timestamps_.size() != entry.timestamps().size()) + return false; + + std::set<base::Time> other_timestamps(entry.timestamps().begin(), + entry.timestamps().end()); + for (size_t i = 0; i < timestamps_.size(); i++) { + if (other_timestamps.count(timestamps_[i]) == 0) + return false; + } + + return true; +} + +bool AutofillEntry::operator<(const AutofillEntry& entry) const { + return key_ < entry.key(); +} + +bool AutofillEntry::IsExpired() const { + base::Time time = ExpirationTime(); + // TODO(georgey): add DCHECK(!timestamps_.empty()) after conversion of the db + // is complete. + return (timestamps_.empty() || timestamps_.back() < time); +} + +// static +base::Time AutofillEntry::ExpirationTime() { + return base::Time::Now() - base::TimeDelta::FromDays(kExpirationPeriodInDays); +} + +// Culls the list of timestamps to the first and last used. +// If sync is enabled, at every browser restart, sync will do a match up of all +// autofill items on the server with all items on the web db. When webdb loads +// all the items in memory(for sync to process. The method is +// |GetAllAutofillEntries|) they will pass through this method for culling. If +// sync finds any of these items were culled it will updates the server AND the +// web db with these new timestamps. However after restart if an autofill item +// exceeds the |kMaxAutofillTimeStamps| it will NOT be written to web db until +// restart. But sync when uploading to the server will only upload this culled +// list. Meaning until restart there will be mis-match in timestamps but +// it should correct itself at startup. +bool AutofillEntry::CullTimeStamps(const std::vector<base::Time>& source, + std::vector<base::Time>* result) { + DCHECK(result); + DCHECK(&source != result); + + // First copy the source to result. + result->clear(); + + if (source.size() <= 2) { + result->insert(result->begin(), source.begin(), source.end()); + return false; + } + + result->push_back(source.front()); + result->push_back(source.back()); + + DVLOG(1) << "Culling timestamps. Current count is : " << source.size(); + + return true; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/webdata/autofill_entry.h b/chromium/components/autofill/core/browser/webdata/autofill_entry.h new file mode 100644 index 00000000000..33a467df59e --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_entry.h @@ -0,0 +1,75 @@ +// 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_CORE_BROWSER_WEBDATA_AUTOFILL_ENTRY_H__ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_ENTRY_H__ + +#include <stddef.h> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/strings/string16.h" +#include "base/time/time.h" + +namespace autofill { + +class AutofillKey { + public: + AutofillKey(); + AutofillKey(const base::string16& name, const base::string16& value); + AutofillKey(const char* name, const char* value); + AutofillKey(const AutofillKey& key); + virtual ~AutofillKey(); + + const base::string16& name() const { return name_; } + const base::string16& value() const { return value_; } + + bool operator==(const AutofillKey& key) const; + bool operator<(const AutofillKey& key) const; + + private: + base::string16 name_; + base::string16 value_; +}; + +class AutofillEntry { + public: + AutofillEntry(const AutofillKey& key, + const std::vector<base::Time>& timestamps); + ~AutofillEntry(); + + const AutofillKey& key() const { return key_; } + const std::vector<base::Time>& timestamps() const { return timestamps_; } + + bool operator==(const AutofillEntry& entry) const; + bool operator<(const AutofillEntry& entry) const; + + bool timestamps_culled() const { return timestamps_culled_; } + + // Checks if last of the timestamps are older than ExpirationTime(). + bool IsExpired() const; + + // The entries last accessed before this time should expire. + static base::Time ExpirationTime(); + + private: + FRIEND_TEST_ALL_PREFIXES(AutofillEntryTest, NoCulling); + FRIEND_TEST_ALL_PREFIXES(AutofillEntryTest, Culling); + FRIEND_TEST_ALL_PREFIXES(AutofillEntryTest, CullByTime); + + // Culls the list of timestamps to 2 - the oldest and most recent. This is a + // precursor to getting rid of the timestamps db altogether. + // See http://crbug.com/118696. + // |source| is expected to be sorted from oldest to newest. + static bool CullTimeStamps(const std::vector<base::Time>& source, + std::vector<base::Time>* result); + + AutofillKey key_; + std::vector<base::Time> timestamps_; + bool timestamps_culled_; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_ENTRY_H__ diff --git a/chromium/components/autofill/core/browser/webdata/autofill_entry_unittest.cc b/chromium/components/autofill/core/browser/webdata/autofill_entry_unittest.cc new file mode 100644 index 00000000000..bef7d578cbf --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_entry_unittest.cc @@ -0,0 +1,82 @@ +// 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 <algorithm> + +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "components/autofill/core/browser/webdata/autofill_entry.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { + +const unsigned int kMaxAutofillTimeStamps = 2; + +TEST(AutofillEntryTest, NoCulling) { + std::vector<base::Time> source, result; + base::Time current = base::Time::Now(); + for (size_t i = 0; i < kMaxAutofillTimeStamps; ++i) + source.push_back(current); + + EXPECT_FALSE(AutofillEntry::CullTimeStamps(source, &result)); + EXPECT_EQ(result.size(), kMaxAutofillTimeStamps); + for (std::vector<base::Time>::const_iterator it = result.begin(); + it != result.end(); ++it) { + EXPECT_EQ(*it, current); + } +} + +TEST(AutofillEntryTest, Culling) { + std::vector<base::Time> source, result; + base::Time current = base::Time::Now(); + const int offset = 10000; + + int64 internal_value = current.ToInternalValue(); + for (size_t i = 0; i < kMaxAutofillTimeStamps * 2 ; ++i) { + source.push_back(base::Time::FromInternalValue( + internal_value + i * offset)); + } + std::sort(source.begin(), source.end()); + EXPECT_TRUE(AutofillEntry::CullTimeStamps(source, &result)); + + EXPECT_EQ(result.size(), kMaxAutofillTimeStamps); + EXPECT_EQ(result.front(), base::Time::FromInternalValue(internal_value)); + int last_offset = (kMaxAutofillTimeStamps * 2 - 1) * offset; + EXPECT_EQ(result.back(), + base::Time::FromInternalValue(last_offset + internal_value)); +} + +TEST(AutofillEntryTest, CullByTime) { + base::TimeDelta one_hour = base::TimeDelta::FromHours(1); + + std::vector<base::Time> timestamps; + base::Time cutoff_time = AutofillEntry::ExpirationTime(); + + // Within the time limit. + timestamps.push_back(cutoff_time + one_hour); + + AutofillKey key(UTF8ToUTF16("test_key"), UTF8ToUTF16("test_value")); + + AutofillEntry entry_within_the_limits(key, timestamps); + EXPECT_FALSE(entry_within_the_limits.IsExpired()); + + // One within the time limit, one outside. + timestamps.push_back(cutoff_time - one_hour); + + AutofillEntry entry_partially_within_the_limits(key, timestamps); + EXPECT_TRUE( + entry_partially_within_the_limits.IsExpired()); + + // All outside the time limit. + timestamps.clear(); + timestamps.push_back(cutoff_time - one_hour); + timestamps.push_back(cutoff_time - one_hour * 2); + timestamps.push_back(cutoff_time - one_hour * 3); + + AutofillEntry entry_outside_the_limits(key, timestamps); + EXPECT_TRUE(entry_outside_the_limits.IsExpired()); + EXPECT_TRUE(entry_outside_the_limits.timestamps_culled()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/webdata/autofill_table.cc b/chromium/components/autofill/core/browser/webdata/autofill_table.cc new file mode 100644 index 00000000000..9066d9c0df0 --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_table.cc @@ -0,0 +1,2193 @@ +// 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/core/browser/webdata/autofill_table.h" + +#include <algorithm> +#include <limits> +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/guid.h" +#include "base/i18n/case_conversion.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/time/time.h" +#include "base/tuple.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/personal_data_manager.h" +#include "components/autofill/core/browser/webdata/autofill_change.h" +#include "components/autofill/core/browser/webdata/autofill_entry.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/webdata/common/web_database.h" +#include "components/webdata/encryptor/encryptor.h" +#include "sql/statement.h" +#include "sql/transaction.h" +#include "ui/base/l10n/l10n_util.h" +#include "url/gurl.h" + +using base::Time; + +namespace autofill { +namespace { + +typedef std::vector<Tuple3<int64, base::string16, base::string16> > + AutofillElementList; + +// TODO(dhollowa): Find a common place for this. It is duplicated in +// personal_data_manager.cc. +template<typename T> +T* address_of(T& v) { + return &v; +} + +base::string16 LimitDataSize(const base::string16& data) { + if (data.size() > AutofillTable::kMaxDataLength) + return data.substr(0, AutofillTable::kMaxDataLength); + + return data; +} + +void BindAutofillProfileToStatement(const AutofillProfile& profile, + sql::Statement* s, + const std::string& app_locale) { + DCHECK(base::IsValidGUID(profile.guid())); + s->BindString(0, profile.guid()); + + base::string16 text = profile.GetRawInfo(COMPANY_NAME); + s->BindString16(1, LimitDataSize(text)); + text = profile.GetRawInfo(ADDRESS_HOME_LINE1); + s->BindString16(2, LimitDataSize(text)); + text = profile.GetRawInfo(ADDRESS_HOME_LINE2); + s->BindString16(3, LimitDataSize(text)); + text = profile.GetRawInfo(ADDRESS_HOME_CITY); + s->BindString16(4, LimitDataSize(text)); + text = profile.GetRawInfo(ADDRESS_HOME_STATE); + s->BindString16(5, LimitDataSize(text)); + text = profile.GetRawInfo(ADDRESS_HOME_ZIP); + s->BindString16(6, LimitDataSize(text)); + text = profile.GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), app_locale); + s->BindString16(7, LimitDataSize(text)); + text = profile.GetRawInfo(ADDRESS_HOME_COUNTRY); + s->BindString16(8, LimitDataSize(text)); + s->BindInt64(9, Time::Now().ToTimeT()); + s->BindString(10, profile.origin()); +} + +AutofillProfile* AutofillProfileFromStatement(const sql::Statement& s, + const std::string& app_locale) { + AutofillProfile* profile = new AutofillProfile; + profile->set_guid(s.ColumnString(0)); + DCHECK(base::IsValidGUID(profile->guid())); + + profile->SetRawInfo(COMPANY_NAME, s.ColumnString16(1)); + profile->SetRawInfo(ADDRESS_HOME_LINE1, s.ColumnString16(2)); + profile->SetRawInfo(ADDRESS_HOME_LINE2, s.ColumnString16(3)); + profile->SetRawInfo(ADDRESS_HOME_CITY, s.ColumnString16(4)); + profile->SetRawInfo(ADDRESS_HOME_STATE, s.ColumnString16(5)); + profile->SetRawInfo(ADDRESS_HOME_ZIP, s.ColumnString16(6)); + // Intentionally skip column 7, which stores the localized country name. + profile->SetRawInfo(ADDRESS_HOME_COUNTRY, s.ColumnString16(8)); + // Intentionally skip column 9, which stores the profile's modification date. + profile->set_origin(s.ColumnString(10)); + + return profile; +} + +void BindCreditCardToStatement(const CreditCard& credit_card, + sql::Statement* s) { + DCHECK(base::IsValidGUID(credit_card.guid())); + s->BindString(0, credit_card.guid()); + + base::string16 text = credit_card.GetRawInfo(CREDIT_CARD_NAME); + s->BindString16(1, LimitDataSize(text)); + text = credit_card.GetRawInfo(CREDIT_CARD_EXP_MONTH); + s->BindString16(2, LimitDataSize(text)); + text = credit_card.GetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR); + s->BindString16(3, LimitDataSize(text)); + text = credit_card.GetRawInfo(CREDIT_CARD_NUMBER); + std::string encrypted_data; + Encryptor::EncryptString16(text, &encrypted_data); + s->BindBlob(4, encrypted_data.data(), + static_cast<int>(encrypted_data.length())); + s->BindInt64(5, Time::Now().ToTimeT()); + s->BindString(6, credit_card.origin()); +} + +CreditCard* CreditCardFromStatement(const sql::Statement& s) { + CreditCard* credit_card = new CreditCard; + + credit_card->set_guid(s.ColumnString(0)); + DCHECK(base::IsValidGUID(credit_card->guid())); + + credit_card->SetRawInfo(CREDIT_CARD_NAME, s.ColumnString16(1)); + credit_card->SetRawInfo(CREDIT_CARD_EXP_MONTH, s.ColumnString16(2)); + credit_card->SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, s.ColumnString16(3)); + int encrypted_number_len = s.ColumnByteLength(4); + base::string16 credit_card_number; + if (encrypted_number_len) { + std::string encrypted_number; + encrypted_number.resize(encrypted_number_len); + memcpy(&encrypted_number[0], s.ColumnBlob(4), encrypted_number_len); + Encryptor::DecryptString16(encrypted_number, &credit_card_number); + } + credit_card->SetRawInfo(CREDIT_CARD_NUMBER, credit_card_number); + // Intentionally skip column 5, which stores the modification date. + credit_card->set_origin(s.ColumnString(6)); + + return credit_card; +} + +bool AddAutofillProfileNamesToProfile(sql::Connection* db, + AutofillProfile* profile) { + sql::Statement s(db->GetUniqueStatement( + "SELECT guid, first_name, middle_name, last_name " + "FROM autofill_profile_names " + "WHERE guid=?")); + s.BindString(0, profile->guid()); + + if (!s.is_valid()) + return false; + + std::vector<base::string16> first_names; + std::vector<base::string16> middle_names; + std::vector<base::string16> last_names; + while (s.Step()) { + DCHECK_EQ(profile->guid(), s.ColumnString(0)); + first_names.push_back(s.ColumnString16(1)); + middle_names.push_back(s.ColumnString16(2)); + last_names.push_back(s.ColumnString16(3)); + } + if (!s.Succeeded()) + return false; + + profile->SetRawMultiInfo(NAME_FIRST, first_names); + profile->SetRawMultiInfo(NAME_MIDDLE, middle_names); + profile->SetRawMultiInfo(NAME_LAST, last_names); + return true; +} + +bool AddAutofillProfileEmailsToProfile(sql::Connection* db, + AutofillProfile* profile) { + sql::Statement s(db->GetUniqueStatement( + "SELECT guid, email " + "FROM autofill_profile_emails " + "WHERE guid=?")); + s.BindString(0, profile->guid()); + + if (!s.is_valid()) + return false; + + std::vector<base::string16> emails; + while (s.Step()) { + DCHECK_EQ(profile->guid(), s.ColumnString(0)); + emails.push_back(s.ColumnString16(1)); + } + if (!s.Succeeded()) + return false; + + profile->SetRawMultiInfo(EMAIL_ADDRESS, emails); + return true; +} + +bool AddAutofillProfilePhonesToProfile(sql::Connection* db, + AutofillProfile* profile) { + sql::Statement s(db->GetUniqueStatement( + "SELECT guid, type, number " + "FROM autofill_profile_phones " + "WHERE guid=? AND type=?")); + + // Value used to be either [(0, phone), (1, fax)] but fax has been removed. + s.BindString(0, profile->guid()); + s.BindInt(1, 0); + + if (!s.is_valid()) + return false; + + std::vector<base::string16> numbers; + while (s.Step()) { + DCHECK_EQ(profile->guid(), s.ColumnString(0)); + numbers.push_back(s.ColumnString16(2)); + } + if (!s.Succeeded()) + return false; + + profile->SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, numbers); + return true; +} + +bool AddAutofillProfileNames(const AutofillProfile& profile, + sql::Connection* db) { + std::vector<base::string16> first_names; + profile.GetRawMultiInfo(NAME_FIRST, &first_names); + std::vector<base::string16> middle_names; + profile.GetRawMultiInfo(NAME_MIDDLE, &middle_names); + std::vector<base::string16> last_names; + profile.GetRawMultiInfo(NAME_LAST, &last_names); + DCHECK_EQ(first_names.size(), middle_names.size()); + DCHECK_EQ(middle_names.size(), last_names.size()); + + for (size_t i = 0; i < first_names.size(); ++i) { + // Add the new name. + sql::Statement s(db->GetUniqueStatement( + "INSERT INTO autofill_profile_names" + " (guid, first_name, middle_name, last_name) " + "VALUES (?,?,?,?)")); + s.BindString(0, profile.guid()); + s.BindString16(1, first_names[i]); + s.BindString16(2, middle_names[i]); + s.BindString16(3, last_names[i]); + + if (!s.Run()) + return false; + } + return true; +} + +bool AddAutofillProfileEmails(const AutofillProfile& profile, + sql::Connection* db) { + std::vector<base::string16> emails; + profile.GetRawMultiInfo(EMAIL_ADDRESS, &emails); + + for (size_t i = 0; i < emails.size(); ++i) { + // Add the new email. + sql::Statement s(db->GetUniqueStatement( + "INSERT INTO autofill_profile_emails" + " (guid, email) " + "VALUES (?,?)")); + s.BindString(0, profile.guid()); + s.BindString16(1, emails[i]); + + if (!s.Run()) + return false; + } + + return true; +} + +bool AddAutofillProfilePhones(const AutofillProfile& profile, + sql::Connection* db) { + std::vector<base::string16> numbers; + profile.GetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, &numbers); + + for (size_t i = 0; i < numbers.size(); ++i) { + // Add the new number. + sql::Statement s(db->GetUniqueStatement( + "INSERT INTO autofill_profile_phones" + " (guid, type, number) " + "VALUES (?,?,?)")); + s.BindString(0, profile.guid()); + // Value used to be either [(0, phone), (1, fax)] but fax has been removed. + s.BindInt(1, 0); + s.BindString16(2, numbers[i]); + + if (!s.Run()) + return false; + } + + return true; +} + +bool AddAutofillProfilePieces(const AutofillProfile& profile, + sql::Connection* db) { + if (!AddAutofillProfileNames(profile, db)) + return false; + + if (!AddAutofillProfileEmails(profile, db)) + return false; + + if (!AddAutofillProfilePhones(profile, db)) + return false; + + return true; +} + +bool RemoveAutofillProfilePieces(const std::string& guid, sql::Connection* db) { + sql::Statement s1(db->GetUniqueStatement( + "DELETE FROM autofill_profile_names WHERE guid = ?")); + s1.BindString(0, guid); + + if (!s1.Run()) + return false; + + sql::Statement s2(db->GetUniqueStatement( + "DELETE FROM autofill_profile_emails WHERE guid = ?")); + s2.BindString(0, guid); + + if (!s2.Run()) + return false; + + sql::Statement s3(db->GetUniqueStatement( + "DELETE FROM autofill_profile_phones WHERE guid = ?")); + s3.BindString(0, guid); + + return s3.Run(); +} + +WebDatabaseTable::TypeKey GetKey() { + // We just need a unique constant. Use the address of a static that + // COMDAT folding won't touch in an optimizing linker. + static int table_key = 0; + return reinterpret_cast<void*>(&table_key); +} + +time_t GetEndTime(const base::Time& end) { + if (end.is_null() || end == base::Time::Max()) + return std::numeric_limits<time_t>::max(); + + return end.ToTimeT(); +} + +} // namespace + +// The maximum length allowed for form data. +const size_t AutofillTable::kMaxDataLength = 1024; + +AutofillTable::AutofillTable(const std::string& app_locale) + : app_locale_(app_locale) { +} + +AutofillTable::~AutofillTable() { +} + +AutofillTable* AutofillTable::FromWebDatabase(WebDatabase* db) { + return static_cast<AutofillTable*>(db->GetTable(GetKey())); +} + +WebDatabaseTable::TypeKey AutofillTable::GetTypeKey() const { + return GetKey(); +} + +bool AutofillTable::Init(sql::Connection* db, sql::MetaTable* meta_table) { + WebDatabaseTable::Init(db, meta_table); + return (InitMainTable() && InitCreditCardsTable() && InitDatesTable() && + InitProfilesTable() && InitProfileNamesTable() && + InitProfileEmailsTable() && InitProfilePhonesTable() && + InitProfileTrashTable()); +} + +bool AutofillTable::IsSyncable() { + return true; +} + +bool AutofillTable::MigrateToVersion(int version, + bool* update_compatible_version) { + // Migrate if necessary. + switch (version) { + case 22: + return ClearAutofillEmptyValueElements(); + case 23: + return MigrateToVersion23AddCardNumberEncryptedColumn(); + case 24: + return MigrateToVersion24CleanupOversizedStringFields(); + case 27: + *update_compatible_version = true; + return MigrateToVersion27UpdateLegacyCreditCards(); + case 30: + *update_compatible_version = true; + return MigrateToVersion30AddDateModifed(); + case 31: + *update_compatible_version = true; + return MigrateToVersion31AddGUIDToCreditCardsAndProfiles(); + case 32: + *update_compatible_version = true; + return MigrateToVersion32UpdateProfilesAndCreditCards(); + case 33: + *update_compatible_version = true; + return MigrateToVersion33ProfilesBasedOnFirstName(); + case 34: + *update_compatible_version = true; + return MigrateToVersion34ProfilesBasedOnCountryCode(); + case 35: + *update_compatible_version = true; + return MigrateToVersion35GreatBritainCountryCodes(); + // Combine migrations 36 and 37. This is due to enhancements to the merge + // step when migrating profiles. The original migration from 35 to 36 did + // not merge profiles with identical addresses, but the migration from 36 to + // 37 does. The step from 35 to 36 should only happen on the Chrome 12 dev + // channel. Chrome 12 beta and release users will jump from 35 to 37 + // directly getting the full benefits of the multi-valued merge as well as + // the culling of bad data. + case 37: + *update_compatible_version = true; + return MigrateToVersion37MergeAndCullOlderProfiles(); + case 51: + // Combine migrations 50 and 51. The migration code from version 49 to 50 + // worked correctly for users with existing 'origin' columns, but failed + // to create these columns for new users. + return MigrateToVersion51AddOriginColumn(); + } + return true; +} + +bool AutofillTable::AddFormFieldValues( + const std::vector<FormFieldData>& elements, + std::vector<AutofillChange>* changes) { + return AddFormFieldValuesTime(elements, changes, Time::Now()); +} + +bool AutofillTable::AddFormFieldValue(const FormFieldData& element, + std::vector<AutofillChange>* changes) { + return AddFormFieldValueTime(element, changes, Time::Now()); +} + +bool AutofillTable::GetFormValuesForElementName( + const base::string16& name, + const base::string16& prefix, + std::vector<base::string16>* values, + int limit) { + DCHECK(values); + sql::Statement s; + + if (prefix.empty()) { + s.Assign(db_->GetUniqueStatement( + "SELECT value FROM autofill " + "WHERE name = ? " + "ORDER BY count DESC " + "LIMIT ?")); + s.BindString16(0, name); + s.BindInt(1, limit); + } else { + base::string16 prefix_lower = base::i18n::ToLower(prefix); + base::string16 next_prefix = prefix_lower; + next_prefix[next_prefix.length() - 1]++; + + s.Assign(db_->GetUniqueStatement( + "SELECT value FROM autofill " + "WHERE name = ? AND " + "value_lower >= ? AND " + "value_lower < ? " + "ORDER BY count DESC " + "LIMIT ?")); + s.BindString16(0, name); + s.BindString16(1, prefix_lower); + s.BindString16(2, next_prefix); + s.BindInt(3, limit); + } + + values->clear(); + while (s.Step()) + values->push_back(s.ColumnString16(0)); + return s.Succeeded(); +} + +bool AutofillTable::HasFormElements() { + sql::Statement s(db_->GetUniqueStatement( + "SELECT COUNT(*) FROM autofill")); + if (!s.Step()) { + NOTREACHED(); + return false; + } + return s.ColumnInt(0) > 0; +} + +bool AutofillTable::RemoveFormElementsAddedBetween( + const Time& delete_begin, + const Time& delete_end, + std::vector<AutofillChange>* changes) { + DCHECK(changes); + // Query for the pair_id, name, and value of all form elements that + // were used between the given times. + sql::Statement s(db_->GetUniqueStatement( + "SELECT DISTINCT a.pair_id, a.name, a.value " + "FROM autofill_dates ad JOIN autofill a ON ad.pair_id = a.pair_id " + "WHERE ad.date_created >= ? AND ad.date_created < ?")); + s.BindInt64(0, delete_begin.ToTimeT()); + s.BindInt64(1, + (delete_end.is_null() || delete_end == base::Time::Max()) ? + std::numeric_limits<int64>::max() : + delete_end.ToTimeT()); + + AutofillElementList elements; + while (s.Step()) { + elements.push_back(MakeTuple(s.ColumnInt64(0), + s.ColumnString16(1), + s.ColumnString16(2))); + } + if (!s.Succeeded()) + return false; + + for (AutofillElementList::iterator itr = elements.begin(); + itr != elements.end(); ++itr) { + int how_many = 0; + if (!RemoveFormElementForTimeRange(itr->a, delete_begin, delete_end, + &how_many)) { + return false; + } + // We store at most 2 time stamps. If we remove both of them we should + // delete the corresponding data. If we delete only one it could still be + // the last timestamp for the data, so check how many timestamps do remain. + bool should_remove = (CountTimestampsData(itr->a) == 0); + if (should_remove) { + if (!RemoveFormElementForID(itr->a)) + return false; + } else { + if (!AddToCountOfFormElement(itr->a, -how_many)) + return false; + } + AutofillChange::Type change_type = + should_remove ? AutofillChange::REMOVE : AutofillChange::UPDATE; + changes->push_back(AutofillChange(change_type, + AutofillKey(itr->b, itr->c))); + } + + return true; +} + +bool AutofillTable::RemoveExpiredFormElements( + std::vector<AutofillChange>* changes) { + DCHECK(changes); + + base::Time delete_end = AutofillEntry::ExpirationTime(); + // Query for the pair_id, name, and value of all form elements that + // were last used before the |delete_end|. + sql::Statement select_for_delete(db_->GetUniqueStatement( + "SELECT DISTINCT pair_id, name, value " + "FROM autofill WHERE pair_id NOT IN " + "(SELECT DISTINCT pair_id " + "FROM autofill_dates WHERE date_created >= ?)")); + select_for_delete.BindInt64(0, delete_end.ToTimeT()); + AutofillElementList entries_to_delete; + while (select_for_delete.Step()) { + entries_to_delete.push_back(MakeTuple(select_for_delete.ColumnInt64(0), + select_for_delete.ColumnString16(1), + select_for_delete.ColumnString16(2))); + } + + if (!select_for_delete.Succeeded()) + return false; + + sql::Statement delete_data_statement(db_->GetUniqueStatement( + "DELETE FROM autofill WHERE pair_id NOT IN (" + "SELECT pair_id FROM autofill_dates WHERE date_created >= ?)")); + delete_data_statement.BindInt64(0, delete_end.ToTimeT()); + if (!delete_data_statement.Run()) + return false; + + sql::Statement delete_times_statement(db_->GetUniqueStatement( + "DELETE FROM autofill_dates WHERE pair_id NOT IN (" + "SELECT pair_id FROM autofill_dates WHERE date_created >= ?)")); + delete_times_statement.BindInt64(0, delete_end.ToTimeT()); + if (!delete_times_statement.Run()) + return false; + + // Cull remaining entries' timestamps. + std::vector<AutofillEntry> entries; + if (!GetAllAutofillEntries(&entries)) + return false; + sql::Statement cull_date_entry(db_->GetUniqueStatement( + "DELETE FROM autofill_dates " + "WHERE pair_id == (SELECT pair_id FROM autofill " + "WHERE name = ? and value = ?)" + "AND date_created != ? AND date_created != ?")); + for (size_t i = 0; i < entries.size(); ++i) { + cull_date_entry.BindString16(0, entries[i].key().name()); + cull_date_entry.BindString16(1, entries[i].key().value()); + cull_date_entry.BindInt64(2, + entries[i].timestamps().empty() ? 0 : + entries[i].timestamps().front().ToTimeT()); + cull_date_entry.BindInt64(3, + entries[i].timestamps().empty() ? 0 : + entries[i].timestamps().back().ToTimeT()); + if (!cull_date_entry.Run()) + return false; + cull_date_entry.Reset(true); + } + + changes->clear(); + changes->reserve(entries_to_delete.size()); + + for (AutofillElementList::iterator it = entries_to_delete.begin(); + it != entries_to_delete.end(); ++it) { + changes->push_back(AutofillChange( + AutofillChange::REMOVE, AutofillKey(it->b, it->c))); + } + return true; +} + +bool AutofillTable::RemoveFormElementForTimeRange(int64 pair_id, + const Time& delete_begin, + const Time& delete_end, + int* how_many) { + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM autofill_dates WHERE pair_id = ? AND " + "date_created >= ? AND date_created < ?")); + s.BindInt64(0, pair_id); + s.BindInt64(1, delete_begin.is_null() ? 0 : delete_begin.ToTimeT()); + s.BindInt64(2, delete_end.is_null() ? std::numeric_limits<int64>::max() : + delete_end.ToTimeT()); + + bool result = s.Run(); + if (how_many) + *how_many = db_->GetLastChangeCount(); + + return result; +} + +int AutofillTable::CountTimestampsData(int64 pair_id) { + sql::Statement s(db_->GetUniqueStatement( + "SELECT COUNT(*) FROM autofill_dates WHERE pair_id = ?")); + s.BindInt64(0, pair_id); + if (!s.Step()) { + NOTREACHED(); + return 0; + } else { + return s.ColumnInt(0); + } +} + +bool AutofillTable::AddToCountOfFormElement(int64 pair_id, + int delta) { + int count = 0; + + if (!GetCountOfFormElement(pair_id, &count)) + return false; + + if (count + delta == 0) { + // Should remove the element earlier in the code. + NOTREACHED(); + return false; + } else { + if (!SetCountOfFormElement(pair_id, count + delta)) + return false; + } + return true; +} + +bool AutofillTable::GetIDAndCountOfFormElement( + const FormFieldData& element, + int64* pair_id, + int* count) { + DCHECK(pair_id); + DCHECK(count); + + sql::Statement s(db_->GetUniqueStatement( + "SELECT pair_id, count FROM autofill " + "WHERE name = ? AND value = ?")); + s.BindString16(0, element.name); + s.BindString16(1, element.value); + + if (!s.is_valid()) + return false; + + *pair_id = 0; + *count = 0; + + if (s.Step()) { + *pair_id = s.ColumnInt64(0); + *count = s.ColumnInt(1); + } + + return true; +} + +bool AutofillTable::GetCountOfFormElement(int64 pair_id, int* count) { + DCHECK(count); + sql::Statement s(db_->GetUniqueStatement( + "SELECT count FROM autofill WHERE pair_id = ?")); + s.BindInt64(0, pair_id); + + if (s.Step()) { + *count = s.ColumnInt(0); + return true; + } + return false; +} + +bool AutofillTable::SetCountOfFormElement(int64 pair_id, int count) { + sql::Statement s(db_->GetUniqueStatement( + "UPDATE autofill SET count = ? WHERE pair_id = ?")); + s.BindInt(0, count); + s.BindInt64(1, pair_id); + + return s.Run(); +} + +bool AutofillTable::InsertFormElement(const FormFieldData& element, + int64* pair_id) { + DCHECK(pair_id); + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO autofill (name, value, value_lower) VALUES (?,?,?)")); + s.BindString16(0, element.name); + s.BindString16(1, element.value); + s.BindString16(2, base::i18n::ToLower(element.value)); + + if (!s.Run()) + return false; + + *pair_id = db_->GetLastInsertRowId(); + return true; +} + +bool AutofillTable::InsertPairIDAndDate(int64 pair_id, + const Time& date_created) { + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO autofill_dates " + "(pair_id, date_created) VALUES (?, ?)")); + s.BindInt64(0, pair_id); + s.BindInt64(1, date_created.ToTimeT()); + + return s.Run(); +} + +bool AutofillTable::DeleteLastAccess(int64 pair_id) { + // Inner SELECT selects the newest |date_created| for a given |pair_id|. + // DELETE deletes only that entry. + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM autofill_dates WHERE pair_id = ? and date_created IN " + "(SELECT date_created FROM autofill_dates WHERE pair_id = ? " + "ORDER BY date_created DESC LIMIT 1)")); + s.BindInt64(0, pair_id); + s.BindInt64(1, pair_id); + + return s.Run(); +} + +bool AutofillTable::AddFormFieldValuesTime( + const std::vector<FormFieldData>& elements, + std::vector<AutofillChange>* changes, + Time time) { + // Only add one new entry for each unique element name. Use |seen_names| to + // track this. Add up to |kMaximumUniqueNames| unique entries per form. + const size_t kMaximumUniqueNames = 256; + std::set<base::string16> seen_names; + bool result = true; + for (std::vector<FormFieldData>::const_iterator itr = elements.begin(); + itr != elements.end(); ++itr) { + if (seen_names.size() >= kMaximumUniqueNames) + break; + if (seen_names.find(itr->name) != seen_names.end()) + continue; + result = result && AddFormFieldValueTime(*itr, changes, time); + seen_names.insert(itr->name); + } + return result; +} + +bool AutofillTable::ClearAutofillEmptyValueElements() { + sql::Statement s(db_->GetUniqueStatement( + "SELECT pair_id FROM autofill WHERE TRIM(value)= \"\"")); + if (!s.is_valid()) + return false; + + std::set<int64> ids; + while (s.Step()) + ids.insert(s.ColumnInt64(0)); + if (!s.Succeeded()) + return false; + + bool success = true; + for (std::set<int64>::const_iterator iter = ids.begin(); iter != ids.end(); + ++iter) { + if (!RemoveFormElementForID(*iter)) + success = false; + } + + return success; +} + +bool AutofillTable::GetAllAutofillEntries(std::vector<AutofillEntry>* entries) { + DCHECK(entries); + sql::Statement s(db_->GetUniqueStatement( + "SELECT name, value, date_created FROM autofill a JOIN " + "autofill_dates ad ON a.pair_id=ad.pair_id")); + + bool first_entry = true; + AutofillKey* current_key_ptr = NULL; + std::vector<Time>* timestamps_ptr = NULL; + base::string16 name, value; + Time time; + while (s.Step()) { + name = s.ColumnString16(0); + value = s.ColumnString16(1); + time = Time::FromTimeT(s.ColumnInt64(2)); + + if (first_entry) { + current_key_ptr = new AutofillKey(name, value); + + timestamps_ptr = new std::vector<Time>; + timestamps_ptr->push_back(time); + + first_entry = false; + } else { + // we've encountered the next entry + if (current_key_ptr->name().compare(name) != 0 || + current_key_ptr->value().compare(value) != 0) { + AutofillEntry entry(*current_key_ptr, *timestamps_ptr); + entries->push_back(entry); + + delete current_key_ptr; + delete timestamps_ptr; + + current_key_ptr = new AutofillKey(name, value); + timestamps_ptr = new std::vector<Time>; + } + timestamps_ptr->push_back(time); + } + } + + // If there is at least one result returned, first_entry will be false. + // For this case we need to do a final cleanup step. + if (!first_entry) { + AutofillEntry entry(*current_key_ptr, *timestamps_ptr); + entries->push_back(entry); + delete current_key_ptr; + delete timestamps_ptr; + } + + return s.Succeeded(); +} + +bool AutofillTable::GetAutofillTimestamps(const base::string16& name, + const base::string16& value, + std::vector<Time>* timestamps) { + DCHECK(timestamps); + sql::Statement s(db_->GetUniqueStatement( + "SELECT date_created FROM autofill a JOIN " + "autofill_dates ad ON a.pair_id=ad.pair_id " + "WHERE a.name = ? AND a.value = ?")); + s.BindString16(0, name); + s.BindString16(1, value); + + while (s.Step()) + timestamps->push_back(Time::FromTimeT(s.ColumnInt64(0))); + + return s.Succeeded(); +} + +bool AutofillTable::UpdateAutofillEntries( + const std::vector<AutofillEntry>& entries) { + if (!entries.size()) + return true; + + // Remove all existing entries. + for (size_t i = 0; i < entries.size(); i++) { + std::string sql = "SELECT pair_id FROM autofill " + "WHERE name = ? AND value = ?"; + sql::Statement s(db_->GetUniqueStatement(sql.c_str())); + s.BindString16(0, entries[i].key().name()); + s.BindString16(1, entries[i].key().value()); + + if (!s.is_valid()) + return false; + + if (s.Step()) { + if (!RemoveFormElementForID(s.ColumnInt64(0))) + return false; + } + } + + // Insert all the supplied autofill entries. + for (size_t i = 0; i < entries.size(); i++) { + if (!InsertAutofillEntry(entries[i])) + return false; + } + + return true; +} + +bool AutofillTable::InsertAutofillEntry(const AutofillEntry& entry) { + std::string sql = "INSERT INTO autofill (name, value, value_lower, count) " + "VALUES (?, ?, ?, ?)"; + sql::Statement s(db_->GetUniqueStatement(sql.c_str())); + s.BindString16(0, entry.key().name()); + s.BindString16(1, entry.key().value()); + s.BindString16(2, base::i18n::ToLower(entry.key().value())); + s.BindInt(3, entry.timestamps().size()); + + if (!s.Run()) + return false; + + int64 pair_id = db_->GetLastInsertRowId(); + for (size_t i = 0; i < entry.timestamps().size(); i++) { + if (!InsertPairIDAndDate(pair_id, entry.timestamps()[i])) + return false; + } + + return true; +} + +bool AutofillTable::AddFormFieldValueTime(const FormFieldData& element, + std::vector<AutofillChange>* changes, + Time time) { + int count = 0; + int64 pair_id; + + if (!GetIDAndCountOfFormElement(element, &pair_id, &count)) + return false; + + if (count == 0 && !InsertFormElement(element, &pair_id)) + return false; + + if (!SetCountOfFormElement(pair_id, count + 1)) + return false; + + // If we already have more than 2 times delete last one, before adding new + // one. + if (count >= 2 && !DeleteLastAccess(pair_id)) + return false; + + if (!InsertPairIDAndDate(pair_id, time)) + return false; + + AutofillChange::Type change_type = + count == 0 ? AutofillChange::ADD : AutofillChange::UPDATE; + changes->push_back( + AutofillChange(change_type, + AutofillKey(element.name, element.value))); + return true; +} + + +bool AutofillTable::RemoveFormElement(const base::string16& name, + const base::string16& value) { + // Find the id for that pair. + sql::Statement s(db_->GetUniqueStatement( + "SELECT pair_id FROM autofill WHERE name = ? AND value= ?")); + s.BindString16(0, name); + s.BindString16(1, value); + + if (s.Step()) + return RemoveFormElementForID(s.ColumnInt64(0)); + return false; +} + +bool AutofillTable::AddAutofillProfile(const AutofillProfile& profile) { + if (IsAutofillGUIDInTrash(profile.guid())) + return true; + + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO autofill_profiles" + "(guid, company_name, address_line_1, address_line_2, city, state," + " zipcode, country, country_code, date_modified, origin)" + "VALUES (?,?,?,?,?,?,?,?,?,?,?)")); + BindAutofillProfileToStatement(profile, &s, app_locale_); + + if (!s.Run()) + return false; + + return AddAutofillProfilePieces(profile, db_); +} + +bool AutofillTable::GetAutofillProfile(const std::string& guid, + AutofillProfile** profile) { + DCHECK(base::IsValidGUID(guid)); + DCHECK(profile); + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid, company_name, address_line_1, address_line_2, city, state," + " zipcode, country, country_code, date_modified, origin " + "FROM autofill_profiles " + "WHERE guid=?")); + s.BindString(0, guid); + + if (!s.Step()) + return false; + + scoped_ptr<AutofillProfile> p(AutofillProfileFromStatement(s, app_locale_)); + + // Get associated name info. + AddAutofillProfileNamesToProfile(db_, p.get()); + + // Get associated email info. + AddAutofillProfileEmailsToProfile(db_, p.get()); + + // Get associated phone info. + AddAutofillProfilePhonesToProfile(db_, p.get()); + + *profile = p.release(); + return true; +} + +bool AutofillTable::GetAutofillProfiles( + std::vector<AutofillProfile*>* profiles) { + DCHECK(profiles); + profiles->clear(); + + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid " + "FROM autofill_profiles")); + + while (s.Step()) { + std::string guid = s.ColumnString(0); + AutofillProfile* profile = NULL; + if (!GetAutofillProfile(guid, &profile)) + return false; + profiles->push_back(profile); + } + + return s.Succeeded(); +} + +bool AutofillTable::UpdateAutofillProfile(const AutofillProfile& profile) { + DCHECK(base::IsValidGUID(profile.guid())); + + // Don't update anything until the trash has been emptied. There may be + // pending modifications to process. + if (!IsAutofillProfilesTrashEmpty()) + return true; + + AutofillProfile* tmp_profile = NULL; + if (!GetAutofillProfile(profile.guid(), &tmp_profile)) + return false; + + // Preserve appropriate modification dates by not updating unchanged profiles. + scoped_ptr<AutofillProfile> old_profile(tmp_profile); + if (old_profile->Compare(profile) == 0 && + old_profile->origin() == profile.origin()) + return true; + + sql::Statement s(db_->GetUniqueStatement( + "UPDATE autofill_profiles " + "SET guid=?, company_name=?, address_line_1=?, address_line_2=?, " + " city=?, state=?, zipcode=?, country=?, country_code=?, " + " date_modified=?, origin=? " + "WHERE guid=?")); + BindAutofillProfileToStatement(profile, &s, app_locale_); + s.BindString(11, profile.guid()); + + bool result = s.Run(); + DCHECK_GT(db_->GetLastChangeCount(), 0); + if (!result) + return result; + + // Remove the old names, emails, and phone numbers. + if (!RemoveAutofillProfilePieces(profile.guid(), db_)) + return false; + + return AddAutofillProfilePieces(profile, db_); +} + +bool AutofillTable::RemoveAutofillProfile(const std::string& guid) { + DCHECK(base::IsValidGUID(guid)); + + if (IsAutofillGUIDInTrash(guid)) { + sql::Statement s_trash(db_->GetUniqueStatement( + "DELETE FROM autofill_profiles_trash WHERE guid = ?")); + s_trash.BindString(0, guid); + + bool success = s_trash.Run(); + DCHECK_GT(db_->GetLastChangeCount(), 0) << "Expected item in trash"; + return success; + } + + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM autofill_profiles WHERE guid = ?")); + s.BindString(0, guid); + + if (!s.Run()) + return false; + + return RemoveAutofillProfilePieces(guid, db_); +} + +bool AutofillTable::ClearAutofillProfiles() { + sql::Statement s1(db_->GetUniqueStatement( + "DELETE FROM autofill_profiles")); + + if (!s1.Run()) + return false; + + sql::Statement s2(db_->GetUniqueStatement( + "DELETE FROM autofill_profile_names")); + + if (!s2.Run()) + return false; + + sql::Statement s3(db_->GetUniqueStatement( + "DELETE FROM autofill_profile_emails")); + + if (!s3.Run()) + return false; + + sql::Statement s4(db_->GetUniqueStatement( + "DELETE FROM autofill_profile_phones")); + + return s4.Run(); +} + +bool AutofillTable::AddCreditCard(const CreditCard& credit_card) { + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO credit_cards" + "(guid, name_on_card, expiration_month, expiration_year, " + " card_number_encrypted, date_modified, origin)" + "VALUES (?,?,?,?,?,?,?)")); + BindCreditCardToStatement(credit_card, &s); + + if (!s.Run()) + return false; + + DCHECK_GT(db_->GetLastChangeCount(), 0); + return true; +} + +bool AutofillTable::GetCreditCard(const std::string& guid, + CreditCard** credit_card) { + DCHECK(base::IsValidGUID(guid)); + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid, name_on_card, expiration_month, expiration_year, " + " card_number_encrypted, date_modified, origin " + "FROM credit_cards " + "WHERE guid = ?")); + s.BindString(0, guid); + + if (!s.Step()) + return false; + + *credit_card = CreditCardFromStatement(s); + return true; +} + +bool AutofillTable::GetCreditCards( + std::vector<CreditCard*>* credit_cards) { + DCHECK(credit_cards); + credit_cards->clear(); + + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid " + "FROM credit_cards")); + + while (s.Step()) { + std::string guid = s.ColumnString(0); + CreditCard* credit_card = NULL; + if (!GetCreditCard(guid, &credit_card)) + return false; + credit_cards->push_back(credit_card); + } + + return s.Succeeded(); +} + +bool AutofillTable::UpdateCreditCard(const CreditCard& credit_card) { + DCHECK(base::IsValidGUID(credit_card.guid())); + + CreditCard* tmp_credit_card = NULL; + if (!GetCreditCard(credit_card.guid(), &tmp_credit_card)) + return false; + + // Preserve appropriate modification dates by not updating unchanged cards. + scoped_ptr<CreditCard> old_credit_card(tmp_credit_card); + if (*old_credit_card == credit_card) + return true; + + sql::Statement s(db_->GetUniqueStatement( + "UPDATE credit_cards " + "SET guid=?, name_on_card=?, expiration_month=?, " + " expiration_year=?, card_number_encrypted=?, date_modified=?, " + " origin=? " + "WHERE guid=?")); + BindCreditCardToStatement(credit_card, &s); + s.BindString(7, credit_card.guid()); + + bool result = s.Run(); + DCHECK_GT(db_->GetLastChangeCount(), 0); + return result; +} + +bool AutofillTable::RemoveCreditCard(const std::string& guid) { + DCHECK(base::IsValidGUID(guid)); + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM credit_cards WHERE guid = ?")); + s.BindString(0, guid); + + return s.Run(); +} + +bool AutofillTable::RemoveAutofillDataModifiedBetween( + const Time& delete_begin, + const Time& delete_end, + std::vector<std::string>* profile_guids, + std::vector<std::string>* credit_card_guids) { + DCHECK(delete_end.is_null() || delete_begin < delete_end); + + time_t delete_begin_t = delete_begin.ToTimeT(); + time_t delete_end_t = GetEndTime(delete_end); + + // Remember Autofill profiles in the time range. + sql::Statement s_profiles_get(db_->GetUniqueStatement( + "SELECT guid FROM autofill_profiles " + "WHERE date_modified >= ? AND date_modified < ?")); + s_profiles_get.BindInt64(0, delete_begin_t); + s_profiles_get.BindInt64(1, delete_end_t); + + profile_guids->clear(); + while (s_profiles_get.Step()) { + std::string guid = s_profiles_get.ColumnString(0); + profile_guids->push_back(guid); + } + if (!s_profiles_get.Succeeded()) + return false; + + // Remove Autofill profiles in the time range. + sql::Statement s_profiles(db_->GetUniqueStatement( + "DELETE FROM autofill_profiles " + "WHERE date_modified >= ? AND date_modified < ?")); + s_profiles.BindInt64(0, delete_begin_t); + s_profiles.BindInt64(1, delete_end_t); + + if (!s_profiles.Run()) + return false; + + // Remember Autofill credit cards in the time range. + sql::Statement s_credit_cards_get(db_->GetUniqueStatement( + "SELECT guid FROM credit_cards " + "WHERE date_modified >= ? AND date_modified < ?")); + s_credit_cards_get.BindInt64(0, delete_begin_t); + s_credit_cards_get.BindInt64(1, delete_end_t); + + credit_card_guids->clear(); + while (s_credit_cards_get.Step()) { + std::string guid = s_credit_cards_get.ColumnString(0); + credit_card_guids->push_back(guid); + } + if (!s_credit_cards_get.Succeeded()) + return false; + + // Remove Autofill credit cards in the time range. + sql::Statement s_credit_cards(db_->GetUniqueStatement( + "DELETE FROM credit_cards " + "WHERE date_modified >= ? AND date_modified < ?")); + s_credit_cards.BindInt64(0, delete_begin_t); + s_credit_cards.BindInt64(1, delete_end_t); + + return s_credit_cards.Run(); +} + +bool AutofillTable::RemoveOriginURLsModifiedBetween( + const Time& delete_begin, + const Time& delete_end, + ScopedVector<AutofillProfile>* profiles) { + DCHECK(delete_end.is_null() || delete_begin < delete_end); + + time_t delete_begin_t = delete_begin.ToTimeT(); + time_t delete_end_t = GetEndTime(delete_end); + + // Remember Autofill profiles with URL origins in the time range. + sql::Statement s_profiles_get(db_->GetUniqueStatement( + "SELECT guid, origin FROM autofill_profiles " + "WHERE date_modified >= ? AND date_modified < ?")); + s_profiles_get.BindInt64(0, delete_begin_t); + s_profiles_get.BindInt64(1, delete_end_t); + + std::vector<std::string> profile_guids; + while (s_profiles_get.Step()) { + std::string guid = s_profiles_get.ColumnString(0); + std::string origin = s_profiles_get.ColumnString(1); + if (GURL(origin).is_valid()) + profile_guids.push_back(guid); + } + if (!s_profiles_get.Succeeded()) + return false; + + // Clear out the origins for the found Autofill profiles. + for (std::vector<std::string>::const_iterator it = profile_guids.begin(); + it != profile_guids.end(); ++it) { + sql::Statement s_profile(db_->GetUniqueStatement( + "UPDATE autofill_profiles SET origin='' WHERE guid=?")); + s_profile.BindString(0, *it); + if (!s_profile.Run()) + return false; + + AutofillProfile* profile; + if (!GetAutofillProfile(*it, &profile)) + return false; + + profiles->push_back(profile); + } + + // Remember Autofill credit cards with URL origins in the time range. + sql::Statement s_credit_cards_get(db_->GetUniqueStatement( + "SELECT guid, origin FROM credit_cards " + "WHERE date_modified >= ? AND date_modified < ?")); + s_credit_cards_get.BindInt64(0, delete_begin_t); + s_credit_cards_get.BindInt64(1, delete_end_t); + + std::vector<std::string> credit_card_guids; + while (s_credit_cards_get.Step()) { + std::string guid = s_credit_cards_get.ColumnString(0); + std::string origin = s_credit_cards_get.ColumnString(1); + if (GURL(origin).is_valid()) + credit_card_guids.push_back(guid); + } + if (!s_credit_cards_get.Succeeded()) + return false; + + // Clear out the origins for the found credit cards. + for (std::vector<std::string>::const_iterator it = credit_card_guids.begin(); + it != credit_card_guids.end(); ++it) { + sql::Statement s_credit_card(db_->GetUniqueStatement( + "UPDATE credit_cards SET origin='' WHERE guid=?")); + s_credit_card.BindString(0, *it); + if (!s_credit_card.Run()) + return false; + } + + return true; +} + +bool AutofillTable::GetAutofillProfilesInTrash( + std::vector<std::string>* guids) { + guids->clear(); + + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid " + "FROM autofill_profiles_trash")); + + while (s.Step()) { + std::string guid = s.ColumnString(0); + guids->push_back(guid); + } + + return s.Succeeded(); +} + +bool AutofillTable::EmptyAutofillProfilesTrash() { + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM autofill_profiles_trash")); + + return s.Run(); +} + + +bool AutofillTable::RemoveFormElementForID(int64 pair_id) { + sql::Statement s(db_->GetUniqueStatement( + "DELETE FROM autofill WHERE pair_id = ?")); + s.BindInt64(0, pair_id); + + if (s.Run()) + return RemoveFormElementForTimeRange(pair_id, Time(), Time(), NULL); + + return false; +} + +bool AutofillTable::AddAutofillGUIDToTrash(const std::string& guid) { + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO autofill_profiles_trash" + " (guid) " + "VALUES (?)")); + s.BindString(0, guid); + + return s.Run(); +} + +bool AutofillTable::IsAutofillProfilesTrashEmpty() { + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid " + "FROM autofill_profiles_trash")); + + return !s.Step(); +} + +bool AutofillTable::IsAutofillGUIDInTrash(const std::string& guid) { + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid " + "FROM autofill_profiles_trash " + "WHERE guid = ?")); + s.BindString(0, guid); + + return s.Step(); +} + +bool AutofillTable::InitMainTable() { + if (!db_->DoesTableExist("autofill")) { + if (!db_->Execute("CREATE TABLE autofill (" + "name VARCHAR, " + "value VARCHAR, " + "value_lower VARCHAR, " + "pair_id INTEGER PRIMARY KEY, " + "count INTEGER DEFAULT 1)")) { + NOTREACHED(); + return false; + } + if (!db_->Execute("CREATE INDEX autofill_name ON autofill (name)")) { + NOTREACHED(); + return false; + } + if (!db_->Execute("CREATE INDEX autofill_name_value_lower ON " + "autofill (name, value_lower)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitCreditCardsTable() { + if (!db_->DoesTableExist("credit_cards")) { + if (!db_->Execute("CREATE TABLE credit_cards ( " + "guid VARCHAR PRIMARY KEY, " + "name_on_card VARCHAR, " + "expiration_month INTEGER, " + "expiration_year INTEGER, " + "card_number_encrypted BLOB, " + "date_modified INTEGER NOT NULL DEFAULT 0, " + "origin VARCHAR DEFAULT '')")) { + NOTREACHED(); + return false; + } + } + + return true; +} + +bool AutofillTable::InitDatesTable() { + if (!db_->DoesTableExist("autofill_dates")) { + if (!db_->Execute("CREATE TABLE autofill_dates ( " + "pair_id INTEGER DEFAULT 0, " + "date_created INTEGER DEFAULT 0)")) { + NOTREACHED(); + return false; + } + if (!db_->Execute("CREATE INDEX autofill_dates_pair_id ON " + "autofill_dates (pair_id)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitProfilesTable() { + if (!db_->DoesTableExist("autofill_profiles")) { + if (!db_->Execute("CREATE TABLE autofill_profiles ( " + "guid VARCHAR PRIMARY KEY, " + "company_name VARCHAR, " + "address_line_1 VARCHAR, " + "address_line_2 VARCHAR, " + "city VARCHAR, " + "state VARCHAR, " + "zipcode VARCHAR, " + "country VARCHAR, " + "country_code VARCHAR, " + "date_modified INTEGER NOT NULL DEFAULT 0, " + "origin VARCHAR DEFAULT '')")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitProfileNamesTable() { + if (!db_->DoesTableExist("autofill_profile_names")) { + if (!db_->Execute("CREATE TABLE autofill_profile_names ( " + "guid VARCHAR, " + "first_name VARCHAR, " + "middle_name VARCHAR, " + "last_name VARCHAR)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitProfileEmailsTable() { + if (!db_->DoesTableExist("autofill_profile_emails")) { + if (!db_->Execute("CREATE TABLE autofill_profile_emails ( " + "guid VARCHAR, " + "email VARCHAR)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitProfilePhonesTable() { + if (!db_->DoesTableExist("autofill_profile_phones")) { + if (!db_->Execute("CREATE TABLE autofill_profile_phones ( " + "guid VARCHAR, " + "type INTEGER DEFAULT 0, " + "number VARCHAR)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +bool AutofillTable::InitProfileTrashTable() { + if (!db_->DoesTableExist("autofill_profiles_trash")) { + if (!db_->Execute("CREATE TABLE autofill_profiles_trash ( " + "guid VARCHAR)")) { + NOTREACHED(); + return false; + } + } + return true; +} + +// Add the card_number_encrypted column if credit card table was not +// created in this build (otherwise the column already exists). +// WARNING: Do not change the order of the execution of the SQL +// statements in this case! Profile corruption and data migration +// issues WILL OCCUR. See http://crbug.com/10913 +// +// The problem is that if a user has a profile which was created before +// r37036, when the credit_cards table was added, and then failed to +// update this profile between the credit card addition and the addition +// of the "encrypted" columns (44963), the next data migration will put +// the user's profile in an incoherent state: The user will update from +// a data profile set to be earlier than 22, and therefore pass through +// this update case. But because the user did not have a credit_cards +// table before starting Chrome, it will have just been initialized +// above, and so already have these columns -- and thus this data +// update step will have failed. +// +// The false assumption in this case is that at this step in the +// migration, the user has a credit card table, and that this +// table does not include encrypted columns! +// Because this case does not roll back the complete set of SQL +// transactions properly in case of failure (that is, it does not +// roll back the table initialization done above), the incoherent +// profile will now see itself as being at version 22 -- but include a +// fully initialized credit_cards table. Every time Chrome runs, it +// will try to update the web database and fail at this step, unless +// we allow for the faulty assumption described above by checking for +// the existence of the columns only AFTER we've executed the commands +// to add them. +bool AutofillTable::MigrateToVersion23AddCardNumberEncryptedColumn() { + if (!db_->DoesColumnExist("credit_cards", "card_number_encrypted")) { + if (!db_->Execute("ALTER TABLE credit_cards ADD COLUMN " + "card_number_encrypted BLOB DEFAULT NULL")) { + LOG(WARNING) << "Could not add card_number_encrypted to " + "credit_cards table."; + return false; + } + } + + if (!db_->DoesColumnExist("credit_cards", "verification_code_encrypted")) { + if (!db_->Execute("ALTER TABLE credit_cards ADD COLUMN " + "verification_code_encrypted BLOB DEFAULT NULL")) { + LOG(WARNING) << "Could not add verification_code_encrypted to " + "credit_cards table."; + return false; + } + } + + return true; +} + +// One-time cleanup for http://crbug.com/38364 - In the presence of +// multi-byte UTF-8 characters, that bug could cause Autofill strings +// to grow larger and more corrupt with each save. The cleanup removes +// any row with a string field larger than a reasonable size. The string +// fields examined here are precisely the ones that were subject to +// corruption by the original bug. +bool AutofillTable::MigrateToVersion24CleanupOversizedStringFields() { + const std::string autofill_is_too_big = + "max(length(name), length(value)) > 500"; + + const std::string credit_cards_is_too_big = + "max(length(label), length(name_on_card), length(type), " + " length(expiration_month), length(expiration_year), " + " length(billing_address), length(shipping_address) " + ") > 500"; + + const std::string autofill_profiles_is_too_big = + "max(length(label), length(first_name), " + " length(middle_name), length(last_name), length(email), " + " length(company_name), length(address_line_1), " + " length(address_line_2), length(city), length(state), " + " length(zipcode), length(country), length(phone)) > 500"; + + std::string query = "DELETE FROM autofill_dates WHERE pair_id IN (" + "SELECT pair_id FROM autofill WHERE " + autofill_is_too_big + ")"; + + if (!db_->Execute(query.c_str())) + return false; + + query = "DELETE FROM autofill WHERE " + autofill_is_too_big; + + if (!db_->Execute(query.c_str())) + return false; + + // Only delete from legacy credit card tables where specific columns exist. + if (db_->DoesColumnExist("credit_cards", "label") && + db_->DoesColumnExist("credit_cards", "name_on_card") && + db_->DoesColumnExist("credit_cards", "type") && + db_->DoesColumnExist("credit_cards", "expiration_month") && + db_->DoesColumnExist("credit_cards", "expiration_year") && + db_->DoesColumnExist("credit_cards", "billing_address") && + db_->DoesColumnExist("credit_cards", "shipping_address") && + db_->DoesColumnExist("autofill_profiles", "label")) { + query = "DELETE FROM credit_cards WHERE (" + credit_cards_is_too_big + + ") OR label IN (SELECT label FROM autofill_profiles WHERE " + + autofill_profiles_is_too_big + ")"; + + if (!db_->Execute(query.c_str())) + return false; + } + + if (db_->DoesColumnExist("autofill_profiles", "label")) { + query = "DELETE FROM autofill_profiles WHERE " + + autofill_profiles_is_too_big; + + if (!db_->Execute(query.c_str())) + return false; + } + + return true; +} + +// Change the credit_cards.billing_address column from a string to an +// int. The stored string is the label of an address, so we have to +// select the unique ID of this address using the label as a foreign +// key into the |autofill_profiles| table. +bool AutofillTable::MigrateToVersion27UpdateLegacyCreditCards() { + // Only migrate from legacy credit card tables where specific columns + // exist. + if (!(db_->DoesColumnExist("credit_cards", "unique_id") && + db_->DoesColumnExist("credit_cards", "billing_address") && + db_->DoesColumnExist("autofill_profiles", "unique_id"))) { + return true; + } + + std::string stmt = + "SELECT credit_cards.unique_id, autofill_profiles.unique_id " + "FROM autofill_profiles, credit_cards " + "WHERE credit_cards.billing_address = autofill_profiles.label"; + sql::Statement s(db_->GetUniqueStatement(stmt.c_str())); + + std::map<int, int> cc_billing_map; + while (s.Step()) + cc_billing_map[s.ColumnInt(0)] = s.ColumnInt(1); + if (!s.Succeeded()) + return false; + + // Windows already stores the IDs as strings in |billing_address|. Try + // to convert those. + if (cc_billing_map.empty()) { + std::string stmt = "SELECT unique_id,billing_address FROM credit_cards"; + sql::Statement s(db_->GetUniqueStatement(stmt.c_str())); + + while (s.Step()) { + int id = 0; + if (base::StringToInt(s.ColumnString(1), &id)) + cc_billing_map[s.ColumnInt(0)] = id; + } + if (!s.Succeeded()) + return false; + } + + if (!db_->Execute("CREATE TABLE credit_cards_temp ( " + "label VARCHAR, " + "unique_id INTEGER PRIMARY KEY, " + "name_on_card VARCHAR, " + "type VARCHAR, " + "card_number VARCHAR, " + "expiration_month INTEGER, " + "expiration_year INTEGER, " + "verification_code VARCHAR, " + "billing_address INTEGER, " + "shipping_address VARCHAR, " + "card_number_encrypted BLOB, " + "verification_code_encrypted BLOB)")) { + return false; + } + + if (!db_->Execute( + "INSERT INTO credit_cards_temp " + "SELECT label,unique_id,name_on_card,type,card_number," + "expiration_month,expiration_year,verification_code,0," + "shipping_address,card_number_encrypted," + "verification_code_encrypted FROM credit_cards")) { + return false; + } + + if (!db_->Execute("DROP TABLE credit_cards")) + return false; + + if (!db_->Execute("ALTER TABLE credit_cards_temp RENAME TO credit_cards")) + return false; + + for (std::map<int, int>::const_iterator iter = cc_billing_map.begin(); + iter != cc_billing_map.end(); ++iter) { + sql::Statement s(db_->GetCachedStatement( + SQL_FROM_HERE, + "UPDATE credit_cards SET billing_address=? WHERE unique_id=?")); + s.BindInt(0, (*iter).second); + s.BindInt(1, (*iter).first); + + if (!s.Run()) + return false; + } + + return true; +} + +bool AutofillTable::MigrateToVersion30AddDateModifed() { + // Add date_modified to autofill_profiles. + if (!db_->DoesColumnExist("autofill_profiles", "date_modified")) { + if (!db_->Execute("ALTER TABLE autofill_profiles ADD COLUMN " + "date_modified INTEGER NON NULL DEFAULT 0")) { + return false; + } + + sql::Statement s(db_->GetUniqueStatement( + "UPDATE autofill_profiles SET date_modified=?")); + s.BindInt64(0, Time::Now().ToTimeT()); + + if (!s.Run()) + return false; + } + + // Add date_modified to credit_cards. + if (!db_->DoesColumnExist("credit_cards", "date_modified")) { + if (!db_->Execute("ALTER TABLE credit_cards ADD COLUMN " + "date_modified INTEGER NON NULL DEFAULT 0")) { + return false; + } + + sql::Statement s(db_->GetUniqueStatement( + "UPDATE credit_cards SET date_modified=?")); + s.BindInt64(0, Time::Now().ToTimeT()); + + if (!s.Run()) + return false; + } + + return true; +} + +bool AutofillTable::MigrateToVersion31AddGUIDToCreditCardsAndProfiles() { + // Note that we need to check for the guid column's existence due to the + // fact that for a version 22 database the |autofill_profiles| table + // gets created fresh with |InitAutofillProfilesTable|. + if (!db_->DoesColumnExist("autofill_profiles", "guid")) { + if (!db_->Execute("ALTER TABLE autofill_profiles ADD COLUMN " + "guid VARCHAR NOT NULL DEFAULT \"\"")) { + return false; + } + + // Set all the |guid| fields to valid values. + + sql::Statement s(db_->GetUniqueStatement("SELECT unique_id " + "FROM autofill_profiles")); + + while (s.Step()) { + sql::Statement update_s( + db_->GetUniqueStatement("UPDATE autofill_profiles " + "SET guid=? WHERE unique_id=?")); + update_s.BindString(0, base::GenerateGUID()); + update_s.BindInt(1, s.ColumnInt(0)); + + if (!update_s.Run()) + return false; + } + if (!s.Succeeded()) + return false; + } + + // Note that we need to check for the guid column's existence due to the + // fact that for a version 22 database the |autofill_profiles| table + // gets created fresh with |InitAutofillProfilesTable|. + if (!db_->DoesColumnExist("credit_cards", "guid")) { + if (!db_->Execute("ALTER TABLE credit_cards ADD COLUMN " + "guid VARCHAR NOT NULL DEFAULT \"\"")) { + return false; + } + + // Set all the |guid| fields to valid values. + + sql::Statement s(db_->GetUniqueStatement("SELECT unique_id " + "FROM credit_cards")); + + while (s.Step()) { + sql::Statement update_s( + db_->GetUniqueStatement("UPDATE credit_cards " + "set guid=? WHERE unique_id=?")); + update_s.BindString(0, base::GenerateGUID()); + update_s.BindInt(1, s.ColumnInt(0)); + + if (!update_s.Run()) + return false; + } + if (!s.Succeeded()) + return false; + } + + return true; +} + +bool AutofillTable::MigrateToVersion32UpdateProfilesAndCreditCards() { + if (db_->DoesColumnExist("autofill_profiles", "unique_id")) { + if (!db_->Execute("CREATE TABLE autofill_profiles_temp ( " + "guid VARCHAR PRIMARY KEY, " + "label VARCHAR, " + "first_name VARCHAR, " + "middle_name VARCHAR, " + "last_name VARCHAR, " + "email VARCHAR, " + "company_name VARCHAR, " + "address_line_1 VARCHAR, " + "address_line_2 VARCHAR, " + "city VARCHAR, " + "state VARCHAR, " + "zipcode VARCHAR, " + "country VARCHAR, " + "phone VARCHAR, " + "date_modified INTEGER NOT NULL DEFAULT 0)")) { + return false; + } + + if (!db_->Execute( + "INSERT INTO autofill_profiles_temp " + "SELECT guid, label, first_name, middle_name, last_name, email, " + "company_name, address_line_1, address_line_2, city, state, " + "zipcode, country, phone, date_modified " + "FROM autofill_profiles")) { + return false; + } + + if (!db_->Execute("DROP TABLE autofill_profiles")) + return false; + + if (!db_->Execute( + "ALTER TABLE autofill_profiles_temp RENAME TO autofill_profiles")) { + return false; + } + } + + if (db_->DoesColumnExist("credit_cards", "unique_id")) { + if (!db_->Execute("CREATE TABLE credit_cards_temp ( " + "guid VARCHAR PRIMARY KEY, " + "label VARCHAR, " + "name_on_card VARCHAR, " + "expiration_month INTEGER, " + "expiration_year INTEGER, " + "card_number_encrypted BLOB, " + "date_modified INTEGER NOT NULL DEFAULT 0)")) { + return false; + } + + if (!db_->Execute( + "INSERT INTO credit_cards_temp " + "SELECT guid, label, name_on_card, expiration_month, " + "expiration_year, card_number_encrypted, date_modified " + "FROM credit_cards")) { + return false; + } + + if (!db_->Execute("DROP TABLE credit_cards")) + return false; + + if (!db_->Execute("ALTER TABLE credit_cards_temp RENAME TO credit_cards")) + return false; + } + + return true; +} + +// Test the existence of the |first_name| column as an indication that +// we need a migration. It is possible that the new |autofill_profiles| +// schema is in place because the table was newly created when migrating +// from a pre-version-22 database. +bool AutofillTable::MigrateToVersion33ProfilesBasedOnFirstName() { + if (db_->DoesColumnExist("autofill_profiles", "first_name")) { + // Create autofill_profiles_temp table that will receive the data. + if (!db_->DoesTableExist("autofill_profiles_temp")) { + if (!db_->Execute("CREATE TABLE autofill_profiles_temp ( " + "guid VARCHAR PRIMARY KEY, " + "company_name VARCHAR, " + "address_line_1 VARCHAR, " + "address_line_2 VARCHAR, " + "city VARCHAR, " + "state VARCHAR, " + "zipcode VARCHAR, " + "country VARCHAR, " + "date_modified INTEGER NOT NULL DEFAULT 0)")) { + return false; + } + } + + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid, first_name, middle_name, last_name, email, " + "company_name, address_line_1, address_line_2, city, state, " + "zipcode, country, phone, date_modified " + "FROM autofill_profiles")); + + while (s.Step()) { + AutofillProfile profile; + profile.set_guid(s.ColumnString(0)); + DCHECK(base::IsValidGUID(profile.guid())); + + profile.SetRawInfo(NAME_FIRST, s.ColumnString16(1)); + profile.SetRawInfo(NAME_MIDDLE, s.ColumnString16(2)); + profile.SetRawInfo(NAME_LAST, s.ColumnString16(3)); + profile.SetRawInfo(EMAIL_ADDRESS, s.ColumnString16(4)); + profile.SetRawInfo(COMPANY_NAME, s.ColumnString16(5)); + profile.SetRawInfo(ADDRESS_HOME_LINE1, s.ColumnString16(6)); + profile.SetRawInfo(ADDRESS_HOME_LINE2, s.ColumnString16(7)); + profile.SetRawInfo(ADDRESS_HOME_CITY, s.ColumnString16(8)); + profile.SetRawInfo(ADDRESS_HOME_STATE, s.ColumnString16(9)); + profile.SetRawInfo(ADDRESS_HOME_ZIP, s.ColumnString16(10)); + profile.SetInfo(AutofillType(ADDRESS_HOME_COUNTRY), s.ColumnString16(11), + app_locale_); + profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, s.ColumnString16(12)); + int64 date_modified = s.ColumnInt64(13); + + sql::Statement s_insert(db_->GetUniqueStatement( + "INSERT INTO autofill_profiles_temp" + "(guid, company_name, address_line_1, address_line_2, city," + " state, zipcode, country, date_modified)" + "VALUES (?,?,?,?,?,?,?,?,?)")); + s_insert.BindString(0, profile.guid()); + s_insert.BindString16(1, profile.GetRawInfo(COMPANY_NAME)); + s_insert.BindString16(2, profile.GetRawInfo(ADDRESS_HOME_LINE1)); + s_insert.BindString16(3, profile.GetRawInfo(ADDRESS_HOME_LINE2)); + s_insert.BindString16(4, profile.GetRawInfo(ADDRESS_HOME_CITY)); + s_insert.BindString16(5, profile.GetRawInfo(ADDRESS_HOME_STATE)); + s_insert.BindString16(6, profile.GetRawInfo(ADDRESS_HOME_ZIP)); + s_insert.BindString16(7, profile.GetRawInfo(ADDRESS_HOME_COUNTRY)); + s_insert.BindInt64(8, date_modified); + + if (!s_insert.Run()) + return false; + + // Add the other bits: names, emails, and phone numbers. + if (!AddAutofillProfilePieces(profile, db_)) + return false; + } // endwhile + if (!s.Succeeded()) + return false; + + if (!db_->Execute("DROP TABLE autofill_profiles")) + return false; + + if (!db_->Execute( + "ALTER TABLE autofill_profiles_temp RENAME TO autofill_profiles")) { + return false; + } + } + + // Remove the labels column from the credit_cards table. + if (db_->DoesColumnExist("credit_cards", "label")) { + if (!db_->Execute("CREATE TABLE credit_cards_temp ( " + "guid VARCHAR PRIMARY KEY, " + "name_on_card VARCHAR, " + "expiration_month INTEGER, " + "expiration_year INTEGER, " + "card_number_encrypted BLOB, " + "date_modified INTEGER NOT NULL DEFAULT 0)")) { + return false; + } + + if (!db_->Execute( + "INSERT INTO credit_cards_temp " + "SELECT guid, name_on_card, expiration_month, " + "expiration_year, card_number_encrypted, date_modified " + "FROM credit_cards")) { + return false; + } + + if (!db_->Execute("DROP TABLE credit_cards")) + return false; + + if (!db_->Execute("ALTER TABLE credit_cards_temp RENAME TO credit_cards")) + return false; + } + + return true; +} + +// Test the existence of the |country_code| column as an indication that +// we need a migration. It is possible that the new |autofill_profiles| +// schema is in place because the table was newly created when migrating +// from a pre-version-22 database. +bool AutofillTable::MigrateToVersion34ProfilesBasedOnCountryCode() { + if (!db_->DoesColumnExist("autofill_profiles", "country_code")) { + if (!db_->Execute("ALTER TABLE autofill_profiles ADD COLUMN " + "country_code VARCHAR")) { + return false; + } + + // Set all the |country_code| fields to match existing |country| values. + sql::Statement s(db_->GetUniqueStatement("SELECT guid, country " + "FROM autofill_profiles")); + + while (s.Step()) { + sql::Statement update_s( + db_->GetUniqueStatement("UPDATE autofill_profiles " + "SET country_code=? WHERE guid=?")); + + base::string16 country = s.ColumnString16(1); + update_s.BindString(0, AutofillCountry::GetCountryCode(country, + app_locale_)); + update_s.BindString(1, s.ColumnString(0)); + + if (!update_s.Run()) + return false; + } + if (!s.Succeeded()) + return false; + } + + return true; +} + +// Correct all country codes with value "UK" to be "GB". This data +// was mistakenly introduced in build 686.0. This migration is to clean +// it up. See http://crbug.com/74511 for details. +bool AutofillTable::MigrateToVersion35GreatBritainCountryCodes() { + sql::Statement s(db_->GetUniqueStatement( + "UPDATE autofill_profiles SET country_code=\"GB\" " + "WHERE country_code=\"UK\"")); + + return s.Run(); +} + +// Merge and cull older profiles where possible. +bool AutofillTable::MigrateToVersion37MergeAndCullOlderProfiles() { + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid, date_modified FROM autofill_profiles")); + + // Accumulate the good profiles. + std::vector<AutofillProfile> accumulated_profiles; + std::vector<AutofillProfile*> accumulated_profiles_p; + std::map<std::string, int64> modification_map; + while (s.Step()) { + std::string guid = s.ColumnString(0); + int64 date_modified = s.ColumnInt64(1); + modification_map.insert( + std::pair<std::string, int64>(guid, date_modified)); + + sql::Statement s(db_->GetUniqueStatement( + "SELECT guid, company_name, address_line_1, address_line_2, city, " + " state, zipcode, country, country_code, date_modified " + "FROM autofill_profiles " + "WHERE guid=?")); + s.BindString(0, guid); + + if (!s.Step()) + return false; + + scoped_ptr<AutofillProfile> profile(new AutofillProfile); + profile->set_guid(s.ColumnString(0)); + DCHECK(base::IsValidGUID(profile->guid())); + + profile->SetRawInfo(COMPANY_NAME, s.ColumnString16(1)); + profile->SetRawInfo(ADDRESS_HOME_LINE1, s.ColumnString16(2)); + profile->SetRawInfo(ADDRESS_HOME_LINE2, s.ColumnString16(3)); + profile->SetRawInfo(ADDRESS_HOME_CITY, s.ColumnString16(4)); + profile->SetRawInfo(ADDRESS_HOME_STATE, s.ColumnString16(5)); + profile->SetRawInfo(ADDRESS_HOME_ZIP, s.ColumnString16(6)); + // Intentionally skip column 7, which stores the localized country name. + profile->SetRawInfo(ADDRESS_HOME_COUNTRY, s.ColumnString16(8)); + // Intentionally skip column 9, which stores the profile's modification + // date. + profile->set_origin(s.ColumnString(10)); + + // Get associated name info. + AddAutofillProfileNamesToProfile(db_, profile.get()); + + // Get associated email info. + AddAutofillProfileEmailsToProfile(db_, profile.get()); + + // Get associated phone info. + AddAutofillProfilePhonesToProfile(db_, profile.get()); + + if (PersonalDataManager::IsValidLearnableProfile(*profile, app_locale_)) { + std::vector<AutofillProfile> merged_profiles; + bool merged = PersonalDataManager::MergeProfile( + *profile, accumulated_profiles_p, app_locale_, &merged_profiles); + + std::swap(accumulated_profiles, merged_profiles); + + accumulated_profiles_p.clear(); + accumulated_profiles_p.resize(accumulated_profiles.size()); + std::transform(accumulated_profiles.begin(), + accumulated_profiles.end(), + accumulated_profiles_p.begin(), + address_of<AutofillProfile>); + + // If the profile got merged trash the original. + if (merged) + AddAutofillGUIDToTrash(profile->guid()); + + } else { + // An invalid profile, so trash it. + AddAutofillGUIDToTrash(profile->guid()); + } + } // endwhile + if (!s.Succeeded()) + return false; + + // Drop the current profiles. + if (!ClearAutofillProfiles()) + return false; + + // Add the newly merged profiles back in. + for (std::vector<AutofillProfile>::const_iterator + iter = accumulated_profiles.begin(); + iter != accumulated_profiles.end(); + ++iter) { + // Save the profile with its original modification date. + std::map<std::string, int64>::const_iterator date_item = + modification_map.find(iter->guid()); + if (date_item == modification_map.end()) + return false; + + sql::Statement s(db_->GetUniqueStatement( + "INSERT INTO autofill_profiles" + "(guid, company_name, address_line_1, address_line_2, city, state," + " zipcode, country, country_code, date_modified)" + "VALUES (?,?,?,?,?,?,?,?,?,?)")); + s.BindString(0, iter->guid()); + base::string16 text = iter->GetRawInfo(COMPANY_NAME); + s.BindString16(1, LimitDataSize(text)); + text = iter->GetRawInfo(ADDRESS_HOME_LINE1); + s.BindString16(2, LimitDataSize(text)); + text = iter->GetRawInfo(ADDRESS_HOME_LINE2); + s.BindString16(3, LimitDataSize(text)); + text = iter->GetRawInfo(ADDRESS_HOME_CITY); + s.BindString16(4, LimitDataSize(text)); + text = iter->GetRawInfo(ADDRESS_HOME_STATE); + s.BindString16(5, LimitDataSize(text)); + text = iter->GetRawInfo(ADDRESS_HOME_ZIP); + s.BindString16(6, LimitDataSize(text)); + text = iter->GetInfo(AutofillType(ADDRESS_HOME_COUNTRY), app_locale_); + s.BindString16(7, LimitDataSize(text)); + text = iter->GetRawInfo(ADDRESS_HOME_COUNTRY); + s.BindString16(8, LimitDataSize(text)); + s.BindInt64(9, date_item->second); + + if (!s.Run()) + return false; + + if (!AddAutofillProfilePieces(*iter, db_)) + return false; + } + + return true; +} + +bool AutofillTable::MigrateToVersion51AddOriginColumn() { + sql::Transaction transaction(db_); + if (!transaction.Begin()) + return false; + + // Add origin to autofill_profiles. + if (!db_->DoesColumnExist("autofill_profiles", "origin") && + !db_->Execute("ALTER TABLE autofill_profiles " + "ADD COLUMN origin VARCHAR DEFAULT ''")) { + return false; + } + + // Add origin to credit_cards. + if (!db_->DoesColumnExist("credit_cards", "origin") && + !db_->Execute("ALTER TABLE credit_cards " + "ADD COLUMN origin VARCHAR DEFAULT ''")) { + return false; + } + + return transaction.Commit(); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/webdata/autofill_table.h b/chromium/components/autofill/core/browser/webdata/autofill_table.h new file mode 100644 index 00000000000..52012e24810 --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_table.h @@ -0,0 +1,388 @@ +// 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_CORE_BROWSER_WEBDATA_AUTOFILL_TABLE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_TABLE_H_ + +#include <vector> + +#include "base/compiler_specific.h" +#include "base/gtest_prod_util.h" +#include "base/memory/scoped_vector.h" +#include "base/strings/string16.h" +#include "components/webdata/common/web_database_table.h" + +class WebDatabase; + +namespace base { +class Time; +} + +namespace autofill { + +class AutofillChange; +class AutofillEntry; +class AutofillProfile; +class AutofillTableTest; +class CreditCard; + +struct FormFieldData; + +// This class manages the various Autofill tables within the SQLite database +// passed to the constructor. It expects the following schemas: +// +// Note: The database stores time in seconds, UTC. +// +// autofill +// name The name of the input as specified in the html. +// value The literal contents of the text field. +// value_lower The contents of the text field made lower_case. +// pair_id An ID number unique to the row in the table. +// count How many times the user has entered the string |value| +// in a field of name |name|. +// +// autofill_dates This table associates a row to each separate time the +// user submits a form containing a certain name/value +// pair. The |pair_id| should match the |pair_id| field +// in the appropriate row of the autofill table. +// pair_id +// date_created +// +// autofill_profiles This table contains Autofill profile data added by the +// user with the Autofill dialog. Most of the columns are +// standard entries in a contact information form. +// +// guid A guid string to uniquely identify the profile. +// Added in version 31. +// company_name +// address_line_1 +// address_line_2 +// city +// state +// zipcode +// country The country name. Deprecated, should be removed once +// the stable channel reaches version 11. +// country_code +// date_modified The date on which this profile was last modified. +// Added in version 30. +// origin The domain of origin for this profile. +// Added in version 50. +// +// autofill_profile_names +// This table contains the multi-valued name fields +// associated with a profile. +// +// guid The guid string that identifies the profile to which +// the name belongs. +// first_name +// middle_name +// last_name +// +// autofill_profile_emails +// This table contains the multi-valued email fields +// associated with a profile. +// +// guid The guid string that identifies the profile to which +// the email belongs. +// email +// +// autofill_profile_phones +// This table contains the multi-valued phone fields +// associated with a profile. +// +// guid The guid string that identifies the profile to which +// the phone number belongs. +// type An integer constant designating either phone type of the +// number. +// TODO(jhawkins): Remove the type column and migrate the +// database. +// number +// +// autofill_profiles_trash +// This table contains guids of "trashed" autofill +// profiles. When a profile is removed its guid is added +// to this table so that Sync can perform deferred removal. +// +// guid The guid string that identifies the trashed profile. +// +// credit_cards This table contains credit card data added by the user +// with the Autofill dialog. Most of the columns are +// standard entries in a credit card form. +// +// guid A guid string to uniquely identify the profile. +// Added in version 31. +// name_on_card +// expiration_month +// expiration_year +// card_number_encrypted Stores encrypted credit card number. +// date_modified The date on which this entry was last modified. +// Added in version 30. +// origin The domain of origin for this profile. +// Added in version 50. +// +class AutofillTable : public WebDatabaseTable { + public: + explicit AutofillTable(const std::string& app_locale); + virtual ~AutofillTable(); + + // Retrieves the AutofillTable* owned by |database|. + static AutofillTable* FromWebDatabase(WebDatabase* db); + + virtual WebDatabaseTable::TypeKey GetTypeKey() const OVERRIDE; + virtual bool Init(sql::Connection* db, sql::MetaTable* meta_table) OVERRIDE; + virtual bool IsSyncable() OVERRIDE; + virtual bool MigrateToVersion(int version, + bool* update_compatible_version) OVERRIDE; + + // Records the form elements in |elements| in the database in the + // autofill table. A list of all added and updated autofill entries + // is returned in the changes out parameter. + bool AddFormFieldValues(const std::vector<FormFieldData>& elements, + std::vector<AutofillChange>* changes); + + // Records a single form element in the database in the autofill table. A list + // of all added and updated autofill entries is returned in the changes out + // parameter. + bool AddFormFieldValue(const FormFieldData& element, + std::vector<AutofillChange>* changes); + + // Retrieves a vector of all values which have been recorded in the autofill + // table as the value in a form element with name |name| and which start with + // |prefix|. The comparison of the prefix is case insensitive. + bool GetFormValuesForElementName(const base::string16& name, + const base::string16& prefix, + std::vector<base::string16>* values, + int limit); + + // Returns whether any form elements are stored in the database. + bool HasFormElements(); + + // Removes rows from autofill_dates if they were created on or after + // |delete_begin| and strictly before |delete_end|. Decrements the + // count of the corresponding rows in the autofill table, and + // removes those rows if the count goes to 0. A list of all changed + // keys and whether each was updater or removed is returned in the + // changes out parameter. + bool RemoveFormElementsAddedBetween(const base::Time& delete_begin, + const base::Time& delete_end, + std::vector<AutofillChange>* changes); + + // Removes rows from autofill_dates if they were accessed strictly before + // |AutofillEntry::ExpirationTime()|. Removes the corresponding row from the + // autofill table. Also culls timestamps to only two. TODO(georgey): remove + // culling in future versions. + bool RemoveExpiredFormElements(std::vector<AutofillChange>* changes); + + // Removes from autofill_dates rows with given pair_id where date_created lies + // between |delete_begin| and |delete_end|. + bool RemoveFormElementForTimeRange(int64 pair_id, + const base::Time& delete_begin, + const base::Time& delete_end, + int* how_many); + + // Increments the count in the row corresponding to |pair_id| by |delta|. + bool AddToCountOfFormElement(int64 pair_id, int delta); + + // Counts how many timestamp data rows are in the |autofill_dates| table for + // a given |pair_id|. GetCountOfFormElement() on the other hand gives the + // |count| property for a given id. + int CountTimestampsData(int64 pair_id); + + // Gets the pair_id and count entries from name and value specified in + // |element|. Sets *pair_id and *count to 0 if there is no such row in + // the table. + bool GetIDAndCountOfFormElement(const FormFieldData& element, + int64* pair_id, + int* count); + + // Gets the count only given the pair_id. + bool GetCountOfFormElement(int64 pair_id, int* count); + + // Updates the count entry in the row corresponding to |pair_id| to |count|. + bool SetCountOfFormElement(int64 pair_id, int count); + + // Adds a new row to the autofill table with name and value given in + // |element|. Sets *pair_id to the pair_id of the new row. + bool InsertFormElement(const FormFieldData& element, + int64* pair_id); + + // Adds a new row to the autofill_dates table. + bool InsertPairIDAndDate(int64 pair_id, const base::Time& date_created); + + // Deletes last access to the Autofill data from the autofill_dates table. + bool DeleteLastAccess(int64 pair_id); + + // Removes row from the autofill tables given |pair_id|. + bool RemoveFormElementForID(int64 pair_id); + + // Removes row from the autofill tables for the given |name| |value| pair. + virtual bool RemoveFormElement(const base::string16& name, + const base::string16& value); + + // Retrieves all of the entries in the autofill table. + virtual bool GetAllAutofillEntries(std::vector<AutofillEntry>* entries); + + // Retrieves a single entry from the autofill table. + virtual bool GetAutofillTimestamps(const base::string16& name, + const base::string16& value, + std::vector<base::Time>* timestamps); + + // Replaces existing autofill entries with the entries supplied in + // the argument. If the entry does not already exist, it will be + // added. + virtual bool UpdateAutofillEntries(const std::vector<AutofillEntry>& entries); + + // Records a single Autofill profile in the autofill_profiles table. + virtual bool AddAutofillProfile(const AutofillProfile& profile); + + // Updates the database values for the specified profile. Mulit-value aware. + virtual bool UpdateAutofillProfile(const AutofillProfile& profile); + + // Removes a row from the autofill_profiles table. |guid| is the identifier + // of the profile to remove. + virtual bool RemoveAutofillProfile(const std::string& guid); + + // Retrieves a profile with guid |guid|. The caller owns |profile|. + bool GetAutofillProfile(const std::string& guid, AutofillProfile** profile); + + // Retrieves all profiles in the database. Caller owns the returned profiles. + virtual bool GetAutofillProfiles(std::vector<AutofillProfile*>* profiles); + + // Records a single credit card in the credit_cards table. + bool AddCreditCard(const CreditCard& credit_card); + + // Updates the database values for the specified credit card. + bool UpdateCreditCard(const CreditCard& credit_card); + + // Removes a row from the credit_cards table. |guid| is the identifer of the + // credit card to remove. + bool RemoveCreditCard(const std::string& guid); + + // Retrieves a credit card with guid |guid|. The caller owns + // |credit_card_id|. + bool GetCreditCard(const std::string& guid, CreditCard** credit_card); + + // Retrieves all credit cards in the database. Caller owns the returned + // credit cards. + virtual bool GetCreditCards(std::vector<CreditCard*>* credit_cards); + + // Removes rows from autofill_profiles and credit_cards if they were created + // on or after |delete_begin| and strictly before |delete_end|. Returns the + // list of deleted profile guids in |profile_guids|. Return value is true if + // all rows were successfully removed. Returns false on database error. In + // that case, the output vector state is undefined, and may be partially + // filled. + bool RemoveAutofillDataModifiedBetween( + const base::Time& delete_begin, + const base::Time& delete_end, + std::vector<std::string>* profile_guids, + std::vector<std::string>* credit_card_guids); + + // Removes origin URLs from the autofill_profiles and credit_cards tables if + // they were written on or after |delete_begin| and strictly before + // |delete_end|. Returns the list of modified profiles in |profiles|. Return + // value is true if all rows were successfully updated. Returns false on + // database error. In that case, the output vector state is undefined, and + // may be partially filled. + bool RemoveOriginURLsModifiedBetween( + const base::Time& delete_begin, + const base::Time& delete_end, + ScopedVector<AutofillProfile>* profiles); + + // Retrieves all profiles in the database that have been deleted since last + // "empty" of the trash. + bool GetAutofillProfilesInTrash(std::vector<std::string>* guids); + + // Empties the Autofill profiles "trash can". + bool EmptyAutofillProfilesTrash(); + + // Removes empty values for autofill that were incorrectly stored in the DB + // See bug http://crbug.com/6111 + bool ClearAutofillEmptyValueElements(); + + // Retrieves all profiles in the database that have been deleted since last + // "empty" of the trash. + bool AddAutofillGUIDToTrash(const std::string& guid); + + // Clear all profiles. + bool ClearAutofillProfiles(); + + // Table migration functions. + bool MigrateToVersion23AddCardNumberEncryptedColumn(); + bool MigrateToVersion24CleanupOversizedStringFields(); + bool MigrateToVersion27UpdateLegacyCreditCards(); + bool MigrateToVersion30AddDateModifed(); + bool MigrateToVersion31AddGUIDToCreditCardsAndProfiles(); + bool MigrateToVersion32UpdateProfilesAndCreditCards(); + bool MigrateToVersion33ProfilesBasedOnFirstName(); + bool MigrateToVersion34ProfilesBasedOnCountryCode(); + bool MigrateToVersion35GreatBritainCountryCodes(); + bool MigrateToVersion37MergeAndCullOlderProfiles(); + bool MigrateToVersion51AddOriginColumn(); + + // Max data length saved in the table; + static const size_t kMaxDataLength; + + private: + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_AddChanges); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_RemoveBetweenChanges); + + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_UpdateDontReplace); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, Autofill_AddFormFieldValues); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, AutofillProfile); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, UpdateAutofillProfile); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, AutofillProfileTrash); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, AutofillProfileTrashInteraction); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, + RemoveAutofillDataModifiedBetween); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, CreditCard); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, UpdateCreditCard); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, + Autofill_GetAllAutofillEntries_OneResult); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, + Autofill_GetAllAutofillEntries_TwoDistinct); + FRIEND_TEST_ALL_PREFIXES(AutofillTableTest, + Autofill_GetAllAutofillEntries_TwoSame); + + // Methods for adding autofill entries at a specified time. For + // testing only. + bool AddFormFieldValuesTime( + const std::vector<FormFieldData>& elements, + std::vector<AutofillChange>* changes, + base::Time time); + bool AddFormFieldValueTime(const FormFieldData& element, + std::vector<AutofillChange>* changes, + base::Time time); + + // Insert a single AutofillEntry into the autofill/autofill_dates tables. + bool InsertAutofillEntry(const AutofillEntry& entry); + + // Checks if the trash is empty. + bool IsAutofillProfilesTrashEmpty(); + + // Checks if the guid is in the trash. + bool IsAutofillGUIDInTrash(const std::string& guid); + + bool InitMainTable(); + bool InitCreditCardsTable(); + bool InitDatesTable(); + bool InitProfilesTable(); + bool InitProfileNamesTable(); + bool InitProfileEmailsTable(); + bool InitProfilePhonesTable(); + bool InitProfileTrashTable(); + + // The application locale. The locale is needed for the migration to version + // 35. Since it must be read on the UI thread, it is set when the table is + // created (on the UI thread), and cached here so that it can be used for + // migrations (on the DB thread). + std::string app_locale_; + + DISALLOW_COPY_AND_ASSIGN(AutofillTable); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_TABLE_H_ diff --git a/chromium/components/autofill/core/browser/webdata/autofill_table_unittest.cc b/chromium/components/autofill/core/browser/webdata/autofill_table_unittest.cc new file mode 100644 index 00000000000..eff54f856f1 --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_table_unittest.cc @@ -0,0 +1,1495 @@ +// 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 <vector> + +#include "base/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/guid.h" +#include "base/path_service.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/webdata/autofill_change.h" +#include "components/autofill/core/browser/webdata/autofill_entry.h" +#include "components/autofill/core/browser/webdata/autofill_table.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/webdata/common/web_database.h" +#include "components/webdata/encryptor/encryptor.h" +#include "sql/statement.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::Time; +using base::TimeDelta; + +namespace autofill { + +// So we can compare AutofillKeys with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const AutofillKey& key) { + return os << UTF16ToASCII(key.name()) << ", " << UTF16ToASCII(key.value()); +} + +// So we can compare AutofillChanges with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const AutofillChange& change) { + switch (change.type()) { + case AutofillChange::ADD: { + os << "ADD"; + break; + } + case AutofillChange::UPDATE: { + os << "UPDATE"; + break; + } + case AutofillChange::REMOVE: { + os << "REMOVE"; + break; + } + } + return os << " " << change.key(); +} + +namespace { + +bool CompareAutofillEntries(const AutofillEntry& a, const AutofillEntry& b) { + std::set<Time> timestamps1(a.timestamps().begin(), a.timestamps().end()); + std::set<Time> timestamps2(b.timestamps().begin(), b.timestamps().end()); + + int compVal = a.key().name().compare(b.key().name()); + if (compVal != 0) { + return compVal < 0; + } + + compVal = a.key().value().compare(b.key().value()); + if (compVal != 0) { + return compVal < 0; + } + + if (timestamps1.size() != timestamps2.size()) { + return timestamps1.size() < timestamps2.size(); + } + + std::set<Time>::iterator it; + for (it = timestamps1.begin(); it != timestamps1.end(); it++) { + timestamps2.erase(*it); + } + + return !timestamps2.empty(); +} + +} // anonymous namespace + +class AutofillTableTest : public testing::Test { + public: + AutofillTableTest() {} + virtual ~AutofillTableTest() {} + + protected: + typedef std::set<AutofillEntry, + bool (*)(const AutofillEntry&, const AutofillEntry&)> AutofillEntrySet; + typedef std::set<AutofillEntry, bool (*)(const AutofillEntry&, + const AutofillEntry&)>::iterator AutofillEntrySetIterator; + + virtual void SetUp() { +#if defined(OS_MACOSX) + Encryptor::UseMockKeychain(true); +#endif + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + file_ = temp_dir_.path().AppendASCII("TestWebDatabase"); + + table_.reset(new AutofillTable("en-US")); + db_.reset(new WebDatabase); + db_->AddTable(table_.get()); + ASSERT_EQ(sql::INIT_OK, db_->Init(file_)); + } + + static AutofillEntry MakeAutofillEntry(const char* name, + const char* value, + time_t timestamp0, + time_t timestamp1) { + std::vector<Time> timestamps; + if (timestamp0 >= 0) + timestamps.push_back(Time::FromTimeT(timestamp0)); + if (timestamp1 >= 0) + timestamps.push_back(Time::FromTimeT(timestamp1)); + return AutofillEntry( + AutofillKey(ASCIIToUTF16(name), ASCIIToUTF16(value)), timestamps); + } + + base::FilePath file_; + base::ScopedTempDir temp_dir_; + scoped_ptr<AutofillTable> table_; + scoped_ptr<WebDatabase> db_; + + private: + DISALLOW_COPY_AND_ASSIGN(AutofillTableTest); +}; + +TEST_F(AutofillTableTest, Autofill) { + Time t1 = Time::Now(); + + // Simulate the submission of a handful of entries in a field called "Name", + // some more often than others. + AutofillChangeList changes; + FormFieldData field; + field.name = ASCIIToUTF16("Name"); + field.value = ASCIIToUTF16("Superman"); + base::Time now = base::Time::Now(); + base::TimeDelta two_seconds = base::TimeDelta::FromSeconds(2); + EXPECT_FALSE(table_->HasFormElements()); + EXPECT_TRUE(table_->AddFormFieldValue(field, &changes)); + EXPECT_TRUE(table_->HasFormElements()); + std::vector<base::string16> v; + for (int i = 0; i < 5; i++) { + field.value = ASCIIToUTF16("Clark Kent"); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, + now + i * two_seconds)); + } + for (int i = 0; i < 3; i++) { + field.value = ASCIIToUTF16("Clark Sutter"); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, + now + i * two_seconds)); + } + for (int i = 0; i < 2; i++) { + field.name = ASCIIToUTF16("Favorite Color"); + field.value = ASCIIToUTF16("Green"); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, + now + i * two_seconds)); + } + + int count = 0; + int64 pair_id = 0; + + // We have added the name Clark Kent 5 times, so count should be 5 and pair_id + // should be somthing non-zero. + field.name = ASCIIToUTF16("Name"); + field.value = ASCIIToUTF16("Clark Kent"); + EXPECT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count)); + EXPECT_EQ(5, count); + EXPECT_NE(0, pair_id); + + // Storing in the data base should be case sensitive, so there should be no + // database entry for clark kent lowercase. + field.value = ASCIIToUTF16("clark kent"); + EXPECT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count)); + EXPECT_EQ(0, count); + + field.name = ASCIIToUTF16("Favorite Color"); + field.value = ASCIIToUTF16("Green"); + EXPECT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count)); + EXPECT_EQ(2, count); + + // This is meant to get a list of suggestions for Name. The empty prefix + // in the second argument means it should return all suggestions for a name + // no matter what they start with. The order that the names occur in the list + // should be decreasing order by count. + EXPECT_TRUE(table_->GetFormValuesForElementName( + ASCIIToUTF16("Name"), base::string16(), &v, 6)); + EXPECT_EQ(3U, v.size()); + if (v.size() == 3) { + EXPECT_EQ(ASCIIToUTF16("Clark Kent"), v[0]); + EXPECT_EQ(ASCIIToUTF16("Clark Sutter"), v[1]); + EXPECT_EQ(ASCIIToUTF16("Superman"), v[2]); + } + + // If we query again limiting the list size to 1, we should only get the most + // frequent entry. + EXPECT_TRUE(table_->GetFormValuesForElementName( + ASCIIToUTF16("Name"), base::string16(), &v, 1)); + EXPECT_EQ(1U, v.size()); + if (v.size() == 1) { + EXPECT_EQ(ASCIIToUTF16("Clark Kent"), v[0]); + } + + // Querying for suggestions given a prefix is case-insensitive, so the prefix + // "cLa" shoud get suggestions for both Clarks. + EXPECT_TRUE(table_->GetFormValuesForElementName( + ASCIIToUTF16("Name"), ASCIIToUTF16("cLa"), &v, 6)); + EXPECT_EQ(2U, v.size()); + if (v.size() == 2) { + EXPECT_EQ(ASCIIToUTF16("Clark Kent"), v[0]); + EXPECT_EQ(ASCIIToUTF16("Clark Sutter"), v[1]); + } + + // Removing all elements since the beginning of this function should remove + // everything from the database. + changes.clear(); + EXPECT_TRUE(table_->RemoveFormElementsAddedBetween(t1, Time(), &changes)); + + const AutofillChange expected_changes[] = { + AutofillChange(AutofillChange::REMOVE, + AutofillKey(ASCIIToUTF16("Name"), + ASCIIToUTF16("Superman"))), + AutofillChange(AutofillChange::REMOVE, + AutofillKey(ASCIIToUTF16("Name"), + ASCIIToUTF16("Clark Kent"))), + AutofillChange(AutofillChange::REMOVE, + AutofillKey(ASCIIToUTF16("Name"), + ASCIIToUTF16("Clark Sutter"))), + AutofillChange(AutofillChange::REMOVE, + AutofillKey(ASCIIToUTF16("Favorite Color"), + ASCIIToUTF16("Green"))), + }; + EXPECT_EQ(arraysize(expected_changes), changes.size()); + for (size_t i = 0; i < arraysize(expected_changes); i++) { + EXPECT_EQ(expected_changes[i], changes[i]); + } + + field.name = ASCIIToUTF16("Name"); + field.value = ASCIIToUTF16("Clark Kent"); + EXPECT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count)); + EXPECT_EQ(0, count); + + EXPECT_TRUE(table_->GetFormValuesForElementName( + ASCIIToUTF16("Name"), base::string16(), &v, 6)); + EXPECT_EQ(0U, v.size()); + + // Now add some values with empty strings. + const base::string16 kValue = ASCIIToUTF16(" toto "); + field.name = ASCIIToUTF16("blank"); + field.value = base::string16(); + EXPECT_TRUE(table_->AddFormFieldValue(field, &changes)); + field.name = ASCIIToUTF16("blank"); + field.value = ASCIIToUTF16(" "); + EXPECT_TRUE(table_->AddFormFieldValue(field, &changes)); + field.name = ASCIIToUTF16("blank"); + field.value = ASCIIToUTF16(" "); + EXPECT_TRUE(table_->AddFormFieldValue(field, &changes)); + field.name = ASCIIToUTF16("blank"); + field.value = kValue; + EXPECT_TRUE(table_->AddFormFieldValue(field, &changes)); + + // They should be stored normally as the DB layer does not check for empty + // values. + v.clear(); + EXPECT_TRUE(table_->GetFormValuesForElementName( + ASCIIToUTF16("blank"), base::string16(), &v, 10)); + EXPECT_EQ(4U, v.size()); + + // Now we'll check that ClearAutofillEmptyValueElements() works as expected. + table_->ClearAutofillEmptyValueElements(); + + v.clear(); + EXPECT_TRUE(table_->GetFormValuesForElementName( + ASCIIToUTF16("blank"), base::string16(), &v, 10)); + ASSERT_EQ(1U, v.size()); + + EXPECT_EQ(kValue, v[0]); +} + +TEST_F(AutofillTableTest, Autofill_RemoveBetweenChanges) { + TimeDelta one_day(TimeDelta::FromDays(1)); + Time t1 = Time::Now(); + Time t2 = t1 + one_day; + + AutofillChangeList changes; + FormFieldData field; + field.name = ASCIIToUTF16("Name"); + field.value = ASCIIToUTF16("Superman"); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, t1)); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, t2)); + + changes.clear(); + EXPECT_TRUE(table_->RemoveFormElementsAddedBetween(t1, t2, &changes)); + ASSERT_EQ(1U, changes.size()); + EXPECT_EQ(AutofillChange(AutofillChange::UPDATE, + AutofillKey(ASCIIToUTF16("Name"), + ASCIIToUTF16("Superman"))), + changes[0]); + changes.clear(); + + EXPECT_TRUE( + table_->RemoveFormElementsAddedBetween(t2, t2 + one_day, &changes)); + ASSERT_EQ(1U, changes.size()); + EXPECT_EQ(AutofillChange(AutofillChange::REMOVE, + AutofillKey(ASCIIToUTF16("Name"), + ASCIIToUTF16("Superman"))), + changes[0]); +} + +TEST_F(AutofillTableTest, Autofill_AddChanges) { + TimeDelta one_day(TimeDelta::FromDays(1)); + Time t1 = Time::Now(); + Time t2 = t1 + one_day; + + AutofillChangeList changes; + FormFieldData field; + field.name = ASCIIToUTF16("Name"); + field.value = ASCIIToUTF16("Superman"); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, t1)); + ASSERT_EQ(1U, changes.size()); + EXPECT_EQ(AutofillChange(AutofillChange::ADD, + AutofillKey(ASCIIToUTF16("Name"), + ASCIIToUTF16("Superman"))), + changes[0]); + + changes.clear(); + EXPECT_TRUE( + table_->AddFormFieldValueTime(field, &changes, t2)); + ASSERT_EQ(1U, changes.size()); + EXPECT_EQ(AutofillChange(AutofillChange::UPDATE, + AutofillKey(ASCIIToUTF16("Name"), + ASCIIToUTF16("Superman"))), + changes[0]); +} + +TEST_F(AutofillTableTest, Autofill_UpdateOneWithOneTimestamp) { + AutofillEntry entry(MakeAutofillEntry("foo", "bar", 1, -1)); + std::vector<AutofillEntry> entries; + entries.push_back(entry); + ASSERT_TRUE(table_->UpdateAutofillEntries(entries)); + + FormFieldData field; + field.name = ASCIIToUTF16("foo"); + field.value = ASCIIToUTF16("bar"); + int64 pair_id; + int count; + ASSERT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count)); + EXPECT_LE(0, pair_id); + EXPECT_EQ(1, count); + + std::vector<AutofillEntry> all_entries; + ASSERT_TRUE(table_->GetAllAutofillEntries(&all_entries)); + ASSERT_EQ(1U, all_entries.size()); + EXPECT_TRUE(entry == all_entries[0]); +} + +TEST_F(AutofillTableTest, Autofill_UpdateOneWithTwoTimestamps) { + AutofillEntry entry(MakeAutofillEntry("foo", "bar", 1, 2)); + std::vector<AutofillEntry> entries; + entries.push_back(entry); + ASSERT_TRUE(table_->UpdateAutofillEntries(entries)); + + FormFieldData field; + field.name = ASCIIToUTF16("foo"); + field.value = ASCIIToUTF16("bar"); + int64 pair_id; + int count; + ASSERT_TRUE(table_->GetIDAndCountOfFormElement(field, &pair_id, &count)); + EXPECT_LE(0, pair_id); + EXPECT_EQ(2, count); + + std::vector<AutofillEntry> all_entries; + ASSERT_TRUE(table_->GetAllAutofillEntries(&all_entries)); + ASSERT_EQ(1U, all_entries.size()); + EXPECT_TRUE(entry == all_entries[0]); +} + +TEST_F(AutofillTableTest, Autofill_GetAutofillTimestamps) { + AutofillEntry entry(MakeAutofillEntry("foo", "bar", 1, 2)); + std::vector<AutofillEntry> entries; + entries.push_back(entry); + ASSERT_TRUE(table_->UpdateAutofillEntries(entries)); + + std::vector<Time> timestamps; + ASSERT_TRUE(table_->GetAutofillTimestamps(ASCIIToUTF16("foo"), + ASCIIToUTF16("bar"), + ×tamps)); + ASSERT_EQ(2U, timestamps.size()); + EXPECT_TRUE(Time::FromTimeT(1) == timestamps[0]); + EXPECT_TRUE(Time::FromTimeT(2) == timestamps[1]); +} + +TEST_F(AutofillTableTest, Autofill_UpdateTwo) { + AutofillEntry entry0(MakeAutofillEntry("foo", "bar0", 1, -1)); + AutofillEntry entry1(MakeAutofillEntry("foo", "bar1", 2, 3)); + std::vector<AutofillEntry> entries; + entries.push_back(entry0); + entries.push_back(entry1); + ASSERT_TRUE(table_->UpdateAutofillEntries(entries)); + + FormFieldData field0; + field0.name = ASCIIToUTF16("foo"); + field0.value = ASCIIToUTF16("bar0"); + int64 pair_id; + int count; + ASSERT_TRUE(table_->GetIDAndCountOfFormElement(field0, &pair_id, &count)); + EXPECT_LE(0, pair_id); + EXPECT_EQ(1, count); + + FormFieldData field1; + field1.name = ASCIIToUTF16("foo"); + field1.value = ASCIIToUTF16("bar1"); + ASSERT_TRUE(table_->GetIDAndCountOfFormElement(field1, &pair_id, &count)); + EXPECT_LE(0, pair_id); + EXPECT_EQ(2, count); +} + +TEST_F(AutofillTableTest, Autofill_UpdateReplace) { + AutofillChangeList changes; + // Add a form field. This will be replaced. + FormFieldData field; + field.name = ASCIIToUTF16("Name"); + field.value = ASCIIToUTF16("Superman"); + EXPECT_TRUE(table_->AddFormFieldValue(field, &changes)); + + AutofillEntry entry(MakeAutofillEntry("Name", "Superman", 1, 2)); + std::vector<AutofillEntry> entries; + entries.push_back(entry); + ASSERT_TRUE(table_->UpdateAutofillEntries(entries)); + + std::vector<AutofillEntry> all_entries; + ASSERT_TRUE(table_->GetAllAutofillEntries(&all_entries)); + ASSERT_EQ(1U, all_entries.size()); + EXPECT_TRUE(entry == all_entries[0]); +} + +TEST_F(AutofillTableTest, Autofill_UpdateDontReplace) { + Time t = Time::Now(); + AutofillEntry existing( + MakeAutofillEntry("Name", "Superman", t.ToTimeT(), -1)); + + AutofillChangeList changes; + // Add a form field. This will NOT be replaced. + FormFieldData field; + field.name = existing.key().name(); + field.value = existing.key().value(); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, t)); + AutofillEntry entry(MakeAutofillEntry("Name", "Clark Kent", 1, 2)); + std::vector<AutofillEntry> entries; + entries.push_back(entry); + ASSERT_TRUE(table_->UpdateAutofillEntries(entries)); + + std::vector<AutofillEntry> all_entries; + ASSERT_TRUE(table_->GetAllAutofillEntries(&all_entries)); + ASSERT_EQ(2U, all_entries.size()); + AutofillEntrySet expected_entries(all_entries.begin(), + all_entries.end(), + CompareAutofillEntries); + EXPECT_EQ(1U, expected_entries.count(existing)); + EXPECT_EQ(1U, expected_entries.count(entry)); +} + +TEST_F(AutofillTableTest, Autofill_AddFormFieldValues) { + Time t = Time::Now(); + + // Add multiple values for "firstname" and "lastname" names. Test that only + // first value of each gets added. Related to security issue: + // http://crbug.com/51727. + std::vector<FormFieldData> elements; + FormFieldData field; + field.name = ASCIIToUTF16("firstname"); + field.value = ASCIIToUTF16("Joe"); + elements.push_back(field); + + field.name = ASCIIToUTF16("firstname"); + field.value = ASCIIToUTF16("Jane"); + elements.push_back(field); + + field.name = ASCIIToUTF16("lastname"); + field.value = ASCIIToUTF16("Smith"); + elements.push_back(field); + + field.name = ASCIIToUTF16("lastname"); + field.value = ASCIIToUTF16("Jones"); + elements.push_back(field); + + std::vector<AutofillChange> changes; + table_->AddFormFieldValuesTime(elements, &changes, t); + + ASSERT_EQ(2U, changes.size()); + EXPECT_EQ(changes[0], AutofillChange(AutofillChange::ADD, + AutofillKey(ASCIIToUTF16("firstname"), + ASCIIToUTF16("Joe")))); + EXPECT_EQ(changes[1], AutofillChange(AutofillChange::ADD, + AutofillKey(ASCIIToUTF16("lastname"), + ASCIIToUTF16("Smith")))); + + std::vector<AutofillEntry> all_entries; + ASSERT_TRUE(table_->GetAllAutofillEntries(&all_entries)); + ASSERT_EQ(2U, all_entries.size()); +} + +TEST_F(AutofillTableTest, AutofillProfile) { + // Add a 'Home' profile. + AutofillProfile home_profile; + home_profile.set_origin(std::string()); + home_profile.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John")); + home_profile.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Q.")); + home_profile.SetRawInfo(NAME_LAST, ASCIIToUTF16("Smith")); + home_profile.SetRawInfo(EMAIL_ADDRESS, ASCIIToUTF16("js@smith.xyz")); + home_profile.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Google")); + home_profile.SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16("1234 Apple Way")); + home_profile.SetRawInfo(ADDRESS_HOME_LINE2, ASCIIToUTF16("unit 5")); + home_profile.SetRawInfo(ADDRESS_HOME_CITY, ASCIIToUTF16("Los Angeles")); + home_profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("CA")); + home_profile.SetRawInfo(ADDRESS_HOME_ZIP, ASCIIToUTF16("90025")); + home_profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + home_profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("18181234567")); + + Time pre_creation_time = Time::Now(); + EXPECT_TRUE(table_->AddAutofillProfile(home_profile)); + Time post_creation_time = Time::Now(); + + // Get the 'Home' profile. + AutofillProfile* db_profile; + ASSERT_TRUE(table_->GetAutofillProfile(home_profile.guid(), &db_profile)); + EXPECT_EQ(home_profile, *db_profile); + sql::Statement s_home(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified " + "FROM autofill_profiles WHERE guid=?")); + s_home.BindString(0, home_profile.guid()); + ASSERT_TRUE(s_home.is_valid()); + ASSERT_TRUE(s_home.Step()); + EXPECT_GE(s_home.ColumnInt64(0), pre_creation_time.ToTimeT()); + EXPECT_LE(s_home.ColumnInt64(0), post_creation_time.ToTimeT()); + EXPECT_FALSE(s_home.Step()); + delete db_profile; + + // Add a 'Billing' profile. + AutofillProfile billing_profile = home_profile; + billing_profile.set_guid(base::GenerateGUID()); + billing_profile.set_origin("https://www.example.com/"); + billing_profile.SetRawInfo(ADDRESS_HOME_LINE1, + ASCIIToUTF16("5678 Bottom Street")); + billing_profile.SetRawInfo(ADDRESS_HOME_LINE2, ASCIIToUTF16("suite 3")); + + pre_creation_time = Time::Now(); + EXPECT_TRUE(table_->AddAutofillProfile(billing_profile)); + post_creation_time = Time::Now(); + + // Get the 'Billing' profile. + ASSERT_TRUE(table_->GetAutofillProfile(billing_profile.guid(), &db_profile)); + EXPECT_EQ(billing_profile, *db_profile); + sql::Statement s_billing(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles WHERE guid=?")); + s_billing.BindString(0, billing_profile.guid()); + ASSERT_TRUE(s_billing.is_valid()); + ASSERT_TRUE(s_billing.Step()); + EXPECT_GE(s_billing.ColumnInt64(0), pre_creation_time.ToTimeT()); + EXPECT_LE(s_billing.ColumnInt64(0), post_creation_time.ToTimeT()); + EXPECT_FALSE(s_billing.Step()); + delete db_profile; + + // Update the 'Billing' profile, name only. + billing_profile.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Jane")); + Time pre_modification_time = Time::Now(); + EXPECT_TRUE(table_->UpdateAutofillProfile(billing_profile)); + Time post_modification_time = Time::Now(); + ASSERT_TRUE(table_->GetAutofillProfile(billing_profile.guid(), &db_profile)); + EXPECT_EQ(billing_profile, *db_profile); + sql::Statement s_billing_updated(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles WHERE guid=?")); + s_billing_updated.BindString(0, billing_profile.guid()); + ASSERT_TRUE(s_billing_updated.is_valid()); + ASSERT_TRUE(s_billing_updated.Step()); + EXPECT_GE(s_billing_updated.ColumnInt64(0), + pre_modification_time.ToTimeT()); + EXPECT_LE(s_billing_updated.ColumnInt64(0), + post_modification_time.ToTimeT()); + EXPECT_FALSE(s_billing_updated.Step()); + delete db_profile; + + // Update the 'Billing' profile. + billing_profile.set_origin("Chrome settings"); + billing_profile.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Janice")); + billing_profile.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("C.")); + billing_profile.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Joplin")); + billing_profile.SetRawInfo(EMAIL_ADDRESS, ASCIIToUTF16("jane@singer.com")); + billing_profile.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Indy")); + billing_profile.SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16("Open Road")); + billing_profile.SetRawInfo(ADDRESS_HOME_LINE2, ASCIIToUTF16("Route 66")); + billing_profile.SetRawInfo(ADDRESS_HOME_CITY, ASCIIToUTF16("NFA")); + billing_profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("NY")); + billing_profile.SetRawInfo(ADDRESS_HOME_ZIP, ASCIIToUTF16("10011")); + billing_profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + billing_profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, + ASCIIToUTF16("18181230000")); + Time pre_modification_time_2 = Time::Now(); + EXPECT_TRUE(table_->UpdateAutofillProfile(billing_profile)); + Time post_modification_time_2 = Time::Now(); + ASSERT_TRUE(table_->GetAutofillProfile(billing_profile.guid(), &db_profile)); + EXPECT_EQ(billing_profile, *db_profile); + sql::Statement s_billing_updated_2( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles WHERE guid=?")); + s_billing_updated_2.BindString(0, billing_profile.guid()); + ASSERT_TRUE(s_billing_updated_2.is_valid()); + ASSERT_TRUE(s_billing_updated_2.Step()); + EXPECT_GE(s_billing_updated_2.ColumnInt64(0), + pre_modification_time_2.ToTimeT()); + EXPECT_LE(s_billing_updated_2.ColumnInt64(0), + post_modification_time_2.ToTimeT()); + EXPECT_FALSE(s_billing_updated_2.Step()); + delete db_profile; + + // Remove the 'Billing' profile. + EXPECT_TRUE(table_->RemoveAutofillProfile(billing_profile.guid())); + EXPECT_FALSE(table_->GetAutofillProfile(billing_profile.guid(), &db_profile)); +} + +TEST_F(AutofillTableTest, AutofillProfileMultiValueNames) { + AutofillProfile p; + const base::string16 kJohnDoe(ASCIIToUTF16("John Doe")); + const base::string16 kJohnPDoe(ASCIIToUTF16("John P. Doe")); + std::vector<base::string16> set_values; + set_values.push_back(kJohnDoe); + set_values.push_back(kJohnPDoe); + p.SetRawMultiInfo(NAME_FULL, set_values); + + EXPECT_TRUE(table_->AddAutofillProfile(p)); + + AutofillProfile* db_profile; + ASSERT_TRUE(table_->GetAutofillProfile(p.guid(), &db_profile)); + EXPECT_EQ(p, *db_profile); + EXPECT_EQ(0, p.Compare(*db_profile)); + delete db_profile; + + // Update the values. + const base::string16 kNoOne(ASCIIToUTF16("No One")); + set_values[1] = kNoOne; + p.SetRawMultiInfo(NAME_FULL, set_values); + EXPECT_TRUE(table_->UpdateAutofillProfile(p)); + ASSERT_TRUE(table_->GetAutofillProfile(p.guid(), &db_profile)); + EXPECT_EQ(p, *db_profile); + EXPECT_EQ(0, p.Compare(*db_profile)); + delete db_profile; + + // Delete values. + set_values.clear(); + p.SetRawMultiInfo(NAME_FULL, set_values); + EXPECT_TRUE(table_->UpdateAutofillProfile(p)); + ASSERT_TRUE(table_->GetAutofillProfile(p.guid(), &db_profile)); + EXPECT_EQ(p, *db_profile); + EXPECT_EQ(0, p.Compare(*db_profile)); + EXPECT_EQ(base::string16(), db_profile->GetRawInfo(NAME_FULL)); + delete db_profile; +} + +TEST_F(AutofillTableTest, AutofillProfileMultiValueEmails) { + AutofillProfile p; + const base::string16 kJohnDoe(ASCIIToUTF16("john@doe.com")); + const base::string16 kJohnPDoe(ASCIIToUTF16("john_p@doe.com")); + std::vector<base::string16> set_values; + set_values.push_back(kJohnDoe); + set_values.push_back(kJohnPDoe); + p.SetRawMultiInfo(EMAIL_ADDRESS, set_values); + + EXPECT_TRUE(table_->AddAutofillProfile(p)); + + AutofillProfile* db_profile; + ASSERT_TRUE(table_->GetAutofillProfile(p.guid(), &db_profile)); + EXPECT_EQ(p, *db_profile); + EXPECT_EQ(0, p.Compare(*db_profile)); + delete db_profile; + + // Update the values. + const base::string16 kNoOne(ASCIIToUTF16("no@one.com")); + set_values[1] = kNoOne; + p.SetRawMultiInfo(EMAIL_ADDRESS, set_values); + EXPECT_TRUE(table_->UpdateAutofillProfile(p)); + ASSERT_TRUE(table_->GetAutofillProfile(p.guid(), &db_profile)); + EXPECT_EQ(p, *db_profile); + EXPECT_EQ(0, p.Compare(*db_profile)); + delete db_profile; + + // Delete values. + set_values.clear(); + p.SetRawMultiInfo(EMAIL_ADDRESS, set_values); + EXPECT_TRUE(table_->UpdateAutofillProfile(p)); + ASSERT_TRUE(table_->GetAutofillProfile(p.guid(), &db_profile)); + EXPECT_EQ(p, *db_profile); + EXPECT_EQ(0, p.Compare(*db_profile)); + EXPECT_EQ(base::string16(), db_profile->GetRawInfo(EMAIL_ADDRESS)); + delete db_profile; +} + +TEST_F(AutofillTableTest, AutofillProfileMultiValuePhone) { + AutofillProfile p; + const base::string16 kJohnDoe(ASCIIToUTF16("4151112222")); + const base::string16 kJohnPDoe(ASCIIToUTF16("4151113333")); + std::vector<base::string16> set_values; + set_values.push_back(kJohnDoe); + set_values.push_back(kJohnPDoe); + p.SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, set_values); + + EXPECT_TRUE(table_->AddAutofillProfile(p)); + + AutofillProfile* db_profile; + ASSERT_TRUE(table_->GetAutofillProfile(p.guid(), &db_profile)); + EXPECT_EQ(p, *db_profile); + EXPECT_EQ(0, p.Compare(*db_profile)); + delete db_profile; + + // Update the values. + const base::string16 kNoOne(ASCIIToUTF16("4151110000")); + set_values[1] = kNoOne; + p.SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, set_values); + EXPECT_TRUE(table_->UpdateAutofillProfile(p)); + ASSERT_TRUE(table_->GetAutofillProfile(p.guid(), &db_profile)); + EXPECT_EQ(p, *db_profile); + EXPECT_EQ(0, p.Compare(*db_profile)); + delete db_profile; + + // Delete values. + set_values.clear(); + p.SetRawMultiInfo(PHONE_HOME_WHOLE_NUMBER, set_values); + EXPECT_TRUE(table_->UpdateAutofillProfile(p)); + ASSERT_TRUE(table_->GetAutofillProfile(p.guid(), &db_profile)); + EXPECT_EQ(p, *db_profile); + EXPECT_EQ(0, p.Compare(*db_profile)); + EXPECT_EQ(base::string16(), db_profile->GetRawInfo(EMAIL_ADDRESS)); + delete db_profile; +} + +TEST_F(AutofillTableTest, AutofillProfileTrash) { + std::vector<std::string> guids; + table_->GetAutofillProfilesInTrash(&guids); + EXPECT_TRUE(guids.empty()); + + ASSERT_TRUE(table_->AddAutofillGUIDToTrash( + "00000000-0000-0000-0000-000000000000")); + ASSERT_TRUE(table_->AddAutofillGUIDToTrash( + "00000000-0000-0000-0000-000000000001")); + ASSERT_TRUE(table_->GetAutofillProfilesInTrash(&guids)); + EXPECT_EQ(2UL, guids.size()); + EXPECT_EQ("00000000-0000-0000-0000-000000000000", guids[0]); + EXPECT_EQ("00000000-0000-0000-0000-000000000001", guids[1]); + + ASSERT_TRUE(table_->EmptyAutofillProfilesTrash()); + ASSERT_TRUE(table_->GetAutofillProfilesInTrash(&guids)); + EXPECT_TRUE(guids.empty()); +} + +TEST_F(AutofillTableTest, AutofillProfileTrashInteraction) { + std::vector<std::string> guids; + table_->GetAutofillProfilesInTrash(&guids); + EXPECT_TRUE(guids.empty()); + + AutofillProfile profile; + profile.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John")); + profile.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Q.")); + profile.SetRawInfo(NAME_LAST, ASCIIToUTF16("Smith")); + profile.SetRawInfo(EMAIL_ADDRESS,ASCIIToUTF16("js@smith.xyz")); + profile.SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16("1 Main St")); + profile.SetRawInfo(ADDRESS_HOME_CITY, ASCIIToUTF16("Los Angeles")); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("CA")); + profile.SetRawInfo(ADDRESS_HOME_ZIP, ASCIIToUTF16("90025")); + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + + // Mark this profile as in the trash. This stops |AddAutofillProfile| from + // adding it. + EXPECT_TRUE(table_->AddAutofillGUIDToTrash(profile.guid())); + EXPECT_TRUE(table_->AddAutofillProfile(profile)); + AutofillProfile* added_profile = NULL; + EXPECT_FALSE(table_->GetAutofillProfile(profile.guid(), &added_profile)); + EXPECT_EQ(static_cast<AutofillProfile*>(NULL), added_profile); + + // Add the profile for real this time. + EXPECT_TRUE(table_->EmptyAutofillProfilesTrash()); + EXPECT_TRUE(table_->GetAutofillProfilesInTrash(&guids)); + EXPECT_TRUE(guids.empty()); + EXPECT_TRUE(table_->AddAutofillProfile(profile)); + EXPECT_TRUE(table_->GetAutofillProfile(profile.guid(), + &added_profile)); + ASSERT_NE(static_cast<AutofillProfile*>(NULL), added_profile); + delete added_profile; + + // Mark this profile as in the trash. This stops |UpdateAutofillProfileMulti| + // from updating it. In normal operation a profile should not be both in the + // trash and in the profiles table simultaneously. + EXPECT_TRUE(table_->AddAutofillGUIDToTrash(profile.guid())); + profile.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Jane")); + EXPECT_TRUE(table_->UpdateAutofillProfile(profile)); + AutofillProfile* updated_profile = NULL; + EXPECT_TRUE(table_->GetAutofillProfile(profile.guid(), &updated_profile)); + ASSERT_NE(static_cast<AutofillProfile*>(NULL), added_profile); + EXPECT_EQ(ASCIIToUTF16("John"), updated_profile->GetRawInfo(NAME_FIRST)); + delete updated_profile; + + // Try to delete the trashed profile. This stops |RemoveAutofillProfile| from + // deleting it. In normal operation deletion is done by migration step, and + // removal from trash is done by |WebDataService|. |RemoveAutofillProfile| + // does remove the item from the trash if it is found however, so that if + // other clients remove it (via Sync say) then it is gone and doesn't need to + // be processed further by |WebDataService|. + EXPECT_TRUE(table_->RemoveAutofillProfile(profile.guid())); + AutofillProfile* removed_profile = NULL; + EXPECT_TRUE(table_->GetAutofillProfile(profile.guid(), &removed_profile)); + EXPECT_FALSE(table_->IsAutofillGUIDInTrash(profile.guid())); + ASSERT_NE(static_cast<AutofillProfile*>(NULL), removed_profile); + delete removed_profile; + + // Check that emptying the trash now allows removal to occur. + EXPECT_TRUE(table_->EmptyAutofillProfilesTrash()); + EXPECT_TRUE(table_->RemoveAutofillProfile(profile.guid())); + removed_profile = NULL; + EXPECT_FALSE(table_->GetAutofillProfile(profile.guid(), &removed_profile)); + EXPECT_EQ(static_cast<AutofillProfile*>(NULL), removed_profile); +} + +TEST_F(AutofillTableTest, CreditCard) { + // Add a 'Work' credit card. + CreditCard work_creditcard; + work_creditcard.set_origin("https://www.example.com/"); + work_creditcard.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Jack Torrance")); + work_creditcard.SetRawInfo(CREDIT_CARD_NUMBER, + ASCIIToUTF16("1234567890123456")); + work_creditcard.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("04")); + work_creditcard.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, + ASCIIToUTF16("2013")); + + Time pre_creation_time = Time::Now(); + EXPECT_TRUE(table_->AddCreditCard(work_creditcard)); + Time post_creation_time = Time::Now(); + + // Get the 'Work' credit card. + CreditCard* db_creditcard; + ASSERT_TRUE(table_->GetCreditCard(work_creditcard.guid(), &db_creditcard)); + EXPECT_EQ(work_creditcard, *db_creditcard); + sql::Statement s_work(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT guid, name_on_card, expiration_month, expiration_year, " + "card_number_encrypted, date_modified " + "FROM credit_cards WHERE guid=?")); + s_work.BindString(0, work_creditcard.guid()); + ASSERT_TRUE(s_work.is_valid()); + ASSERT_TRUE(s_work.Step()); + EXPECT_GE(s_work.ColumnInt64(5), pre_creation_time.ToTimeT()); + EXPECT_LE(s_work.ColumnInt64(5), post_creation_time.ToTimeT()); + EXPECT_FALSE(s_work.Step()); + delete db_creditcard; + + // Add a 'Target' credit card. + CreditCard target_creditcard; + target_creditcard.set_origin(std::string()); + target_creditcard.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Jack Torrance")); + target_creditcard.SetRawInfo(CREDIT_CARD_NUMBER, + ASCIIToUTF16("1111222233334444")); + target_creditcard.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("06")); + target_creditcard.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, + ASCIIToUTF16("2012")); + + pre_creation_time = Time::Now(); + EXPECT_TRUE(table_->AddCreditCard(target_creditcard)); + post_creation_time = Time::Now(); + ASSERT_TRUE(table_->GetCreditCard(target_creditcard.guid(), &db_creditcard)); + EXPECT_EQ(target_creditcard, *db_creditcard); + sql::Statement s_target(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT guid, name_on_card, expiration_month, expiration_year, " + "card_number_encrypted, date_modified " + "FROM credit_cards WHERE guid=?")); + s_target.BindString(0, target_creditcard.guid()); + ASSERT_TRUE(s_target.is_valid()); + ASSERT_TRUE(s_target.Step()); + EXPECT_GE(s_target.ColumnInt64(5), pre_creation_time.ToTimeT()); + EXPECT_LE(s_target.ColumnInt64(5), post_creation_time.ToTimeT()); + EXPECT_FALSE(s_target.Step()); + delete db_creditcard; + + // Update the 'Target' credit card. + target_creditcard.set_origin("Interactive Autofill dialog"); + target_creditcard.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Charles Grady")); + Time pre_modification_time = Time::Now(); + EXPECT_TRUE(table_->UpdateCreditCard(target_creditcard)); + Time post_modification_time = Time::Now(); + ASSERT_TRUE(table_->GetCreditCard(target_creditcard.guid(), &db_creditcard)); + EXPECT_EQ(target_creditcard, *db_creditcard); + sql::Statement s_target_updated(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT guid, name_on_card, expiration_month, expiration_year, " + "card_number_encrypted, date_modified " + "FROM credit_cards WHERE guid=?")); + s_target_updated.BindString(0, target_creditcard.guid()); + ASSERT_TRUE(s_target_updated.is_valid()); + ASSERT_TRUE(s_target_updated.Step()); + EXPECT_GE(s_target_updated.ColumnInt64(5), pre_modification_time.ToTimeT()); + EXPECT_LE(s_target_updated.ColumnInt64(5), post_modification_time.ToTimeT()); + EXPECT_FALSE(s_target_updated.Step()); + delete db_creditcard; + + // Remove the 'Target' credit card. + EXPECT_TRUE(table_->RemoveCreditCard(target_creditcard.guid())); + EXPECT_FALSE(table_->GetCreditCard(target_creditcard.guid(), &db_creditcard)); +} + +TEST_F(AutofillTableTest, UpdateAutofillProfile) { + // Add a profile to the db. + AutofillProfile profile; + profile.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John")); + profile.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Q.")); + profile.SetRawInfo(NAME_LAST, ASCIIToUTF16("Smith")); + profile.SetRawInfo(EMAIL_ADDRESS, ASCIIToUTF16("js@example.com")); + profile.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Google")); + profile.SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16("1234 Apple Way")); + profile.SetRawInfo(ADDRESS_HOME_LINE2, ASCIIToUTF16("unit 5")); + profile.SetRawInfo(ADDRESS_HOME_CITY, ASCIIToUTF16("Los Angeles")); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("CA")); + profile.SetRawInfo(ADDRESS_HOME_ZIP, ASCIIToUTF16("90025")); + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("18181234567")); + table_->AddAutofillProfile(profile); + + // Set a mocked value for the profile's creation time. + const time_t mock_creation_date = Time::Now().ToTimeT() - 13; + sql::Statement s_mock_creation_date( + db_->GetSQLConnection()->GetUniqueStatement( + "UPDATE autofill_profiles SET date_modified = ?")); + ASSERT_TRUE(s_mock_creation_date.is_valid()); + s_mock_creation_date.BindInt64(0, mock_creation_date); + ASSERT_TRUE(s_mock_creation_date.Run()); + + // Get the profile. + AutofillProfile* tmp_profile; + ASSERT_TRUE(table_->GetAutofillProfile(profile.guid(), &tmp_profile)); + scoped_ptr<AutofillProfile> db_profile(tmp_profile); + EXPECT_EQ(profile, *db_profile); + sql::Statement s_original(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles")); + ASSERT_TRUE(s_original.is_valid()); + ASSERT_TRUE(s_original.Step()); + EXPECT_EQ(mock_creation_date, s_original.ColumnInt64(0)); + EXPECT_FALSE(s_original.Step()); + + // Now, update the profile and save the update to the database. + // The modification date should change to reflect the update. + profile.SetRawInfo(EMAIL_ADDRESS, ASCIIToUTF16("js@smith.xyz")); + table_->UpdateAutofillProfile(profile); + + // Get the profile. + ASSERT_TRUE(table_->GetAutofillProfile(profile.guid(), &tmp_profile)); + db_profile.reset(tmp_profile); + EXPECT_EQ(profile, *db_profile); + sql::Statement s_updated(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles")); + ASSERT_TRUE(s_updated.is_valid()); + ASSERT_TRUE(s_updated.Step()); + EXPECT_LT(mock_creation_date, s_updated.ColumnInt64(0)); + EXPECT_FALSE(s_updated.Step()); + + // Set a mocked value for the profile's modification time. + const time_t mock_modification_date = Time::Now().ToTimeT() - 7; + sql::Statement s_mock_modification_date( + db_->GetSQLConnection()->GetUniqueStatement( + "UPDATE autofill_profiles SET date_modified = ?")); + ASSERT_TRUE(s_mock_modification_date.is_valid()); + s_mock_modification_date.BindInt64(0, mock_modification_date); + ASSERT_TRUE(s_mock_modification_date.Run()); + + // Finally, call into |UpdateAutofillProfile()| without changing the + // profile. The modification date should not change. + table_->UpdateAutofillProfile(profile); + + // Get the profile. + ASSERT_TRUE(table_->GetAutofillProfile(profile.guid(), &tmp_profile)); + db_profile.reset(tmp_profile); + EXPECT_EQ(profile, *db_profile); + sql::Statement s_unchanged(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles")); + ASSERT_TRUE(s_unchanged.is_valid()); + ASSERT_TRUE(s_unchanged.Step()); + EXPECT_EQ(mock_modification_date, s_unchanged.ColumnInt64(0)); + EXPECT_FALSE(s_unchanged.Step()); +} + +TEST_F(AutofillTableTest, UpdateCreditCard) { + // Add a credit card to the db. + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Jack Torrance")); + credit_card.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16("1234567890123456")); + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("04")); + credit_card.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("2013")); + table_->AddCreditCard(credit_card); + + // Set a mocked value for the credit card's creation time. + const time_t mock_creation_date = Time::Now().ToTimeT() - 13; + sql::Statement s_mock_creation_date( + db_->GetSQLConnection()->GetUniqueStatement( + "UPDATE credit_cards SET date_modified = ?")); + ASSERT_TRUE(s_mock_creation_date.is_valid()); + s_mock_creation_date.BindInt64(0, mock_creation_date); + ASSERT_TRUE(s_mock_creation_date.Run()); + + // Get the credit card. + CreditCard* tmp_credit_card; + ASSERT_TRUE(table_->GetCreditCard(credit_card.guid(), &tmp_credit_card)); + scoped_ptr<CreditCard> db_credit_card(tmp_credit_card); + EXPECT_EQ(credit_card, *db_credit_card); + sql::Statement s_original(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM credit_cards")); + ASSERT_TRUE(s_original.is_valid()); + ASSERT_TRUE(s_original.Step()); + EXPECT_EQ(mock_creation_date, s_original.ColumnInt64(0)); + EXPECT_FALSE(s_original.Step()); + + // Now, update the credit card and save the update to the database. + // The modification date should change to reflect the update. + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("01")); + table_->UpdateCreditCard(credit_card); + + // Get the credit card. + ASSERT_TRUE(table_->GetCreditCard(credit_card.guid(), &tmp_credit_card)); + db_credit_card.reset(tmp_credit_card); + EXPECT_EQ(credit_card, *db_credit_card); + sql::Statement s_updated(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM credit_cards")); + ASSERT_TRUE(s_updated.is_valid()); + ASSERT_TRUE(s_updated.Step()); + EXPECT_LT(mock_creation_date, s_updated.ColumnInt64(0)); + EXPECT_FALSE(s_updated.Step()); + + // Set a mocked value for the credit card's modification time. + const time_t mock_modification_date = Time::Now().ToTimeT() - 7; + sql::Statement s_mock_modification_date( + db_->GetSQLConnection()->GetUniqueStatement( + "UPDATE credit_cards SET date_modified = ?")); + ASSERT_TRUE(s_mock_modification_date.is_valid()); + s_mock_modification_date.BindInt64(0, mock_modification_date); + ASSERT_TRUE(s_mock_modification_date.Run()); + + // Finally, call into |UpdateCreditCard()| without changing the credit card. + // The modification date should not change. + table_->UpdateCreditCard(credit_card); + + // Get the credit card. + ASSERT_TRUE(table_->GetCreditCard(credit_card.guid(), &tmp_credit_card)); + db_credit_card.reset(tmp_credit_card); + EXPECT_EQ(credit_card, *db_credit_card); + sql::Statement s_unchanged(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM credit_cards")); + ASSERT_TRUE(s_unchanged.is_valid()); + ASSERT_TRUE(s_unchanged.Step()); + EXPECT_EQ(mock_modification_date, s_unchanged.ColumnInt64(0)); + EXPECT_FALSE(s_unchanged.Step()); +} + +TEST_F(AutofillTableTest, UpdateProfileOriginOnly) { + // Add a profile to the db. + AutofillProfile profile; + profile.SetRawInfo(NAME_FIRST, ASCIIToUTF16("John")); + profile.SetRawInfo(NAME_MIDDLE, ASCIIToUTF16("Q.")); + profile.SetRawInfo(NAME_LAST, ASCIIToUTF16("Smith")); + profile.SetRawInfo(EMAIL_ADDRESS, ASCIIToUTF16("js@example.com")); + profile.SetRawInfo(COMPANY_NAME, ASCIIToUTF16("Google")); + profile.SetRawInfo(ADDRESS_HOME_LINE1, ASCIIToUTF16("1234 Apple Way")); + profile.SetRawInfo(ADDRESS_HOME_LINE2, ASCIIToUTF16("unit 5")); + profile.SetRawInfo(ADDRESS_HOME_CITY, ASCIIToUTF16("Los Angeles")); + profile.SetRawInfo(ADDRESS_HOME_STATE, ASCIIToUTF16("CA")); + profile.SetRawInfo(ADDRESS_HOME_ZIP, ASCIIToUTF16("90025")); + profile.SetRawInfo(ADDRESS_HOME_COUNTRY, ASCIIToUTF16("US")); + profile.SetRawInfo(PHONE_HOME_WHOLE_NUMBER, ASCIIToUTF16("18181234567")); + table_->AddAutofillProfile(profile); + + // Set a mocked value for the profile's creation time. + const time_t mock_creation_date = Time::Now().ToTimeT() - 13; + sql::Statement s_mock_creation_date( + db_->GetSQLConnection()->GetUniqueStatement( + "UPDATE autofill_profiles SET date_modified = ?")); + ASSERT_TRUE(s_mock_creation_date.is_valid()); + s_mock_creation_date.BindInt64(0, mock_creation_date); + ASSERT_TRUE(s_mock_creation_date.Run()); + + // Get the profile. + AutofillProfile* tmp_profile; + ASSERT_TRUE(table_->GetAutofillProfile(profile.guid(), &tmp_profile)); + scoped_ptr<AutofillProfile> db_profile(tmp_profile); + EXPECT_EQ(profile, *db_profile); + sql::Statement s_original(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles")); + ASSERT_TRUE(s_original.is_valid()); + ASSERT_TRUE(s_original.Step()); + EXPECT_EQ(mock_creation_date, s_original.ColumnInt64(0)); + EXPECT_FALSE(s_original.Step()); + + // Now, update just the profile's origin and save the update to the database. + // The modification date should change to reflect the update. + profile.set_origin("https://www.example.com/"); + table_->UpdateAutofillProfile(profile); + + // Get the profile. + ASSERT_TRUE(table_->GetAutofillProfile(profile.guid(), &tmp_profile)); + db_profile.reset(tmp_profile); + EXPECT_EQ(profile, *db_profile); + sql::Statement s_updated(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles")); + ASSERT_TRUE(s_updated.is_valid()); + ASSERT_TRUE(s_updated.Step()); + EXPECT_LT(mock_creation_date, s_updated.ColumnInt64(0)); + EXPECT_FALSE(s_updated.Step()); +} + +TEST_F(AutofillTableTest, UpdateCreditCardOriginOnly) { + // Add a credit card to the db. + CreditCard credit_card; + credit_card.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Jack Torrance")); + credit_card.SetRawInfo(CREDIT_CARD_NUMBER, ASCIIToUTF16("1234567890123456")); + credit_card.SetRawInfo(CREDIT_CARD_EXP_MONTH, ASCIIToUTF16("04")); + credit_card.SetRawInfo(CREDIT_CARD_EXP_4_DIGIT_YEAR, ASCIIToUTF16("2013")); + table_->AddCreditCard(credit_card); + + // Set a mocked value for the credit card's creation time. + const time_t mock_creation_date = Time::Now().ToTimeT() - 13; + sql::Statement s_mock_creation_date( + db_->GetSQLConnection()->GetUniqueStatement( + "UPDATE credit_cards SET date_modified = ?")); + ASSERT_TRUE(s_mock_creation_date.is_valid()); + s_mock_creation_date.BindInt64(0, mock_creation_date); + ASSERT_TRUE(s_mock_creation_date.Run()); + + // Get the credit card. + CreditCard* tmp_credit_card; + ASSERT_TRUE(table_->GetCreditCard(credit_card.guid(), &tmp_credit_card)); + scoped_ptr<CreditCard> db_credit_card(tmp_credit_card); + EXPECT_EQ(credit_card, *db_credit_card); + sql::Statement s_original(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM credit_cards")); + ASSERT_TRUE(s_original.is_valid()); + ASSERT_TRUE(s_original.Step()); + EXPECT_EQ(mock_creation_date, s_original.ColumnInt64(0)); + EXPECT_FALSE(s_original.Step()); + + // Now, update just the credit card's origin and save the update to the + // database. The modification date should change to reflect the update. + credit_card.set_origin("https://www.example.com/"); + table_->UpdateCreditCard(credit_card); + + // Get the credit card. + ASSERT_TRUE(table_->GetCreditCard(credit_card.guid(), &tmp_credit_card)); + db_credit_card.reset(tmp_credit_card); + EXPECT_EQ(credit_card, *db_credit_card); + sql::Statement s_updated(db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM credit_cards")); + ASSERT_TRUE(s_updated.is_valid()); + ASSERT_TRUE(s_updated.Step()); + EXPECT_LT(mock_creation_date, s_updated.ColumnInt64(0)); + EXPECT_FALSE(s_updated.Step()); +} + +TEST_F(AutofillTableTest, RemoveAutofillDataModifiedBetween) { + // Populate the autofill_profiles and credit_cards tables. + ASSERT_TRUE(db_->GetSQLConnection()->Execute( + "INSERT INTO autofill_profiles (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000000', 11);" + "INSERT INTO autofill_profiles (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000001', 21);" + "INSERT INTO autofill_profiles (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000002', 31);" + "INSERT INTO autofill_profiles (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000003', 41);" + "INSERT INTO autofill_profiles (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000004', 51);" + "INSERT INTO autofill_profiles (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000005', 61);" + "INSERT INTO credit_cards (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000006', 17);" + "INSERT INTO credit_cards (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000007', 27);" + "INSERT INTO credit_cards (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000008', 37);" + "INSERT INTO credit_cards (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000009', 47);" + "INSERT INTO credit_cards (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000010', 57);" + "INSERT INTO credit_cards (guid, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000011', 67);")); + + // Remove all entries modified in the bounded time range [17,41). + std::vector<std::string> profile_guids; + std::vector<std::string> credit_card_guids; + table_->RemoveAutofillDataModifiedBetween( + Time::FromTimeT(17), Time::FromTimeT(41), + &profile_guids, &credit_card_guids); + ASSERT_EQ(2UL, profile_guids.size()); + EXPECT_EQ("00000000-0000-0000-0000-000000000001", profile_guids[0]); + EXPECT_EQ("00000000-0000-0000-0000-000000000002", profile_guids[1]); + sql::Statement s_autofill_profiles_bounded( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles")); + ASSERT_TRUE(s_autofill_profiles_bounded.is_valid()); + ASSERT_TRUE(s_autofill_profiles_bounded.Step()); + EXPECT_EQ(11, s_autofill_profiles_bounded.ColumnInt64(0)); + ASSERT_TRUE(s_autofill_profiles_bounded.Step()); + EXPECT_EQ(41, s_autofill_profiles_bounded.ColumnInt64(0)); + ASSERT_TRUE(s_autofill_profiles_bounded.Step()); + EXPECT_EQ(51, s_autofill_profiles_bounded.ColumnInt64(0)); + ASSERT_TRUE(s_autofill_profiles_bounded.Step()); + EXPECT_EQ(61, s_autofill_profiles_bounded.ColumnInt64(0)); + EXPECT_FALSE(s_autofill_profiles_bounded.Step()); + ASSERT_EQ(3UL, credit_card_guids.size()); + EXPECT_EQ("00000000-0000-0000-0000-000000000006", credit_card_guids[0]); + EXPECT_EQ("00000000-0000-0000-0000-000000000007", credit_card_guids[1]); + EXPECT_EQ("00000000-0000-0000-0000-000000000008", credit_card_guids[2]); + sql::Statement s_credit_cards_bounded( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM credit_cards")); + ASSERT_TRUE(s_credit_cards_bounded.is_valid()); + ASSERT_TRUE(s_credit_cards_bounded.Step()); + EXPECT_EQ(47, s_credit_cards_bounded.ColumnInt64(0)); + ASSERT_TRUE(s_credit_cards_bounded.Step()); + EXPECT_EQ(57, s_credit_cards_bounded.ColumnInt64(0)); + ASSERT_TRUE(s_credit_cards_bounded.Step()); + EXPECT_EQ(67, s_credit_cards_bounded.ColumnInt64(0)); + EXPECT_FALSE(s_credit_cards_bounded.Step()); + + // Remove all entries modified on or after time 51 (unbounded range). + table_->RemoveAutofillDataModifiedBetween( + Time::FromTimeT(51), Time(), + &profile_guids, &credit_card_guids); + ASSERT_EQ(2UL, profile_guids.size()); + EXPECT_EQ("00000000-0000-0000-0000-000000000004", profile_guids[0]); + EXPECT_EQ("00000000-0000-0000-0000-000000000005", profile_guids[1]); + sql::Statement s_autofill_profiles_unbounded( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles")); + ASSERT_TRUE(s_autofill_profiles_unbounded.is_valid()); + ASSERT_TRUE(s_autofill_profiles_unbounded.Step()); + EXPECT_EQ(11, s_autofill_profiles_unbounded.ColumnInt64(0)); + ASSERT_TRUE(s_autofill_profiles_unbounded.Step()); + EXPECT_EQ(41, s_autofill_profiles_unbounded.ColumnInt64(0)); + EXPECT_FALSE(s_autofill_profiles_unbounded.Step()); + ASSERT_EQ(2UL, credit_card_guids.size()); + EXPECT_EQ("00000000-0000-0000-0000-000000000010", credit_card_guids[0]); + EXPECT_EQ("00000000-0000-0000-0000-000000000011", credit_card_guids[1]); + sql::Statement s_credit_cards_unbounded( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM credit_cards")); + ASSERT_TRUE(s_credit_cards_unbounded.is_valid()); + ASSERT_TRUE(s_credit_cards_unbounded.Step()); + EXPECT_EQ(47, s_credit_cards_unbounded.ColumnInt64(0)); + EXPECT_FALSE(s_credit_cards_unbounded.Step()); + + // Remove all remaining entries. + table_->RemoveAutofillDataModifiedBetween( + Time(), Time(), + &profile_guids, &credit_card_guids); + ASSERT_EQ(2UL, profile_guids.size()); + EXPECT_EQ("00000000-0000-0000-0000-000000000000", profile_guids[0]); + EXPECT_EQ("00000000-0000-0000-0000-000000000003", profile_guids[1]); + sql::Statement s_autofill_profiles_empty( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles")); + ASSERT_TRUE(s_autofill_profiles_empty.is_valid()); + EXPECT_FALSE(s_autofill_profiles_empty.Step()); + ASSERT_EQ(1UL, credit_card_guids.size()); + EXPECT_EQ("00000000-0000-0000-0000-000000000009", credit_card_guids[0]); + sql::Statement s_credit_cards_empty( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified FROM credit_cards")); + ASSERT_TRUE(s_credit_cards_empty.is_valid()); + EXPECT_FALSE(s_credit_cards_empty.Step()); +} + +TEST_F(AutofillTableTest, RemoveOriginURLsModifiedBetween) { + // Populate the autofill_profiles and credit_cards tables. + ASSERT_TRUE(db_->GetSQLConnection()->Execute( + "INSERT INTO autofill_profiles (guid, origin, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000000', '', 11);" + "INSERT INTO autofill_profiles (guid, origin, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000001', " + " 'https://www.example.com/', 21);" + "INSERT INTO autofill_profiles (guid, origin, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000002', 'Chrome settings', 31);" + "INSERT INTO credit_cards (guid, origin, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000003', '', 17);" + "INSERT INTO credit_cards (guid, origin, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000004', " + " 'https://www.example.com/', 27);" + "INSERT INTO credit_cards (guid, origin, date_modified) " + "VALUES('00000000-0000-0000-0000-000000000005', 'Chrome settings', " + " 37);")); + + // Remove all origin URLs set in the bounded time range [21,27). + ScopedVector<AutofillProfile> profiles; + table_->RemoveOriginURLsModifiedBetween( + Time::FromTimeT(21), Time::FromTimeT(27), &profiles); + ASSERT_EQ(1UL, profiles.size()); + EXPECT_EQ("00000000-0000-0000-0000-000000000001", profiles[0]->guid()); + sql::Statement s_autofill_profiles_bounded( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified, origin FROM autofill_profiles")); + ASSERT_TRUE(s_autofill_profiles_bounded.is_valid()); + ASSERT_TRUE(s_autofill_profiles_bounded.Step()); + EXPECT_EQ(11, s_autofill_profiles_bounded.ColumnInt64(0)); + EXPECT_EQ(std::string(), s_autofill_profiles_bounded.ColumnString(1)); + ASSERT_TRUE(s_autofill_profiles_bounded.Step()); + EXPECT_EQ(21, s_autofill_profiles_bounded.ColumnInt64(0)); + EXPECT_EQ(std::string(), s_autofill_profiles_bounded.ColumnString(1)); + ASSERT_TRUE(s_autofill_profiles_bounded.Step()); + EXPECT_EQ(31, s_autofill_profiles_bounded.ColumnInt64(0)); + EXPECT_EQ("Chrome settings", s_autofill_profiles_bounded.ColumnString(1)); + sql::Statement s_credit_cards_bounded( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified, origin FROM credit_cards")); + ASSERT_TRUE(s_credit_cards_bounded.is_valid()); + ASSERT_TRUE(s_credit_cards_bounded.Step()); + EXPECT_EQ(17, s_credit_cards_bounded.ColumnInt64(0)); + EXPECT_EQ(std::string(), s_credit_cards_bounded.ColumnString(1)); + ASSERT_TRUE(s_credit_cards_bounded.Step()); + EXPECT_EQ(27, s_credit_cards_bounded.ColumnInt64(0)); + EXPECT_EQ("https://www.example.com/", + s_credit_cards_bounded.ColumnString(1)); + ASSERT_TRUE(s_credit_cards_bounded.Step()); + EXPECT_EQ(37, s_credit_cards_bounded.ColumnInt64(0)); + EXPECT_EQ("Chrome settings", s_credit_cards_bounded.ColumnString(1)); + + // Remove all origin URLS. + profiles.clear(); + table_->RemoveOriginURLsModifiedBetween(Time(), Time(), &profiles); + EXPECT_EQ(0UL, profiles.size()); + sql::Statement s_autofill_profiles_all( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified, origin FROM autofill_profiles")); + ASSERT_TRUE(s_autofill_profiles_all.is_valid()); + ASSERT_TRUE(s_autofill_profiles_all.Step()); + EXPECT_EQ(11, s_autofill_profiles_all.ColumnInt64(0)); + EXPECT_EQ(std::string(), s_autofill_profiles_all.ColumnString(1)); + ASSERT_TRUE(s_autofill_profiles_all.Step()); + EXPECT_EQ(21, s_autofill_profiles_all.ColumnInt64(0)); + EXPECT_EQ(std::string(), s_autofill_profiles_all.ColumnString(1)); + ASSERT_TRUE(s_autofill_profiles_all.Step()); + EXPECT_EQ(31, s_autofill_profiles_all.ColumnInt64(0)); + EXPECT_EQ("Chrome settings", s_autofill_profiles_all.ColumnString(1)); + sql::Statement s_credit_cards_all( + db_->GetSQLConnection()->GetUniqueStatement( + "SELECT date_modified, origin FROM credit_cards")); + ASSERT_TRUE(s_credit_cards_all.is_valid()); + ASSERT_TRUE(s_credit_cards_all.Step()); + EXPECT_EQ(17, s_credit_cards_all.ColumnInt64(0)); + EXPECT_EQ(std::string(), s_credit_cards_all.ColumnString(1)); + ASSERT_TRUE(s_credit_cards_all.Step()); + EXPECT_EQ(27, s_credit_cards_all.ColumnInt64(0)); + EXPECT_EQ(std::string(), s_credit_cards_all.ColumnString(1)); + ASSERT_TRUE(s_credit_cards_all.Step()); + EXPECT_EQ(37, s_credit_cards_all.ColumnInt64(0)); + EXPECT_EQ("Chrome settings", s_credit_cards_all.ColumnString(1)); +} + +TEST_F(AutofillTableTest, Autofill_GetAllAutofillEntries_NoResults) { + std::vector<AutofillEntry> entries; + ASSERT_TRUE(table_->GetAllAutofillEntries(&entries)); + + EXPECT_EQ(0U, entries.size()); +} + +TEST_F(AutofillTableTest, Autofill_GetAllAutofillEntries_OneResult) { + AutofillChangeList changes; + std::map<std::string, std::vector<Time> > name_value_times_map; + + time_t start = 0; + std::vector<Time> timestamps1; + FormFieldData field; + field.name = ASCIIToUTF16("Name"); + field.value = ASCIIToUTF16("Superman"); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, + Time::FromTimeT(start))); + timestamps1.push_back(Time::FromTimeT(start)); + std::string key1("NameSuperman"); + name_value_times_map.insert(std::pair<std::string, + std::vector<Time> > (key1, timestamps1)); + + AutofillEntrySet expected_entries(CompareAutofillEntries); + AutofillKey ak1(ASCIIToUTF16("Name"), ASCIIToUTF16("Superman")); + AutofillEntry ae1(ak1, timestamps1); + + expected_entries.insert(ae1); + + std::vector<AutofillEntry> entries; + ASSERT_TRUE(table_->GetAllAutofillEntries(&entries)); + AutofillEntrySet entry_set(entries.begin(), entries.end(), + CompareAutofillEntries); + + // make sure the lists of entries match + ASSERT_EQ(expected_entries.size(), entry_set.size()); + AutofillEntrySetIterator it; + for (it = entry_set.begin(); it != entry_set.end(); it++) { + expected_entries.erase(*it); + } + + EXPECT_EQ(0U, expected_entries.size()); +} + +TEST_F(AutofillTableTest, Autofill_GetAllAutofillEntries_TwoDistinct) { + AutofillChangeList changes; + std::map<std::string, std::vector<Time> > name_value_times_map; + time_t start = 0; + + std::vector<Time> timestamps1; + FormFieldData field; + field.name = ASCIIToUTF16("Name"); + field.value = ASCIIToUTF16("Superman"); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, + Time::FromTimeT(start))); + timestamps1.push_back(Time::FromTimeT(start)); + std::string key1("NameSuperman"); + name_value_times_map.insert(std::pair<std::string, + std::vector<Time> > (key1, timestamps1)); + + start++; + std::vector<Time> timestamps2; + field.name = ASCIIToUTF16("Name"); + field.value = ASCIIToUTF16("Clark Kent"); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, + Time::FromTimeT(start))); + timestamps2.push_back(Time::FromTimeT(start)); + std::string key2("NameClark Kent"); + name_value_times_map.insert(std::pair<std::string, + std::vector<Time> > (key2, timestamps2)); + + AutofillEntrySet expected_entries(CompareAutofillEntries); + AutofillKey ak1(ASCIIToUTF16("Name"), ASCIIToUTF16("Superman")); + AutofillKey ak2(ASCIIToUTF16("Name"), ASCIIToUTF16("Clark Kent")); + AutofillEntry ae1(ak1, timestamps1); + AutofillEntry ae2(ak2, timestamps2); + + expected_entries.insert(ae1); + expected_entries.insert(ae2); + + std::vector<AutofillEntry> entries; + ASSERT_TRUE(table_->GetAllAutofillEntries(&entries)); + AutofillEntrySet entry_set(entries.begin(), entries.end(), + CompareAutofillEntries); + + // make sure the lists of entries match + ASSERT_EQ(expected_entries.size(), entry_set.size()); + AutofillEntrySetIterator it; + for (it = entry_set.begin(); it != entry_set.end(); it++) { + expected_entries.erase(*it); + } + + EXPECT_EQ(0U, expected_entries.size()); +} + +TEST_F(AutofillTableTest, Autofill_GetAllAutofillEntries_TwoSame) { + AutofillChangeList changes; + std::map<std::string, std::vector<Time> > name_value_times_map; + + time_t start = 0; + std::vector<Time> timestamps; + for (int i = 0; i < 2; i++) { + FormFieldData field; + field.name = ASCIIToUTF16("Name"); + field.value = ASCIIToUTF16("Superman"); + EXPECT_TRUE(table_->AddFormFieldValueTime(field, &changes, + Time::FromTimeT(start))); + timestamps.push_back(Time::FromTimeT(start)); + start++; + } + + std::string key("NameSuperman"); + name_value_times_map.insert(std::pair<std::string, + std::vector<Time> > (key, timestamps)); + + AutofillEntrySet expected_entries(CompareAutofillEntries); + AutofillKey ak1(ASCIIToUTF16("Name"), ASCIIToUTF16("Superman")); + AutofillEntry ae1(ak1, timestamps); + + expected_entries.insert(ae1); + + std::vector<AutofillEntry> entries; + ASSERT_TRUE(table_->GetAllAutofillEntries(&entries)); + AutofillEntrySet entry_set(entries.begin(), entries.end(), + CompareAutofillEntries); + + // make sure the lists of entries match + ASSERT_EQ(expected_entries.size(), entry_set.size()); + AutofillEntrySetIterator it; + for (it = entry_set.begin(); it != entry_set.end(); it++) { + expected_entries.erase(*it); + } + + EXPECT_EQ(0U, expected_entries.size()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/webdata/autofill_webdata.h b/chromium/components/autofill/core/browser/webdata/autofill_webdata.h new file mode 100644 index 00000000000..fbd4107b894 --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_webdata.h @@ -0,0 +1,107 @@ +// 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_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_H_ + +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "components/webdata/common/web_data_service_base.h" + +namespace base { + +class Time; + +} // namespace base + +class Profile; +class WebDataServiceConsumer; + +namespace autofill { + +class AutofillProfile; +class CreditCard; +struct FormFieldData; + +// Pure virtual interface for retrieving Autofill data. API users +// should use AutofillWebDataService. +class AutofillWebData { + public: + virtual ~AutofillWebData() {} + + // Schedules a task to add form fields to the web database. + virtual void AddFormFields( + const std::vector<FormFieldData>& fields) = 0; + + // Initiates the request for a vector of values which have been entered in + // form input fields named |name|. The method OnWebDataServiceRequestDone of + // |consumer| gets called back when the request is finished, with the vector + // included in the argument |result|. + virtual WebDataServiceBase::Handle GetFormValuesForElementName( + const base::string16& name, + const base::string16& prefix, + int limit, + WebDataServiceConsumer* consumer) = 0; + + // Checks if there are any form elements in the database. + virtual WebDataServiceBase::Handle HasFormElements( + WebDataServiceConsumer* consumer) = 0; + + // Removes form elements recorded for Autocomplete from the database. + virtual void RemoveFormElementsAddedBetween( + const base::Time& delete_begin, const base::Time& delete_end) = 0; + + virtual void RemoveFormValueForElementName(const base::string16& name, + const base::string16& value) = 0; + + // Schedules a task to add an Autofill profile to the web database. + virtual void AddAutofillProfile(const AutofillProfile& profile) = 0; + + // Schedules a task to update an Autofill profile in the web database. + virtual void UpdateAutofillProfile(const AutofillProfile& profile) = 0; + + // Schedules a task to remove an Autofill profile from the web database. + // |guid| is the identifer of the profile to remove. + virtual void RemoveAutofillProfile(const std::string& guid) = 0; + + // Initiates the request for all Autofill profiles. The method + // OnWebDataServiceRequestDone of |consumer| gets called when the request is + // finished, with the profiles included in the argument |result|. The + // consumer owns the profiles. + virtual WebDataServiceBase::Handle GetAutofillProfiles( + WebDataServiceConsumer* consumer) = 0; + + // Schedules a task to add credit card to the web database. + virtual void AddCreditCard(const CreditCard& credit_card) = 0; + + // Schedules a task to update credit card in the web database. + virtual void UpdateCreditCard(const CreditCard& credit_card) = 0; + + // Schedules a task to remove a credit card from the web database. + // |guid| is identifer of the credit card to remove. + virtual void RemoveCreditCard(const std::string& guid) = 0; + + // Initiates the request for all credit cards. The method + // OnWebDataServiceRequestDone of |consumer| gets called when the request is + // finished, with the credit cards included in the argument |result|. The + // consumer owns the credit cards. + virtual WebDataServiceBase::Handle GetCreditCards( + WebDataServiceConsumer* consumer) = 0; + + // Removes Autofill records from the database. + virtual void RemoveAutofillDataModifiedBetween( + const base::Time& delete_begin, const base::Time& delete_end) = 0; + + // Removes origin URLs associated with Autofill profiles and credit cards from + // the database. + virtual void RemoveOriginURLsModifiedBetween( + const base::Time& delete_begin, const base::Time& delete_end) = 0; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_H_ diff --git a/chromium/components/autofill/core/browser/webdata/autofill_webdata_backend.h b/chromium/components/autofill/core/browser/webdata/autofill_webdata_backend.h new file mode 100644 index 00000000000..2f463018dc3 --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_webdata_backend.h @@ -0,0 +1,43 @@ +// 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_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_BACKEND_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_BACKEND_H_ + +class WebDatabase; + +namespace autofill { + +class AutofillWebDataServiceObserverOnDBThread; + +// Interface for doing Autofill work directly on the DB thread (used by +// Sync, mostly), without fully exposing the AutofillWebDataBackend to clients. +class AutofillWebDataBackend { + public: + virtual ~AutofillWebDataBackend() {} + + // Get a raw pointer to the WebDatabase. + virtual WebDatabase* GetDatabase() = 0; + + // Add an observer to be notified of changes on the DB thread. + virtual void AddObserver( + AutofillWebDataServiceObserverOnDBThread* observer) = 0; + + // Remove an observer. + virtual void RemoveObserver( + AutofillWebDataServiceObserverOnDBThread* observer) = 0; + + // Remove expired elements from the database and commit if needed. + virtual void RemoveExpiredFormElements() = 0; + + // Notifies listeners on the UI thread that multiple changes have been made to + // to Autofill records of the database. + // NOTE: This method is intended to be called from the DB thread. It + // asynchronously notifies listeners on the UI thread. + virtual void NotifyOfMultipleAutofillChanges() = 0; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_BACKEND_H_ diff --git a/chromium/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc b/chromium/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc new file mode 100644 index 00000000000..b3da704df06 --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc @@ -0,0 +1,384 @@ +// 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/core/browser/webdata/autofill_webdata_backend_impl.h" + +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/stl_util.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/webdata/autofill_change.h" +#include "components/autofill/core/browser/webdata/autofill_entry.h" +#include "components/autofill/core/browser/webdata/autofill_table.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service_observer.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/webdata/common/web_data_service_backend.h" + +using base::Bind; +using base::Time; +using content::BrowserThread; + +namespace autofill { + +AutofillWebDataBackendImpl::AutofillWebDataBackendImpl( + scoped_refptr<WebDataServiceBackend> web_database_backend, + const base::Closure& on_changed_callback) + : web_database_backend_(web_database_backend), + on_changed_callback_(on_changed_callback) { +} + +void AutofillWebDataBackendImpl::AddObserver( + AutofillWebDataServiceObserverOnDBThread* observer) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + db_observer_list_.AddObserver(observer); +} + +void AutofillWebDataBackendImpl::RemoveObserver( + AutofillWebDataServiceObserverOnDBThread* observer) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + db_observer_list_.RemoveObserver(observer); +} + +AutofillWebDataBackendImpl::~AutofillWebDataBackendImpl() { + DCHECK(!user_data_.get()); // Forgot to call ResetUserData? +} + +WebDatabase* AutofillWebDataBackendImpl::GetDatabase() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + return web_database_backend_->database(); +} + +void AutofillWebDataBackendImpl::RemoveExpiredFormElements() { + web_database_backend_->ExecuteWriteTask( + Bind(&AutofillWebDataBackendImpl::RemoveExpiredFormElementsImpl, + this)); +} + +void AutofillWebDataBackendImpl::NotifyOfMultipleAutofillChanges() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + BrowserThread::PostTask(BrowserThread::UI, + FROM_HERE, + on_changed_callback_); +} + +base::SupportsUserData* AutofillWebDataBackendImpl::GetDBUserData() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!user_data_) + user_data_.reset(new SupportsUserDataAggregatable()); + return user_data_.get(); +} + +void AutofillWebDataBackendImpl::ResetUserData() { + user_data_.reset(); +} + +WebDatabase::State AutofillWebDataBackendImpl::AddFormElements( + const std::vector<FormFieldData>& fields, WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + AutofillChangeList changes; + if (!AutofillTable::FromWebDatabase(db)->AddFormFieldValues( + fields, &changes)) { + NOTREACHED(); + return WebDatabase::COMMIT_NOT_NEEDED; + } + + // Post the notifications including the list of affected keys. + // This is sent here so that work resulting from this notification will be + // done on the DB thread, and not the UI thread. + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, + db_observer_list_, + AutofillEntriesChanged(changes)); + + return WebDatabase::COMMIT_NEEDED; +} + +scoped_ptr<WDTypedResult> +AutofillWebDataBackendImpl::GetFormValuesForElementName( + const base::string16& name, const base::string16& prefix, int limit, + WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + std::vector<base::string16> values; + AutofillTable::FromWebDatabase(db)->GetFormValuesForElementName( + name, prefix, &values, limit); + return scoped_ptr<WDTypedResult>( + new WDResult<std::vector<base::string16> >(AUTOFILL_VALUE_RESULT, + values)); +} + +scoped_ptr<WDTypedResult> AutofillWebDataBackendImpl::HasFormElements( + WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + bool value = AutofillTable::FromWebDatabase(db)->HasFormElements(); + return scoped_ptr<WDTypedResult>( + new WDResult<bool>(AUTOFILL_VALUE_RESULT, value)); +} + +WebDatabase::State AutofillWebDataBackendImpl::RemoveFormElementsAddedBetween( + const base::Time& delete_begin, + const base::Time& delete_end, + WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + AutofillChangeList changes; + + if (AutofillTable::FromWebDatabase(db)->RemoveFormElementsAddedBetween( + delete_begin, delete_end, &changes)) { + if (!changes.empty()) { + // Post the notifications including the list of affected keys. + // This is sent here so that work resulting from this notification + // will be done on the DB thread, and not the UI thread. + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, + db_observer_list_, + AutofillEntriesChanged(changes)); + } + return WebDatabase::COMMIT_NEEDED; + } + return WebDatabase::COMMIT_NOT_NEEDED; +} + +WebDatabase::State AutofillWebDataBackendImpl::RemoveFormValueForElementName( + const base::string16& name, const base::string16& value, WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + + if (AutofillTable::FromWebDatabase(db)->RemoveFormElement(name, value)) { + AutofillChangeList changes; + changes.push_back( + AutofillChange(AutofillChange::REMOVE, AutofillKey(name, value))); + + // Post the notifications including the list of affected keys. + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, + db_observer_list_, + AutofillEntriesChanged(changes)); + + return WebDatabase::COMMIT_NEEDED; + } + return WebDatabase::COMMIT_NOT_NEEDED; +} + +WebDatabase::State AutofillWebDataBackendImpl::AddAutofillProfile( + const AutofillProfile& profile, WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!AutofillTable::FromWebDatabase(db)->AddAutofillProfile(profile)) { + NOTREACHED(); + return WebDatabase::COMMIT_NOT_NEEDED; + } + + // Send GUID-based notification. + AutofillProfileChange change( + AutofillProfileChange::ADD, profile.guid(), &profile); + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, + db_observer_list_, + AutofillProfileChanged(change)); + + return WebDatabase::COMMIT_NEEDED; +} + +WebDatabase::State AutofillWebDataBackendImpl::UpdateAutofillProfile( + const AutofillProfile& profile, WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + // Only perform the update if the profile exists. It is currently + // valid to try to update a missing profile. We simply drop the write and + // the caller will detect this on the next refresh. + AutofillProfile* original_profile = NULL; + if (!AutofillTable::FromWebDatabase(db)->GetAutofillProfile(profile.guid(), + &original_profile)) { + return WebDatabase::COMMIT_NOT_NEEDED; + } + scoped_ptr<AutofillProfile> scoped_profile(original_profile); + + if (!AutofillTable::FromWebDatabase(db)->UpdateAutofillProfile(profile)) { + NOTREACHED(); + return WebDatabase::COMMIT_NEEDED; + } + + // Send GUID-based notification. + AutofillProfileChange change( + AutofillProfileChange::UPDATE, profile.guid(), &profile); + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, + db_observer_list_, + AutofillProfileChanged(change)); + + return WebDatabase::COMMIT_NEEDED; +} + +WebDatabase::State AutofillWebDataBackendImpl::RemoveAutofillProfile( + const std::string& guid, WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + AutofillProfile* profile = NULL; + if (!AutofillTable::FromWebDatabase(db)->GetAutofillProfile(guid, &profile)) { + NOTREACHED(); + return WebDatabase::COMMIT_NOT_NEEDED; + } + scoped_ptr<AutofillProfile> scoped_profile(profile); + + if (!AutofillTable::FromWebDatabase(db)->RemoveAutofillProfile(guid)) { + NOTREACHED(); + return WebDatabase::COMMIT_NOT_NEEDED; + } + + // Send GUID-based notification. + AutofillProfileChange change(AutofillProfileChange::REMOVE, guid, NULL); + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, + db_observer_list_, + AutofillProfileChanged(change)); + + return WebDatabase::COMMIT_NEEDED; +} + +scoped_ptr<WDTypedResult> AutofillWebDataBackendImpl::GetAutofillProfiles( + WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + std::vector<AutofillProfile*> profiles; + AutofillTable::FromWebDatabase(db)->GetAutofillProfiles(&profiles); + return scoped_ptr<WDTypedResult>( + new WDDestroyableResult<std::vector<AutofillProfile*> >( + AUTOFILL_PROFILES_RESULT, + profiles, + base::Bind(&AutofillWebDataBackendImpl::DestroyAutofillProfileResult, + base::Unretained(this)))); +} + +WebDatabase::State AutofillWebDataBackendImpl::AddCreditCard( + const CreditCard& credit_card, WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!AutofillTable::FromWebDatabase(db)->AddCreditCard(credit_card)) { + NOTREACHED(); + return WebDatabase::COMMIT_NOT_NEEDED; + } + + return WebDatabase::COMMIT_NEEDED; +} + +WebDatabase::State AutofillWebDataBackendImpl::UpdateCreditCard( + const CreditCard& credit_card, WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + // It is currently valid to try to update a missing profile. We simply drop + // the write and the caller will detect this on the next refresh. + CreditCard* original_credit_card = NULL; + if (!AutofillTable::FromWebDatabase(db)->GetCreditCard(credit_card.guid(), + &original_credit_card)) { + return WebDatabase::COMMIT_NOT_NEEDED; + } + scoped_ptr<CreditCard> scoped_credit_card(original_credit_card); + + if (!AutofillTable::FromWebDatabase(db)->UpdateCreditCard(credit_card)) { + NOTREACHED(); + return WebDatabase::COMMIT_NOT_NEEDED; + } + return WebDatabase::COMMIT_NEEDED; +} + +WebDatabase::State AutofillWebDataBackendImpl::RemoveCreditCard( + const std::string& guid, WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!AutofillTable::FromWebDatabase(db)->RemoveCreditCard(guid)) { + NOTREACHED(); + return WebDatabase::COMMIT_NOT_NEEDED; + } + return WebDatabase::COMMIT_NEEDED; +} + +scoped_ptr<WDTypedResult> AutofillWebDataBackendImpl::GetCreditCards( + WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + std::vector<CreditCard*> credit_cards; + AutofillTable::FromWebDatabase(db)->GetCreditCards(&credit_cards); + return scoped_ptr<WDTypedResult>( + new WDDestroyableResult<std::vector<CreditCard*> >( + AUTOFILL_CREDITCARDS_RESULT, + credit_cards, + base::Bind(&AutofillWebDataBackendImpl::DestroyAutofillCreditCardResult, + base::Unretained(this)))); +} + +WebDatabase::State + AutofillWebDataBackendImpl::RemoveAutofillDataModifiedBetween( + const base::Time& delete_begin, + const base::Time& delete_end, + WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + std::vector<std::string> profile_guids; + std::vector<std::string> credit_card_guids; + if (AutofillTable::FromWebDatabase(db)->RemoveAutofillDataModifiedBetween( + delete_begin, + delete_end, + &profile_guids, + &credit_card_guids)) { + for (std::vector<std::string>::iterator iter = profile_guids.begin(); + iter != profile_guids.end(); ++iter) { + AutofillProfileChange change(AutofillProfileChange::REMOVE, *iter, NULL); + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, + db_observer_list_, + AutofillProfileChanged(change)); + } + // Note: It is the caller's responsibility to post notifications for any + // changes, e.g. by calling the Refresh() method of PersonalDataManager. + return WebDatabase::COMMIT_NEEDED; + } + return WebDatabase::COMMIT_NOT_NEEDED; +} + +WebDatabase::State AutofillWebDataBackendImpl::RemoveOriginURLsModifiedBetween( + const base::Time& delete_begin, + const base::Time& delete_end, + WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + ScopedVector<AutofillProfile> profiles; + if (AutofillTable::FromWebDatabase(db)->RemoveOriginURLsModifiedBetween( + delete_begin, delete_end, &profiles)) { + for (std::vector<AutofillProfile*>::const_iterator it = profiles.begin(); + it != profiles.end(); ++it) { + AutofillProfileChange change(AutofillProfileChange::UPDATE, + (*it)->guid(), *it); + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, + db_observer_list_, + AutofillProfileChanged(change)); + } + // Note: It is the caller's responsibility to post notifications for any + // changes, e.g. by calling the Refresh() method of PersonalDataManager. + return WebDatabase::COMMIT_NEEDED; + } + return WebDatabase::COMMIT_NOT_NEEDED; +} + +WebDatabase::State AutofillWebDataBackendImpl::RemoveExpiredFormElementsImpl( + WebDatabase* db) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + AutofillChangeList changes; + + if (AutofillTable::FromWebDatabase(db)->RemoveExpiredFormElements(&changes)) { + if (!changes.empty()) { + // Post the notifications including the list of affected keys. + // This is sent here so that work resulting from this notification + // will be done on the DB thread, and not the UI thread. + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, + db_observer_list_, + AutofillEntriesChanged(changes)); + } + return WebDatabase::COMMIT_NEEDED; + } + return WebDatabase::COMMIT_NOT_NEEDED; +} + +void AutofillWebDataBackendImpl::DestroyAutofillProfileResult( + const WDTypedResult* result) { + DCHECK(result->GetType() == AUTOFILL_PROFILES_RESULT); + const WDResult<std::vector<AutofillProfile*> >* r = + static_cast<const WDResult<std::vector<AutofillProfile*> >*>(result); + std::vector<AutofillProfile*> profiles = r->GetValue(); + STLDeleteElements(&profiles); +} + +void AutofillWebDataBackendImpl::DestroyAutofillCreditCardResult( + const WDTypedResult* result) { + DCHECK(result->GetType() == AUTOFILL_CREDITCARDS_RESULT); + const WDResult<std::vector<CreditCard*> >* r = + static_cast<const WDResult<std::vector<CreditCard*> >*>(result); + + std::vector<CreditCard*> credit_cards = r->GetValue(); + STLDeleteElements(&credit_cards); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h b/chromium/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h new file mode 100644 index 00000000000..8015dd4212c --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h @@ -0,0 +1,185 @@ +// 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_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_BACKEND_IMPL_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_BACKEND_IMPL_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/supports_user_data.h" +#include "components/autofill/core/browser/webdata/autofill_webdata.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_backend.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/webdata/common/web_data_results.h" +#include "components/webdata/common/web_data_service_base.h" +#include "components/webdata/common/web_data_service_consumer.h" +#include "components/webdata/common/web_database.h" +#include "content/public/browser/browser_thread.h" + +class WebDataServiceBackend; + +namespace autofill { + +class AutofillChange; +class AutofillProfile; +class AutofillWebDataServiceObserverOnDBThread; +class CreditCard; + +// Backend implentation for the AutofillWebDataService. This class runs on the +// DB thread, as it handles reads and writes to the WebDatabase, and functions +// in it should only be called from that thread. Most functions here are just +// the implementations of the corresponding functions in the Autofill +// WebDataService. +class AutofillWebDataBackendImpl + : public base::RefCountedThreadSafe<AutofillWebDataBackendImpl, + content::BrowserThread::DeleteOnDBThread>, + public AutofillWebDataBackend { + public: + // |web_database_backend| is used to access the WebDatabase directly for + // Sync-related operations. |on_changed_callback| is a closure which can be + // used to notify the UI thread of changes initiated by Sync (this callback + // may be called multiple times). + AutofillWebDataBackendImpl( + scoped_refptr<WebDataServiceBackend> web_database_backend, + const base::Closure& on_changed_callback); + + // AutofillWebDataBackend implementation. + virtual void AddObserver(AutofillWebDataServiceObserverOnDBThread* observer) + OVERRIDE; + virtual void RemoveObserver( + AutofillWebDataServiceObserverOnDBThread* observer) OVERRIDE; + virtual WebDatabase* GetDatabase() OVERRIDE; + virtual void RemoveExpiredFormElements() OVERRIDE; + virtual void NotifyOfMultipleAutofillChanges() OVERRIDE; + + // Returns a SupportsUserData objects that may be used to store data + // owned by the DB thread on this object. Should be called only from + // the DB thread, and will be destroyed on the DB thread soon after + // |ShutdownOnUIThread()| is called. + base::SupportsUserData* GetDBUserData(); + + void ResetUserData(); + + // Adds form fields to the web database. + WebDatabase::State AddFormElements(const std::vector<FormFieldData>& fields, + WebDatabase* db); + + // Returns a vector of values which have been entered in form input fields + // named |name|. + scoped_ptr<WDTypedResult> GetFormValuesForElementName( + const base::string16& name, + const base::string16& prefix, + int limit, + WebDatabase* db); + + // Returns true if there are any elements in the form. + scoped_ptr<WDTypedResult> HasFormElements(WebDatabase* db); + + // Removes form elements recorded for Autocomplete from the database. + WebDatabase::State RemoveFormElementsAddedBetween( + const base::Time& delete_begin, + const base::Time& delete_end, + WebDatabase* db); + + + // Removes the Form-value |value| which has been entered in form input fields + // named |name| from the database. + WebDatabase::State RemoveFormValueForElementName(const base::string16& name, + const base::string16& value, + WebDatabase* db); + + // Adds an Autofill profile to the web database. + WebDatabase::State AddAutofillProfile(const AutofillProfile& profile, + WebDatabase* db); + + // Updates an Autofill profile in the web database. + WebDatabase::State UpdateAutofillProfile(const AutofillProfile& profile, + WebDatabase* db); + + // Removes an Autofill profile from the web database. + WebDatabase::State RemoveAutofillProfile(const std::string& guid, + WebDatabase* db); + + // Returns all Autofill profiles from the web database. + scoped_ptr<WDTypedResult> GetAutofillProfiles(WebDatabase* db); + + // Adds a credit card to the web database. + WebDatabase::State AddCreditCard(const CreditCard& credit_card, + WebDatabase* db); + + // Updates a credit card in the web database. + WebDatabase::State UpdateCreditCard(const CreditCard& credit_card, + WebDatabase* db); + + // Removes a credit card from the web database. + WebDatabase::State RemoveCreditCard(const std::string& guid, + WebDatabase* db); + + // Returns a vector of all credit cards from the web database. + scoped_ptr<WDTypedResult> GetCreditCards(WebDatabase* db); + + // Removes Autofill records from the database. + WebDatabase::State RemoveAutofillDataModifiedBetween( + const base::Time& delete_begin, + const base::Time& delete_end, + WebDatabase* db); + + // Removes origin URLs associated with Autofill profiles and credit cards from + // the database. + WebDatabase::State RemoveOriginURLsModifiedBetween( + const base::Time& delete_begin, + const base::Time& delete_end, + WebDatabase* db); + + protected: + virtual ~AutofillWebDataBackendImpl(); + + private: + friend struct content::BrowserThread::DeleteOnThread< + content::BrowserThread::DB>; + friend class base::DeleteHelper<AutofillWebDataBackendImpl>; + // We have to friend RCTS<> so WIN shared-lib build is happy + // (http://crbug/112250). + friend class base::RefCountedThreadSafe<AutofillWebDataBackendImpl, + content::BrowserThread::DeleteOnDBThread>; + + // This makes the destructor public, and thus allows us to aggregate + // SupportsUserData. It is private by default to prevent incorrect + // usage in class hierarchies where it is inherited by + // reference-counted objects. + class SupportsUserDataAggregatable : public base::SupportsUserData { + public: + SupportsUserDataAggregatable() {} + virtual ~SupportsUserDataAggregatable() {} + private: + DISALLOW_COPY_AND_ASSIGN(SupportsUserDataAggregatable); + }; + + // Storage for user data to be accessed only on the DB thread. May + // be used e.g. for SyncableService subclasses that need to be owned + // by this object. Is created on first call to |GetDBUserData()|. + scoped_ptr<SupportsUserDataAggregatable> user_data_; + + WebDatabase::State RemoveExpiredFormElementsImpl(WebDatabase* db); + + // Callbacks to ensure that sensitive info is destroyed if request is + // cancelled. + void DestroyAutofillProfileResult(const WDTypedResult* result); + void DestroyAutofillCreditCardResult(const WDTypedResult* result); + + ObserverList<AutofillWebDataServiceObserverOnDBThread> db_observer_list_; + + // WebDataServiceBackend allows direct access to DB. + // TODO(caitkp): Make it so nobody but us needs direct DB access anymore. + scoped_refptr<WebDataServiceBackend> web_database_backend_; + + base::Closure on_changed_callback_; + + DISALLOW_COPY_AND_ASSIGN(AutofillWebDataBackendImpl); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_BACKEND_IMPL_H_ diff --git a/chromium/components/autofill/core/browser/webdata/autofill_webdata_service.cc b/chromium/components/autofill/core/browser/webdata/autofill_webdata_service.cc new file mode 100644 index 00000000000..bc4f65a779b --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_webdata_service.cc @@ -0,0 +1,217 @@ +// 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/core/browser/webdata/autofill_webdata_service.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/webdata/autofill_change.h" +#include "components/autofill/core/browser/webdata/autofill_entry.h" +#include "components/autofill/core/browser/webdata/autofill_table.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_backend_impl.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service_observer.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/webdata/common/web_data_service_backend.h" +#include "components/webdata/common/web_database_service.h" + +using base::Bind; +using base::Time; +using content::BrowserThread; + +namespace autofill { + +AutofillWebDataService::AutofillWebDataService( + scoped_refptr<WebDatabaseService> wdbs, + const ProfileErrorCallback& callback) + : WebDataServiceBase(wdbs, callback, + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI)), + weak_ptr_factory_(this), + autofill_backend_(NULL) { + + base::Closure on_changed_callback = Bind( + &AutofillWebDataService::NotifyAutofillMultipleChangedOnUIThread, + weak_ptr_factory_.GetWeakPtr()); + + autofill_backend_ = new AutofillWebDataBackendImpl( + wdbs_->GetBackend(), + on_changed_callback); +} + +AutofillWebDataService::AutofillWebDataService() + : WebDataServiceBase(NULL, WebDataServiceBase::ProfileErrorCallback(), + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI)), + weak_ptr_factory_(this), + autofill_backend_(new AutofillWebDataBackendImpl(NULL, base::Closure())) { +} + +void AutofillWebDataService::ShutdownOnUIThread() { + weak_ptr_factory_.InvalidateWeakPtrs(); + BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, + Bind(&AutofillWebDataBackendImpl::ResetUserData, + autofill_backend_)); + WebDataServiceBase::ShutdownOnUIThread(); +} + +void AutofillWebDataService::AddFormFields( + const std::vector<FormFieldData>& fields) { + wdbs_->ScheduleDBTask(FROM_HERE, + Bind(&AutofillWebDataBackendImpl::AddFormElements, + autofill_backend_, fields)); +} + +WebDataServiceBase::Handle AutofillWebDataService::GetFormValuesForElementName( + const base::string16& name, const base::string16& prefix, int limit, + WebDataServiceConsumer* consumer) { + return wdbs_->ScheduleDBTaskWithResult(FROM_HERE, + Bind(&AutofillWebDataBackendImpl::GetFormValuesForElementName, + autofill_backend_, name, prefix, limit), consumer); +} + +WebDataServiceBase::Handle AutofillWebDataService::HasFormElements( + WebDataServiceConsumer* consumer) { + return wdbs_->ScheduleDBTaskWithResult(FROM_HERE, + Bind(&AutofillWebDataBackendImpl::HasFormElements, autofill_backend_), + consumer); +} + +void AutofillWebDataService::RemoveFormElementsAddedBetween( + const Time& delete_begin, const Time& delete_end) { + wdbs_->ScheduleDBTask(FROM_HERE, + Bind(&AutofillWebDataBackendImpl::RemoveFormElementsAddedBetween, + autofill_backend_, delete_begin, delete_end)); +} + +void AutofillWebDataService::RemoveFormValueForElementName( + const base::string16& name, const base::string16& value) { + wdbs_->ScheduleDBTask(FROM_HERE, + Bind(&AutofillWebDataBackendImpl::RemoveFormValueForElementName, + autofill_backend_, name, value)); +} + +void AutofillWebDataService::AddAutofillProfile( + const AutofillProfile& profile) { + wdbs_->ScheduleDBTask(FROM_HERE, + Bind(&AutofillWebDataBackendImpl::AddAutofillProfile, + autofill_backend_, profile)); +} + +void AutofillWebDataService::UpdateAutofillProfile( + const AutofillProfile& profile) { + wdbs_->ScheduleDBTask(FROM_HERE, + Bind(&AutofillWebDataBackendImpl::UpdateAutofillProfile, + autofill_backend_, profile)); +} + +void AutofillWebDataService::RemoveAutofillProfile( + const std::string& guid) { + wdbs_->ScheduleDBTask(FROM_HERE, + Bind(&AutofillWebDataBackendImpl::RemoveAutofillProfile, + autofill_backend_, guid)); +} + +WebDataServiceBase::Handle AutofillWebDataService::GetAutofillProfiles( + WebDataServiceConsumer* consumer) { + return wdbs_->ScheduleDBTaskWithResult(FROM_HERE, + Bind(&AutofillWebDataBackendImpl::GetAutofillProfiles, autofill_backend_), + consumer); +} + +void AutofillWebDataService::AddCreditCard(const CreditCard& credit_card) { + wdbs_->ScheduleDBTask( + FROM_HERE, + Bind(&AutofillWebDataBackendImpl::AddCreditCard, + autofill_backend_, credit_card)); +} + +void AutofillWebDataService::UpdateCreditCard( + const CreditCard& credit_card) { + wdbs_->ScheduleDBTask( + FROM_HERE, + Bind(&AutofillWebDataBackendImpl::UpdateCreditCard, + autofill_backend_, credit_card)); +} + +void AutofillWebDataService::RemoveCreditCard(const std::string& guid) { + wdbs_->ScheduleDBTask( + FROM_HERE, + Bind(&AutofillWebDataBackendImpl::RemoveCreditCard, + autofill_backend_, guid)); +} + +WebDataServiceBase::Handle AutofillWebDataService::GetCreditCards( + WebDataServiceConsumer* consumer) { + return wdbs_->ScheduleDBTaskWithResult(FROM_HERE, + Bind(&AutofillWebDataBackendImpl::GetCreditCards, autofill_backend_), + consumer); +} + +void AutofillWebDataService::RemoveAutofillDataModifiedBetween( + const Time& delete_begin, + const Time& delete_end) { + wdbs_->ScheduleDBTask( + FROM_HERE, + Bind(&AutofillWebDataBackendImpl::RemoveAutofillDataModifiedBetween, + autofill_backend_, delete_begin, delete_end)); +} + +void AutofillWebDataService::RemoveOriginURLsModifiedBetween( + const Time& delete_begin, const Time& delete_end) { + wdbs_->ScheduleDBTask( + FROM_HERE, + Bind(&AutofillWebDataBackendImpl::RemoveOriginURLsModifiedBetween, + autofill_backend_, delete_begin, delete_end)); +} + +void AutofillWebDataService::AddObserver( + AutofillWebDataServiceObserverOnDBThread* observer) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (autofill_backend_.get()) + autofill_backend_->AddObserver(observer); +} + +void AutofillWebDataService::RemoveObserver( + AutofillWebDataServiceObserverOnDBThread* observer) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (autofill_backend_.get()) + autofill_backend_->RemoveObserver(observer); +} + +void AutofillWebDataService::AddObserver( + AutofillWebDataServiceObserverOnUIThread* observer) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + ui_observer_list_.AddObserver(observer); +} + +void AutofillWebDataService::RemoveObserver( + AutofillWebDataServiceObserverOnUIThread* observer) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + ui_observer_list_.RemoveObserver(observer); +} + +base::SupportsUserData* AutofillWebDataService::GetDBUserData() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + return autofill_backend_->GetDBUserData(); +} + +void AutofillWebDataService::GetAutofillBackend( + const base::Callback<void(AutofillWebDataBackend*)>& callback) { + BrowserThread::PostTask(BrowserThread::DB, + FROM_HERE, + base::Bind(callback, autofill_backend_)); +} + +AutofillWebDataService::~AutofillWebDataService() { +} + +void AutofillWebDataService::NotifyAutofillMultipleChangedOnUIThread() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnUIThread, + ui_observer_list_, + AutofillMultipleChanged()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/browser/webdata/autofill_webdata_service.h b/chromium/components/autofill/core/browser/webdata/autofill_webdata_service.h new file mode 100644 index 00000000000..59907fd13d0 --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_webdata_service.h @@ -0,0 +1,126 @@ +// 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_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_SERVICE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_SERVICE_H_ + +#include <vector> + +#include "base/memory/ref_counted.h" +#include "base/memory/weak_ptr.h" +#include "base/observer_list.h" +#include "base/supports_user_data.h" +#include "components/autofill/core/browser/webdata/autofill_webdata.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/webdata/common/web_data_results.h" +#include "components/webdata/common/web_data_service_base.h" +#include "components/webdata/common/web_data_service_consumer.h" +#include "components/webdata/common/web_database.h" + +class WebDatabaseService; + +namespace content { +class BrowserContext; +} + +namespace autofill { + +class AutofillChange; +class AutofillProfile; +class AutofillWebDataBackend; +class AutofillWebDataBackendImpl; +class AutofillWebDataServiceObserverOnDBThread; +class AutofillWebDataServiceObserverOnUIThread; +class CreditCard; + +// API for Autofill web data. +class AutofillWebDataService : public AutofillWebData, + public WebDataServiceBase { + public: + AutofillWebDataService(); + + AutofillWebDataService(scoped_refptr<WebDatabaseService> wdbs, + const ProfileErrorCallback& callback); + + // Retrieve an AutofillWebDataService for the given context. + // Can return NULL in some contexts. + static scoped_refptr<AutofillWebDataService> FromBrowserContext( + content::BrowserContext* context); + + // WebDataServiceBase implementation. + virtual void ShutdownOnUIThread() OVERRIDE; + + // AutofillWebData implementation. + virtual void AddFormFields( + const std::vector<FormFieldData>& fields) OVERRIDE; + virtual WebDataServiceBase::Handle GetFormValuesForElementName( + const base::string16& name, + const base::string16& prefix, + int limit, + WebDataServiceConsumer* consumer) OVERRIDE; + + virtual WebDataServiceBase::Handle HasFormElements( + WebDataServiceConsumer* consumer) OVERRIDE; + virtual void RemoveFormElementsAddedBetween( + const base::Time& delete_begin, const base::Time& delete_end) OVERRIDE; + virtual void RemoveFormValueForElementName( + const base::string16& name, + const base::string16& value) OVERRIDE; + virtual void AddAutofillProfile(const AutofillProfile& profile) OVERRIDE; + virtual void UpdateAutofillProfile(const AutofillProfile& profile) OVERRIDE; + virtual void RemoveAutofillProfile(const std::string& guid) OVERRIDE; + virtual WebDataServiceBase::Handle GetAutofillProfiles( + WebDataServiceConsumer* consumer) OVERRIDE; + virtual void AddCreditCard(const CreditCard& credit_card) OVERRIDE; + virtual void UpdateCreditCard(const CreditCard& credit_card) OVERRIDE; + virtual void RemoveCreditCard(const std::string& guid) OVERRIDE; + virtual WebDataServiceBase::Handle GetCreditCards( + WebDataServiceConsumer* consumer) OVERRIDE; + virtual void RemoveAutofillDataModifiedBetween( + const base::Time& delete_begin, const base::Time& delete_end) OVERRIDE; + virtual void RemoveOriginURLsModifiedBetween( + const base::Time& delete_begin, const base::Time& delete_end) OVERRIDE; + + void AddObserver(AutofillWebDataServiceObserverOnDBThread* observer); + void RemoveObserver(AutofillWebDataServiceObserverOnDBThread* observer); + + void AddObserver(AutofillWebDataServiceObserverOnUIThread* observer); + void RemoveObserver(AutofillWebDataServiceObserverOnUIThread* observer); + + // Returns a SupportsUserData objects that may be used to store data + // owned by the DB thread on this object. Should be called only from + // the DB thread, and will be destroyed on the DB thread soon after + // |ShutdownOnUIThread()| is called. + base::SupportsUserData* GetDBUserData(); + + // Takes a callback which will be called on the DB thread with a pointer to an + // |AutofillWebdataBackend|. This backend can be used to access or update the + // WebDatabase directly on the DB thread. + void GetAutofillBackend( + const base::Callback<void(AutofillWebDataBackend*)>& callback); + + protected: + virtual ~AutofillWebDataService(); + + virtual void NotifyAutofillMultipleChangedOnUIThread(); + + base::WeakPtr<AutofillWebDataService> AsWeakPtr() { + return weak_ptr_factory_.GetWeakPtr(); + } + + private: + ObserverList<AutofillWebDataServiceObserverOnUIThread> ui_observer_list_; + + // This factory is used on the UI thread. All vended weak pointers are + // invalidated in ShutdownOnUIThread(). + base::WeakPtrFactory<AutofillWebDataService> weak_ptr_factory_; + + scoped_refptr<AutofillWebDataBackendImpl> autofill_backend_; + + DISALLOW_COPY_AND_ASSIGN(AutofillWebDataService); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_SERVICE_H_ diff --git a/chromium/components/autofill/core/browser/webdata/autofill_webdata_service_observer.h b/chromium/components/autofill/core/browser/webdata/autofill_webdata_service_observer.h new file mode 100644 index 00000000000..f4333e58bc6 --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/autofill_webdata_service_observer.h @@ -0,0 +1,37 @@ +// 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_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_SERVICE_OBSERVER_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_SERVICE_OBSERVER_H_ + +#include "components/autofill/core/browser/webdata/autofill_change.h" + +namespace autofill { + +class AutofillWebDataServiceObserverOnDBThread { + public: + // Called on DB thread whenever Autofill entries are changed. + virtual void AutofillEntriesChanged(const AutofillChangeList& changes) {} + + // Called on DB thread when an AutofillProfile has been added/removed/updated + // in the WebDatabase. + virtual void AutofillProfileChanged(const AutofillProfileChange& change) {} + + protected: + virtual ~AutofillWebDataServiceObserverOnDBThread() {} +}; + +class AutofillWebDataServiceObserverOnUIThread { + public: + // Called on UI thread when multiple Autofill entries have been modified by + // Sync. + virtual void AutofillMultipleChanged() {} + + protected: + virtual ~AutofillWebDataServiceObserverOnUIThread() {} +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WEBDATA_SERVICE_OBSERVER_H_ diff --git a/chromium/components/autofill/core/browser/webdata/web_data_service_unittest.cc b/chromium/components/autofill/core/browser/webdata/web_data_service_unittest.cc new file mode 100644 index 00000000000..ae7c25cb9d3 --- /dev/null +++ b/chromium/components/autofill/core/browser/webdata/web_data_service_unittest.cc @@ -0,0 +1,574 @@ +// 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 <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/files/scoped_temp_dir.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "base/strings/string16.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/synchronization/waitable_event.h" +#include "base/time/time.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/webdata/autofill_change.h" +#include "components/autofill/core/browser/webdata/autofill_entry.h" +#include "components/autofill/core/browser/webdata/autofill_table.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service_observer.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/webdata/common/web_data_results.h" +#include "components/webdata/common/web_data_service_base.h" +#include "components/webdata/common/web_data_service_consumer.h" +#include "components/webdata/common/web_database_service.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using base::Time; +using base::TimeDelta; +using base::WaitableEvent; +using content::BrowserThread; +using testing::_; +using testing::DoDefault; +using testing::ElementsAreArray; +using testing::Pointee; +using testing::Property; + +namespace { + +template <class T> +class AutofillWebDataServiceConsumer: public WebDataServiceConsumer { + public: + AutofillWebDataServiceConsumer() : handle_(0) {} + virtual ~AutofillWebDataServiceConsumer() {} + + virtual void OnWebDataServiceRequestDone(WebDataService::Handle handle, + const WDTypedResult* result) { + using content::BrowserThread; + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + handle_ = handle; + const WDResult<T>* wrapped_result = + static_cast<const WDResult<T>*>(result); + result_ = wrapped_result->GetValue(); + + base::MessageLoop::current()->Quit(); + } + + WebDataService::Handle handle() { return handle_; } + T& result() { return result_; } + + private: + WebDataService::Handle handle_; + T result_; + DISALLOW_COPY_AND_ASSIGN(AutofillWebDataServiceConsumer); +}; + +} // namespace + +namespace autofill { + +static const int kWebDataServiceTimeoutSeconds = 8; + +ACTION_P(SignalEvent, event) { + event->Signal(); +} + +class MockAutofillWebDataServiceObserver + : public AutofillWebDataServiceObserverOnDBThread { + public: + MOCK_METHOD1(AutofillEntriesChanged, + void(const AutofillChangeList& changes)); + MOCK_METHOD1(AutofillProfileChanged, + void(const AutofillProfileChange& change)); +}; + +class WebDataServiceTest : public testing::Test { + public: + WebDataServiceTest() + : ui_thread_(BrowserThread::UI, &message_loop_), + db_thread_(BrowserThread::DB) {} + + protected: + virtual void SetUp() { + db_thread_.Start(); + + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + base::FilePath path = temp_dir_.path().AppendASCII("TestWebDB"); + + wdbs_ = new WebDatabaseService( + path, + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI), + BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)); + wdbs_->AddTable(scoped_ptr<WebDatabaseTable>(new AutofillTable("en-US"))); + wdbs_->LoadDatabase(); + + wds_ = new AutofillWebDataService( + wdbs_, WebDataServiceBase::ProfileErrorCallback()); + wds_->Init(); + } + + virtual void TearDown() { + wds_->ShutdownOnUIThread(); + wdbs_->ShutdownDatabase(); + wds_ = NULL; + wdbs_ = NULL; + WaitForDatabaseThread(); + + base::MessageLoop::current()->PostTask(FROM_HERE, + base::MessageLoop::QuitClosure()); + base::MessageLoop::current()->Run(); + db_thread_.Stop(); + } + + void WaitForDatabaseThread() { + base::WaitableEvent done(false, false); + BrowserThread::PostTask( + BrowserThread::DB, + FROM_HERE, + base::Bind(&base::WaitableEvent::Signal, base::Unretained(&done))); + done.Wait(); + } + + base::MessageLoopForUI message_loop_; + content::TestBrowserThread ui_thread_; + content::TestBrowserThread db_thread_; + base::FilePath profile_dir_; + scoped_refptr<AutofillWebDataService> wds_; + scoped_refptr<WebDatabaseService> wdbs_; + base::ScopedTempDir temp_dir_; +}; + +class WebDataServiceAutofillTest : public WebDataServiceTest { + public: + WebDataServiceAutofillTest() + : WebDataServiceTest(), + unique_id1_(1), + unique_id2_(2), + test_timeout_(TimeDelta::FromSeconds(kWebDataServiceTimeoutSeconds)), + done_event_(false, false) {} + + protected: + virtual void SetUp() { + WebDataServiceTest::SetUp(); + name1_ = ASCIIToUTF16("name1"); + name2_ = ASCIIToUTF16("name2"); + value1_ = ASCIIToUTF16("value1"); + value2_ = ASCIIToUTF16("value2"); + + void(AutofillWebDataService::*add_observer_func)( + AutofillWebDataServiceObserverOnDBThread*) = + &AutofillWebDataService::AddObserver; + BrowserThread::PostTask( + BrowserThread::DB, + FROM_HERE, + base::Bind(add_observer_func, wds_, &observer_)); + WaitForDatabaseThread(); + } + + virtual void TearDown() { + void(AutofillWebDataService::*remove_observer_func)( + AutofillWebDataServiceObserverOnDBThread*) = + &AutofillWebDataService::RemoveObserver; + BrowserThread::PostTask( + BrowserThread::DB, + FROM_HERE, + base::Bind(remove_observer_func, wds_, &observer_)); + WaitForDatabaseThread(); + + WebDataServiceTest::TearDown(); + } + + void AppendFormField(const base::string16& name, + const base::string16& value, + std::vector<FormFieldData>* form_fields) { + FormFieldData field; + field.name = name; + field.value = value; + form_fields->push_back(field); + } + + base::string16 name1_; + base::string16 name2_; + base::string16 value1_; + base::string16 value2_; + int unique_id1_, unique_id2_; + const TimeDelta test_timeout_; + testing::NiceMock<MockAutofillWebDataServiceObserver> observer_; + WaitableEvent done_event_; +}; + +// Simple consumer for Keywords data. Stores the result data and quits UI +// message loop when callback is invoked. +class KeywordsConsumer : public WebDataServiceConsumer { + public: + KeywordsConsumer() : load_succeeded(false) {} + + virtual void OnWebDataServiceRequestDone( + WebDataService::Handle h, + const WDTypedResult* result) OVERRIDE { + if (result) { + load_succeeded = true; + DCHECK(result->GetType() == KEYWORDS_RESULT); + keywords_result = + reinterpret_cast<const WDResult<WDKeywordsResult>*>( + result)->GetValue(); + } + + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + base::MessageLoop::current()->Quit(); + } + + // True if keywords data was loaded successfully. + bool load_succeeded; + // Result data from completion callback. + WDKeywordsResult keywords_result; +}; + +TEST_F(WebDataServiceAutofillTest, FormFillAdd) { + const AutofillChange expected_changes[] = { + AutofillChange(AutofillChange::ADD, AutofillKey(name1_, value1_)), + AutofillChange(AutofillChange::ADD, AutofillKey(name2_, value2_)) + }; + + // This will verify that the correct notification is triggered, + // passing the correct list of autofill keys in the details. + EXPECT_CALL(observer_, + AutofillEntriesChanged(ElementsAreArray(expected_changes))) + .WillOnce(SignalEvent(&done_event_)); + + std::vector<FormFieldData> form_fields; + AppendFormField(name1_, value1_, &form_fields); + AppendFormField(name2_, value2_, &form_fields); + wds_->AddFormFields(form_fields); + + // The event will be signaled when the mock observer is notified. + done_event_.TimedWait(test_timeout_); + + AutofillWebDataServiceConsumer<std::vector<base::string16> > consumer; + WebDataService::Handle handle; + static const int limit = 10; + handle = wds_->GetFormValuesForElementName( + name1_, base::string16(), limit, &consumer); + + // The message loop will exit when the consumer is called. + base::MessageLoop::current()->Run(); + + EXPECT_EQ(handle, consumer.handle()); + ASSERT_EQ(1U, consumer.result().size()); + EXPECT_EQ(value1_, consumer.result()[0]); +} + +TEST_F(WebDataServiceAutofillTest, FormFillRemoveOne) { + // First add some values to autofill. + EXPECT_CALL(observer_, AutofillEntriesChanged(_)) + .WillOnce(SignalEvent(&done_event_)); + std::vector<FormFieldData> form_fields; + AppendFormField(name1_, value1_, &form_fields); + wds_->AddFormFields(form_fields); + + // The event will be signaled when the mock observer is notified. + done_event_.TimedWait(test_timeout_); + + // This will verify that the correct notification is triggered, + // passing the correct list of autofill keys in the details. + const AutofillChange expected_changes[] = { + AutofillChange(AutofillChange::REMOVE, AutofillKey(name1_, value1_)) + }; + EXPECT_CALL(observer_, + AutofillEntriesChanged(ElementsAreArray(expected_changes))) + .WillOnce(SignalEvent(&done_event_)); + wds_->RemoveFormValueForElementName(name1_, value1_); + + // The event will be signaled when the mock observer is notified. + done_event_.TimedWait(test_timeout_); +} + +TEST_F(WebDataServiceAutofillTest, FormFillRemoveMany) { + TimeDelta one_day(TimeDelta::FromDays(1)); + Time t = Time::Now(); + + EXPECT_CALL(observer_, AutofillEntriesChanged(_)) + .WillOnce(SignalEvent(&done_event_)); + + std::vector<FormFieldData> form_fields; + AppendFormField(name1_, value1_, &form_fields); + AppendFormField(name2_, value2_, &form_fields); + wds_->AddFormFields(form_fields); + + // The event will be signaled when the mock observer is notified. + done_event_.TimedWait(test_timeout_); + + // This will verify that the correct notification is triggered, + // passing the correct list of autofill keys in the details. + const AutofillChange expected_changes[] = { + AutofillChange(AutofillChange::REMOVE, AutofillKey(name1_, value1_)), + AutofillChange(AutofillChange::REMOVE, AutofillKey(name2_, value2_)) + }; + EXPECT_CALL(observer_, + AutofillEntriesChanged(ElementsAreArray(expected_changes))) + .WillOnce(SignalEvent(&done_event_)); + wds_->RemoveFormElementsAddedBetween(t, t + one_day); + + // The event will be signaled when the mock observer is notified. + done_event_.TimedWait(test_timeout_); +} + +TEST_F(WebDataServiceAutofillTest, ProfileAdd) { + AutofillProfile profile; + + // Check that GUID-based notification was sent. + const AutofillProfileChange expected_change( + AutofillProfileChange::ADD, profile.guid(), &profile); + EXPECT_CALL(observer_, AutofillProfileChanged(expected_change)) + .WillOnce(SignalEvent(&done_event_)); + + wds_->AddAutofillProfile(profile); + done_event_.TimedWait(test_timeout_); + + // Check that it was added. + AutofillWebDataServiceConsumer<std::vector<AutofillProfile*> > consumer; + WebDataService::Handle handle = wds_->GetAutofillProfiles(&consumer); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle, consumer.handle()); + ASSERT_EQ(1U, consumer.result().size()); + EXPECT_EQ(profile, *consumer.result()[0]); + STLDeleteElements(&consumer.result()); +} + +TEST_F(WebDataServiceAutofillTest, ProfileRemove) { + AutofillProfile profile; + + // Add a profile. + EXPECT_CALL(observer_, AutofillProfileChanged(_)) + .WillOnce(SignalEvent(&done_event_)); + wds_->AddAutofillProfile(profile); + done_event_.TimedWait(test_timeout_); + + // Check that it was added. + AutofillWebDataServiceConsumer<std::vector<AutofillProfile*> > consumer; + WebDataService::Handle handle = wds_->GetAutofillProfiles(&consumer); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle, consumer.handle()); + ASSERT_EQ(1U, consumer.result().size()); + EXPECT_EQ(profile, *consumer.result()[0]); + STLDeleteElements(&consumer.result()); + + // Check that GUID-based notification was sent. + const AutofillProfileChange expected_change( + AutofillProfileChange::REMOVE, profile.guid(), NULL); + EXPECT_CALL(observer_, AutofillProfileChanged(expected_change)) + .WillOnce(SignalEvent(&done_event_)); + + // Remove the profile. + wds_->RemoveAutofillProfile(profile.guid()); + done_event_.TimedWait(test_timeout_); + + // Check that it was removed. + AutofillWebDataServiceConsumer<std::vector<AutofillProfile*> > consumer2; + WebDataService::Handle handle2 = wds_->GetAutofillProfiles(&consumer2); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle2, consumer2.handle()); + ASSERT_EQ(0U, consumer2.result().size()); +} + +TEST_F(WebDataServiceAutofillTest, ProfileUpdate) { + AutofillProfile profile1; + profile1.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Abe")); + AutofillProfile profile2; + profile2.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Alice")); + + EXPECT_CALL(observer_, AutofillProfileChanged(_)) + .WillOnce(DoDefault()) + .WillOnce(SignalEvent(&done_event_)); + + wds_->AddAutofillProfile(profile1); + wds_->AddAutofillProfile(profile2); + done_event_.TimedWait(test_timeout_); + + // Check that they were added. + AutofillWebDataServiceConsumer<std::vector<AutofillProfile*> > consumer; + WebDataService::Handle handle = wds_->GetAutofillProfiles(&consumer); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle, consumer.handle()); + ASSERT_EQ(2U, consumer.result().size()); + EXPECT_EQ(profile1, *consumer.result()[0]); + EXPECT_EQ(profile2, *consumer.result()[1]); + STLDeleteElements(&consumer.result()); + + AutofillProfile profile1_changed(profile1); + profile1_changed.SetRawInfo(NAME_FIRST, ASCIIToUTF16("Bill")); + const AutofillProfileChange expected_change( + AutofillProfileChange::UPDATE, profile1.guid(), &profile1_changed); + + EXPECT_CALL(observer_, AutofillProfileChanged(expected_change)) + .WillOnce(SignalEvent(&done_event_)); + + // Update the profile. + wds_->UpdateAutofillProfile(profile1_changed); + done_event_.TimedWait(test_timeout_); + + // Check that the updates were made. + AutofillWebDataServiceConsumer<std::vector<AutofillProfile*> > consumer2; + WebDataService::Handle handle2 = wds_->GetAutofillProfiles(&consumer2); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle2, consumer2.handle()); + ASSERT_EQ(2U, consumer2.result().size()); + EXPECT_NE(profile1, *consumer2.result()[0]); + EXPECT_EQ(profile1_changed, *consumer2.result()[0]); + EXPECT_EQ(profile2, *consumer2.result()[1]); + STLDeleteElements(&consumer2.result()); +} + +TEST_F(WebDataServiceAutofillTest, CreditAdd) { + CreditCard card; + wds_->AddCreditCard(card); + WaitForDatabaseThread(); + + // Check that it was added. + AutofillWebDataServiceConsumer<std::vector<CreditCard*> > consumer; + WebDataService::Handle handle = wds_->GetCreditCards(&consumer); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle, consumer.handle()); + ASSERT_EQ(1U, consumer.result().size()); + EXPECT_EQ(card, *consumer.result()[0]); + STLDeleteElements(&consumer.result()); +} + +TEST_F(WebDataServiceAutofillTest, CreditCardRemove) { + CreditCard credit_card; + + // Add a credit card. + wds_->AddCreditCard(credit_card); + WaitForDatabaseThread(); + + // Check that it was added. + AutofillWebDataServiceConsumer<std::vector<CreditCard*> > consumer; + WebDataService::Handle handle = wds_->GetCreditCards(&consumer); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle, consumer.handle()); + ASSERT_EQ(1U, consumer.result().size()); + EXPECT_EQ(credit_card, *consumer.result()[0]); + STLDeleteElements(&consumer.result()); + + // Remove the credit card. + wds_->RemoveCreditCard(credit_card.guid()); + WaitForDatabaseThread(); + + // Check that it was removed. + AutofillWebDataServiceConsumer<std::vector<CreditCard*> > consumer2; + WebDataService::Handle handle2 = wds_->GetCreditCards(&consumer2); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle2, consumer2.handle()); + ASSERT_EQ(0U, consumer2.result().size()); +} + +TEST_F(WebDataServiceAutofillTest, CreditUpdate) { + CreditCard card1; + card1.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Abe")); + CreditCard card2; + card2.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Alice")); + + wds_->AddCreditCard(card1); + wds_->AddCreditCard(card2); + WaitForDatabaseThread(); + + // Check that they got added. + AutofillWebDataServiceConsumer<std::vector<CreditCard*> > consumer; + WebDataService::Handle handle = wds_->GetCreditCards(&consumer); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle, consumer.handle()); + ASSERT_EQ(2U, consumer.result().size()); + EXPECT_EQ(card1, *consumer.result()[0]); + EXPECT_EQ(card2, *consumer.result()[1]); + STLDeleteElements(&consumer.result()); + + CreditCard card1_changed(card1); + card1_changed.SetRawInfo(CREDIT_CARD_NAME, ASCIIToUTF16("Bill")); + + wds_->UpdateCreditCard(card1_changed); + WaitForDatabaseThread(); + + // Check that the updates were made. + AutofillWebDataServiceConsumer<std::vector<CreditCard*> > consumer2; + WebDataService::Handle handle2 = wds_->GetCreditCards(&consumer2); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle2, consumer2.handle()); + ASSERT_EQ(2U, consumer2.result().size()); + EXPECT_NE(card1, *consumer2.result()[0]); + EXPECT_EQ(card1_changed, *consumer2.result()[0]); + EXPECT_EQ(card2, *consumer2.result()[1]); + STLDeleteElements(&consumer2.result()); +} + +TEST_F(WebDataServiceAutofillTest, AutofillRemoveModifiedBetween) { + // Add a profile. + EXPECT_CALL(observer_, AutofillProfileChanged(_)) + .WillOnce(SignalEvent(&done_event_)); + AutofillProfile profile; + wds_->AddAutofillProfile(profile); + done_event_.TimedWait(test_timeout_); + + // Check that it was added. + AutofillWebDataServiceConsumer<std::vector<AutofillProfile*> > + profile_consumer; + WebDataService::Handle handle = wds_->GetAutofillProfiles(&profile_consumer); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle, profile_consumer.handle()); + ASSERT_EQ(1U, profile_consumer.result().size()); + EXPECT_EQ(profile, *profile_consumer.result()[0]); + STLDeleteElements(&profile_consumer.result()); + + // Add a credit card. + CreditCard credit_card; + wds_->AddCreditCard(credit_card); + WaitForDatabaseThread(); + + // Check that it was added. + AutofillWebDataServiceConsumer<std::vector<CreditCard*> > card_consumer; + handle = wds_->GetCreditCards(&card_consumer); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle, card_consumer.handle()); + ASSERT_EQ(1U, card_consumer.result().size()); + EXPECT_EQ(credit_card, *card_consumer.result()[0]); + STLDeleteElements(&card_consumer.result()); + + // Check that GUID-based notification was sent for the profile. + const AutofillProfileChange expected_profile_change( + AutofillProfileChange::REMOVE, profile.guid(), NULL); + EXPECT_CALL(observer_, AutofillProfileChanged(expected_profile_change)) + .WillOnce(SignalEvent(&done_event_)); + + // Remove the profile using time range of "all time". + wds_->RemoveAutofillDataModifiedBetween(Time(), Time()); + done_event_.TimedWait(test_timeout_); + WaitForDatabaseThread(); + + // Check that the profile was removed. + AutofillWebDataServiceConsumer<std::vector<AutofillProfile*> > + profile_consumer2; + WebDataService::Handle handle2 = + wds_->GetAutofillProfiles(&profile_consumer2); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle2, profile_consumer2.handle()); + ASSERT_EQ(0U, profile_consumer2.result().size()); + + // Check that the credit card was removed. + AutofillWebDataServiceConsumer<std::vector<CreditCard*> > card_consumer2; + handle2 = wds_->GetCreditCards(&card_consumer2); + base::MessageLoop::current()->Run(); + EXPECT_EQ(handle2, card_consumer2.handle()); + ASSERT_EQ(0U, card_consumer2.result().size()); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/OWNERS b/chromium/components/autofill/core/common/OWNERS new file mode 100644 index 00000000000..0c955d37e53 --- /dev/null +++ b/chromium/components/autofill/core/common/OWNERS @@ -0,0 +1,9 @@ +# Changes to IPC messages require a security review to avoid introducing +# new sandbox escapes. +per-file autofill_messages*.h=set noparent +per-file autofill_messages*.h=cdn@chromium.org +per-file autofill_messages*.h=cevans@chromium.org +per-file autofill_messages*.h=jln@chromium.org +per-file autofill_messages*.h=jschuh@chromium.org +per-file autofill_messages*.h=palmer@chromium.org +per-file autofill_messages*.h=tsepez@chromium.org diff --git a/chromium/components/autofill/core/common/autocheckout_status.h b/chromium/components/autofill/core/common/autocheckout_status.h new file mode 100644 index 00000000000..81bc7005e80 --- /dev/null +++ b/chromium/components/autofill/core/common/autocheckout_status.h @@ -0,0 +1,22 @@ +// 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_CORE_COMMON_AUTOCHECKOUT_STATUS_H_ +#define COMPONENTS_AUTOFILL_CORE_COMMON_AUTOCHECKOUT_STATUS_H_ + +namespace autofill { + +enum AutocheckoutStatus { + SUCCESS, + MISSING_FIELDMAPPING, + MISSING_ADVANCE, + MISSING_CLICK_ELEMENT_BEFORE_FORM_FILLING, + MISSING_CLICK_ELEMENT_AFTER_FORM_FILLING, + CANNOT_PROCEED, + AUTOCHECKOUT_STATUS_NUM_STATUS, +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_AUTOCHECKOUT_STATUS_H_ diff --git a/chromium/components/autofill/core/common/autofill_constants.cc b/chromium/components/autofill/core/common/autofill_constants.cc new file mode 100644 index 00000000000..ed4159af7ec --- /dev/null +++ b/chromium/components/autofill/core/common/autofill_constants.cc @@ -0,0 +1,20 @@ +// 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/core/common/autofill_constants.h" + +#include "build/build_config.h" + +namespace autofill { + +const char kHelpURL[] = +#if defined(OS_CHROMEOS) + "https://support.google.com/chromeos/?p=settings_autofill"; +#else + "https://support.google.com/chrome/?p=settings_autofill"; +#endif + +const size_t kRequiredAutofillFields = 3; + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/autofill_constants.h b/chromium/components/autofill/core/common/autofill_constants.h new file mode 100644 index 00000000000..9386b45f3c3 --- /dev/null +++ b/chromium/components/autofill/core/common/autofill_constants.h @@ -0,0 +1,25 @@ +// 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. + +// Contains constants specific to the Autofill component. + +#ifndef COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_CONSTANTS_H_ +#define COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_CONSTANTS_H_ + +#include <stddef.h> // For size_t + +namespace autofill { + +// Help URL for the Autofill dialog. +extern const char kHelpURL[]; + +// 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. +extern const size_t kRequiredAutofillFields; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_CONSTANTS_H_ diff --git a/chromium/components/autofill/core/common/autofill_message_generator.cc b/chromium/components/autofill/core/common/autofill_message_generator.cc new file mode 100644 index 00000000000..48988146ab4 --- /dev/null +++ b/chromium/components/autofill/core/common/autofill_message_generator.cc @@ -0,0 +1,33 @@ +// 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. + +// Get basic type definitions. +#define IPC_MESSAGE_IMPL +#include "components/autofill/core/common/autofill_message_generator.h" + +// Generate constructors. +#include "ipc/struct_constructor_macros.h" +#include "components/autofill/core/common/autofill_message_generator.h" + +// Generate destructors. +#include "ipc/struct_destructor_macros.h" +#include "components/autofill/core/common/autofill_message_generator.h" + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#include "components/autofill/core/common/autofill_message_generator.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#include "components/autofill/core/common/autofill_message_generator.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#include "components/autofill/core/common/autofill_message_generator.h" +} // namespace IPC diff --git a/chromium/components/autofill/core/common/autofill_message_generator.h b/chromium/components/autofill/core/common/autofill_message_generator.h new file mode 100644 index 00000000000..31a29649550 --- /dev/null +++ b/chromium/components/autofill/core/common/autofill_message_generator.h @@ -0,0 +1,7 @@ +// 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. + +// Multiply-included file, hence no include guard. + +#include "components/autofill/core/common/autofill_messages.h" diff --git a/chromium/components/autofill/core/common/autofill_messages.h b/chromium/components/autofill/core/common/autofill_messages.h new file mode 100644 index 00000000000..13524dfd7d2 --- /dev/null +++ b/chromium/components/autofill/core/common/autofill_messages.h @@ -0,0 +1,297 @@ +// 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. + +// Multiply-included message file, hence no include guard. + +#include <string> + +#include "base/time/time.h" +#include "components/autofill/core/common/autocheckout_status.h" +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_data_predictions.h" +#include "components/autofill/core/common/form_field_data.h" +#include "components/autofill/core/common/form_field_data_predictions.h" +#include "components/autofill/core/common/forms_seen_state.h" +#include "components/autofill/core/common/password_form_fill_data.h" +#include "components/autofill/core/common/web_element_descriptor.h" +#include "content/public/common/common_param_traits.h" +#include "content/public/common/common_param_traits_macros.h" +#include "content/public/common/password_form.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_message_utils.h" +#include "third_party/WebKit/public/web/WebFormElement.h" +#include "ui/gfx/rect.h" +#include "url/gurl.h" + +#define IPC_MESSAGE_START AutofillMsgStart + +IPC_ENUM_TRAITS_MAX_VALUE(autofill::AutocheckoutStatus, + autofill::AUTOCHECKOUT_STATUS_NUM_STATUS - 1) +IPC_ENUM_TRAITS_MAX_VALUE(autofill::FormsSeenState, + autofill::FORMS_SEEN_STATE_NUM_STATES - 1) +IPC_ENUM_TRAITS_MAX_VALUE(base::i18n::TextDirection, + base::i18n::TEXT_DIRECTION_NUM_DIRECTIONS - 1) + +IPC_STRUCT_TRAITS_BEGIN(autofill::WebElementDescriptor) + IPC_STRUCT_TRAITS_MEMBER(descriptor) + IPC_STRUCT_TRAITS_MEMBER(retrieval_method) +IPC_STRUCT_TRAITS_END() + +IPC_ENUM_TRAITS_MAX_VALUE(autofill::WebElementDescriptor::RetrievalMethod, + autofill::WebElementDescriptor::NONE) + +IPC_STRUCT_TRAITS_BEGIN(autofill::FormFieldData) + IPC_STRUCT_TRAITS_MEMBER(label) + IPC_STRUCT_TRAITS_MEMBER(name) + IPC_STRUCT_TRAITS_MEMBER(value) + IPC_STRUCT_TRAITS_MEMBER(form_control_type) + IPC_STRUCT_TRAITS_MEMBER(autocomplete_attribute) + IPC_STRUCT_TRAITS_MEMBER(max_length) + IPC_STRUCT_TRAITS_MEMBER(is_autofilled) + IPC_STRUCT_TRAITS_MEMBER(is_checked) + IPC_STRUCT_TRAITS_MEMBER(is_checkable) + IPC_STRUCT_TRAITS_MEMBER(is_focusable) + IPC_STRUCT_TRAITS_MEMBER(should_autocomplete) + IPC_STRUCT_TRAITS_MEMBER(text_direction) + IPC_STRUCT_TRAITS_MEMBER(option_values) + IPC_STRUCT_TRAITS_MEMBER(option_contents) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(autofill::FormFieldDataPredictions) + IPC_STRUCT_TRAITS_MEMBER(field) + IPC_STRUCT_TRAITS_MEMBER(signature) + IPC_STRUCT_TRAITS_MEMBER(heuristic_type) + IPC_STRUCT_TRAITS_MEMBER(server_type) + IPC_STRUCT_TRAITS_MEMBER(overall_type) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(autofill::FormData) + IPC_STRUCT_TRAITS_MEMBER(name) + IPC_STRUCT_TRAITS_MEMBER(method) + IPC_STRUCT_TRAITS_MEMBER(origin) + IPC_STRUCT_TRAITS_MEMBER(action) + IPC_STRUCT_TRAITS_MEMBER(user_submitted) + IPC_STRUCT_TRAITS_MEMBER(fields) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(autofill::FormDataPredictions) + IPC_STRUCT_TRAITS_MEMBER(data) + IPC_STRUCT_TRAITS_MEMBER(signature) + IPC_STRUCT_TRAITS_MEMBER(experiment_id) + IPC_STRUCT_TRAITS_MEMBER(fields) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(autofill::UsernamesCollectionKey) + IPC_STRUCT_TRAITS_MEMBER(username) + IPC_STRUCT_TRAITS_MEMBER(password) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(autofill::PasswordFormFillData) + IPC_STRUCT_TRAITS_MEMBER(basic_data) + IPC_STRUCT_TRAITS_MEMBER(preferred_realm) + IPC_STRUCT_TRAITS_MEMBER(additional_logins) + IPC_STRUCT_TRAITS_MEMBER(other_possible_usernames) + IPC_STRUCT_TRAITS_MEMBER(wait_for_username) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(autofill::PasswordAndRealm) + IPC_STRUCT_TRAITS_MEMBER(password) + IPC_STRUCT_TRAITS_MEMBER(realm) +IPC_STRUCT_TRAITS_END() + +IPC_ENUM_TRAITS_MAX_VALUE( + WebKit::WebFormElement::AutocompleteResult, + WebKit::WebFormElement::AutocompleteResultErrorInvalid) + +// Autofill messages sent from the browser to the renderer. + +// Request to parse all the forms without field count limit. +IPC_MESSAGE_ROUTED0(AutofillMsg_GetAllForms) + +// Reply to the AutofillHostMsg_FillAutofillFormData message with the +// Autofill form data. +IPC_MESSAGE_ROUTED2(AutofillMsg_FormDataFilled, + int /* id of the request message */, + autofill::FormData /* form data */) + +// Fill a password form and prepare field autocomplete for multiple +// matching logins. Lets the renderer know if it should disable the popup +// because the browser process will own the popup UI. +IPC_MESSAGE_ROUTED1(AutofillMsg_FillPasswordForm, + autofill::PasswordFormFillData /* the fill form data*/) + +// Send the heuristic and server field type predictions to the renderer. +IPC_MESSAGE_ROUTED1( + AutofillMsg_FieldTypePredictionsAvailable, + std::vector<autofill::FormDataPredictions> /* forms */) + +// Tells the renderer that the next form will be filled for real. +IPC_MESSAGE_ROUTED0(AutofillMsg_SetAutofillActionFill) + +// Clears the currently displayed Autofill results. +IPC_MESSAGE_ROUTED0(AutofillMsg_ClearForm) + +// Tells the renderer that the next form will be filled as a preview. +IPC_MESSAGE_ROUTED0(AutofillMsg_SetAutofillActionPreview) + +// Tells the renderer that the Autofill previewed form should be cleared. +IPC_MESSAGE_ROUTED0(AutofillMsg_ClearPreviewedForm) + +// Sets the currently selected node's value. +IPC_MESSAGE_ROUTED1(AutofillMsg_SetNodeText, + base::string16 /* new node text */) + +// Sets the currently selected node's value to be the given data list value. +IPC_MESSAGE_ROUTED1(AutofillMsg_AcceptDataListSuggestion, + base::string16 /* accepted data list value */) + +// Tells the renderer to populate the correct password fields with this +// generated password. +IPC_MESSAGE_ROUTED1(AutofillMsg_GeneratedPasswordAccepted, + base::string16 /* generated_password */) + +// Tells the renderer whether password generation is enabled. +IPC_MESSAGE_ROUTED1(AutofillMsg_PasswordGenerationEnabled, + bool /* is_enabled */) + +// Tells the renderer that the password field has accept the suggestion. +IPC_MESSAGE_ROUTED1(AutofillMsg_AcceptPasswordAutofillSuggestion, + base::string16 /* username value*/) + +// Tells the renderer that this password form is not blacklisted. A form can +// be blacklisted if a user chooses "never save passwords for this site". +IPC_MESSAGE_ROUTED1(AutofillMsg_FormNotBlacklisted, + content::PasswordForm /* form checked */) + +// Sent when requestAutocomplete() finishes (either succesfully or with an +// error). If it was a success, the renderer fills the form that requested +// autocomplete with the |form_data| values input by the user. +IPC_MESSAGE_ROUTED2(AutofillMsg_RequestAutocompleteResult, + WebKit::WebFormElement::AutocompleteResult /* result */, + autofill::FormData /* form_data */) + +// Sent when a page should be filled using Autocheckout. This happens when the +// Autofill server hints that a page is Autocheckout enabled. +IPC_MESSAGE_ROUTED4(AutofillMsg_FillFormsAndClick, + std::vector<autofill::FormData> /* form_data */, + std::vector<autofill::WebElementDescriptor> /* + click_elements_before_form_fill */, + std::vector<autofill::WebElementDescriptor> /* + click_elements_after_form_fill */, + autofill::WebElementDescriptor /* element_descriptor */) + +// Sent when Autocheckout is supported for the current page. The page has to +// be whitelisted and the Autofill server must have returned Autocheckout page +// metadata. +IPC_MESSAGE_ROUTED0(AutofillMsg_AutocheckoutSupported) + +// Sent when the current page is actually displayed in the browser, possibly +// after being preloaded. +IPC_MESSAGE_ROUTED0(AutofillMsg_PageShown) + +// Autofill messages sent from the renderer to the browser. + +// TODO(creis): check in the browser that the renderer actually has permission +// for the URL to avoid compromised renderers talking to the browser. + +// Notification that forms have been seen that are candidates for +// filling/submitting by the AutofillManager. +IPC_MESSAGE_ROUTED3(AutofillHostMsg_FormsSeen, + std::vector<autofill::FormData> /* forms */, + base::TimeTicks /* timestamp */, + autofill::FormsSeenState /* state */) + +// Notification that password forms have been seen that are candidates for +// filling/submitting by the password manager. +IPC_MESSAGE_ROUTED1(AutofillHostMsg_PasswordFormsParsed, + std::vector<content::PasswordForm> /* forms */) + +// Notification that initial layout has occurred and the following password +// forms are visible on the page (e.g. not set to display:none.) +IPC_MESSAGE_ROUTED1(AutofillHostMsg_PasswordFormsRendered, + std::vector<content::PasswordForm> /* forms */) + +// Notification that a form has been submitted. The user hit the button. +IPC_MESSAGE_ROUTED2(AutofillHostMsg_FormSubmitted, + autofill::FormData /* form */, + base::TimeTicks /* timestamp */) + +// Notification that a form field's value has changed. +IPC_MESSAGE_ROUTED3(AutofillHostMsg_TextFieldDidChange, + autofill::FormData /* the form */, + autofill::FormFieldData /* the form field */, + base::TimeTicks /* timestamp */) + +// Shows the Autocheckout bubble if the conditions are right. +IPC_MESSAGE_ROUTED2(AutofillHostMsg_MaybeShowAutocheckoutBubble, + autofill::FormData /* form */, + gfx::RectF /* bounding_box */) + +// Queries the browser for Autofill suggestions for a form input field. +IPC_MESSAGE_ROUTED5(AutofillHostMsg_QueryFormFieldAutofill, + int /* id of this message */, + autofill::FormData /* the form */, + autofill::FormFieldData /* the form field */, + gfx::RectF /* input field bounds, window-relative */, + bool /* display warning if autofill disabled */) + +// Instructs the browser to fill in the values for a form using Autofill +// profile data. +IPC_MESSAGE_ROUTED4(AutofillHostMsg_FillAutofillFormData, + int /* id of this message */, + autofill::FormData /* the form */, + autofill::FormFieldData /* the form field */, + int /* profile unique ID */) + +// Sent when a form is previewed with Autofill suggestions. +IPC_MESSAGE_ROUTED0(AutofillHostMsg_DidPreviewAutofillFormData) + +// Sent when a form is filled with Autofill suggestions. +IPC_MESSAGE_ROUTED1(AutofillHostMsg_DidFillAutofillFormData, + base::TimeTicks /* timestamp */) + +// Sent when a form receives a request to do interactive autocomplete. +IPC_MESSAGE_ROUTED2(AutofillHostMsg_RequestAutocomplete, + autofill::FormData /* form_data */, + GURL /* frame_url */) + +// Instructs the browser to show the Autofill dialog. +IPC_MESSAGE_ROUTED0(AutofillHostMsg_ShowAutofillDialog) + +// Send when a text field is done editing. +IPC_MESSAGE_ROUTED0(AutofillHostMsg_DidEndTextFieldEditing) + +// Instructs the browser to hide the Autofill UI. +IPC_MESSAGE_ROUTED0(AutofillHostMsg_HideAutofillUI) + +// Sent when the renderer filled an Autocheckout page and clicked the proceed +// button or if there was an error. +IPC_MESSAGE_ROUTED1(AutofillHostMsg_AutocheckoutPageCompleted, + autofill::AutocheckoutStatus /* status */) + +// Instructs the browser to show the password generation bubble at the +// specified location. This location should be specified in the renderers +// coordinate system. Form is the form associated with the password field. +IPC_MESSAGE_ROUTED3(AutofillHostMsg_ShowPasswordGenerationPopup, + gfx::Rect /* source location */, + int /* max length of the password */, + content::PasswordForm) + +// Instruct the browser that a password mapping has been found for a field. +IPC_MESSAGE_ROUTED2(AutofillHostMsg_AddPasswordFormMapping, + autofill::FormFieldData, /* the user name field */ + autofill::PasswordFormFillData /* password pairings */) + +// Instruct the browser to show a popup with the following suggestions from the +// password manager. +IPC_MESSAGE_ROUTED4(AutofillHostMsg_ShowPasswordSuggestions, + autofill::FormFieldData /* the form field */, + gfx::RectF /* input field bounds, window-relative */, + std::vector<base::string16> /* suggestions */, + std::vector<base::string16> /* realms */) + +// Inform browser of data list values for the curent field. +IPC_MESSAGE_ROUTED2(AutofillHostMsg_SetDataList, + std::vector<base::string16> /* values */, + std::vector<base::string16> /* labels */) diff --git a/chromium/components/autofill/core/common/autofill_pref_names.cc b/chromium/components/autofill/core/common/autofill_pref_names.cc new file mode 100644 index 00000000000..622783c0540 --- /dev/null +++ b/chromium/components/autofill/core/common/autofill_pref_names.cc @@ -0,0 +1,25 @@ +// 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/core/common/autofill_pref_names.h" + +namespace autofill { +namespace prefs { + +// Boolean that is true when auxiliary Autofill profiles are enabled. +// Currently applies to Address Book "me" card on Mac. False on Win and Linux. +const char kAutofillAuxiliaryProfilesEnabled[] = + "autofill.auxiliary_profiles_enabled"; + +// Boolean that is true if Autofill is enabled and allowed to save profile data. +const char kAutofillEnabled[] = "autofill.enabled"; + +// Double that indicates negative (for not matched forms) upload rate. +const char kAutofillNegativeUploadRate[] = "autofill.negative_upload_rate"; + +// Double that indicates positive (for matched forms) upload rate. +const char kAutofillPositiveUploadRate[] = "autofill.positive_upload_rate"; + +} // namespace prefs +} // namespace autofill diff --git a/chromium/components/autofill/core/common/autofill_pref_names.h b/chromium/components/autofill/core/common/autofill_pref_names.h new file mode 100644 index 00000000000..e52cc7285f8 --- /dev/null +++ b/chromium/components/autofill/core/common/autofill_pref_names.h @@ -0,0 +1,21 @@ +// 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_CORE_COMMON_AUTOFILL_PREF_NAMES_H_ +#define COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PREF_NAMES_H_ + +namespace autofill { +namespace prefs { + +// Alphabetical list of preference names specific to the Autofill +// component. Keep alphabetized, and document each in the .cc file. +extern const char kAutofillAuxiliaryProfilesEnabled[]; +extern const char kAutofillEnabled[]; +extern const char kAutofillNegativeUploadRate[]; +extern const char kAutofillPositiveUploadRate[]; + +} // namespace prefs +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_PREF_NAMES_H_ diff --git a/chromium/components/autofill/core/common/autofill_switches.cc b/chromium/components/autofill/core/common/autofill_switches.cc new file mode 100644 index 00000000000..a41a9c7b96a --- /dev/null +++ b/chromium/components/autofill/core/common/autofill_switches.cc @@ -0,0 +1,49 @@ +// 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/core/common/autofill_switches.h" + +namespace autofill { +namespace switches { + +// Flag used to tell Chrome the Autochecout whitelist url. +const char kAutocheckoutWhitelistUrl[] = "autocheckout-whitelist-url"; + +// Flag used to tell Chrome the base url of the Autofill service. +const char kAutofillServiceUrl[] = "autofill-service-url"; + +// Bypass autocheckout whitelist check, so all sites are enabled. +const char kBypassAutocheckoutWhitelist[] = "bypass-autocheckout-whitelist"; + +// Disables an interactive autocomplete UI. See kEnableInteractiveAutocomplete +// for a description. +const char kDisableInteractiveAutocomplete[] = + "disable-interactive-autocomplete"; + +// Enable autofill for new elements like checkboxes. crbug.com/157636 +const char kEnableExperimentalFormFilling[] = + "enable-experimental-form-filling"; + +// Enables an interactive autocomplete UI and a way to invoke this UI from +// WebKit by enabling HTMLFormElement#requestAutocomplete (and associated +// autocomplete* events and logic). +const char kEnableInteractiveAutocomplete[] = "enable-interactive-autocomplete"; + +// Annotates forms with Autofill field type predictions. +const char kShowAutofillTypePredictions[] = "show-autofill-type-predictions"; + +// Secure service URL for Online Wallet service. Used as the base url to escrow +// credit card numbers. +const char kWalletSecureServiceUrl[] = "wallet-secure-service-url"; + +// Service URL for Online Wallet service. Used as the base url for Online Wallet +// API calls. +const char kWalletServiceUrl[] = "wallet-service-url"; + +// Enable production Online Wallet service. If this flag is not set, the sandbox +// service will be used. +const char kWalletServiceUseProd[] = "wallet-service-use-prod"; + +} // namespace switches +} // namespace autofill diff --git a/chromium/components/autofill/core/common/autofill_switches.h b/chromium/components/autofill/core/common/autofill_switches.h new file mode 100644 index 00000000000..84d76d59c59 --- /dev/null +++ b/chromium/components/autofill/core/common/autofill_switches.h @@ -0,0 +1,27 @@ +// 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_CORE_COMMON_AUTOFILL_SWITCHES_H_ +#define COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_SWITCHES_H_ + +namespace autofill { +namespace switches { + +// All switches in alphabetical order. The switches should be documented +// alongside the definition of their values in the .cc file. +extern const char kAutocheckoutWhitelistUrl[]; +extern const char kAutofillServiceUrl[]; +extern const char kBypassAutocheckoutWhitelist[]; +extern const char kDisableInteractiveAutocomplete[]; +extern const char kEnableExperimentalFormFilling[]; +extern const char kEnableInteractiveAutocomplete[]; +extern const char kShowAutofillTypePredictions[]; +extern const char kWalletSecureServiceUrl[]; +extern const char kWalletServiceUrl[]; +extern const char kWalletServiceUseProd[]; + +} // namespace switches +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_AUTOFILL_SWITCHES_H_ diff --git a/chromium/components/autofill/core/common/form_data.cc b/chromium/components/autofill/core/common/form_data.cc new file mode 100644 index 00000000000..e0c3c1c4636 --- /dev/null +++ b/chromium/components/autofill/core/common/form_data.cc @@ -0,0 +1,40 @@ +// 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/core/common/form_data.h" + +#include "base/strings/string_util.h" + +namespace autofill { + +FormData::FormData() + : user_submitted(false) { +} + +FormData::FormData(const FormData& data) + : name(data.name), + method(data.method), + origin(data.origin), + action(data.action), + user_submitted(data.user_submitted), + fields(data.fields) { +} + +FormData::~FormData() { +} + +bool FormData::operator==(const FormData& form) const { + return name == form.name && + StringToLowerASCII(method) == StringToLowerASCII(form.method) && + origin == form.origin && + action == form.action && + user_submitted == form.user_submitted && + fields == form.fields; +} + +bool FormData::operator!=(const FormData& form) const { + return !operator==(form); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/form_data.h b/chromium/components/autofill/core/common/form_data.h new file mode 100644 index 00000000000..202fb8d3f95 --- /dev/null +++ b/chromium/components/autofill/core/common/form_data.h @@ -0,0 +1,42 @@ +// 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_CORE_COMMON_FORM_DATA_H__ +#define COMPONENTS_AUTOFILL_CORE_COMMON_FORM_DATA_H__ + +#include <vector> + +#include "base/strings/string16.h" +#include "components/autofill/core/common/form_field_data.h" +#include "url/gurl.h" + +namespace autofill { + +// Holds information about a form to be filled and/or submitted. +struct FormData { + FormData(); + FormData(const FormData& data); + ~FormData(); + + // Used by FormStructureTest. + bool operator==(const FormData& form) const; + bool operator!=(const FormData& form) const; + + // The name of the form. + base::string16 name; + // GET or POST. + base::string16 method; + // The URL (minus query parameters) containing the form. + GURL origin; + // The action target of the form. + GURL action; + // true if this form was submitted by a user gesture and not javascript. + bool user_submitted; + // A vector of all the input fields in the form. + std::vector<FormFieldData> fields; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_FORM_DATA_H__ diff --git a/chromium/components/autofill/core/common/form_data_predictions.cc b/chromium/components/autofill/core/common/form_data_predictions.cc new file mode 100644 index 00000000000..8375c3066c9 --- /dev/null +++ b/chromium/components/autofill/core/common/form_data_predictions.cc @@ -0,0 +1,35 @@ +// 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/core/common/form_data_predictions.h" + +namespace autofill { + +FormDataPredictions::FormDataPredictions() { +} + +FormDataPredictions::FormDataPredictions(const FormDataPredictions& other) + : data(other.data), + signature(other.signature), + experiment_id(other.experiment_id), + fields(other.fields) { +} + +FormDataPredictions::~FormDataPredictions() { +} + +bool FormDataPredictions::operator==( + const FormDataPredictions& predictions) const { + return (data == predictions.data && + signature == predictions.signature && + experiment_id == predictions.experiment_id && + fields == predictions.fields); +} + +bool FormDataPredictions::operator!=( + const FormDataPredictions& predictions) const { + return !operator==(predictions); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/form_data_predictions.h b/chromium/components/autofill/core/common/form_data_predictions.h new file mode 100644 index 00000000000..80b04be16cb --- /dev/null +++ b/chromium/components/autofill/core/common/form_data_predictions.h @@ -0,0 +1,38 @@ +// 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_CORE_COMMON_FORM_DATA_PREDICTIONS_H__ +#define COMPONENTS_AUTOFILL_CORE_COMMON_FORM_DATA_PREDICTIONS_H__ + +#include <string> +#include <vector> + +#include "components/autofill/core/common/form_data.h" +#include "components/autofill/core/common/form_field_data_predictions.h" + +namespace autofill { + +// Holds information about a form to be filled and/or submitted. +struct FormDataPredictions { + // Data for this form. + FormData data; + // The form signature for communication with the crowdsourcing server. + std::string signature; + // The experiment id for the server predictions. + std::string experiment_id; + // The form fields and their predicted field types. + std::vector<FormFieldDataPredictions> fields; + + FormDataPredictions(); + FormDataPredictions(const FormDataPredictions& other); + ~FormDataPredictions(); + + // Added for the sake of testing. + bool operator==(const FormDataPredictions& predictions) const; + bool operator!=(const FormDataPredictions& predictions) const; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_FORM_DATA_PREDICTIONS_H__ diff --git a/chromium/components/autofill/core/common/form_field_data.cc b/chromium/components/autofill/core/common/form_field_data.cc new file mode 100644 index 00000000000..1de786dcf3c --- /dev/null +++ b/chromium/components/autofill/core/common/form_field_data.cc @@ -0,0 +1,73 @@ +// 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/core/common/form_field_data.h" + +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" + +namespace autofill { + +FormFieldData::FormFieldData() + : max_length(0), + is_autofilled(false), + is_checked(false), + is_checkable(false), + is_focusable(false), + should_autocomplete(true), + text_direction(base::i18n::UNKNOWN_DIRECTION) { +} + +FormFieldData::~FormFieldData() { +} + +bool FormFieldData::operator==(const FormFieldData& field) const { + // A FormFieldData stores a value, but the value is not part of the identity + // of the field, so we don't want to compare the values. + return (label == field.label && + name == field.name && + form_control_type == field.form_control_type && + autocomplete_attribute == field.autocomplete_attribute && + max_length == field.max_length); +} + +bool FormFieldData::operator!=(const FormFieldData& field) const { + return !operator==(field); +} + +bool FormFieldData::operator<(const FormFieldData& field) const { + if (label == field.label) + return name < field.name; + + return label < field.label; +} + +std::ostream& operator<<(std::ostream& os, const FormFieldData& field) { + return os + << UTF16ToUTF8(field.label) + << " " + << UTF16ToUTF8(field.name) + << " " + << UTF16ToUTF8(field.value) + << " " + << field.form_control_type + << " " + << field.autocomplete_attribute + << " " + << field.max_length + << " " + << (field.is_autofilled ? "true" : "false") + << " " + << (field.is_checked ? "true" : "false") + << " " + << (field.is_checkable ? "true" : "false") + << " " + << (field.is_focusable ? "true" : "false") + << " " + << (field.should_autocomplete ? "true" : "false") + << " " + << field.text_direction; +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/form_field_data.h b/chromium/components/autofill/core/common/form_field_data.h new file mode 100644 index 00000000000..f0e7579cd0a --- /dev/null +++ b/chromium/components/autofill/core/common/form_field_data.h @@ -0,0 +1,69 @@ +// 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_CORE_COMMON_FORM_FIELD_DATA_H_ +#define COMPONENTS_AUTOFILL_CORE_COMMON_FORM_FIELD_DATA_H_ + +#include <vector> + +#include "base/i18n/rtl.h" +#include "base/strings/string16.h" + +namespace autofill { + +// Stores information about a field in a form. +struct FormFieldData { + FormFieldData(); + ~FormFieldData(); + + // Equality tests for identity which does not include |value| or + // |is_autofilled|. + // TODO(dhollowa): These operators need to be revised when we implement field + // ids. + bool operator==(const FormFieldData& field) const; + bool operator!=(const FormFieldData& field) const; + // Comparison operator exposed for STL map. Uses label, then name to sort. + bool operator<(const FormFieldData& field) const; + + base::string16 label; + base::string16 name; + base::string16 value; + std::string form_control_type; + std::string autocomplete_attribute; + size_t max_length; + bool is_autofilled; + bool is_checked; + bool is_checkable; + bool is_focusable; + bool should_autocomplete; + base::i18n::TextDirection text_direction; + + // For the HTML snippet |<option value="US">United States</option>|, the + // value is "US" and the contents are "United States". + std::vector<base::string16> option_values; + std::vector<base::string16> option_contents; +}; + +// So we can compare FormFieldDatas with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const FormFieldData& field); + +// Prefer to use this macro in place of |EXPECT_EQ()| for comparing +// |FormFieldData|s in test code. +#define EXPECT_FORM_FIELD_DATA_EQUALS(expected, actual) \ + do { \ + EXPECT_EQ(expected.label, actual.label); \ + EXPECT_EQ(expected.name, actual.name); \ + EXPECT_EQ(expected.value, actual.value); \ + EXPECT_EQ(expected.form_control_type, actual.form_control_type); \ + EXPECT_EQ(expected.autocomplete_attribute, actual.autocomplete_attribute); \ + EXPECT_EQ(expected.max_length, actual.max_length); \ + EXPECT_EQ(expected.is_autofilled, actual.is_autofilled); \ + EXPECT_EQ(expected.is_checked, actual.is_checked); \ + EXPECT_EQ(expected.is_checkable, actual.is_checkable); \ + } while (0) + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_FORM_FIELD_DATA_H_ + diff --git a/chromium/components/autofill/core/common/form_field_data_predictions.cc b/chromium/components/autofill/core/common/form_field_data_predictions.cc new file mode 100644 index 00000000000..6e06559fd35 --- /dev/null +++ b/chromium/components/autofill/core/common/form_field_data_predictions.cc @@ -0,0 +1,38 @@ +// 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/core/common/form_field_data_predictions.h" + +namespace autofill { + +FormFieldDataPredictions::FormFieldDataPredictions() { +} + +FormFieldDataPredictions::FormFieldDataPredictions( + const FormFieldDataPredictions& other) + : field(other.field), + signature(other.signature), + heuristic_type(other.heuristic_type), + server_type(other.server_type), + overall_type(other.overall_type) { +} + +FormFieldDataPredictions::~FormFieldDataPredictions() { +} + +bool FormFieldDataPredictions::operator==( + const FormFieldDataPredictions& predictions) const { + return (field == predictions.field && + signature == predictions.signature && + heuristic_type == predictions.heuristic_type && + server_type == predictions.server_type && + overall_type == predictions.overall_type); +} + +bool FormFieldDataPredictions::operator!=( + const FormFieldDataPredictions& predictions) const { + return !operator==(predictions); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/form_field_data_predictions.h b/chromium/components/autofill/core/common/form_field_data_predictions.h new file mode 100644 index 00000000000..86f8bdaccdf --- /dev/null +++ b/chromium/components/autofill/core/common/form_field_data_predictions.h @@ -0,0 +1,34 @@ +// 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_CORE_COMMON_FORM_FIELD_DATA_PREDICTIONS_H_ +#define COMPONENTS_AUTOFILL_CORE_COMMON_FORM_FIELD_DATA_PREDICTIONS_H_ + +#include <string> +#include <vector> + +#include "components/autofill/core/common/form_field_data.h" + +namespace autofill { + +// Stores information about a field in a form. +struct FormFieldDataPredictions { + FormFieldDataPredictions(); + FormFieldDataPredictions(const FormFieldDataPredictions& other); + ~FormFieldDataPredictions(); + + FormFieldData field; + std::string signature; + std::string heuristic_type; + std::string server_type; + std::string overall_type; + + // Added for the sake of testing. + bool operator==(const FormFieldDataPredictions& predictions) const; + bool operator!=(const FormFieldDataPredictions& predictions) const; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_FORM_FIELD_DATA_PREDICTIONS_H_ diff --git a/chromium/components/autofill/core/common/forms_seen_state.h b/chromium/components/autofill/core/common/forms_seen_state.h new file mode 100644 index 00000000000..da417f96b54 --- /dev/null +++ b/chromium/components/autofill/core/common/forms_seen_state.h @@ -0,0 +1,24 @@ +// 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_COMMON_FORMS_SEEN_PARAM_H_ +#define COMPONENTS_AUTOFILL_COMMON_FORMS_SEEN_PARAM_H_ + +namespace autofill { + +// Specifies the nature of the forms triggering the call. +enum FormsSeenState { + // Forms present on page load with no additional unsent forms. + NO_SPECIAL_FORMS_SEEN = 0, + // If this an Autocheckout page, there are more that should be checked. + PARTIAL_FORMS_SEEN = 1, + // Forms were added dynamically post-page load. + DYNAMIC_FORMS_SEEN = 2, + // Number of states. + FORMS_SEEN_STATE_NUM_STATES = 3, +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_COMMON_FORMS_SEEN_PARAM_H_ diff --git a/chromium/components/autofill/core/common/password_form_fill_data.cc b/chromium/components/autofill/core/common/password_form_fill_data.cc new file mode 100644 index 00000000000..80b26989cce --- /dev/null +++ b/chromium/components/autofill/core/common/password_form_fill_data.cc @@ -0,0 +1,84 @@ +// 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/core/common/password_form_fill_data.h" + +#include "base/logging.h" +#include "base/strings/utf_string_conversions.h" +#include "components/autofill/core/common/form_field_data.h" + +namespace autofill { + +UsernamesCollectionKey::UsernamesCollectionKey() {} + +UsernamesCollectionKey::~UsernamesCollectionKey() {} + +bool UsernamesCollectionKey::operator<( + const UsernamesCollectionKey& other) const { + if (username != other.username) + return username < other.username; + if (password != other.password) + return password < other.password; + return realm < other.realm; +} + +PasswordFormFillData::PasswordFormFillData() : wait_for_username(false) { +} + +PasswordFormFillData::~PasswordFormFillData() { +} + +void InitPasswordFormFillData( + const content::PasswordForm& form_on_page, + const content::PasswordFormMap& matches, + const content::PasswordForm* const preferred_match, + bool wait_for_username_before_autofill, + bool enable_other_possible_usernames, + PasswordFormFillData* result) { + // Note that many of the |FormFieldData| members are not initialized for + // |username_field| and |password_field| because they are currently not used + // by the password autocomplete code. + FormFieldData username_field; + username_field.name = form_on_page.username_element; + username_field.value = preferred_match->username_value; + FormFieldData password_field; + password_field.name = form_on_page.password_element; + password_field.value = preferred_match->password_value; + password_field.form_control_type = "password"; + + // Fill basic form data. + result->basic_data.origin = form_on_page.origin; + result->basic_data.action = form_on_page.action; + result->basic_data.fields.push_back(username_field); + result->basic_data.fields.push_back(password_field); + result->wait_for_username = wait_for_username_before_autofill; + + result->preferred_realm = preferred_match->original_signon_realm; + + // Copy additional username/value pairs. + content::PasswordFormMap::const_iterator iter; + for (iter = matches.begin(); iter != matches.end(); iter++) { + if (iter->second != preferred_match) { + PasswordAndRealm value; + value.password = iter->second->password_value; + value.realm = iter->second->original_signon_realm; + result->additional_logins[iter->first] = value; + } + if (enable_other_possible_usernames && + !iter->second->other_possible_usernames.empty()) { + // Note that there may be overlap between other_possible_usernames and + // other saved usernames or with other other_possible_usernames. For now + // we will ignore this overlap as it should be a rare occurence. We may + // want to revisit this in the future. + UsernamesCollectionKey key; + key.username = iter->first; + key.password = iter->second->password_value; + key.realm = iter->second->original_signon_realm; + result->other_possible_usernames[key] = + iter->second->other_possible_usernames; + } + } +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/password_form_fill_data.h b/chromium/components/autofill/core/common/password_form_fill_data.h new file mode 100644 index 00000000000..9bcd5388d7a --- /dev/null +++ b/chromium/components/autofill/core/common/password_form_fill_data.h @@ -0,0 +1,87 @@ +// 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_CORE_COMMON_PASSWORD_FORM_FILL_DATA_H_ +#define COMPONENTS_AUTOFILL_CORE_COMMON_PASSWORD_FORM_FILL_DATA_H_ + +#include <map> + +#include "base/memory/scoped_ptr.h" +#include "components/autofill/core/common/form_data.h" +#include "content/public/common/password_form.h" + +namespace autofill { + +// Helper struct for PasswordFormFillData +struct UsernamesCollectionKey { + UsernamesCollectionKey(); + ~UsernamesCollectionKey(); + + // Defined so that this struct can be used as a key in a std::map. + bool operator<(const UsernamesCollectionKey& other) const; + + base::string16 username; + base::string16 password; + std::string realm; +}; + +struct PasswordAndRealm { + base::string16 password; + std::string realm; +}; + +// Structure used for autofilling password forms. Note that the realms in this +// struct are only set when the password's realm differs from the realm of the +// form that we are filling. +struct PasswordFormFillData { + typedef std::map<base::string16, PasswordAndRealm> LoginCollection; + typedef std::map<UsernamesCollectionKey, + std::vector<base::string16> > UsernamesCollection; + + // Identifies the HTML form on the page and preferred username/password for + // login. + FormData basic_data; + + // The signon realm of the preferred user/pass pair. + std::string preferred_realm; + + // A list of other matching username->PasswordAndRealm pairs for the form. + LoginCollection additional_logins; + + // A list of possible usernames in the case where we aren't completely sure + // that the original saved username is correct. This data is keyed by the + // saved username/password to ensure uniqueness, though the username is not + // used. + UsernamesCollection other_possible_usernames; + + // Tells us whether we need to wait for the user to enter a valid username + // before we autofill the password. By default, this is off unless the + // PasswordManager determined there is an additional risk associated with this + // form. This can happen, for example, if action URI's of the observed form + // and our saved representation don't match up. + bool wait_for_username; + + PasswordFormFillData(); + ~PasswordFormFillData(); +}; + +// Create a FillData structure in preparation for autofilling a form, +// from basic_data identifying which form to fill, and a collection of +// matching stored logins to use as username/password values. +// |preferred_match| should equal (address) one of matches. +// |wait_for_username_before_autofill| is true if we should not autofill +// anything until the user typed in a valid username and blurred the field. +// If |enable_possible_usernames| is true, we will populate possible_usernames +// in |result|. +void InitPasswordFormFillData( + const content::PasswordForm& form_on_page, + const content::PasswordFormMap& matches, + const content::PasswordForm* const preferred_match, + bool wait_for_username_before_autofill, + bool enable_other_possible_usernames, + PasswordFormFillData* result); + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_PASSWORD_FORM_FILL_DATA_H__ diff --git a/chromium/components/autofill/core/common/password_form_fill_data_unittest.cc b/chromium/components/autofill/core/common/password_form_fill_data_unittest.cc new file mode 100644 index 00000000000..1242774b12a --- /dev/null +++ b/chromium/components/autofill/core/common/password_form_fill_data_unittest.cc @@ -0,0 +1,171 @@ +// 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/core/common/password_form_fill_data.h" + +#include "base/strings/utf_string_conversions.h" +#include "content/public/common/password_form.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::PasswordForm; +using content::PasswordFormMap; + +namespace autofill { + +// Tests that the when there is a single preferred match, and no extra +// matches, the PasswordFormFillData is filled in correctly. +TEST(PasswordFormFillDataTest, TestSinglePreferredMatch) { + // Create the current form on the page. + PasswordForm form_on_page; + form_on_page.origin = GURL("https://foo.com/"); + form_on_page.action = GURL("https://foo.com/login"); + form_on_page.username_element = ASCIIToUTF16("username"); + form_on_page.username_value = ASCIIToUTF16("test@gmail.com"); + form_on_page.password_element = ASCIIToUTF16("password"); + form_on_page.password_value = ASCIIToUTF16("test"); + form_on_page.submit_element = ASCIIToUTF16(""); + form_on_page.signon_realm = "https://foo.com/"; + form_on_page.ssl_valid = true; + form_on_page.preferred = false; + form_on_page.scheme = PasswordForm::SCHEME_HTML; + + // Create an exact match in the database. + PasswordForm preferred_match; + preferred_match.origin = GURL("https://foo.com/"); + preferred_match.action = GURL("https://foo.com/login"); + preferred_match.username_element = ASCIIToUTF16("username"); + preferred_match.username_value = ASCIIToUTF16("test@gmail.com"); + preferred_match.password_element = ASCIIToUTF16("password"); + preferred_match.password_value = ASCIIToUTF16("test"); + preferred_match.submit_element = ASCIIToUTF16(""); + preferred_match.signon_realm = "https://foo.com/"; + preferred_match.ssl_valid = true; + preferred_match.preferred = true; + preferred_match.scheme = PasswordForm::SCHEME_HTML; + + PasswordFormMap matches; + + PasswordFormFillData result; + InitPasswordFormFillData(form_on_page, + matches, + &preferred_match, + true, + false, + &result); + + // |wait_for_username| should reflect the |wait_for_username_before_autofill| + // argument of InitPasswordFormFillData which in this case is true. + EXPECT_TRUE(result.wait_for_username); + // The preferred realm should be empty since it's the same as the realm of + // the form. + EXPECT_EQ(result.preferred_realm, ""); + + PasswordFormFillData result2; + InitPasswordFormFillData(form_on_page, + matches, + &preferred_match, + false, + false, + &result2); + + // |wait_for_username| should reflect the |wait_for_username_before_autofill| + // argument of InitPasswordFormFillData which in this case is false. + EXPECT_FALSE(result2.wait_for_username); +} + +// Tests that the InitPasswordFormFillData behaves correctly when there is a +// preferred match that was found using public suffix matching, an additional +// result that also used public suffix matching, and a third result that was +// found without using public suffix matching. +TEST(PasswordFormFillDataTest, TestPublicSuffixDomainMatching) { + // Create the current form on the page. + PasswordForm form_on_page; + form_on_page.origin = GURL("https://foo.com/"); + form_on_page.action = GURL("https://foo.com/login"); + form_on_page.username_element = ASCIIToUTF16("username"); + form_on_page.username_value = ASCIIToUTF16("test@gmail.com"); + form_on_page.password_element = ASCIIToUTF16("password"); + form_on_page.password_value = ASCIIToUTF16("test"); + form_on_page.submit_element = ASCIIToUTF16(""); + form_on_page.signon_realm = "https://foo.com/"; + form_on_page.ssl_valid = true; + form_on_page.preferred = false; + form_on_page.scheme = PasswordForm::SCHEME_HTML; + + // Create a match from the database that matches using public suffix. + PasswordForm preferred_match; + preferred_match.origin = GURL("https://mobile.foo.com/"); + preferred_match.action = GURL("https://mobile.foo.com/login"); + preferred_match.username_element = ASCIIToUTF16("username"); + preferred_match.username_value = ASCIIToUTF16("test@gmail.com"); + preferred_match.password_element = ASCIIToUTF16("password"); + preferred_match.password_value = ASCIIToUTF16("test"); + preferred_match.submit_element = ASCIIToUTF16(""); + preferred_match.signon_realm = "https://mobile.foo.com/"; + preferred_match.original_signon_realm = "https://foo.com/"; + preferred_match.ssl_valid = true; + preferred_match.preferred = true; + preferred_match.scheme = PasswordForm::SCHEME_HTML; + + // Create a match that matches exactly, so |original_signon_realm| is not set. + PasswordForm exact_match; + exact_match.origin = GURL("https://foo.com/"); + exact_match.action = GURL("https://foo.com/login"); + exact_match.username_element = ASCIIToUTF16("username"); + exact_match.username_value = ASCIIToUTF16("test1@gmail.com"); + exact_match.password_element = ASCIIToUTF16("password"); + exact_match.password_value = ASCIIToUTF16("test"); + exact_match.submit_element = ASCIIToUTF16(""); + exact_match.signon_realm = "https://foo.com/"; + exact_match.ssl_valid = true; + exact_match.preferred = false; + exact_match.scheme = PasswordForm::SCHEME_HTML; + + // Create a match that was matched using public suffix, so + // |original_signon_realm| is set to where the result came from. + PasswordForm public_suffix_match; + public_suffix_match.origin = GURL("https://foo.com/"); + public_suffix_match.action = GURL("https://foo.com/login"); + public_suffix_match.username_element = ASCIIToUTF16("username"); + public_suffix_match.username_value = ASCIIToUTF16("test2@gmail.com"); + public_suffix_match.password_element = ASCIIToUTF16("password"); + public_suffix_match.password_value = ASCIIToUTF16("test"); + public_suffix_match.submit_element = ASCIIToUTF16(""); + public_suffix_match.original_signon_realm = "https://subdomain.foo.com/"; + public_suffix_match.signon_realm = "https://foo.com/"; + public_suffix_match.ssl_valid = true; + public_suffix_match.preferred = false; + public_suffix_match.scheme = PasswordForm::SCHEME_HTML; + + // Add one exact match and one public suffix match. + PasswordFormMap matches; + matches[exact_match.username_value] = &exact_match; + matches[public_suffix_match.username_value] = &public_suffix_match; + + PasswordFormFillData result; + InitPasswordFormFillData(form_on_page, + matches, + &preferred_match, + true, + false, + &result); + EXPECT_TRUE(result.wait_for_username); + // The preferred realm should match the original signon realm from the + // preferred match so the user can see where the result came from. + EXPECT_EQ(result.preferred_realm, + preferred_match.original_signon_realm); + + // The realm of the exact match should be empty. + PasswordFormFillData::LoginCollection::const_iterator iter = + result.additional_logins.find(exact_match.username_value); + EXPECT_EQ(iter->second.realm, ""); + + // The realm of the public suffix match should be set to the original signon + // realm so the user can see where the result came from. + iter = result.additional_logins.find(public_suffix_match.username_value); + EXPECT_EQ(iter->second.realm, public_suffix_match.original_signon_realm); +} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/password_generation_util.cc b/chromium/components/autofill/core/common/password_generation_util.cc new file mode 100644 index 00000000000..15a3c4c1188 --- /dev/null +++ b/chromium/components/autofill/core/common/password_generation_util.cc @@ -0,0 +1,42 @@ +// 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/core/common/password_generation_util.h" + +#include "base/metrics/histogram.h" + +namespace autofill { +namespace password_generation { + +PasswordGenerationActions::PasswordGenerationActions() + : learn_more_visited(false), + password_accepted(false), + password_edited(false), + password_regenerated(false) { +} + +PasswordGenerationActions::~PasswordGenerationActions() { +} + +void LogUserActions(PasswordGenerationActions actions) { + UserAction action = IGNORE_FEATURE; + if (actions.password_accepted) { + if (actions.password_edited) + action = ACCEPT_AFTER_EDITING; + else + action = ACCEPT_ORIGINAL_PASSWORD; + } else if (actions.learn_more_visited) { + action = LEARN_MORE; + } + UMA_HISTOGRAM_ENUMERATION("PasswordGeneration.UserActions", + action, ACTION_ENUM_COUNT); +} + +void LogPasswordGenerationEvent(PasswordGenerationEvent event) { + UMA_HISTOGRAM_ENUMERATION("PasswordGeneration.Event", + event, EVENT_ENUM_COUNT); +} + +} // namespace password_generation +} // namespace autofill diff --git a/chromium/components/autofill/core/common/password_generation_util.h b/chromium/components/autofill/core/common/password_generation_util.h new file mode 100644 index 00000000000..f3f47b398a7 --- /dev/null +++ b/chromium/components/autofill/core/common/password_generation_util.h @@ -0,0 +1,77 @@ +// 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_CORE_COMMON_PASSWORD_GENERATION_UTIL_H_ +#define COMPONENTS_AUTOFILL_CORE_COMMON_PASSWORD_GENERATION_UTIL_H_ + +namespace autofill { +namespace password_generation { + +// Enumerates various events related to the password generation process. +enum PasswordGenerationEvent { + // No Account creation form is detected. + NO_SIGN_UP_DETECTED, + + // Account creation form is detected. + SIGN_UP_DETECTED, + + // Password generation icon is shown inside the first password field. + ICON_SHOWN, + + // Password generation bubble is shown after user clicks on the icon. + BUBBLE_SHOWN, + + // Number of enum entries, used for UMA histogram reporting macros. + EVENT_ENUM_COUNT +}; + +// Wrapper to store the user interactions with the password generation bubble. +struct PasswordGenerationActions { + // Whether the user has clicked on the learn more link. + bool learn_more_visited; + + // Whether the user has accepted the generated password. + bool password_accepted; + + // Whether the user has manually edited password entry. + bool password_edited; + + // Whether the user has clicked on the regenerate button. + bool password_regenerated; + + PasswordGenerationActions(); + ~PasswordGenerationActions(); +}; + +void LogUserActions(PasswordGenerationActions actions); + +void LogPasswordGenerationEvent(PasswordGenerationEvent event); + +// Enumerates user actions after password generation bubble is shown. +// These are visible for testing purposes. +enum UserAction { + // User closes the bubble without any meaningful actions (e.g. use backspace + // key, close the bubble, click outside the bubble, etc). + IGNORE_FEATURE, + + // User navigates to the learn more page. Note that in the current + // implementation this will result in closing the bubble so this action + // doesn't overlap with the following two actions. + LEARN_MORE, + + // User accepts the generated password without manually editing it (but + // including changing it through the regenerate button). + ACCEPT_ORIGINAL_PASSWORD, + + // User accepts the gererated password after manually editing it. + ACCEPT_AFTER_EDITING, + + // Number of enum entries, used for UMA histogram reporting macros. + ACTION_ENUM_COUNT +}; + +} // namespace password_generation +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_PASSWORD_GENERATION_UTIL_H_ diff --git a/chromium/components/autofill/core/common/web_element_descriptor.cc b/chromium/components/autofill/core/common/web_element_descriptor.cc new file mode 100644 index 00000000000..ffb5c05e27f --- /dev/null +++ b/chromium/components/autofill/core/common/web_element_descriptor.cc @@ -0,0 +1,12 @@ +// 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/core/common/web_element_descriptor.h" + +namespace autofill { + +autofill::WebElementDescriptor::WebElementDescriptor() + : retrieval_method(NONE) {} + +} // namespace autofill diff --git a/chromium/components/autofill/core/common/web_element_descriptor.h b/chromium/components/autofill/core/common/web_element_descriptor.h new file mode 100644 index 00000000000..6f41cbb5d73 --- /dev/null +++ b/chromium/components/autofill/core/common/web_element_descriptor.h @@ -0,0 +1,31 @@ +// 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_CORE_COMMON_WEB_ELEMENT_DESCRIPTOR_H_ +#define COMPONENTS_AUTOFILL_CORE_COMMON_WEB_ELEMENT_DESCRIPTOR_H_ + +#include <string> + +namespace autofill { + +// Holds information that can be used to retrieve an element. +struct WebElementDescriptor { + enum RetrievalMethod { + CSS_SELECTOR, + ID, + NONE, + }; + + WebElementDescriptor(); + + // Information to retrieve element with. + std::string descriptor; + + // Which retrieval method to use. + RetrievalMethod retrieval_method; +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_COMMON_WEB_ELEMENT_DESCRIPTOR_H_ diff --git a/chromium/components/autofill_strings.grdp b/chromium/components/autofill_strings.grdp new file mode 100644 index 00000000000..16fe9390d27 --- /dev/null +++ b/chromium/components/autofill_strings.grdp @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="utf-8"?> +<grit-part> + + <message name="IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM" desc="The entry in the suggestions dropdown that clears an auto-filled form."> + Clear form + </message> + <message name="IDS_AUTOFILL_WARNING_FORM_DISABLED" desc="Warning text to show when autofill is disabled by the website for a given form."> + This webpage has disabled automatic filling for this form. + </message> + <message name="IDS_AUTOFILL_WARNING_INSECURE_CONNECTION" desc="Warning text to show when credit card autofill is disabled because the website is not using a secure connection."> + Automatic credit card filling is disabled because this form does not use a secure connection. + </message> + <message name="IDS_AUTOFILL_CC_AMEX" desc="American Express credit card name."> + American Express + </message> + <message name="IDS_AUTOFILL_CC_DINERS" desc="Diners Club credit card name."> + Diners Club + </message> + <message name="IDS_AUTOFILL_CC_DISCOVER" desc="Discover credit card name."> + Discover + </message> + <message name="IDS_AUTOFILL_CC_JCB" desc="JCB credit card name."> + JCB + </message> + <message name="IDS_AUTOFILL_CC_MASTERCARD" desc="MasterCard credit card name."> + MasterCard + </message> + <message name="IDS_AUTOFILL_CC_UNION_PAY" desc="China UnionPay credit card name."> + China UnionPay + </message> + <message name="IDS_AUTOFILL_CC_VISA" desc="Visa credit card name."> + Visa + </message> + <message name="IDS_AUTOFILL_MAC_ADDRESS_LINE_SEPARATOR" desc="The separator character used to join multi-line addresses on the Mac."> + , ''' + </message> + <message name="IDS_AUTOFILL_ADDRESS_SUMMARY_SEPARATOR" desc="The separator character used in the summary of an address."> + , ''' + </message> + <message name="IDS_CREDIT_CARD_NUMBER_PREVIEW_FORMAT" desc="Credit card preview format"> + <ph name="OBFUSCATED_CC_NUMBER">$1<ex>************1234</ex> + </ph>, Exp: <ph name="CC_EXPIRATION_DATE">$2<ex>03/2020</ex> + </ph> + </message> + + <!-- These are all variants of the same logical field: The major subdivision below the "country" level. --> + <message name="IDS_AUTOFILL_FIELD_LABEL_STATE" desc="The label of the State entry."> + State + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_AREA" desc="The label of the Area entry."> + Area + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_COUNTY" desc="The label of the County entry."> + County + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_DEPARTMENT" desc="The label of the Department entry."> + Department + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_DISTRICT" desc="The label of the District entry."> + District + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_EMIRATE" desc="The label of the Emirate entry."> + Emirate + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_ISLAND" desc="The label of the Island entry."> + Island + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_PARISH" desc="The label of the Parish entry."> + Parish + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_PREFECTURE" desc="The label of the Prefecture entry."> + Prefecture + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_PROVINCE" desc="The label of the Province entry."> + Province + </message> + <!-- End state variants. --> + + <message name="IDS_AUTOFILL_FIELD_LABEL_ZIP_CODE" desc="The label of the ZIP code entry."> + ZIP code + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_POSTAL_CODE" desc="The label of the Postal code entry."> + Postal code + </message> + <message name="IDS_AUTOFILL_FIELD_LABEL_COUNTRY" desc="The label of the Country entry."> + Country + </message> + + <message name="IDS_AUTOFILL_SHOW_PREDICTIONS_TITLE" desc="The title for form elements when annotated with Autofill predictions."> + heuristic type: <ph name="HEURISTIC_TYPE">$1<ex>NAME_FIRST</ex></ph> + server type: <ph name="SERVER_TYPE">$2<ex>NAME_FIRST</ex></ph> + field signature: <ph name="FIELD_SIGNATURE">$3<ex>12345678</ex></ph> + form signature: <ph name="FORM_SIGNATURE">$4<ex>1234567812345678</ex></ph> + experiment id: "<ph name="EXPERIMENT_ID">$5<ex>ar1</ex></ph>" + </message> + + <!-- Autofill dialog: legal documents --> + <message name="IDS_AUTOFILL_DIALOG_PRIVACY_POLICY_LINK" desc="The text of an extra link that is appended to the end of whatever other legal documents need to be accepted or updated."> + Privacy Policy + </message> + + <message name="IDS_AUTOFILL_OPTIONS_POPUP" desc="The value of the Autofill options menu entry."> + Chrome Autofill settings + </message> + +</grit-part> diff --git a/chromium/components/breakpad.gypi b/chromium/components/breakpad.gypi new file mode 100644 index 00000000000..c8938709265 --- /dev/null +++ b/chromium/components/breakpad.gypi @@ -0,0 +1,68 @@ +# 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. + +{ + 'target_defaults': { + 'variables': { + 'breakpad_component_target': 0, + }, + 'target_conditions': [ + ['breakpad_component_target==1', { + 'sources': [ + 'breakpad/breakpad_client.cc', + 'breakpad/breakpad_client.h', + ], + }], + ], + }, + 'targets': [ + { + 'target_name': 'breakpad_component', + 'type': 'static_library', + 'variables': { + 'breakpad_component_target': 1, + }, + 'dependencies': [ + '../base/base.gyp:base', + ], + }, + ], + 'conditions': [ + ['OS=="win" and target_arch=="ia32"', { + 'targets': [ + { + 'target_name': 'breakpad_win64', + 'type': 'static_library', + 'variables': { + 'breakpad_component_target': 1, + }, + 'dependencies': [ + '../base/base.gyp:base_nacl_win64', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + }, + ], + }], + ['OS=="mac"', { + 'targets': [ + { + # TODO(jochen): for now, this target is a copy of breakpad, however, + # in the future, it should provide a dummy implementation for Mac. + 'target_name': 'breakpad_stubs', + 'type': 'static_library', + 'variables': { + 'breakpad_component_target': 1, + }, + 'dependencies': [ + '../base/base.gyp:base', + ], + }, + ], + }], + ], +} diff --git a/chromium/components/breakpad/OWNERS b/chromium/components/breakpad/OWNERS new file mode 100644 index 00000000000..cf2020f2a0d --- /dev/null +++ b/chromium/components/breakpad/OWNERS @@ -0,0 +1,4 @@ +cpu@chromium.org +jochen@chromium.org +rsesek@chromium.org +thestig@chromum.org diff --git a/chromium/components/breakpad/breakpad_client.cc b/chromium/components/breakpad/breakpad_client.cc new file mode 100644 index 00000000000..a4c8c45562c --- /dev/null +++ b/chromium/components/breakpad/breakpad_client.cc @@ -0,0 +1,118 @@ +// 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/breakpad/breakpad_client.h" + +#include "base/files/file_path.h" +#include "base/logging.h" + +namespace breakpad { + +namespace { + +BreakpadClient* g_client = NULL; + +} // namespace + +void SetBreakpadClient(BreakpadClient* client) { + g_client = client; +} + +BreakpadClient* GetBreakpadClient() { + DCHECK(g_client); + return g_client; +} + +BreakpadClient::BreakpadClient() {} +BreakpadClient::~BreakpadClient() {} + +#if defined(OS_WIN) +bool BreakpadClient::GetAlternativeCrashDumpLocation( + base::FilePath* crash_dir) { + return false; +} + +void BreakpadClient::GetProductNameAndVersion(const base::FilePath& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name) { +} + +bool BreakpadClient::ShouldShowRestartDialog(base::string16* title, + base::string16* message, + bool* is_rtl_locale) { + return false; +} + +bool BreakpadClient::AboutToRestart() { + return true; +} + +base::string16 BreakpadClient::GetCrashGUID() { + return base::string16(); +} + +bool BreakpadClient::GetDeferredUploadsSupported(bool is_per_usr_install) { + return false; +} + +bool BreakpadClient::GetIsPerUserInstall(const base::FilePath& exe_path) { + return false; +} + +bool BreakpadClient::GetShouldDumpLargerDumps(bool is_per_user_install) { + return false; +} + +int BreakpadClient::GetResultCodeRespawnFailed() { + return 0; +} +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) +void BreakpadClient::GetProductNameAndVersion(std::string* product_name, + std::string* version) { +} + +base::FilePath BreakpadClient::GetReporterLogFilename() { + return base::FilePath(); +} +#endif + +bool BreakpadClient::GetCrashDumpLocation(base::FilePath* crash_dir) { + return false; +} + +#if defined(OS_POSIX) +void BreakpadClient::SetDumpWithoutCrashingFunction(void (*function)()) { +} +#endif + +size_t BreakpadClient::RegisterCrashKeys() { + return 0; +} + +bool BreakpadClient::IsRunningUnattended() { + return false; +} + +#if defined(OS_WIN) || defined(OS_MACOSX) +bool BreakpadClient::GetCollectStatsConsent() { + return false; +} +#endif + +#if defined(OS_ANDROID) +int BreakpadClient::GetAndroidMinidumpDescriptor() { + return 0; +} +#endif + +#if defined(OS_MACOSX) +void BreakpadClient::InstallAdditionalFilters(BreakpadRef breakpad) { +} +#endif + +} // namespace breakpad diff --git a/chromium/components/breakpad/breakpad_client.h b/chromium/components/breakpad/breakpad_client.h new file mode 100644 index 00000000000..35b430807ba --- /dev/null +++ b/chromium/components/breakpad/breakpad_client.h @@ -0,0 +1,131 @@ +// 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_BREAKPAD_BREAKPAD_CLIENT_H_ +#define COMPONENTS_BREAKPAD_BREAKPAD_CLIENT_H_ + +#include <string> + +#include "base/strings/string16.h" +#include "build/build_config.h" + +namespace base { +class FilePath; +} + +#if defined(OS_MACOSX) +// We don't want to directly include +// breakpad/src/client/mac/Framework/Breakpad.h here, so we repeat the +// definition of BreakpadRef. +// +// On Mac, when compiling without breakpad support, a stub implementation is +// compiled in. Not having any includes of the breakpad library allows for +// reusing this header for the stub. +typedef void* BreakpadRef; +#endif + +namespace breakpad { + +class BreakpadClient; + +// Setter and getter for the client. The client should be set early, before any +// breakpad code is called, and should stay alive throughout the entire runtime. +void SetBreakpadClient(BreakpadClient* client); + +// Breakpad's embedder API should only be used by breakpad. +BreakpadClient* GetBreakpadClient(); + +// Interface that the embedder implements. +class BreakpadClient { + public: + BreakpadClient(); + virtual ~BreakpadClient(); + +#if defined(OS_WIN) + // Returns true if an alternative location to store the minidump files was + // specified. Returns true if |crash_dir| was set. + virtual bool GetAlternativeCrashDumpLocation(base::FilePath* crash_dir); + + // Returns a textual description of the product type and version to include + // in the crash report. + virtual void GetProductNameAndVersion(const base::FilePath& exe_path, + base::string16* product_name, + base::string16* version, + base::string16* special_build, + base::string16* channel_name); + + // Returns true if a restart dialog should be displayed. In that case, + // |message| and |title| are set to a message to display in a dialog box with + // the given title before restarting, and |is_rtl_locale| indicates whether + // to display the text as RTL. + virtual bool ShouldShowRestartDialog(base::string16* title, + base::string16* message, + bool* is_rtl_locale); + + // Returns true if it is ok to restart the application. Invoked right before + // restarting after a crash. + virtual bool AboutToRestart(); + + // Returns a GUID to embed in the crash report. + virtual base::string16 GetCrashGUID(); + + // Returns true if the crash report uploader supports deferred uploads. + virtual bool GetDeferredUploadsSupported(bool is_per_user_install); + + // Returns true if the running binary is a per-user installation. + virtual bool GetIsPerUserInstall(const base::FilePath& exe_path); + + // Returns true if larger crash dumps should be dumped. + virtual bool GetShouldDumpLargerDumps(bool is_per_user_install); + + // Returns the result code to return when breakpad failed to respawn a + // crashed process. + virtual int GetResultCodeRespawnFailed(); +#endif + +#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_IOS) + // Returns a textual description of the product type and version to include + // in the crash report. + virtual void GetProductNameAndVersion(std::string* product_name, + std::string* version); + + virtual base::FilePath GetReporterLogFilename(); +#endif + + // The location where minidump files should be written. Returns true if + // |crash_dir| was set. + virtual bool GetCrashDumpLocation(base::FilePath* crash_dir); + +#if defined(OS_POSIX) + // Sets a function that'll be invoked to dump the current process when + // without crashing. + virtual void SetDumpWithoutCrashingFunction(void (*function)()); +#endif + + // Register all of the potential crash keys that can be sent to the crash + // reporting server. Returns the size of the union of all keys. + virtual size_t RegisterCrashKeys(); + + // Returns true if running in unattended mode (for automated testing). + virtual bool IsRunningUnattended(); + +#if defined(OS_WIN) || defined(OS_MACOSX) + // Returns true if the user has given consent to collect stats. + virtual bool GetCollectStatsConsent(); +#endif + +#if defined(OS_ANDROID) + // Returns the descriptor key of the android minidump global descriptor. + virtual int GetAndroidMinidumpDescriptor(); +#endif + +#if defined(OS_MACOSX) + // Install additional breakpad filter callbacks. + virtual void InstallAdditionalFilters(BreakpadRef breakpad); +#endif +}; + +} // namespace breakpad + +#endif // COMPONENTS_BREAKPAD_BREAKPAD_CLIENT_H_ diff --git a/chromium/components/browser_context_keyed_service.gypi b/chromium/components/browser_context_keyed_service.gypi new file mode 100644 index 00000000000..f9cbee0c4bc --- /dev/null +++ b/chromium/components/browser_context_keyed_service.gypi @@ -0,0 +1,44 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'browser_context_keyed_service', + 'type': '<(component)', + 'defines': [ + 'BROWSER_CONTEXT_KEYED_SERVICE_IMPLEMENTATION', + ], + 'include_dirs': [ + '..', + ], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [ 4267, ], + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:base_prefs', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../content/content.gyp:content_common', + 'user_prefs', + ], + 'sources': [ + 'browser_context_keyed_service/browser_context_keyed_service_export.h', + 'browser_context_keyed_service/browser_context_dependency_manager.cc', + 'browser_context_keyed_service/browser_context_dependency_manager.h', + 'browser_context_keyed_service/browser_context_keyed_base_factory.h', + 'browser_context_keyed_service/browser_context_keyed_base_factory.cc', + 'browser_context_keyed_service/browser_context_keyed_service.h', + 'browser_context_keyed_service/browser_context_keyed_service_factory.cc', + 'browser_context_keyed_service/browser_context_keyed_service_factory.h', + 'browser_context_keyed_service/dependency_graph.cc', + 'browser_context_keyed_service/dependency_graph.h', + 'browser_context_keyed_service/dependency_node.h', + 'browser_context_keyed_service/refcounted_browser_context_keyed_service.cc', + 'browser_context_keyed_service/refcounted_browser_context_keyed_service.h', + 'browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.cc', + 'browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.h', + ], + }, + ], +} diff --git a/chromium/components/browser_context_keyed_service/DEPS b/chromium/components/browser_context_keyed_service/DEPS new file mode 100644 index 00000000000..1c35d9ca694 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+content/public/browser", +] diff --git a/chromium/components/browser_context_keyed_service/OWNERS b/chromium/components/browser_context_keyed_service/OWNERS new file mode 100644 index 00000000000..0a6b6d0a421 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/OWNERS @@ -0,0 +1,4 @@ +erg@chromium.org +mirandac@chromium.org +phajdan.jr@chromium.org +rlp@chromium.org diff --git a/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.cc b/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.cc new file mode 100644 index 00000000000..1031f4e8310 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.cc @@ -0,0 +1,159 @@ +// 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/browser_context_keyed_service/browser_context_dependency_manager.h" + +#include <algorithm> +#include <deque> +#include <iterator> + +#include "base/bind.h" +#include "base/debug/trace_event.h" +#include "components/browser_context_keyed_service/browser_context_keyed_base_factory.h" +#include "content/public/browser/browser_context.h" + +#ifndef NDEBUG +#include "base/command_line.h" +#include "base/file_util.h" + +// Dumps dependency information about our browser context keyed services +// into a dot file in the browser context directory. +const char kDumpBrowserContextDependencyGraphFlag[] = + "dump-browser-context-graph"; +#endif // NDEBUG + +void BrowserContextDependencyManager::AddComponent( + BrowserContextKeyedBaseFactory* component) { + dependency_graph_.AddNode(component); +} + +void BrowserContextDependencyManager::RemoveComponent( + BrowserContextKeyedBaseFactory* component) { + dependency_graph_.RemoveNode(component); +} + +void BrowserContextDependencyManager::AddEdge( + BrowserContextKeyedBaseFactory* depended, + BrowserContextKeyedBaseFactory* dependee) { + dependency_graph_.AddEdge(depended, dependee); +} + +void BrowserContextDependencyManager::CreateBrowserContextServices( + content::BrowserContext* context, bool is_testing_context) { + TRACE_EVENT0("browser", + "BrowserContextDependencyManager::CreateBrowserContextServices") +#ifndef NDEBUG + // Unmark |context| as dead. This exists because of unit tests, which will + // often have similar stack structures. 0xWhatever might be created, go out + // of scope, and then a new BrowserContext object might be created + // at 0xWhatever. + dead_context_pointers_.erase(context); +#endif + + std::vector<DependencyNode*> construction_order; + if (!dependency_graph_.GetConstructionOrder(&construction_order)) { + NOTREACHED(); + } + +#ifndef NDEBUG + DumpBrowserContextDependencies(context); +#endif + + for (size_t i = 0; i < construction_order.size(); i++) { + BrowserContextKeyedBaseFactory* factory = + static_cast<BrowserContextKeyedBaseFactory*>(construction_order[i]); + + if (!context->IsOffTheRecord()) { + // We only register preferences on normal contexts because the incognito + // context shares the pref service with the normal one. + factory->RegisterUserPrefsOnBrowserContext(context); + } + + if (is_testing_context && factory->ServiceIsNULLWhileTesting()) { + factory->SetEmptyTestingFactory(context); + } else if (factory->ServiceIsCreatedWithBrowserContext()) { + // Create the service. + factory->CreateServiceNow(context); + } + } +} + +void BrowserContextDependencyManager::DestroyBrowserContextServices( + content::BrowserContext* context) { + std::vector<DependencyNode*> destruction_order; + if (!dependency_graph_.GetDestructionOrder(&destruction_order)) { + NOTREACHED(); + } + +#ifndef NDEBUG + DumpBrowserContextDependencies(context); +#endif + + for (size_t i = 0; i < destruction_order.size(); i++) { + BrowserContextKeyedBaseFactory* factory = + static_cast<BrowserContextKeyedBaseFactory*>(destruction_order[i]); + factory->BrowserContextShutdown(context); + } + +#ifndef NDEBUG + // The context is now dead to the rest of the program. + dead_context_pointers_.insert(context); +#endif + + for (size_t i = 0; i < destruction_order.size(); i++) { + BrowserContextKeyedBaseFactory* factory = + static_cast<BrowserContextKeyedBaseFactory*>(destruction_order[i]); + factory->BrowserContextDestroyed(context); + } +} + +#ifndef NDEBUG +void BrowserContextDependencyManager::AssertBrowserContextWasntDestroyed( + content::BrowserContext* context) { + if (dead_context_pointers_.find(context) != dead_context_pointers_.end()) { + NOTREACHED() << "Attempted to access a BrowserContext that was ShutDown(). " + << "This is most likely a heap smasher in progress. After " + << "BrowserContextKeyedService::Shutdown() completes, your " + << "service MUST NOT refer to depended BrowserContext " + << "services again."; + } +} +#endif + +// static +BrowserContextDependencyManager* +BrowserContextDependencyManager::GetInstance() { + return Singleton<BrowserContextDependencyManager>::get(); +} + +BrowserContextDependencyManager::BrowserContextDependencyManager() { +} + +BrowserContextDependencyManager::~BrowserContextDependencyManager() { +} + +#ifndef NDEBUG +namespace { + +std::string BrowserContextKeyedBaseFactoryGetNodeName(DependencyNode* node) { + return static_cast<BrowserContextKeyedBaseFactory*>(node)->name(); +} + +} // namespace + +void BrowserContextDependencyManager::DumpBrowserContextDependencies( + content::BrowserContext* context) { + // Whenever we try to build a destruction ordering, we should also dump a + // dependency graph to "/path/to/context/context-dependencies.dot". + if (CommandLine::ForCurrentProcess()->HasSwitch( + kDumpBrowserContextDependencyGraphFlag)) { + base::FilePath dot_file = + context->GetPath().AppendASCII("browser-context-dependencies.dot"); + std::string contents = dependency_graph_.DumpAsGraphviz( + "BrowserContext", + base::Bind(&BrowserContextKeyedBaseFactoryGetNodeName)); + file_util::WriteFile(dot_file, contents.c_str(), contents.size()); + } +} +#endif // NDEBUG diff --git a/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.h b/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.h new file mode 100644 index 00000000000..3e96eff98b5 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/browser_context_dependency_manager.h @@ -0,0 +1,79 @@ +// 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_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_DEPENDENCY_MANAGER_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_DEPENDENCY_MANAGER_H_ + +#include "base/memory/singleton.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_export.h" +#include "components/browser_context_keyed_service/dependency_graph.h" + +#ifndef NDEBUG +#include <set> +#endif + +class BrowserContextKeyedBaseFactory; + +namespace content { +class BrowserContext; +} + +// A singleton that listens for context destruction notifications and +// rebroadcasts them to each BrowserContextKeyedBaseFactory in a safe order +// based on the stated dependencies by each service. +class BROWSER_CONTEXT_KEYED_SERVICE_EXPORT BrowserContextDependencyManager { + public: + // Adds/Removes a component from our list of live components. Removing will + // also remove live dependency links. + void AddComponent(BrowserContextKeyedBaseFactory* component); + void RemoveComponent(BrowserContextKeyedBaseFactory* component); + + // Adds a dependency between two factories. + void AddEdge(BrowserContextKeyedBaseFactory* depended, + BrowserContextKeyedBaseFactory* dependee); + + // Called by each BrowserContext to alert us of its creation. Several services + // want to be started when a context is created. Testing configuration is also + // done at this time. (If you want your BrowserContextKeyedService to be + // started with the BrowserContext, override BrowserContextKeyedBaseFactory:: + // ServiceIsCreatedWithBrowserContext() to return true.) + void CreateBrowserContextServices(content::BrowserContext* context, + bool is_testing_context); + + // Called by each BrowserContext to alert us that we should destroy services + // associated with it. + void DestroyBrowserContextServices(content::BrowserContext* context); + +#ifndef NDEBUG + // Debugging assertion called as part of GetServiceForBrowserContext in debug + // mode. This will NOTREACHED() whenever the user is trying to access a stale + // BrowserContext*. + void AssertBrowserContextWasntDestroyed(content::BrowserContext* context); +#endif + + static BrowserContextDependencyManager* GetInstance(); + + private: + friend class BrowserContextDependencyManagerUnittests; + friend struct DefaultSingletonTraits<BrowserContextDependencyManager>; + + BrowserContextDependencyManager(); + virtual ~BrowserContextDependencyManager(); + +#ifndef NDEBUG + void DumpBrowserContextDependencies(content::BrowserContext* context); +#endif + + DependencyGraph dependency_graph_; + +#ifndef NDEBUG + // A list of context objects that have gone through the Shutdown() + // phase. These pointers are most likely invalid, but we keep track of their + // locations in memory so we can nicely assert if we're asked to do anything + // with them. + std::set<content::BrowserContext*> dead_context_pointers_; +#endif +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_DEPENDENCY_MANAGER_H_ diff --git a/chromium/components/browser_context_keyed_service/browser_context_dependency_manager_unittest.cc b/chromium/components/browser_context_keyed_service/browser_context_dependency_manager_unittest.cc new file mode 100644 index 00000000000..e55294182d4 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/browser_context_dependency_manager_unittest.cc @@ -0,0 +1,174 @@ +// 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 "testing/gtest/include/gtest/gtest.h" + +#include "components/browser_context_keyed_service/browser_context_dependency_manager.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h" + +class BrowserContextDependencyManagerUnittests : public ::testing::Test { + protected: + // To get around class access: + void DependOn(BrowserContextKeyedServiceFactory* child, + BrowserContextKeyedServiceFactory* parent) { + child->DependsOn(parent); + } + + BrowserContextDependencyManager* manager() { return &dependency_manager_; } + + std::vector<std::string>* shutdown_order() { return &shutdown_order_; } + + private: + BrowserContextDependencyManager dependency_manager_; + + std::vector<std::string> shutdown_order_; +}; + +class TestService : public BrowserContextKeyedServiceFactory { + public: + TestService(const std::string& name, + std::vector<std::string>* fill_on_shutdown, + BrowserContextDependencyManager* manager) + : BrowserContextKeyedServiceFactory("TestService", manager), + name_(name), + fill_on_shutdown_(fill_on_shutdown) { + } + + virtual BrowserContextKeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const OVERRIDE { + ADD_FAILURE() << "This isn't part of the tests!"; + return NULL; + } + + virtual void BrowserContextShutdown( + content::BrowserContext* context) OVERRIDE { + fill_on_shutdown_->push_back(name_); + } + + private: + const std::string name_; + std::vector<std::string>* fill_on_shutdown_; +}; + +// Tests that we can deal with a single component. +TEST_F(BrowserContextDependencyManagerUnittests, SingleCase) { + TestService service("service", shutdown_order(), manager()); + + manager()->DestroyBrowserContextServices(NULL); + + ASSERT_EQ(1U, shutdown_order()->size()); + EXPECT_STREQ("service", (*shutdown_order())[0].c_str()); +} + +// Tests that we get a simple one component depends on the other case. +TEST_F(BrowserContextDependencyManagerUnittests, SimpleDependency) { + TestService parent("parent", shutdown_order(), manager()); + TestService child("child", shutdown_order(), manager()); + DependOn(&child, &parent); + + manager()->DestroyBrowserContextServices(NULL); + + ASSERT_EQ(2U, shutdown_order()->size()); + EXPECT_STREQ("child", (*shutdown_order())[0].c_str()); + EXPECT_STREQ("parent", (*shutdown_order())[1].c_str()); +} + +// Tests two children, one parent +TEST_F(BrowserContextDependencyManagerUnittests, TwoChildrenOneParent) { + TestService parent("parent", shutdown_order(), manager()); + TestService child1("child1", shutdown_order(), manager()); + TestService child2("child2", shutdown_order(), manager()); + DependOn(&child1, &parent); + DependOn(&child2, &parent); + + manager()->DestroyBrowserContextServices(NULL); + + ASSERT_EQ(3U, shutdown_order()->size()); + EXPECT_STREQ("child2", (*shutdown_order())[0].c_str()); + EXPECT_STREQ("child1", (*shutdown_order())[1].c_str()); + EXPECT_STREQ("parent", (*shutdown_order())[2].c_str()); +} + +// Tests an M configuration +TEST_F(BrowserContextDependencyManagerUnittests, MConfiguration) { + TestService parent1("parent1", shutdown_order(), manager()); + TestService parent2("parent2", shutdown_order(), manager()); + + TestService child_of_1("child_of_1", shutdown_order(), manager()); + DependOn(&child_of_1, &parent1); + + TestService child_of_12("child_of_12", shutdown_order(), manager()); + DependOn(&child_of_12, &parent1); + DependOn(&child_of_12, &parent2); + + TestService child_of_2("child_of_2", shutdown_order(), manager()); + DependOn(&child_of_2, &parent2); + + manager()->DestroyBrowserContextServices(NULL); + + ASSERT_EQ(5U, shutdown_order()->size()); + EXPECT_STREQ("child_of_2", (*shutdown_order())[0].c_str()); + EXPECT_STREQ("child_of_12", (*shutdown_order())[1].c_str()); + EXPECT_STREQ("child_of_1", (*shutdown_order())[2].c_str()); + EXPECT_STREQ("parent2", (*shutdown_order())[3].c_str()); + EXPECT_STREQ("parent1", (*shutdown_order())[4].c_str()); +} + +// Tests that it can deal with a simple diamond. +TEST_F(BrowserContextDependencyManagerUnittests, DiamondConfiguration) { + TestService parent("parent", shutdown_order(), manager()); + + TestService middle_row_1("middle_row_1", shutdown_order(), manager()); + DependOn(&middle_row_1, &parent); + + TestService middle_row_2("middle_row_2", shutdown_order(), manager()); + DependOn(&middle_row_2, &parent); + + TestService bottom("bottom", shutdown_order(), manager()); + DependOn(&bottom, &middle_row_1); + DependOn(&bottom, &middle_row_2); + + manager()->DestroyBrowserContextServices(NULL); + + ASSERT_EQ(4U, shutdown_order()->size()); + EXPECT_STREQ("bottom", (*shutdown_order())[0].c_str()); + EXPECT_STREQ("middle_row_2", (*shutdown_order())[1].c_str()); + EXPECT_STREQ("middle_row_1", (*shutdown_order())[2].c_str()); + EXPECT_STREQ("parent", (*shutdown_order())[3].c_str()); +} + +// A final test that works with a more complex graph. +TEST_F(BrowserContextDependencyManagerUnittests, ComplexGraph) { + TestService everything_depends_on_me("everything_depends_on_me", + shutdown_order(), manager()); + + TestService intermediary_service("intermediary_service", + shutdown_order(), manager()); + DependOn(&intermediary_service, &everything_depends_on_me); + + TestService specialized_service("specialized_service", + shutdown_order(), manager()); + DependOn(&specialized_service, &everything_depends_on_me); + DependOn(&specialized_service, &intermediary_service); + + TestService other_root("other_root", shutdown_order(), manager()); + + TestService other_intermediary("other_intermediary", + shutdown_order(), manager()); + DependOn(&other_intermediary, &other_root); + + TestService bottom("bottom", shutdown_order(), manager()); + DependOn(&bottom, &specialized_service); + DependOn(&bottom, &other_intermediary); + + manager()->DestroyBrowserContextServices(NULL); + + ASSERT_EQ(6U, shutdown_order()->size()); + EXPECT_STREQ("bottom", (*shutdown_order())[0].c_str()); + EXPECT_STREQ("specialized_service", (*shutdown_order())[1].c_str()); + EXPECT_STREQ("other_intermediary", (*shutdown_order())[2].c_str()); + EXPECT_STREQ("intermediary_service", (*shutdown_order())[3].c_str()); + EXPECT_STREQ("other_root", (*shutdown_order())[4].c_str()); + EXPECT_STREQ("everything_depends_on_me", (*shutdown_order())[5].c_str()); +} diff --git a/chromium/components/browser_context_keyed_service/browser_context_keyed_base_factory.cc b/chromium/components/browser_context_keyed_service/browser_context_keyed_base_factory.cc new file mode 100644 index 00000000000..e6caa69d63c --- /dev/null +++ b/chromium/components/browser_context_keyed_service/browser_context_keyed_base_factory.cc @@ -0,0 +1,113 @@ +// 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/browser_context_keyed_service/browser_context_keyed_base_factory.h" + +#include "base/prefs/pref_service.h" +#include "components/browser_context_keyed_service/browser_context_dependency_manager.h" +#include "components/user_prefs/pref_registry_syncable.h" +#include "components/user_prefs/user_prefs.h" +#include "content/public/browser/browser_context.h" + +BrowserContextKeyedBaseFactory::BrowserContextKeyedBaseFactory( + const char* name, BrowserContextDependencyManager* manager) + : dependency_manager_(manager) +#ifndef NDEBUG + , service_name_(name) +#endif +{ + dependency_manager_->AddComponent(this); +} + +BrowserContextKeyedBaseFactory::~BrowserContextKeyedBaseFactory() { + dependency_manager_->RemoveComponent(this); +} + +void BrowserContextKeyedBaseFactory::DependsOn( + BrowserContextKeyedBaseFactory* rhs) { + dependency_manager_->AddEdge(rhs, this); +} + +content::BrowserContext* BrowserContextKeyedBaseFactory::GetBrowserContextToUse( + content::BrowserContext* context) const { + DCHECK(CalledOnValidThread()); + +#ifndef NDEBUG + dependency_manager_->AssertBrowserContextWasntDestroyed(context); +#endif + + // Safe default for the Incognito mode: no service. + if (context->IsOffTheRecord()) + return NULL; + + return context; +} + +void BrowserContextKeyedBaseFactory::RegisterUserPrefsOnBrowserContext( + content::BrowserContext* context) { + // Safe timing for pref registration is hard. Previously, we made + // BrowserContext responsible for all pref registration on every service + // that used BrowserContext. Now we don't and there are timing issues. + // + // With normal contexts, prefs can simply be registered at + // BrowserContextDependencyManager::CreateBrowserContextServices time. + // With incognito contexts, we just never register since incognito contexts + // share the same pref services with their parent contexts. + // + // TestingBrowserContexts throw a wrench into the mix, in that some tests will + // swap out the PrefService after we've registered user prefs on the original + // PrefService. Test code that does this is responsible for either manually + // invoking RegisterProfilePrefs() on the appropriate + // BrowserContextKeyedServiceFactory associated with the prefs they need, + // or they can use SetTestingFactory() and create a service (since service + // creation with a factory method causes registration to happen at service + // creation time). + // + // Now that services are responsible for declaring their preferences, we have + // to enforce a uniquenes check here because some tests create one context and + // multiple services of the same type attached to that context (serially, not + // parallel) and we don't want to register multiple times on the same context. + DCHECK(!context->IsOffTheRecord()); + + std::set<content::BrowserContext*>::iterator it = + registered_preferences_.find(context); + if (it == registered_preferences_.end()) { + PrefService* prefs = user_prefs::UserPrefs::Get(context); + user_prefs::PrefRegistrySyncable* registry = + static_cast<user_prefs::PrefRegistrySyncable*>( + prefs->DeprecatedGetPrefRegistry()); + RegisterProfilePrefs(registry); + registered_preferences_.insert(context); + } +} + +bool +BrowserContextKeyedBaseFactory::ServiceIsCreatedWithBrowserContext() const { + return false; +} + +bool BrowserContextKeyedBaseFactory::ServiceIsNULLWhileTesting() const { + return false; +} + +void BrowserContextKeyedBaseFactory::BrowserContextDestroyed( + content::BrowserContext* context) { + // While object destruction can be customized in ways where the object is + // only dereferenced, this still must run on the UI thread. + DCHECK(CalledOnValidThread()); + + registered_preferences_.erase(context); +} + +bool BrowserContextKeyedBaseFactory::ArePreferencesSetOn( + content::BrowserContext* context) const { + return registered_preferences_.find(context) != + registered_preferences_.end(); +} + +void BrowserContextKeyedBaseFactory::MarkPreferencesSetOn( + content::BrowserContext* context) { + DCHECK(!ArePreferencesSetOn(context)); + registered_preferences_.insert(context); +} diff --git a/chromium/components/browser_context_keyed_service/browser_context_keyed_base_factory.h b/chromium/components/browser_context_keyed_service/browser_context_keyed_base_factory.h new file mode 100644 index 00000000000..1c937d3edeb --- /dev/null +++ b/chromium/components/browser_context_keyed_service/browser_context_keyed_base_factory.h @@ -0,0 +1,140 @@ +// 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_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_BASE_FACTORY_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_BASE_FACTORY_H_ + +#include <set> + +#include "base/threading/non_thread_safe.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_export.h" +#include "components/browser_context_keyed_service/dependency_node.h" + +class BrowserContextDependencyManager; +class PrefService; + +namespace content { +class BrowserContext; +} + +namespace user_prefs { +class PrefRegistrySyncable; +} + +// Base class for Factories that take a BrowserContext object and return some +// service. +// +// Unless you're trying to make a new type of Factory, you probably don't want +// this class, but its subclasses: BrowserContextKeyedServiceFactory and +// RefcountedBrowserContextKeyedServiceFactory. This object describes general +// dependency management between Factories; subclasses react to lifecycle +// events and implement memory management. +class BROWSER_CONTEXT_KEYED_SERVICE_EXPORT +BrowserContextKeyedBaseFactory + : public base::NonThreadSafe, + NON_EXPORTED_BASE(public DependencyNode) { + public: + // Registers preferences used in this service on the pref service of + // |context|. This is the public interface and is safe to be called multiple + // times because testing code can have multiple services of the same type + // attached to a single |context|. + void RegisterUserPrefsOnBrowserContext(content::BrowserContext* context); + +#ifndef NDEBUG + // Returns our name. We don't keep track of this in release mode. + const char* name() const { return service_name_; } +#endif + + protected: + BrowserContextKeyedBaseFactory(const char* name, + BrowserContextDependencyManager* manager); + virtual ~BrowserContextKeyedBaseFactory(); + + // The main public interface for declaring dependencies between services + // created by factories. + void DependsOn(BrowserContextKeyedBaseFactory* rhs); + + // Interface for people building a concrete FooServiceFactory: -------------- + + // Finds which browser context (if any) to use. + virtual content::BrowserContext* GetBrowserContextToUse( + content::BrowserContext* context) const; + + // Register any user preferences on this service. This is called during + // CreateBrowserContextService() since preferences are registered on a per + // BrowserContext basis. + virtual void RegisterProfilePrefs( + user_prefs::PrefRegistrySyncable* registry) {} + + // By default, we create instances of a service lazily and wait until + // GetForBrowserContext() is called on our subclass. Some services need to be + // created as soon as the BrowserContext has been brought up. + virtual bool ServiceIsCreatedWithBrowserContext() const; + + // By default, TestingBrowserContexts will be treated like normal contexts. + // You can override this so that by default, the service associated with the + // TestingBrowserContext is NULL. (This is just a shortcut around + // SetTestingFactory() to make sure our contexts don't directly refer to the + // services they use.) + virtual bool ServiceIsNULLWhileTesting() const; + + // Interface for people building a type of BrowserContextKeyedFactory: ------- + + // A helper object actually listens for notifications about BrowserContext + // destruction, calculates the order in which things are destroyed and then + // does a two pass shutdown. + // + // It is up to the individual factory types to determine what this two pass + // shutdown means. The general framework guarantees the following: + // + // - Each BrowserContextShutdown() is called in dependency order (and you may + // reach out to other services during this phase). + // + // - Each BrowserContextDestroyed() is called in dependency order. We will + // NOTREACHED() if you attempt to GetForBrowserContext() any other service. + // You should delete/deref/do other final memory management things during + // this phase. You must also call the base class method as the last thing + // you do. + virtual void BrowserContextShutdown(content::BrowserContext* context) = 0; + virtual void BrowserContextDestroyed(content::BrowserContext* context); + + // Returns whether we've registered the preferences on this context. + bool ArePreferencesSetOn(content::BrowserContext* context) const; + + // Mark context as Preferences set. + void MarkPreferencesSetOn(content::BrowserContext* context); + + private: + friend class BrowserContextDependencyManager; + friend class BrowserContextDependencyManagerUnittests; + + // These two methods are for tight integration with the + // BrowserContextDependencyManager. + + // Because of ServiceIsNULLWhileTesting(), we need a way to tell different + // subclasses that they should disable testing. + virtual void SetEmptyTestingFactory(content::BrowserContext* context) = 0; + + // We also need a generalized, non-returning method that generates the object + // now for when we're creating the context. + virtual void CreateServiceNow(content::BrowserContext* context) = 0; + + // Which BrowserContextDependencyManager we should communicate with. + // In real code, this will always be + // BrowserContextDependencyManager::GetInstance(), but unit tests will want + // to use their own copy. + BrowserContextDependencyManager* dependency_manager_; + + // BrowserContexts that have this service's preferences registered on them. + std::set<content::BrowserContext*> registered_preferences_; + +#if !defined(NDEBUG) + // A static string passed in to our constructor. Should be unique across all + // services. This is used only for debugging in debug mode. (We can print + // pretty graphs with GraphViz with this information.) + const char* service_name_; +#endif +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_BASE_FACTORY_H_ diff --git a/chromium/components/browser_context_keyed_service/browser_context_keyed_service.h b/chromium/components/browser_context_keyed_service/browser_context_keyed_service.h new file mode 100644 index 00000000000..9bf286e37d2 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/browser_context_keyed_service.h @@ -0,0 +1,32 @@ +// 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_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_H_ + +#include "components/browser_context_keyed_service/browser_context_keyed_service_export.h" + +class BrowserContextKeyedServiceFactory; + +// Base class for all BrowserContextKeyedServices to allow for correct +// destruction order. +// +// Many services that hang off BrowserContext have a two-pass shutdown. Many +// subsystems need a first pass shutdown phase where they drop references. Not +// all services will need this, so there's a default implementation. Only once +// every system has been given a chance to drop references do we start deleting +// objects. +class BROWSER_CONTEXT_KEYED_SERVICE_EXPORT BrowserContextKeyedService { + public: + // The first pass is to call Shutdown on a BrowserContextKeyedService. + virtual void Shutdown() {} + + protected: + friend class BrowserContextKeyedServiceFactory; + + // The second pass is the actual deletion of each object. + virtual ~BrowserContextKeyedService() {} +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_H_ diff --git a/chromium/components/browser_context_keyed_service/browser_context_keyed_service_export.h b/chromium/components/browser_context_keyed_service/browser_context_keyed_service_export.h new file mode 100644 index 00000000000..0d0978486c0 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/browser_context_keyed_service_export.h @@ -0,0 +1,29 @@ +// 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_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_EXPORT_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(BROWSER_CONTEXT_KEYED_SERVICE_IMPLEMENTATION) +#define BROWSER_CONTEXT_KEYED_SERVICE_EXPORT __declspec(dllexport) +#else +#define BROWSER_CONTEXT_KEYED_SERVICE_EXPORT __declspec(dllimport) +#endif // defined(BROWSER_CONTEXT_KEYED_SERVICE_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(BROWSER_CONTEXT_KEYED_SERVICE_IMPLEMENTATION) +#define BROWSER_CONTEXT_KEYED_SERVICE_EXPORT __attribute__((visibility("default"))) +#else +#define BROWSER_CONTEXT_KEYED_SERVICE_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define BROWSER_CONTEXT_KEYED_SERVICE_EXPORT +#endif + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_EXPORT_H_ diff --git a/chromium/components/browser_context_keyed_service/browser_context_keyed_service_factory.cc b/chromium/components/browser_context_keyed_service/browser_context_keyed_service_factory.cc new file mode 100644 index 00000000000..619318ef584 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/browser_context_keyed_service_factory.cc @@ -0,0 +1,130 @@ +// 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/browser_context_keyed_service/browser_context_keyed_service_factory.h" + +#include <map> + +#include "base/logging.h" +#include "base/stl_util.h" +#include "components/browser_context_keyed_service/browser_context_dependency_manager.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service.h" +#include "content/public/browser/browser_context.h" + +void BrowserContextKeyedServiceFactory::SetTestingFactory( + content::BrowserContext* context, FactoryFunction factory) { + // Destroying the context may cause us to lose data about whether |context| + // has our preferences registered on it (since the context object itself + // isn't dead). See if we need to readd it once we've gone through normal + // destruction. + bool add_context = ArePreferencesSetOn(context); + + // We have to go through the shutdown and destroy mechanisms because there + // are unit tests that create a service on a context and then change the + // testing service mid-test. + BrowserContextShutdown(context); + BrowserContextDestroyed(context); + + if (add_context) + MarkPreferencesSetOn(context); + + factories_[context] = factory; +} + +BrowserContextKeyedService* +BrowserContextKeyedServiceFactory::SetTestingFactoryAndUse( + content::BrowserContext* context, + FactoryFunction factory) { + DCHECK(factory); + SetTestingFactory(context, factory); + return GetServiceForBrowserContext(context, true); +} + +BrowserContextKeyedServiceFactory::BrowserContextKeyedServiceFactory( + const char* name, BrowserContextDependencyManager* manager) + : BrowserContextKeyedBaseFactory(name, manager) { +} + +BrowserContextKeyedServiceFactory::~BrowserContextKeyedServiceFactory() { + DCHECK(mapping_.empty()); +} + +BrowserContextKeyedService* +BrowserContextKeyedServiceFactory::GetServiceForBrowserContext( + content::BrowserContext* context, + bool create) { + context = GetBrowserContextToUse(context); + if (!context) + return NULL; + + // NOTE: If you modify any of the logic below, make sure to update the + // refcounted version in refcounted_context_keyed_service_factory.cc! + BrowserContextKeyedServices::const_iterator it = mapping_.find(context); + if (it != mapping_.end()) + return it->second; + + // Object not found. + if (!create) + return NULL; // And we're forbidden from creating one. + + // Create new object. + // Check to see if we have a per-BrowserContext testing factory that we should + // use instead of default behavior. + BrowserContextKeyedService* service = NULL; + BrowserContextOverriddenFunctions::const_iterator jt = + factories_.find(context); + if (jt != factories_.end()) { + if (jt->second) { + if (!context->IsOffTheRecord()) + RegisterUserPrefsOnBrowserContext(context); + service = jt->second(context); + } + } else { + service = BuildServiceInstanceFor(context); + } + + Associate(context, service); + return service; +} + +void BrowserContextKeyedServiceFactory::Associate( + content::BrowserContext* context, + BrowserContextKeyedService* service) { + DCHECK(!ContainsKey(mapping_, context)); + mapping_.insert(std::make_pair(context, service)); +} + +void BrowserContextKeyedServiceFactory::BrowserContextShutdown( + content::BrowserContext* context) { + BrowserContextKeyedServices::iterator it = mapping_.find(context); + if (it != mapping_.end() && it->second) + it->second->Shutdown(); +} + +void BrowserContextKeyedServiceFactory::BrowserContextDestroyed( + content::BrowserContext* context) { + BrowserContextKeyedServices::iterator it = mapping_.find(context); + if (it != mapping_.end()) { + delete it->second; + mapping_.erase(it); + } + + // For unit tests, we also remove the factory function both so we don't + // maintain a big map of dead pointers, but also since we may have a second + // object that lives at the same address (see other comments about unit tests + // in this file). + factories_.erase(context); + + BrowserContextKeyedBaseFactory::BrowserContextDestroyed(context); +} + +void BrowserContextKeyedServiceFactory::SetEmptyTestingFactory( + content::BrowserContext* context) { + SetTestingFactory(context, NULL); +} + +void BrowserContextKeyedServiceFactory::CreateServiceNow( + content::BrowserContext* context) { + GetServiceForBrowserContext(context, true); +} diff --git a/chromium/components/browser_context_keyed_service/browser_context_keyed_service_factory.h b/chromium/components/browser_context_keyed_service/browser_context_keyed_service_factory.h new file mode 100644 index 00000000000..2775e4cc45b --- /dev/null +++ b/chromium/components/browser_context_keyed_service/browser_context_keyed_service_factory.h @@ -0,0 +1,122 @@ +// 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_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ + +#include <map> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "components/browser_context_keyed_service/browser_context_keyed_base_factory.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_export.h" + +class BrowserContextDependencyManager; +class BrowserContextKeyedService; + +// Base class for Factories that take a BrowserContext object and return some +// service on a one-to-one mapping. Each factory that derives from this class +// *must* be a Singleton (only unit tests don't do that). +// +// We do this because services depend on each other and we need to control +// shutdown/destruction order. In each derived classes' constructors, the +// implementors must explicitly state which services are depended on. +class BROWSER_CONTEXT_KEYED_SERVICE_EXPORT BrowserContextKeyedServiceFactory + : public BrowserContextKeyedBaseFactory { + public: + // A function that supplies the instance of a BrowserContextKeyedService + // for a given BrowserContext. This is used primarily for testing, where + // we want to feed a specific mock into the BCKSF system. + typedef BrowserContextKeyedService* + (*FactoryFunction)(content::BrowserContext* context); + + // Associates |factory| with |context| so that |factory| is used to create + // the BrowserContextKeyedService when requested. |factory| can be NULL + // to signal that BrowserContextKeyedService should be NULL. Multiple calls to + // SetTestingFactory() are allowed; previous services will be shut down. + void SetTestingFactory(content::BrowserContext* context, + FactoryFunction factory); + + // Associates |factory| with |context| and immediately returns the created + // BrowserContextKeyedService. Since the factory will be used immediately, + // it may not be NULL. + BrowserContextKeyedService* SetTestingFactoryAndUse( + content::BrowserContext* context, + FactoryFunction factory); + + protected: + // BrowserContextKeyedServiceFactories must communicate with a + // BrowserContextDependencyManager. For all non-test code, write your subclass + // constructors like this: + // + // MyServiceFactory::MyServiceFactory() + // : BrowserContextKeyedServiceFactory( + // "MyService", + // BrowserContextDependencyManager::GetInstance()) + // {} + BrowserContextKeyedServiceFactory(const char* name, + BrowserContextDependencyManager* manager); + virtual ~BrowserContextKeyedServiceFactory(); + + // Common implementation that maps |context| to some service object. Deals + // with incognito contexts per subclass instructions with + // GetBrowserContextRedirectedInIncognito() and + // GetBrowserContextOwnInstanceInIncognito() through the + // GetBrowserContextToUse() method on the base. If |create| is true, the + // service will be created using BuildServiceInstanceFor() if it doesn't + // already exist. + BrowserContextKeyedService* GetServiceForBrowserContext( + content::BrowserContext* context, + bool create); + + // Maps |context| to |service| with debug checks to prevent duplication. + void Associate(content::BrowserContext* context, + BrowserContextKeyedService* service); + + // All subclasses of BrowserContextKeyedServiceFactory must return a + // BrowserContextKeyedService instead of just a BrowserContextKeyedBase. + virtual BrowserContextKeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const = 0; + + // A helper object actually listens for notifications about BrowserContext + // destruction, calculates the order in which things are destroyed and then + // does a two pass shutdown. + // + // First, BrowserContextShutdown() is called on every ServiceFactory and will + // usually call BrowserContextKeyedService::Shutdown(), which gives each + // BrowserContextKeyedService a chance to remove dependencies on other + // services that it may be holding. + // + // Secondly, BrowserContextDestroyed() is called on every ServiceFactory + // and the default implementation removes it from |mapping_| and deletes + // the pointer. + virtual void BrowserContextShutdown( + content::BrowserContext* context) OVERRIDE; + virtual void BrowserContextDestroyed( + content::BrowserContext* context) OVERRIDE; + + virtual void SetEmptyTestingFactory( + content::BrowserContext* context) OVERRIDE; + virtual void CreateServiceNow(content::BrowserContext* context) OVERRIDE; + + private: + friend class BrowserContextDependencyManager; + friend class BrowserContextDependencyManagerUnittests; + + typedef std::map<content::BrowserContext*, BrowserContextKeyedService*> + BrowserContextKeyedServices; + typedef std::map<content::BrowserContext*, FactoryFunction> + BrowserContextOverriddenFunctions; + + // The mapping between a BrowserContext and its service. + std::map<content::BrowserContext*, BrowserContextKeyedService*> mapping_; + + // The mapping between a BrowserContext and its overridden FactoryFunction. + std::map<content::BrowserContext*, FactoryFunction> factories_; + + DISALLOW_COPY_AND_ASSIGN(BrowserContextKeyedServiceFactory); +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ diff --git a/chromium/components/browser_context_keyed_service/dependency_graph.cc b/chromium/components/browser_context_keyed_service/dependency_graph.cc new file mode 100644 index 00000000000..253f5dd0fd1 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/dependency_graph.cc @@ -0,0 +1,166 @@ +// 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/browser_context_keyed_service/dependency_graph.h" + +#include <algorithm> +#include <deque> +#include <iterator> + +DependencyGraph::DependencyGraph() { +} + +DependencyGraph::~DependencyGraph() { +} + +void DependencyGraph::AddNode(DependencyNode* node) { + all_nodes_.push_back(node); + construction_order_.clear(); +} + +void DependencyGraph::RemoveNode(DependencyNode* node) { + all_nodes_.erase(std::remove(all_nodes_.begin(), + all_nodes_.end(), + node), + all_nodes_.end()); + + // Remove all dependency edges that contain this node. + EdgeMap::iterator it = edges_.begin(); + while (it != edges_.end()) { + EdgeMap::iterator temp = it; + ++it; + + if (temp->first == node || temp->second == node) + edges_.erase(temp); + } + + construction_order_.clear(); +} + +void DependencyGraph::AddEdge(DependencyNode* depended, + DependencyNode* dependee) { + edges_.insert(std::make_pair(depended, dependee)); + construction_order_.clear(); +} + +bool DependencyGraph::GetConstructionOrder( + std::vector<DependencyNode*>* order) { + if (construction_order_.empty() && !BuildConstructionOrder()) + return false; + + *order = construction_order_; + return true; +} + +bool DependencyGraph::GetDestructionOrder( + std::vector<DependencyNode*>* order) { + if (construction_order_.empty() && !BuildConstructionOrder()) + return false; + + *order = construction_order_; + + // Destroy nodes in reverse order. + std::reverse(order->begin(), order->end()); + + return true; +} + +bool DependencyGraph::BuildConstructionOrder() { + // Step 1: Build a set of nodes with no incoming edges. + std::deque<DependencyNode*> queue; + std::copy(all_nodes_.begin(), + all_nodes_.end(), + std::back_inserter(queue)); + + std::deque<DependencyNode*>::iterator queue_end = queue.end(); + for (EdgeMap::const_iterator it = edges_.begin(); + it != edges_.end(); ++it) { + queue_end = std::remove(queue.begin(), queue_end, it->second); + } + queue.erase(queue_end, queue.end()); + + // Step 2: Do the Kahn topological sort. + std::vector<DependencyNode*> output; + EdgeMap edges(edges_); + while (!queue.empty()) { + DependencyNode* node = queue.front(); + queue.pop_front(); + output.push_back(node); + + std::pair<EdgeMap::iterator, EdgeMap::iterator> range = + edges.equal_range(node); + EdgeMap::iterator it = range.first; + while (it != range.second) { + DependencyNode* dest = it->second; + EdgeMap::iterator temp = it; + it++; + edges.erase(temp); + + bool has_incoming_edges = false; + for (EdgeMap::iterator jt = edges.begin(); jt != edges.end(); ++jt) { + if (jt->second == dest) { + has_incoming_edges = true; + break; + } + } + + if (!has_incoming_edges) + queue.push_back(dest); + } + } + + if (!edges.empty()) { + // Dependency graph has a cycle. + return false; + } + + construction_order_ = output; + return true; +} + +std::string DependencyGraph::DumpAsGraphviz( + const std::string& toplevel_name, + const base::Callback<std::string(DependencyNode*)>& + node_name_callback) const { + std::string result("digraph {\n"); + + // Make a copy of all nodes. + std::deque<DependencyNode*> nodes; + std::copy(all_nodes_.begin(), all_nodes_.end(), std::back_inserter(nodes)); + + // State all dependencies and remove |second| so we don't generate an + // implicit dependency on the top level node. + std::deque<DependencyNode*>::iterator nodes_end(nodes.end()); + result.append(" /* Dependencies */\n"); + for (EdgeMap::const_iterator it = edges_.begin(); it != edges_.end(); ++it) { + result.append(" "); + result.append(node_name_callback.Run(it->second)); + result.append(" -> "); + result.append(node_name_callback.Run(it->first)); + result.append(";\n"); + + nodes_end = std::remove(nodes.begin(), nodes_end, it->second); + } + nodes.erase(nodes_end, nodes.end()); + + // Every node that doesn't depend on anything else will implicitly depend on + // the top level node. + result.append("\n /* Toplevel attachments */\n"); + for (std::deque<DependencyNode*>::const_iterator it = + nodes.begin(); it != nodes.end(); ++it) { + result.append(" "); + result.append(node_name_callback.Run(*it)); + result.append(" -> "); + result.append(toplevel_name); + result.append(";\n"); + } + + result.append("\n /* Toplevel node */\n"); + result.append(" "); + result.append(toplevel_name); + result.append(" [shape=box];\n"); + + result.append("}\n"); + return result; +} diff --git a/chromium/components/browser_context_keyed_service/dependency_graph.h b/chromium/components/browser_context_keyed_service/dependency_graph.h new file mode 100644 index 00000000000..017049f8bf0 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/dependency_graph.h @@ -0,0 +1,68 @@ +// 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_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_GRAPH_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_GRAPH_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_export.h" + +class DependencyNode; + +// Dynamic graph of dependencies between nodes. +class BROWSER_CONTEXT_KEYED_SERVICE_EXPORT DependencyGraph { + public: + DependencyGraph(); + ~DependencyGraph(); + + // Adds/Removes a node from our list of live nodes. Removing will + // also remove live dependency links. + void AddNode(DependencyNode* node); + void RemoveNode(DependencyNode* node); + + // Adds a dependency between two nodes. + void AddEdge(DependencyNode* depended, DependencyNode* dependee); + + // Topologically sorts nodes to produce a safe construction order + // (all nodes after their dependees). + bool GetConstructionOrder( + std::vector<DependencyNode*>* order) WARN_UNUSED_RESULT; + + // Topologically sorts nodes to produce a safe destruction order + // (all nodes before their dependees). + bool GetDestructionOrder( + std::vector<DependencyNode*>* order) WARN_UNUSED_RESULT; + + // Returns representation of the dependency graph in graphviz format. + std::string DumpAsGraphviz( + const std::string& toplevel_name, + const base::Callback<std::string(DependencyNode*)>& + node_name_callback) const; + + private: + typedef std::multimap<DependencyNode*, DependencyNode*> EdgeMap; + + // Populates |construction_order_| with computed construction order. + // Returns true on success. + bool BuildConstructionOrder() WARN_UNUSED_RESULT; + + // Keeps track of all live nodes (see AddNode, RemoveNode). + std::vector<DependencyNode*> all_nodes_; + + // Keeps track of edges of the dependency graph. + EdgeMap edges_; + + // Cached construction order (needs rebuild with BuildConstructionOrder + // when empty). + std::vector<DependencyNode*> construction_order_; + + DISALLOW_COPY_AND_ASSIGN(DependencyGraph); +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_GRAPH_H_ diff --git a/chromium/components/browser_context_keyed_service/dependency_graph_unittest.cc b/chromium/components/browser_context_keyed_service/dependency_graph_unittest.cc new file mode 100644 index 00000000000..541ada720a6 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/dependency_graph_unittest.cc @@ -0,0 +1,161 @@ +// 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/browser_context_keyed_service/dependency_graph.h" +#include "components/browser_context_keyed_service/dependency_node.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class DependencyGraphTest : public testing::Test { +}; + +class DummyNode : public DependencyNode { + public: + explicit DummyNode(DependencyGraph* graph) : dependency_graph_(graph) { + dependency_graph_->AddNode(this); + } + + ~DummyNode() { + dependency_graph_->RemoveNode(this); + } + + private: + DependencyGraph* dependency_graph_; + + DISALLOW_COPY_AND_ASSIGN(DummyNode); +}; + +// Tests that we can deal with a single component. +TEST_F(DependencyGraphTest, SingleCase) { + DependencyGraph graph; + DummyNode node(&graph); + + std::vector<DependencyNode*> construction_order; + EXPECT_TRUE(graph.GetConstructionOrder(&construction_order)); + ASSERT_EQ(1U, construction_order.size()); + EXPECT_EQ(&node, construction_order[0]); + + std::vector<DependencyNode*> destruction_order; + EXPECT_TRUE(graph.GetDestructionOrder(&destruction_order)); + ASSERT_EQ(1U, destruction_order.size()); + EXPECT_EQ(&node, destruction_order[0]); +} + +// Tests that we get a simple one component depends on the other case. +TEST_F(DependencyGraphTest, SimpleDependency) { + DependencyGraph graph; + DummyNode parent(&graph); + DummyNode child(&graph); + + graph.AddEdge(&parent, &child); + + std::vector<DependencyNode*> construction_order; + EXPECT_TRUE(graph.GetConstructionOrder(&construction_order)); + ASSERT_EQ(2U, construction_order.size()); + EXPECT_EQ(&parent, construction_order[0]); + EXPECT_EQ(&child, construction_order[1]); + + std::vector<DependencyNode*> destruction_order; + EXPECT_TRUE(graph.GetDestructionOrder(&destruction_order)); + ASSERT_EQ(2U, destruction_order.size()); + EXPECT_EQ(&child, destruction_order[0]); + EXPECT_EQ(&parent, destruction_order[1]); +} + +// Tests two children, one parent. +TEST_F(DependencyGraphTest, TwoChildrenOneParent) { + DependencyGraph graph; + DummyNode parent(&graph); + DummyNode child1(&graph); + DummyNode child2(&graph); + + graph.AddEdge(&parent, &child1); + graph.AddEdge(&parent, &child2); + + std::vector<DependencyNode*> construction_order; + EXPECT_TRUE(graph.GetConstructionOrder(&construction_order)); + ASSERT_EQ(3U, construction_order.size()); + EXPECT_EQ(&parent, construction_order[0]); + EXPECT_EQ(&child1, construction_order[1]); + EXPECT_EQ(&child2, construction_order[2]); + + std::vector<DependencyNode*> destruction_order; + EXPECT_TRUE(graph.GetDestructionOrder(&destruction_order)); + ASSERT_EQ(3U, destruction_order.size()); + EXPECT_EQ(&child2, destruction_order[0]); + EXPECT_EQ(&child1, destruction_order[1]); + EXPECT_EQ(&parent, destruction_order[2]); +} + +// Tests an M configuration. +TEST_F(DependencyGraphTest, MConfiguration) { + DependencyGraph graph; + + DummyNode parent1(&graph); + DummyNode parent2(&graph); + + DummyNode child_of_1(&graph); + graph.AddEdge(&parent1, &child_of_1); + + DummyNode child_of_12(&graph); + graph.AddEdge(&parent1, &child_of_12); + graph.AddEdge(&parent2, &child_of_12); + + DummyNode child_of_2(&graph); + graph.AddEdge(&parent2, &child_of_2); + + std::vector<DependencyNode*> construction_order; + EXPECT_TRUE(graph.GetConstructionOrder(&construction_order)); + ASSERT_EQ(5U, construction_order.size()); + EXPECT_EQ(&parent1, construction_order[0]); + EXPECT_EQ(&parent2, construction_order[1]); + EXPECT_EQ(&child_of_1, construction_order[2]); + EXPECT_EQ(&child_of_12, construction_order[3]); + EXPECT_EQ(&child_of_2, construction_order[4]); + + std::vector<DependencyNode*> destruction_order; + EXPECT_TRUE(graph.GetDestructionOrder(&destruction_order)); + ASSERT_EQ(5U, destruction_order.size()); + EXPECT_EQ(&child_of_2, destruction_order[0]); + EXPECT_EQ(&child_of_12, destruction_order[1]); + EXPECT_EQ(&child_of_1, destruction_order[2]); + EXPECT_EQ(&parent2, destruction_order[3]); + EXPECT_EQ(&parent1, destruction_order[4]); +} + +// Tests that it can deal with a simple diamond. +TEST_F(DependencyGraphTest, DiamondConfiguration) { + DependencyGraph graph; + + DummyNode parent(&graph); + + DummyNode middle1(&graph); + graph.AddEdge(&parent, &middle1); + + DummyNode middle2(&graph); + graph.AddEdge(&parent, &middle2); + + DummyNode bottom(&graph); + graph.AddEdge(&middle1, &bottom); + graph.AddEdge(&middle2, &bottom); + + std::vector<DependencyNode*> construction_order; + EXPECT_TRUE(graph.GetConstructionOrder(&construction_order)); + ASSERT_EQ(4U, construction_order.size()); + EXPECT_EQ(&parent, construction_order[0]); + EXPECT_EQ(&middle1, construction_order[1]); + EXPECT_EQ(&middle2, construction_order[2]); + EXPECT_EQ(&bottom, construction_order[3]); + + std::vector<DependencyNode*> destruction_order; + EXPECT_TRUE(graph.GetDestructionOrder(&destruction_order)); + ASSERT_EQ(4U, destruction_order.size()); + EXPECT_EQ(&bottom, destruction_order[0]); + EXPECT_EQ(&middle2, destruction_order[1]); + EXPECT_EQ(&middle1, destruction_order[2]); + EXPECT_EQ(&parent, destruction_order[3]); +} + +} // namespace diff --git a/chromium/components/browser_context_keyed_service/dependency_node.h b/chromium/components/browser_context_keyed_service/dependency_node.h new file mode 100644 index 00000000000..d870b673295 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/dependency_node.h @@ -0,0 +1,16 @@ +// 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_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_NODE_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_NODE_H_ + +// Base class representing a node in a DependencyGraph. +class DependencyNode { + protected: + // This is intended to be used by the subclasses, not directly. + DependencyNode() {} + ~DependencyNode() {} +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_DEPENDENCY_NODE_H_ diff --git a/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.cc b/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.cc new file mode 100644 index 00000000000..ee06dd05dd5 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.cc @@ -0,0 +1,34 @@ +// 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/browser_context_keyed_service/refcounted_browser_context_keyed_service.h" + +namespace impl { + +// static +void RefcountedBrowserContextKeyedServiceTraits::Destruct( + const RefcountedBrowserContextKeyedService* obj) { + if (obj->requires_destruction_on_thread_ && + !content::BrowserThread::CurrentlyOn(obj->thread_id_)) { + content::BrowserThread::DeleteSoon(obj->thread_id_, FROM_HERE, obj); + } else { + delete obj; + } +} + +} // namespace impl + +RefcountedBrowserContextKeyedService::RefcountedBrowserContextKeyedService() + : requires_destruction_on_thread_(false), + thread_id_(content::BrowserThread::UI) { +} + +RefcountedBrowserContextKeyedService::RefcountedBrowserContextKeyedService( + const content::BrowserThread::ID thread_id) + : requires_destruction_on_thread_(true), + thread_id_(thread_id) { +} + +RefcountedBrowserContextKeyedService::~RefcountedBrowserContextKeyedService() {} + diff --git a/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.h b/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.h new file mode 100644 index 00000000000..c705898afac --- /dev/null +++ b/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service.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_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_H_ + +#include "base/memory/ref_counted.h" +#include "base/sequenced_task_runner_helpers.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_export.h" +#include "content/public/browser/browser_thread.h" + +class RefcountedBrowserContextKeyedService; + +namespace impl { + +struct BROWSER_CONTEXT_KEYED_SERVICE_EXPORT + RefcountedBrowserContextKeyedServiceTraits { + static void Destruct(const RefcountedBrowserContextKeyedService* obj); +}; + +} // namespace impl + +// Base class for refcounted objects that hang off the BrowserContext. +// +// The two pass shutdown described in BrowserContextKeyedService works a bit +// differently because there could be outstanding references on other +// threads. ShutdownOnUIThread() will be called on the UI thread, and then the +// destructor will run when the last reference is dropped, which may or may not +// be after the corresponding BrowserContext has been destroyed. +// +// Optionally, if you initialize your service with the constructor that takes a +// thread ID, your service will be deleted on that thread. We can't use +// content::DeleteOnThread<> directly because +// RefcountedBrowserContextKeyedService must be one type that +// RefcountedBrowserContextKeyedServiceFactory can use. +class BROWSER_CONTEXT_KEYED_SERVICE_EXPORT RefcountedBrowserContextKeyedService + : public base::RefCountedThreadSafe< + RefcountedBrowserContextKeyedService, + impl::RefcountedBrowserContextKeyedServiceTraits> { + public: + // Unlike BrowserContextKeyedService, ShutdownOnUI is not optional. You must + // do something to drop references during the first pass Shutdown() because + // this is the only point where you are guaranteed that something is running + // on the UI thread. The PKSF framework will ensure that this is only called + // on the UI thread; you do not need to check for that yourself. + virtual void ShutdownOnUIThread() = 0; + + protected: + // If your service does not need to be deleted on a specific thread, use the + // default constructor. + RefcountedBrowserContextKeyedService(); + + // If you need your service to be deleted on a specific thread (for example, + // you're converting a service that used content::DeleteOnThread<IO>), then + // use this constructor with the ID of the thread. + explicit RefcountedBrowserContextKeyedService( + const content::BrowserThread::ID thread_id); + + // The second pass destruction can happen anywhere unless you specify which + // thread this service must be destroyed on by using the second constructor. + virtual ~RefcountedBrowserContextKeyedService(); + + private: + friend struct impl::RefcountedBrowserContextKeyedServiceTraits; + friend class base::DeleteHelper<RefcountedBrowserContextKeyedService>; + friend class base::RefCountedThreadSafe<RefcountedBrowserContextKeyedService, + impl::RefcountedBrowserContextKeyedServiceTraits>; + + // Do we have to delete this object on a specific thread? + bool requires_destruction_on_thread_; + content::BrowserThread::ID thread_id_; +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_H_ diff --git a/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.cc b/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.cc new file mode 100644 index 00000000000..060ddc1eb1d --- /dev/null +++ b/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.cc @@ -0,0 +1,130 @@ +// 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/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.h" + +#include "base/logging.h" +#include "base/stl_util.h" +#include "content/public/browser/browser_context.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service.h" +#include "components/browser_context_keyed_service/refcounted_browser_context_keyed_service.h" + +void RefcountedBrowserContextKeyedServiceFactory::SetTestingFactory( + content::BrowserContext* context, + FactoryFunction factory) { + // Destroying the context may cause us to lose data about whether |context| + // has our preferences registered on it (since the context object itself + // isn't dead). See if we need to readd it once we've gone through normal + // destruction. + bool add_context = ArePreferencesSetOn(context); + + // We have to go through the shutdown and destroy mechanisms because there + // are unit tests that create a service on a context and then change the + // testing service mid-test. + BrowserContextShutdown(context); + BrowserContextDestroyed(context); + + if (add_context) + MarkPreferencesSetOn(context); + + factories_[context] = factory; +} + +scoped_refptr<RefcountedBrowserContextKeyedService> +RefcountedBrowserContextKeyedServiceFactory::SetTestingFactoryAndUse( + content::BrowserContext* context, + FactoryFunction factory) { + DCHECK(factory); + SetTestingFactory(context, factory); + return GetServiceForBrowserContext(context, true); +} + +RefcountedBrowserContextKeyedServiceFactory:: +RefcountedBrowserContextKeyedServiceFactory( + const char* name, + BrowserContextDependencyManager* manager) + : BrowserContextKeyedBaseFactory(name, manager) { +} + +RefcountedBrowserContextKeyedServiceFactory:: +~RefcountedBrowserContextKeyedServiceFactory() { + DCHECK(mapping_.empty()); +} + +scoped_refptr<RefcountedBrowserContextKeyedService> +RefcountedBrowserContextKeyedServiceFactory::GetServiceForBrowserContext( + content::BrowserContext* context, + bool create) { + context = GetBrowserContextToUse(context); + if (!context) + return NULL; + + // NOTE: If you modify any of the logic below, make sure to update the + // non-refcounted version in context_keyed_service_factory.cc! + RefCountedStorage::const_iterator it = mapping_.find(context); + if (it != mapping_.end()) + return it->second; + + // Object not found. + if (!create) + return NULL; // And we're forbidden from creating one. + + // Create new object. + // Check to see if we have a per-BrowserContext testing factory that we should + // use instead of default behavior. + scoped_refptr<RefcountedBrowserContextKeyedService> service; + BrowserContextOverriddenFunctions::const_iterator jt = + factories_.find(context); + if (jt != factories_.end()) { + if (jt->second) { + if (!context->IsOffTheRecord()) + RegisterUserPrefsOnBrowserContext(context); + service = jt->second(context); + } + } else { + service = BuildServiceInstanceFor(context); + } + + Associate(context, service); + return service; +} + +void RefcountedBrowserContextKeyedServiceFactory::Associate( + content::BrowserContext* context, + const scoped_refptr<RefcountedBrowserContextKeyedService>& service) { + DCHECK(!ContainsKey(mapping_, context)); + mapping_.insert(std::make_pair(context, service)); +} + +void RefcountedBrowserContextKeyedServiceFactory::BrowserContextShutdown( + content::BrowserContext* context) { + RefCountedStorage::iterator it = mapping_.find(context); + if (it != mapping_.end() && it->second.get()) + it->second->ShutdownOnUIThread(); +} + +void RefcountedBrowserContextKeyedServiceFactory::BrowserContextDestroyed( + content::BrowserContext* context) { + // We "merely" drop our reference to the service. Hopefully this will cause + // the service to be destroyed. If not, oh well. + mapping_.erase(context); + + // For unit tests, we also remove the factory function both so we don't + // maintain a big map of dead pointers, but also since we may have a second + // object that lives at the same address (see other comments about unit tests + // in this file). + factories_.erase(context); + + BrowserContextKeyedBaseFactory::BrowserContextDestroyed(context); +} + +void RefcountedBrowserContextKeyedServiceFactory::SetEmptyTestingFactory( + content::BrowserContext* context) { + SetTestingFactory(context, NULL); +} + +void RefcountedBrowserContextKeyedServiceFactory::CreateServiceNow( + content::BrowserContext* context) { + GetServiceForBrowserContext(context, true); +} diff --git a/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.h b/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.h new file mode 100644 index 00000000000..34eff157e90 --- /dev/null +++ b/chromium/components/browser_context_keyed_service/refcounted_browser_context_keyed_service_factory.h @@ -0,0 +1,100 @@ +// 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_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ +#define COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_factory.h" +#include "components/browser_context_keyed_service/browser_context_keyed_service_export.h" +#include "components/browser_context_keyed_service/refcounted_browser_context_keyed_service.h" + +class RefcountedBrowserContextKeyedService; + +namespace content { +class BrowserContext; +} + +// A specialized BrowserContextKeyedServiceFactory that manages a +// RefcountedThreadSafe<>. +// +// While the factory returns RefcountedThreadSafe<>s, the factory itself is a +// base::NotThreadSafe. Only call methods on this object on the UI thread. +// +// Implementers of RefcountedBrowserContextKeyedService should note that +// we guarantee that ShutdownOnUIThread() is called on the UI thread, but actual +// object destruction can happen anywhere. +class BROWSER_CONTEXT_KEYED_SERVICE_EXPORT +RefcountedBrowserContextKeyedServiceFactory + : public BrowserContextKeyedBaseFactory { + public: + // A function that supplies the instance of a BrowserContextKeyedService for + // a given BrowserContext. This is used primarily for testing, where we want + // to feed a specific mock into the BCKSF system. + typedef scoped_refptr<RefcountedBrowserContextKeyedService> + (*FactoryFunction)(content::BrowserContext* context); + + // Associates |factory| with |context| so that |factory| is used to create + // the BrowserContextKeyedService when requested. |factory| can be NULL + // to signal that BrowserContextKeyedService should be NULL. Multiple calls to + // SetTestingFactory() are allowed; previous services will be shut down. + void SetTestingFactory(content::BrowserContext* context, + FactoryFunction factory); + + // Associates |factory| with |context| and immediately returns the created + // BrowserContextKeyedService. Since the factory will be used immediately, + // it may not be NULL. + scoped_refptr<RefcountedBrowserContextKeyedService> SetTestingFactoryAndUse( + content::BrowserContext* context, + FactoryFunction factory); + + protected: + RefcountedBrowserContextKeyedServiceFactory( + const char* name, + BrowserContextDependencyManager* manager); + virtual ~RefcountedBrowserContextKeyedServiceFactory(); + + scoped_refptr<RefcountedBrowserContextKeyedService> + GetServiceForBrowserContext( + content::BrowserContext* context, + bool create); + + // Maps |context| to |service| with debug checks to prevent duplication. + void Associate( + content::BrowserContext* context, + const scoped_refptr<RefcountedBrowserContextKeyedService>& service); + + // All subclasses of RefcountedBrowserContextKeyedServiceFactory must return + // a RefcountedBrowserContextKeyedService instead of just + // a BrowserContextKeyedBase. + virtual scoped_refptr<RefcountedBrowserContextKeyedService> + BuildServiceInstanceFor(content::BrowserContext* context) const = 0; + + virtual void BrowserContextShutdown( + content::BrowserContext* context) OVERRIDE; + virtual void BrowserContextDestroyed( + content::BrowserContext* context) OVERRIDE; + virtual void SetEmptyTestingFactory( + content::BrowserContext* context) OVERRIDE; + virtual void CreateServiceNow(content::BrowserContext* context) OVERRIDE; + + private: + typedef std::map<content::BrowserContext*, + scoped_refptr<RefcountedBrowserContextKeyedService> > + RefCountedStorage; + typedef std::map<content::BrowserContext*, + FactoryFunction> BrowserContextOverriddenFunctions; + + // The mapping between a BrowserContext and its refcounted service. + RefCountedStorage mapping_; + + // The mapping between a BrowserContext and its overridden FactoryFunction. + BrowserContextOverriddenFunctions factories_; + + DISALLOW_COPY_AND_ASSIGN(RefcountedBrowserContextKeyedServiceFactory); +}; + +#endif // COMPONENTS_BROWSER_CONTEXT_KEYED_SERVICE_REFCOUNTED_BROWSER_CONTEXT_KEYED_SERVICE_FACTORY_H_ diff --git a/chromium/components/component_strings.grd b/chromium/components/component_strings.grd new file mode 100644 index 00000000000..ba3840e2dbb --- /dev/null +++ b/chromium/components/component_strings.grd @@ -0,0 +1,167 @@ +<?xml version="1.0" encoding="utf-8"?> + +<grit latest_public_release="0" current_release="1" + source_lang_id="en" enc_check="möl"> + <outputs> + <output filename="grit/component_strings.h" type="rc_header"> + <emit emit_type='prepend'></emit> + </output> + <output filename="component_strings_am.pak" type="data_package" lang="am" /> + <output filename="component_strings_ar.pak" type="data_package" lang="ar" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_ast.pak" type="data_package" lang="ast" /> + </if> + <output filename="component_strings_bg.pak" type="data_package" lang="bg" /> + <output filename="component_strings_bn.pak" type="data_package" lang="bn" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_bs.pak" type="data_package" lang="bs" /> + </if> + <output filename="component_strings_ca.pak" type="data_package" lang="ca" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_ca@valencia.pak" type="data_package" lang="ca@valencia" /> + </if> + <output filename="component_strings_cs.pak" type="data_package" lang="cs" /> + <output filename="component_strings_da.pak" type="data_package" lang="da" /> + <output filename="component_strings_de.pak" type="data_package" lang="de" /> + <output filename="component_strings_el.pak" type="data_package" lang="el" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_en-AU.pak" type="data_package" lang="en-AU" /> + </if> + <output filename="component_strings_en-GB.pak" type="data_package" lang="en-GB" /> + <output filename="component_strings_en-US.pak" type="data_package" lang="en" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_eo.pak" type="data_package" lang="eo" /> + </if> + <output filename="component_strings_es.pak" type="data_package" lang="es" /> + <output filename="component_strings_es-419.pak" type="data_package" lang="es-419" /> + <output filename="component_strings_et.pak" type="data_package" lang="et" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_eu.pak" type="data_package" lang="eu" /> + </if> + <output filename="component_strings_fa.pak" type="data_package" lang="fa" /> + <output filename="component_strings_fake-bidi.pak" type="data_package" lang="fake-bidi" /> + <output filename="component_strings_fi.pak" type="data_package" lang="fi" /> + <output filename="component_strings_fil.pak" type="data_package" lang="fil" /> + <output filename="component_strings_fr.pak" type="data_package" lang="fr" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_gl.pak" type="data_package" lang="gl" /> + </if> + <output filename="component_strings_gu.pak" type="data_package" lang="gu" /> + <output filename="component_strings_he.pak" type="data_package" lang="he" /> + <output filename="component_strings_hi.pak" type="data_package" lang="hi" /> + <output filename="component_strings_hr.pak" type="data_package" lang="hr" /> + <output filename="component_strings_hu.pak" type="data_package" lang="hu" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_hy.pak" type="data_package" lang="hy" /> + <output filename="component_strings_ia.pak" type="data_package" lang="ia" /> + </if> + <output filename="component_strings_id.pak" type="data_package" lang="id" /> + <output filename="component_strings_it.pak" type="data_package" lang="it" /> + <output filename="component_strings_ja.pak" type="data_package" lang="ja" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_ka.pak" type="data_package" lang="ka" /> + </if> + <output filename="component_strings_kn.pak" type="data_package" lang="kn" /> + <output filename="component_strings_ko.pak" type="data_package" lang="ko" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_ku.pak" type="data_package" lang="ku" /> + <output filename="component_strings_kw.pak" type="data_package" lang="kw" /> + </if> + <output filename="component_strings_lt.pak" type="data_package" lang="lt" /> + <output filename="component_strings_lv.pak" type="data_package" lang="lv" /> + <output filename="component_strings_ml.pak" type="data_package" lang="ml" /> + <output filename="component_strings_mr.pak" type="data_package" lang="mr" /> + <output filename="component_strings_ms.pak" type="data_package" lang="ms" /> + <output filename="component_strings_nl.pak" type="data_package" lang="nl" /> + <!-- The translation console uses 'no' for Norwegian Bokmål. It should + be 'nb'. --> + <output filename="component_strings_nb.pak" type="data_package" lang="no" /> + <output filename="component_strings_pl.pak" type="data_package" lang="pl" /> + <if expr="pp_ifdef('ios')"> + <!-- iOS uses pt for pt-BR --> + <output filename="component_strings_pt.pak" type="data_package" lang="pt-BR" /> + </if> + <if expr="not pp_ifdef('ios')"> + <output filename="component_strings_pt-BR.pak" type="data_package" lang="pt-BR" /> + </if> + <output filename="component_strings_pt-PT.pak" type="data_package" lang="pt-PT" /> + <output filename="component_strings_ro.pak" type="data_package" lang="ro" /> + <output filename="component_strings_ru.pak" type="data_package" lang="ru" /> + <output filename="component_strings_sk.pak" type="data_package" lang="sk" /> + <output filename="component_strings_sl.pak" type="data_package" lang="sl" /> + <output filename="component_strings_sr.pak" type="data_package" lang="sr" /> + <output filename="component_strings_sv.pak" type="data_package" lang="sv" /> + <output filename="component_strings_sw.pak" type="data_package" lang="sw" /> + <output filename="component_strings_ta.pak" type="data_package" lang="ta" /> + <output filename="component_strings_te.pak" type="data_package" lang="te" /> + <output filename="component_strings_th.pak" type="data_package" lang="th" /> + <output filename="component_strings_tr.pak" type="data_package" lang="tr" /> + <if expr="pp_ifdef('use_third_party_translations')"> + <output filename="component_strings_ug.pak" type="data_package" lang="ug" /> + </if> + <output filename="component_strings_uk.pak" type="data_package" lang="uk" /> + <output filename="component_strings_vi.pak" type="data_package" lang="vi" /> + <output filename="component_strings_zh-CN.pak" type="data_package" lang="zh-CN" /> + <output filename="component_strings_zh-TW.pak" type="data_package" lang="zh-TW" /> + </outputs> + <translations> + <file path="strings/component_strings_am.xtb" lang="am" /> + <file path="strings/component_strings_ar.xtb" lang="ar" /> + <file path="strings/component_strings_bg.xtb" lang="bg" /> + <file path="strings/component_strings_bn.xtb" lang="bn" /> + <file path="strings/component_strings_ca.xtb" lang="ca" /> + <file path="strings/component_strings_cs.xtb" lang="cs" /> + <file path="strings/component_strings_da.xtb" lang="da" /> + <file path="strings/component_strings_de.xtb" lang="de" /> + <file path="strings/component_strings_el.xtb" lang="el" /> + <file path="strings/component_strings_en-GB.xtb" lang="en-GB" /> + <file path="strings/component_strings_es.xtb" lang="es" /> + <file path="strings/component_strings_es-419.xtb" lang="es-419" /> + <file path="strings/component_strings_et.xtb" lang="et" /> + <file path="strings/component_strings_fa.xtb" lang="fa" /> + <file path="strings/component_strings_fi.xtb" lang="fi" /> + <file path="strings/component_strings_fil.xtb" lang="fil" /> + <file path="strings/component_strings_fr.xtb" lang="fr" /> + <file path="strings/component_strings_gu.xtb" lang="gu" /> + <file path="strings/component_strings_hi.xtb" lang="hi" /> + <file path="strings/component_strings_hr.xtb" lang="hr" /> + <file path="strings/component_strings_hu.xtb" lang="hu" /> + <file path="strings/component_strings_id.xtb" lang="id" /> + <file path="strings/component_strings_it.xtb" lang="it" /> + <!-- The translation console uses 'iw' for Hebrew, but we use 'he'. --> + <file path="strings/component_strings_iw.xtb" lang="he" /> + <file path="strings/component_strings_ja.xtb" lang="ja" /> + <file path="strings/component_strings_kn.xtb" lang="kn" /> + <file path="strings/component_strings_ko.xtb" lang="ko" /> + <file path="strings/component_strings_lt.xtb" lang="lt" /> + <file path="strings/component_strings_lv.xtb" lang="lv" /> + <file path="strings/component_strings_ml.xtb" lang="ml" /> + <file path="strings/component_strings_mr.xtb" lang="mr" /> + <file path="strings/component_strings_ms.xtb" lang="ms" /> + <file path="strings/component_strings_nl.xtb" lang="nl" /> + <file path="strings/component_strings_no.xtb" lang="no" /> + <file path="strings/component_strings_pl.xtb" lang="pl" /> + <file path="strings/component_strings_pt-BR.xtb" lang="pt-BR" /> + <file path="strings/component_strings_pt-PT.xtb" lang="pt-PT" /> + <file path="strings/component_strings_ro.xtb" lang="ro" /> + <file path="strings/component_strings_ru.xtb" lang="ru" /> + <file path="strings/component_strings_sk.xtb" lang="sk" /> + <file path="strings/component_strings_sl.xtb" lang="sl" /> + <file path="strings/component_strings_sr.xtb" lang="sr" /> + <file path="strings/component_strings_sv.xtb" lang="sv" /> + <file path="strings/component_strings_sw.xtb" lang="sw" /> + <file path="strings/component_strings_ta.xtb" lang="ta" /> + <file path="strings/component_strings_te.xtb" lang="te" /> + <file path="strings/component_strings_th.xtb" lang="th" /> + <file path="strings/component_strings_tr.xtb" lang="tr" /> + <file path="strings/component_strings_uk.xtb" lang="uk" /> + <file path="strings/component_strings_vi.xtb" lang="vi" /> + <file path="strings/component_strings_zh-CN.xtb" lang="zh-CN" /> + <file path="strings/component_strings_zh-TW.xtb" lang="zh-TW" /> + </translations> + <release seq="1" allow_pseudo="false"> + <messages fallback_to_english="true"> + <part file="autofill_strings.grdp" /> + </messages> + </release> +</grit> diff --git a/chromium/components/component_strings.gyp b/chromium/components/component_strings.gyp new file mode 100644 index 00000000000..a186f1ebe17 --- /dev/null +++ b/chromium/components/component_strings.gyp @@ -0,0 +1,30 @@ +# 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. + +{ + 'variables': { + 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/components', + }, + 'targets': [ + { + 'target_name': 'component_strings', + 'type': 'none', + 'actions': [ + { + 'action_name': 'component_strings', + 'variables': { + 'grit_grd_file': 'component_strings.grd', + 'grit_out_dir': '<(SHARED_INTERMEDIATE_DIR)/components/strings', + }, + 'includes': [ '../build/grit_action.gypi' ], + }, + ], + 'direct_dependent_settings': { + 'include_dirs': [ + '<(SHARED_INTERMEDIATE_DIR)/components/strings', + ], + }, + }, + ], +} diff --git a/chromium/components/components.gyp b/chromium/components/components.gyp new file mode 100644 index 00000000000..602650c37d0 --- /dev/null +++ b/chromium/components/components.gyp @@ -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. + +{ + 'variables': { + # This turns on e.g. the filename-based detection of which + # platforms to include source files on (e.g. files ending in + # _mac.h or _mac.cc are only compiled on MacOSX). + 'chromium_code': 1, + }, + 'includes': [ + 'autofill.gypi', + 'auto_login_parser.gypi', + 'breakpad.gypi', + 'browser_context_keyed_service.gypi', + 'components_tests.gypi', + 'json_schema.gypi', + 'navigation_interception.gypi', + 'policy.gypi', + 'sessions.gypi', + 'user_prefs.gypi', + 'visitedlink.gypi', + 'webdata.gypi', + 'web_contents_delegate_android.gypi', + 'web_modal.gypi', + ], +} diff --git a/chromium/components/components_tests.gypi b/chromium/components/components_tests.gypi new file mode 100644 index 00000000000..61346d07a88 --- /dev/null +++ b/chromium/components/components_tests.gypi @@ -0,0 +1,156 @@ +# 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. + +{ + 'conditions': [ + ['OS != "ios"', { + 'targets': [ + { + 'target_name': 'components_unittests', + 'type': '<(gtest_target_type)', + 'sources': [ + 'auto_login_parser/auto_login_parser_unittest.cc', + 'browser_context_keyed_service/browser_context_dependency_manager_unittest.cc', + 'browser_context_keyed_service/dependency_graph_unittest.cc', + 'json_schema/json_schema_validator_unittest.cc', + 'json_schema/json_schema_validator_unittest_base.cc', + 'json_schema/json_schema_validator_unittest_base.h', + 'navigation_interception/intercept_navigation_resource_throttle_unittest.cc', + 'sessions/serialized_navigation_entry_unittest.cc', + 'test/run_all_unittests.cc', + 'visitedlink/test/visitedlink_unittest.cc', + 'webdata/encryptor/encryptor_password_mac_unittest.cc', + 'webdata/encryptor/encryptor_unittest.cc', + 'web_modal/web_contents_modal_dialog_manager_unittest.cc', + ], + 'include_dirs': [ + '..', + ], + 'dependencies': [ + '../base/base.gyp:test_support_base', + '../testing/gmock.gyp:gmock', + '../testing/gtest.gyp:gtest', + + # Dependencies of auto_login_parser + 'auto_login_parser', + + # Dependencies of browser_context_keyed_service + 'browser_context_keyed_service', + + # Dependencies of encryptor + 'encryptor', + + # Dependencies of json_schema + 'json_schema', + + # Dependencies of intercept_navigation_resource_throttle_unittest.cc + '../content/content.gyp:test_support_content', + '../skia/skia.gyp:skia', + 'navigation_interception', + + # Dependencies of policy + 'policy_component', + + # Dependencies of sessions + '../third_party/protobuf/protobuf.gyp:protobuf_lite', + 'sessions', + 'sessions_test_support', + + # Dependencies of visitedlink + 'visitedlink_browser', + 'visitedlink_renderer', + '../content/content_resources.gyp:content_resources', + + 'web_modal', + ], + 'conditions': [ + ['OS == "android"', { + 'sources!': [ + 'web_modal/web_contents_modal_dialog_manager_unittest.cc', + ], + 'dependencies!': [ + 'web_modal', + ], + }], + ['OS == "android" and gtest_target_type == "shared_library"', { + 'dependencies': [ + '../testing/android/native_test.gyp:native_test_native_code', + ] + }], + ['OS=="win" and win_use_allocator_shim==1', { + 'dependencies': [ + '../base/allocator/allocator.gyp:allocator', + ], + }], + ['android_webview_build == 0', { + 'dependencies': [ + '../sync/sync.gyp:sync', + ], + }], + ['OS=="linux" and component=="shared_library" and linux_use_tcmalloc==1', { + 'dependencies': [ + '<(DEPTH)/base/allocator/allocator.gyp:allocator', + ], + 'link_settings': { + 'ldflags': ['-rdynamic'], + }, + }], + ['configuration_policy==1', { + 'sources': [ + 'policy/core/common/policy_schema_unittest.cc', + ], + }], + ], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [4267, ], + }, + { + 'target_name': 'components_perftests', + 'type': '<(gtest_target_type)', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:test_support_perf', + '../content/content.gyp:test_support_content', + '../testing/gtest.gyp:gtest', + '../ui/compositor/compositor.gyp:compositor', + 'visitedlink_browser', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'visitedlink/test/visitedlink_perftest.cc', + ], + 'conditions': [ + ['OS == "android" and gtest_target_type == "shared_library"', { + 'dependencies': [ + '../testing/android/native_test.gyp:native_test_native_code', + ], + }], + ], + # TODO(jschuh): crbug.com/167187 fix size_t to int truncations. + 'msvs_disabled_warnings': [ 4267, ], + }, + ], + 'conditions': [ + ['OS == "android" and gtest_target_type == "shared_library"', { + 'targets': [ + { + 'target_name': 'components_unittests_apk', + 'type': 'none', + 'dependencies': [ + 'components_unittests', + ], + 'variables': { + 'test_suite_name': 'components_unittests', + 'input_shlib_path': '<(SHARED_LIB_DIR)/<(SHARED_LIB_PREFIX)components_unittests<(SHARED_LIB_SUFFIX)', + }, + 'includes': [ '../build/apk_test.gypi' ], + }, + ], + }], + ], + }], + ], +} diff --git a/chromium/components/components_unittests.isolate b/chromium/components/components_unittests.isolate new file mode 100644 index 00000000000..f60a380f60d --- /dev/null +++ b/chromium/components/components_unittests.isolate @@ -0,0 +1,17 @@ +# 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. +{ + 'conditions': [ + ['OS=="android"', { + 'variables': { + 'isolate_dependency_tracked': [ + '<(PRODUCT_DIR)/content_resources.pak', + ], + 'isolate_dependency_untracked': [ + 'test/data/', + ], + }, + }], + ], +} diff --git a/chromium/components/json_schema.gypi b/chromium/components/json_schema.gypi new file mode 100644 index 00000000000..510f97d6fe4 --- /dev/null +++ b/chromium/components/json_schema.gypi @@ -0,0 +1,25 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'json_schema', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../ui/ui.gyp:ui', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'json_schema/json_schema_constants.cc', + 'json_schema/json_schema_constants.h', + 'json_schema/json_schema_validator.cc', + 'json_schema/json_schema_validator.h', + ], + }, + ], +} diff --git a/chromium/components/json_schema/DEPS b/chromium/components/json_schema/DEPS new file mode 100644 index 00000000000..e7cf2c6ff61 --- /dev/null +++ b/chromium/components/json_schema/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+ui/base", +] diff --git a/chromium/components/json_schema/OWNERS b/chromium/components/json_schema/OWNERS new file mode 100644 index 00000000000..f7e95c9f18f --- /dev/null +++ b/chromium/components/json_schema/OWNERS @@ -0,0 +1,5 @@ +asargent@chromium.org +calamity@chromium.org +kalman@chromium.org +koz@chromium.org +mpcomplete@chromium.org diff --git a/chromium/components/json_schema/README b/chromium/components/json_schema/README new file mode 100644 index 00000000000..c7453db06dc --- /dev/null +++ b/chromium/components/json_schema/README @@ -0,0 +1,6 @@ +The //components/json_schema component provides: + +a) JSON schema constants, which can be used to inspect schema objects. + +b) The JSONSchemaValidator class, which can be used to parse and validate JSON +schemas, and to validate JSON objects against the parsed schema. diff --git a/chromium/components/json_schema/json_schema_constants.cc b/chromium/components/json_schema/json_schema_constants.cc new file mode 100644 index 00000000000..0152cfc054d --- /dev/null +++ b/chromium/components/json_schema/json_schema_constants.cc @@ -0,0 +1,38 @@ +// 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/json_schema/json_schema_constants.h" + +namespace json_schema_constants { + +const char kAdditionalProperties[] = "additionalProperties"; +const char kAny[] = "any"; +const char kArray[] = "array"; +const char kBoolean[] = "boolean"; +const char kChoices[] = "choices"; +const char kDescription[] = "description"; +const char kEnum[] = "enum"; +const char kId[] = "id"; +const char kInteger[] = "integer"; +const char kItems[] = "items"; +const char kMaximum[] = "maximum"; +const char kMaxItems[] = "maxItems"; +const char kMaxLength[] = "maxLength"; +const char kMinimum[] = "minimum"; +const char kMinItems[] = "minItems"; +const char kMinLength[] = "minLength"; +const char kNull[] = "null"; +const char kNumber[] = "number"; +const char kObject[] = "object"; +const char kOptional[] = "optional"; +const char kPattern[] = "pattern"; +const char kPatternProperties[] = "patternProperties"; +const char kProperties[] = "properties"; +const char kRef[] = "$ref"; +const char kSchema[] = "$schema"; +const char kString[] = "string"; +const char kTitle[] = "title"; +const char kType[] = "type"; + +} // namespace json_schema_constants diff --git a/chromium/components/json_schema/json_schema_constants.h b/chromium/components/json_schema/json_schema_constants.h new file mode 100644 index 00000000000..5c64ebb5606 --- /dev/null +++ b/chromium/components/json_schema/json_schema_constants.h @@ -0,0 +1,42 @@ +// 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_JSON_SCHEMA_JSON_SCHEMA_CONSTANTS_H_ +#define COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_CONSTANTS_H_ + +// These constants are shared by code that uses JSON schemas. +namespace json_schema_constants { + +extern const char kAdditionalProperties[]; +extern const char kAny[]; +extern const char kArray[]; +extern const char kBoolean[]; +extern const char kChoices[]; +extern const char kDescription[]; +extern const char kEnum[]; +extern const char kId[]; +extern const char kInteger[]; +extern const char kItems[]; +extern const char kMaximum[]; +extern const char kMaxItems[]; +extern const char kMaxLength[]; +extern const char kMinimum[]; +extern const char kMinItems[]; +extern const char kMinLength[]; +extern const char kNull[]; +extern const char kNumber[]; +extern const char kObject[]; +extern const char kOptional[]; +extern const char kPattern[]; +extern const char kPatternProperties[]; +extern const char kProperties[]; +extern const char kRef[]; +extern const char kSchema[]; +extern const char kString[]; +extern const char kTitle[]; +extern const char kType[]; + +} // namespace json_schema_constants + +#endif // COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_CONSTANTS_H_ diff --git a/chromium/components/json_schema/json_schema_validator.cc b/chromium/components/json_schema/json_schema_validator.cc new file mode 100644 index 00000000000..3816a760970 --- /dev/null +++ b/chromium/components/json_schema/json_schema_validator.cc @@ -0,0 +1,727 @@ +// 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/json_schema/json_schema_validator.h" + +#include <algorithm> +#include <cfloat> +#include <cmath> + +#include "base/json/json_reader.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "components/json_schema/json_schema_constants.h" +#include "ui/base/l10n/l10n_util.h" + +namespace schema = json_schema_constants; + +namespace { + +double GetNumberValue(const base::Value* value) { + double result = 0; + CHECK(value->GetAsDouble(&result)) + << "Unexpected value type: " << value->GetType(); + return result; +} + +bool IsValidType(const std::string& type) { + static const char* kValidTypes[] = { + schema::kAny, + schema::kArray, + schema::kBoolean, + schema::kInteger, + schema::kNull, + schema::kNumber, + schema::kObject, + schema::kString, + }; + const char** end = kValidTypes + arraysize(kValidTypes); + return std::find(kValidTypes, end, type) != end; +} + +// Maps a schema attribute name to its expected type. +struct ExpectedType { + const char* key; + base::Value::Type type; +}; + +// Helper for std::lower_bound. +bool CompareToString(const ExpectedType& entry, const std::string& key) { + return entry.key < key; +} + +bool IsValidSchema(const base::DictionaryValue* dict, std::string* error) { + // This array must be sorted, so that std::lower_bound can perform a + // binary search. + static const ExpectedType kExpectedTypes[] = { + // Note: kRef == "$ref", kSchema == "$schema" + { schema::kRef, base::Value::TYPE_STRING }, + { schema::kSchema, base::Value::TYPE_STRING }, + + { schema::kAdditionalProperties, base::Value::TYPE_DICTIONARY }, + { schema::kChoices, base::Value::TYPE_LIST }, + { schema::kDescription, base::Value::TYPE_STRING }, + { schema::kEnum, base::Value::TYPE_LIST }, + { schema::kId, base::Value::TYPE_STRING }, + { schema::kMaxItems, base::Value::TYPE_INTEGER }, + { schema::kMaxLength, base::Value::TYPE_INTEGER }, + { schema::kMaximum, base::Value::TYPE_DOUBLE }, + { schema::kMinItems, base::Value::TYPE_INTEGER }, + { schema::kMinLength, base::Value::TYPE_INTEGER }, + { schema::kMinimum, base::Value::TYPE_DOUBLE }, + { schema::kOptional, base::Value::TYPE_BOOLEAN }, + { schema::kProperties, base::Value::TYPE_DICTIONARY }, + { schema::kTitle, base::Value::TYPE_STRING }, + }; + + bool has_type = false; + const base::ListValue* list_value = NULL; + const base::DictionaryValue* dictionary_value = NULL; + std::string string_value; + + for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd(); it.Advance()) { + // Validate the "type" attribute, which may be a string or a list. + if (it.key() == schema::kType) { + switch (it.value().GetType()) { + case base::Value::TYPE_STRING: + it.value().GetAsString(&string_value); + if (!IsValidType(string_value)) { + *error = "Invalid value for type attribute"; + return false; + } + break; + case base::Value::TYPE_LIST: + it.value().GetAsList(&list_value); + for (size_t i = 0; i < list_value->GetSize(); ++i) { + if (!list_value->GetString(i, &string_value) || + !IsValidType(string_value)) { + *error = "Invalid value for type attribute"; + return false; + } + } + break; + default: + *error = "Invalid value for type attribute"; + return false; + } + has_type = true; + continue; + } + + // Validate the "items" attribute, which is a schema or a list of schemas. + if (it.key() == schema::kItems) { + if (it.value().GetAsDictionary(&dictionary_value)) { + if (!IsValidSchema(dictionary_value, error)) { + DCHECK(!error->empty()); + return false; + } + } else if (it.value().GetAsList(&list_value)) { + for (size_t i = 0; i < list_value->GetSize(); ++i) { + if (!list_value->GetDictionary(i, &dictionary_value)) { + *error = base::StringPrintf( + "Invalid entry in items attribute at index %d", + static_cast<int>(i)); + return false; + } + if (!IsValidSchema(dictionary_value, error)) { + DCHECK(!error->empty()); + return false; + } + } + } else { + *error = "Invalid value for items attribute"; + return false; + } + continue; + } + + // All the other attributes have a single valid type. + const ExpectedType* end = kExpectedTypes + arraysize(kExpectedTypes); + const ExpectedType* entry = std::lower_bound( + kExpectedTypes, end, it.key(), CompareToString); + if (entry == end || entry->key != it.key()) { + *error = base::StringPrintf("Invalid attribute %s", it.key().c_str()); + return false; + } + if (!it.value().IsType(entry->type)) { + *error = base::StringPrintf("Invalid value for %s attribute", + it.key().c_str()); + return false; + } + + // base::Value::TYPE_INTEGER attributes must be >= 0. + // This applies to "minItems", "maxItems", "minLength" and "maxLength". + if (it.value().IsType(base::Value::TYPE_INTEGER)) { + int integer_value; + it.value().GetAsInteger(&integer_value); + if (integer_value < 0) { + *error = base::StringPrintf("Value of %s must be >= 0, got %d", + it.key().c_str(), integer_value); + return false; + } + } + + // Validate the "properties" attribute. Each entry maps a key to a schema. + if (it.key() == schema::kProperties) { + it.value().GetAsDictionary(&dictionary_value); + for (base::DictionaryValue::Iterator it(*dictionary_value); + !it.IsAtEnd(); it.Advance()) { + if (!it.value().GetAsDictionary(&dictionary_value)) { + *error = "Invalid value for properties attribute"; + return false; + } + if (!IsValidSchema(dictionary_value, error)) { + DCHECK(!error->empty()); + return false; + } + } + } + + // Validate "additionalProperties" attribute, which is a schema. + if (it.key() == schema::kAdditionalProperties) { + it.value().GetAsDictionary(&dictionary_value); + if (!IsValidSchema(dictionary_value, error)) { + DCHECK(!error->empty()); + return false; + } + } + + // Validate the values contained in an "enum" attribute. + if (it.key() == schema::kEnum) { + it.value().GetAsList(&list_value); + for (size_t i = 0; i < list_value->GetSize(); ++i) { + const base::Value* value = NULL; + list_value->Get(i, &value); + switch (value->GetType()) { + case base::Value::TYPE_NULL: + case base::Value::TYPE_BOOLEAN: + case base::Value::TYPE_INTEGER: + case base::Value::TYPE_DOUBLE: + case base::Value::TYPE_STRING: + break; + default: + *error = "Invalid value in enum attribute"; + return false; + } + } + } + + // Validate the schemas contained in a "choices" attribute. + if (it.key() == schema::kChoices) { + it.value().GetAsList(&list_value); + for (size_t i = 0; i < list_value->GetSize(); ++i) { + if (!list_value->GetDictionary(i, &dictionary_value)) { + *error = "Invalid choices attribute"; + return false; + } + if (!IsValidSchema(dictionary_value, error)) { + DCHECK(!error->empty()); + return false; + } + } + } + } + + if (!has_type) { + *error = "Schema must have a type attribute"; + return false; + } + + return true; +} + +} // namespace + + +JSONSchemaValidator::Error::Error() { +} + +JSONSchemaValidator::Error::Error(const std::string& message) + : path(message) { +} + +JSONSchemaValidator::Error::Error(const std::string& path, + const std::string& message) + : path(path), message(message) { +} + + +const char JSONSchemaValidator::kUnknownTypeReference[] = + "Unknown schema reference: *."; +const char JSONSchemaValidator::kInvalidChoice[] = + "Value does not match any valid type choices."; +const char JSONSchemaValidator::kInvalidEnum[] = + "Value does not match any valid enum choices."; +const char JSONSchemaValidator::kObjectPropertyIsRequired[] = + "Property is required."; +const char JSONSchemaValidator::kUnexpectedProperty[] = + "Unexpected property."; +const char JSONSchemaValidator::kArrayMinItems[] = + "Array must have at least * items."; +const char JSONSchemaValidator::kArrayMaxItems[] = + "Array must not have more than * items."; +const char JSONSchemaValidator::kArrayItemRequired[] = + "Item is required."; +const char JSONSchemaValidator::kStringMinLength[] = + "String must be at least * characters long."; +const char JSONSchemaValidator::kStringMaxLength[] = + "String must not be more than * characters long."; +const char JSONSchemaValidator::kStringPattern[] = + "String must match the pattern: *."; +const char JSONSchemaValidator::kNumberMinimum[] = + "Value must not be less than *."; +const char JSONSchemaValidator::kNumberMaximum[] = + "Value must not be greater than *."; +const char JSONSchemaValidator::kInvalidType[] = + "Expected '*' but got '*'."; +const char JSONSchemaValidator::kInvalidTypeIntegerNumber[] = + "Expected 'integer' but got 'number', consider using Math.round()."; + + +// static +std::string JSONSchemaValidator::GetJSONSchemaType(const base::Value* value) { + switch (value->GetType()) { + case base::Value::TYPE_NULL: + return schema::kNull; + case base::Value::TYPE_BOOLEAN: + return schema::kBoolean; + case base::Value::TYPE_INTEGER: + return schema::kInteger; + case base::Value::TYPE_DOUBLE: { + double double_value = 0; + value->GetAsDouble(&double_value); + if (std::abs(double_value) <= std::pow(2.0, DBL_MANT_DIG) && + double_value == floor(double_value)) { + return schema::kInteger; + } else { + return schema::kNumber; + } + } + case base::Value::TYPE_STRING: + return schema::kString; + case base::Value::TYPE_DICTIONARY: + return schema::kObject; + case base::Value::TYPE_LIST: + return schema::kArray; + default: + NOTREACHED() << "Unexpected value type: " << value->GetType(); + return std::string(); + } +} + +// static +std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format, + const std::string& s1) { + std::string ret_val = format; + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); + return ret_val; +} + +// static +std::string JSONSchemaValidator::FormatErrorMessage(const std::string& format, + const std::string& s1, + const std::string& s2) { + std::string ret_val = format; + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s1); + ReplaceFirstSubstringAfterOffset(&ret_val, 0, "*", s2); + return ret_val; +} + +// static +scoped_ptr<base::DictionaryValue> JSONSchemaValidator::IsValidSchema( + const std::string& schema, + std::string* error) { + base::JSONParserOptions options = base::JSON_PARSE_RFC; + scoped_ptr<base::Value> json( + base::JSONReader::ReadAndReturnError(schema, options, NULL, error)); + if (!json) + return scoped_ptr<base::DictionaryValue>(); + base::DictionaryValue* dict = NULL; + if (!json->GetAsDictionary(&dict)) { + *error = "Schema must be a JSON object"; + return scoped_ptr<base::DictionaryValue>(); + } + if (!::IsValidSchema(dict, error)) + return scoped_ptr<base::DictionaryValue>(); + ignore_result(json.release()); + return make_scoped_ptr(dict); +} + +JSONSchemaValidator::JSONSchemaValidator(base::DictionaryValue* schema) + : schema_root_(schema), default_allow_additional_properties_(false) { +} + +JSONSchemaValidator::JSONSchemaValidator(base::DictionaryValue* schema, + base::ListValue* types) + : schema_root_(schema), default_allow_additional_properties_(false) { + if (!types) + return; + + for (size_t i = 0; i < types->GetSize(); ++i) { + base::DictionaryValue* type = NULL; + CHECK(types->GetDictionary(i, &type)); + + std::string id; + CHECK(type->GetString(schema::kId, &id)); + + CHECK(types_.find(id) == types_.end()); + types_[id] = type; + } +} + +JSONSchemaValidator::~JSONSchemaValidator() {} + +bool JSONSchemaValidator::Validate(const base::Value* instance) { + errors_.clear(); + Validate(instance, schema_root_, std::string()); + return errors_.empty(); +} + +void JSONSchemaValidator::Validate(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path) { + // If this schema defines itself as reference type, save it in this.types. + std::string id; + if (schema->GetString(schema::kId, &id)) { + TypeMap::iterator iter = types_.find(id); + if (iter == types_.end()) + types_[id] = schema; + else + DCHECK(iter->second == schema); + } + + // If the schema has a $ref property, the instance must validate against + // that schema. It must be present in types_ to be referenced. + std::string ref; + if (schema->GetString(schema::kRef, &ref)) { + TypeMap::iterator type = types_.find(ref); + if (type == types_.end()) { + errors_.push_back( + Error(path, FormatErrorMessage(kUnknownTypeReference, ref))); + } else { + Validate(instance, type->second, path); + } + return; + } + + // If the schema has a choices property, the instance must validate against at + // least one of the items in that array. + const base::ListValue* choices = NULL; + if (schema->GetList(schema::kChoices, &choices)) { + ValidateChoices(instance, choices, path); + return; + } + + // If the schema has an enum property, the instance must be one of those + // values. + const base::ListValue* enumeration = NULL; + if (schema->GetList(schema::kEnum, &enumeration)) { + ValidateEnum(instance, enumeration, path); + return; + } + + std::string type; + schema->GetString(schema::kType, &type); + CHECK(!type.empty()); + if (type != schema::kAny) { + if (!ValidateType(instance, type, path)) + return; + + // These casts are safe because of checks in ValidateType(). + if (type == schema::kObject) { + ValidateObject(static_cast<const base::DictionaryValue*>(instance), + schema, + path); + } else if (type == schema::kArray) { + ValidateArray(static_cast<const base::ListValue*>(instance), + schema, path); + } else if (type == schema::kString) { + // Intentionally NOT downcasting to StringValue*. TYPE_STRING only implies + // GetAsString() can safely be carried out, not that it's a StringValue. + ValidateString(instance, schema, path); + } else if (type == schema::kNumber || type == schema::kInteger) { + ValidateNumber(instance, schema, path); + } else if (type != schema::kBoolean && type != schema::kNull) { + NOTREACHED() << "Unexpected type: " << type; + } + } +} + +void JSONSchemaValidator::ValidateChoices(const base::Value* instance, + const base::ListValue* choices, + const std::string& path) { + size_t original_num_errors = errors_.size(); + + for (size_t i = 0; i < choices->GetSize(); ++i) { + const base::DictionaryValue* choice = NULL; + CHECK(choices->GetDictionary(i, &choice)); + + Validate(instance, choice, path); + if (errors_.size() == original_num_errors) + return; + + // We discard the error from each choice. We only want to know if any of the + // validations succeeded. + errors_.resize(original_num_errors); + } + + // Now add a generic error that no choices matched. + errors_.push_back(Error(path, kInvalidChoice)); + return; +} + +void JSONSchemaValidator::ValidateEnum(const base::Value* instance, + const base::ListValue* choices, + const std::string& path) { + for (size_t i = 0; i < choices->GetSize(); ++i) { + const base::Value* choice = NULL; + CHECK(choices->Get(i, &choice)); + switch (choice->GetType()) { + case base::Value::TYPE_NULL: + case base::Value::TYPE_BOOLEAN: + case base::Value::TYPE_STRING: + if (instance->Equals(choice)) + return; + break; + + case base::Value::TYPE_INTEGER: + case base::Value::TYPE_DOUBLE: + if (instance->IsType(base::Value::TYPE_INTEGER) || + instance->IsType(base::Value::TYPE_DOUBLE)) { + if (GetNumberValue(choice) == GetNumberValue(instance)) + return; + } + break; + + default: + NOTREACHED() << "Unexpected type in enum: " << choice->GetType(); + } + } + + errors_.push_back(Error(path, kInvalidEnum)); +} + +void JSONSchemaValidator::ValidateObject(const base::DictionaryValue* instance, + const base::DictionaryValue* schema, + const std::string& path) { + const base::DictionaryValue* properties = NULL; + schema->GetDictionary(schema::kProperties, &properties); + if (properties) { + for (base::DictionaryValue::Iterator it(*properties); !it.IsAtEnd(); + it.Advance()) { + std::string prop_path = path.empty() ? it.key() : (path + "." + it.key()); + const base::DictionaryValue* prop_schema = NULL; + CHECK(it.value().GetAsDictionary(&prop_schema)); + + const base::Value* prop_value = NULL; + if (instance->Get(it.key(), &prop_value)) { + Validate(prop_value, prop_schema, prop_path); + } else { + // Properties are required unless there is an optional field set to + // 'true'. + bool is_optional = false; + prop_schema->GetBoolean(schema::kOptional, &is_optional); + if (!is_optional) { + errors_.push_back(Error(prop_path, kObjectPropertyIsRequired)); + } + } + } + } + + const base::DictionaryValue* additional_properties_schema = NULL; + if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema)) + return; + + // Validate additional properties. + for (base::DictionaryValue::Iterator it(*instance); !it.IsAtEnd(); + it.Advance()) { + if (properties && properties->HasKey(it.key())) + continue; + + std::string prop_path = path.empty() ? it.key() : path + "." + it.key(); + if (!additional_properties_schema) { + errors_.push_back(Error(prop_path, kUnexpectedProperty)); + } else { + Validate(&it.value(), additional_properties_schema, prop_path); + } + } +} + +void JSONSchemaValidator::ValidateArray(const base::ListValue* instance, + const base::DictionaryValue* schema, + const std::string& path) { + const base::DictionaryValue* single_type = NULL; + size_t instance_size = instance->GetSize(); + if (schema->GetDictionary(schema::kItems, &single_type)) { + int min_items = 0; + if (schema->GetInteger(schema::kMinItems, &min_items)) { + CHECK(min_items >= 0); + if (instance_size < static_cast<size_t>(min_items)) { + errors_.push_back(Error(path, FormatErrorMessage( + kArrayMinItems, base::IntToString(min_items)))); + } + } + + int max_items = 0; + if (schema->GetInteger(schema::kMaxItems, &max_items)) { + CHECK(max_items >= 0); + if (instance_size > static_cast<size_t>(max_items)) { + errors_.push_back(Error(path, FormatErrorMessage( + kArrayMaxItems, base::IntToString(max_items)))); + } + } + + // If the items property is a single schema, each item in the array must + // validate against that schema. + for (size_t i = 0; i < instance_size; ++i) { + const base::Value* item = NULL; + CHECK(instance->Get(i, &item)); + std::string i_str = base::Uint64ToString(i); + std::string item_path = path.empty() ? i_str : (path + "." + i_str); + Validate(item, single_type, item_path); + } + + return; + } + + // Otherwise, the list must be a tuple type, where each item in the list has a + // particular schema. + ValidateTuple(instance, schema, path); +} + +void JSONSchemaValidator::ValidateTuple(const base::ListValue* instance, + const base::DictionaryValue* schema, + const std::string& path) { + const base::ListValue* tuple_type = NULL; + schema->GetList(schema::kItems, &tuple_type); + size_t tuple_size = tuple_type ? tuple_type->GetSize() : 0; + if (tuple_type) { + for (size_t i = 0; i < tuple_size; ++i) { + std::string i_str = base::Uint64ToString(i); + std::string item_path = path.empty() ? i_str : (path + "." + i_str); + const base::DictionaryValue* item_schema = NULL; + CHECK(tuple_type->GetDictionary(i, &item_schema)); + const base::Value* item_value = NULL; + instance->Get(i, &item_value); + if (item_value && item_value->GetType() != base::Value::TYPE_NULL) { + Validate(item_value, item_schema, item_path); + } else { + bool is_optional = false; + item_schema->GetBoolean(schema::kOptional, &is_optional); + if (!is_optional) { + errors_.push_back(Error(item_path, kArrayItemRequired)); + return; + } + } + } + } + + const base::DictionaryValue* additional_properties_schema = NULL; + if (SchemaAllowsAnyAdditionalItems(schema, &additional_properties_schema)) + return; + + size_t instance_size = instance->GetSize(); + if (additional_properties_schema) { + // Any additional properties must validate against the additionalProperties + // schema. + for (size_t i = tuple_size; i < instance_size; ++i) { + std::string i_str = base::Uint64ToString(i); + std::string item_path = path.empty() ? i_str : (path + "." + i_str); + const base::Value* item_value = NULL; + CHECK(instance->Get(i, &item_value)); + Validate(item_value, additional_properties_schema, item_path); + } + } else if (instance_size > tuple_size) { + errors_.push_back(Error(path, FormatErrorMessage( + kArrayMaxItems, base::Uint64ToString(tuple_size)))); + } +} + +void JSONSchemaValidator::ValidateString(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path) { + std::string value; + CHECK(instance->GetAsString(&value)); + + int min_length = 0; + if (schema->GetInteger(schema::kMinLength, &min_length)) { + CHECK(min_length >= 0); + if (value.size() < static_cast<size_t>(min_length)) { + errors_.push_back(Error(path, FormatErrorMessage( + kStringMinLength, base::IntToString(min_length)))); + } + } + + int max_length = 0; + if (schema->GetInteger(schema::kMaxLength, &max_length)) { + CHECK(max_length >= 0); + if (value.size() > static_cast<size_t>(max_length)) { + errors_.push_back(Error(path, FormatErrorMessage( + kStringMaxLength, base::IntToString(max_length)))); + } + } + + CHECK(!schema->HasKey(schema::kPattern)) << "Pattern is not supported."; +} + +void JSONSchemaValidator::ValidateNumber(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path) { + double value = GetNumberValue(instance); + + // TODO(aa): It would be good to test that the double is not infinity or nan, + // but isnan and isinf aren't defined on Windows. + + double minimum = 0; + if (schema->GetDouble(schema::kMinimum, &minimum)) { + if (value < minimum) + errors_.push_back(Error(path, FormatErrorMessage( + kNumberMinimum, base::DoubleToString(minimum)))); + } + + double maximum = 0; + if (schema->GetDouble(schema::kMaximum, &maximum)) { + if (value > maximum) + errors_.push_back(Error(path, FormatErrorMessage( + kNumberMaximum, base::DoubleToString(maximum)))); + } +} + +bool JSONSchemaValidator::ValidateType(const base::Value* instance, + const std::string& expected_type, + const std::string& path) { + std::string actual_type = GetJSONSchemaType(instance); + if (expected_type == actual_type || + (expected_type == schema::kNumber && actual_type == schema::kInteger)) { + return true; + } else if (expected_type == schema::kInteger && + actual_type == schema::kNumber) { + errors_.push_back(Error(path, kInvalidTypeIntegerNumber)); + return false; + } else { + errors_.push_back(Error(path, FormatErrorMessage( + kInvalidType, expected_type, actual_type))); + return false; + } +} + +bool JSONSchemaValidator::SchemaAllowsAnyAdditionalItems( + const base::DictionaryValue* schema, + const base::DictionaryValue** additional_properties_schema) { + // If the validator allows additional properties globally, and this schema + // doesn't override, then we can exit early. + schema->GetDictionary(schema::kAdditionalProperties, + additional_properties_schema); + + if (*additional_properties_schema) { + std::string additional_properties_type(schema::kAny); + CHECK((*additional_properties_schema)->GetString( + schema::kType, &additional_properties_type)); + return additional_properties_type == schema::kAny; + } else { + return default_allow_additional_properties_; + } +} diff --git a/chromium/components/json_schema/json_schema_validator.h b/chromium/components/json_schema/json_schema_validator.h new file mode 100644 index 00000000000..4584a9da77a --- /dev/null +++ b/chromium/components/json_schema/json_schema_validator.h @@ -0,0 +1,235 @@ +// 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_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_ +#define COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_ + +#include <map> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" + +namespace base { +class DictionaryValue; +class ListValue; +class StringValue; +class Value; +} + +//============================================================================== +// This class implements a subset of JSON Schema. +// See: http://www.json.com/json-schema-proposal/ for more details. +// +// There is also an older JavaScript implementation of the same functionality in +// chrome/renderer/resources/json_schema.js. +// +// The following features of JSON Schema are not implemented: +// - requires +// - unique +// - disallow +// - union types (but replaced with 'choices') +// - number.maxDecimal +// - string.pattern +// +// The following properties are not applicable to the interface exposed by +// this class: +// - options +// - readonly +// - title +// - description +// - format +// - default +// - transient +// - hidden +// +// There are also these departures from the JSON Schema proposal: +// - null counts as 'unspecified' for optional values +// - added the 'choices' property, to allow specifying a list of possible types +// for a value +// - by default an "object" typed schema does not allow additional properties. +// if present, "additionalProperties" is to be a schema against which all +// additional properties will be validated. +//============================================================================== +class JSONSchemaValidator { + public: + // Details about a validation error. + struct Error { + Error(); + + explicit Error(const std::string& message); + + Error(const std::string& path, const std::string& message); + + // The path to the location of the error in the JSON structure. + std::string path; + + // An english message describing the error. + std::string message; + }; + + // Error messages. + static const char kUnknownTypeReference[]; + static const char kInvalidChoice[]; + static const char kInvalidEnum[]; + static const char kObjectPropertyIsRequired[]; + static const char kUnexpectedProperty[]; + static const char kArrayMinItems[]; + static const char kArrayMaxItems[]; + static const char kArrayItemRequired[]; + static const char kStringMinLength[]; + static const char kStringMaxLength[]; + static const char kStringPattern[]; + static const char kNumberMinimum[]; + static const char kNumberMaximum[]; + static const char kInvalidType[]; + static const char kInvalidTypeIntegerNumber[]; + + // Classifies a Value as one of the JSON schema primitive types. + static std::string GetJSONSchemaType(const base::Value* value); + + // Utility methods to format error messages. The first method can have one + // wildcard represented by '*', which is replaced with s1. The second method + // can have two, which are replaced by s1 and s2. + static std::string FormatErrorMessage(const std::string& format, + const std::string& s1); + static std::string FormatErrorMessage(const std::string& format, + const std::string& s1, + const std::string& s2); + + // Verifies if |schema| is a valid JSON v3 schema. When this validation passes + // then |schema| is valid JSON that can be parsed into a DictionaryValue, + // and that DictionaryValue can be used to build a JSONSchemaValidator. + // Returns the parsed DictionaryValue when |schema| validated, otherwise + // returns NULL. In that case, |error| contains an error description. + static scoped_ptr<base::DictionaryValue> IsValidSchema( + const std::string& schema, + std::string* error); + + // Creates a validator for the specified schema. + // + // NOTE: This constructor assumes that |schema| is well formed and valid. + // Errors will result in CHECK at runtime; this constructor should not be used + // with untrusted schemas. + explicit JSONSchemaValidator(base::DictionaryValue* schema); + + // Creates a validator for the specified schema and user-defined types. Each + // type must be a valid JSONSchema type description with an additional "id" + // field. Schema objects in |schema| can refer to these types with the "$ref" + // property. + // + // NOTE: This constructor assumes that |schema| and |types| are well-formed + // and valid. Errors will result in CHECK at runtime; this constructor should + // not be used with untrusted schemas. + JSONSchemaValidator(base::DictionaryValue* schema, base::ListValue* types); + + ~JSONSchemaValidator(); + + // Whether the validator allows additional items for objects and lists, beyond + // those defined by their schema, by default. + // + // This setting defaults to false: all items in an instance list or object + // must be defined by the corresponding schema. + // + // This setting can be overridden on individual object and list schemas by + // setting the "additionalProperties" field. + bool default_allow_additional_properties() const { + return default_allow_additional_properties_; + } + + void set_default_allow_additional_properties(bool val) { + default_allow_additional_properties_ = val; + } + + // Returns any errors from the last call to to Validate(). + const std::vector<Error>& errors() const { + return errors_; + } + + // Validates a JSON value. Returns true if the instance is valid, false + // otherwise. If false is returned any errors are available from the errors() + // getter. + bool Validate(const base::Value* instance); + + private: + typedef std::map<std::string, const base::DictionaryValue*> TypeMap; + + // Each of the below methods handle a subset of the validation process. The + // path paramater is the path to |instance| from the root of the instance tree + // and is used in error messages. + + // Validates any instance node against any schema node. This is called for + // every node in the instance tree, and it just decides which of the more + // detailed methods to call. + void Validate(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validates a node against a list of possible schemas. If any one of the + // schemas match, the node is valid. + void ValidateChoices(const base::Value* instance, + const base::ListValue* choices, + const std::string& path); + + // Validates a node against a list of exact primitive values, eg 42, "foobar". + void ValidateEnum(const base::Value* instance, + const base::ListValue* choices, + const std::string& path); + + // Validates a JSON object against an object schema node. + void ValidateObject(const base::DictionaryValue* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validates a JSON array against an array schema node. + void ValidateArray(const base::ListValue* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validates a JSON array against an array schema node configured to be a + // tuple. In a tuple, there is one schema node for each item expected in the + // array. + void ValidateTuple(const base::ListValue* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validate a JSON string against a string schema node. + void ValidateString(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validate a JSON number against a number schema node. + void ValidateNumber(const base::Value* instance, + const base::DictionaryValue* schema, + const std::string& path); + + // Validates that the JSON node |instance| has |expected_type|. + bool ValidateType(const base::Value* instance, + const std::string& expected_type, + const std::string& path); + + // Returns true if |schema| will allow additional items of any type. + bool SchemaAllowsAnyAdditionalItems( + const base::DictionaryValue* schema, + const base::DictionaryValue** addition_items_schema); + + // The root schema node. + base::DictionaryValue* schema_root_; + + // Map of user-defined name to type. + TypeMap types_; + + // Whether we allow additional properties on objects by default. This can be + // overridden by the allow_additional_properties flag on an Object schema. + bool default_allow_additional_properties_; + + // Errors accumulated since the last call to Validate(). + std::vector<Error> errors_; + + + DISALLOW_COPY_AND_ASSIGN(JSONSchemaValidator); +}; + +#endif // COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_H_ diff --git a/chromium/components/json_schema/json_schema_validator_unittest.cc b/chromium/components/json_schema/json_schema_validator_unittest.cc new file mode 100644 index 00000000000..4844ed1a888 --- /dev/null +++ b/chromium/components/json_schema/json_schema_validator_unittest.cc @@ -0,0 +1,129 @@ +// 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 "base/values.h" +#include "components/json_schema/json_schema_validator.h" +#include "components/json_schema/json_schema_validator_unittest_base.h" +#include "testing/gtest/include/gtest/gtest.h" + +class JSONSchemaValidatorCPPTest : public JSONSchemaValidatorTestBase { + public: + JSONSchemaValidatorCPPTest() + : JSONSchemaValidatorTestBase(JSONSchemaValidatorTestBase::CPP) { + } + + protected: + virtual void ExpectValid(const std::string& test_source, + base::Value* instance, + base::DictionaryValue* schema, + base::ListValue* types) OVERRIDE { + JSONSchemaValidator validator(schema, types); + if (validator.Validate(instance)) + return; + + for (size_t i = 0; i < validator.errors().size(); ++i) { + ADD_FAILURE() << test_source << ": " + << validator.errors()[i].path << ": " + << validator.errors()[i].message; + } + } + + virtual void ExpectNotValid( + const std::string& test_source, + base::Value* instance, base::DictionaryValue* schema, + base::ListValue* types, + const std::string& expected_error_path, + const std::string& expected_error_message) OVERRIDE { + JSONSchemaValidator validator(schema, types); + if (validator.Validate(instance)) { + ADD_FAILURE() << test_source; + return; + } + + ASSERT_EQ(1u, validator.errors().size()) << test_source; + EXPECT_EQ(expected_error_path, validator.errors()[0].path) << test_source; + EXPECT_EQ(expected_error_message, validator.errors()[0].message) + << test_source; + } +}; + +TEST_F(JSONSchemaValidatorCPPTest, Test) { + RunTests(); +} + +TEST(JSONSchemaValidator, IsValidSchema) { + std::string error; + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("\0", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("string", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("\"string\"", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("[]", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema("{}", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{ \"type\": 123 }", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{ \"type\": \"invalid\" }", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{" + " \"type\": \"object\"," + " \"properties\": []" // Invalid properties type. + "}", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{" + " \"type\": \"string\"," + " \"maxLength\": -1" // Must be >= 0. + "}", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{" + " \"type\": \"string\"," + " \"enum\": [ {} ]," // "enum" must contain simple values. + "}", &error)); + EXPECT_FALSE(JSONSchemaValidator::IsValidSchema( + "{" + " \"type\": \"array\"," + " \"items\": [ 123 ]," // "items" must contain a schema or schemas. + "}", &error)); + EXPECT_TRUE(JSONSchemaValidator::IsValidSchema( + "{ \"type\": \"object\" }", &error)) << error; + EXPECT_TRUE(JSONSchemaValidator::IsValidSchema( + "{ \"type\": [\"object\", \"array\"] }", &error)) << error; + EXPECT_TRUE(JSONSchemaValidator::IsValidSchema( + "{" + " \"type\": [\"object\", \"array\"]," + " \"properties\": {" + " \"string-property\": {" + " \"type\": \"string\"," + " \"minLength\": 1," + " \"maxLength\": 100," + " \"title\": \"The String Policy\"," + " \"description\": \"This policy controls the String widget.\"" + " }," + " \"integer-property\": {" + " \"type\": \"number\"," + " \"minimum\": 1000.0," + " \"maximum\": 9999.0" + " }," + " \"enum-property\": {" + " \"type\": \"integer\"," + " \"enum\": [0, 1, 10, 100]" + " }," + " \"items-property\": {" + " \"type\": \"array\"," + " \"items\": {" + " \"type\": \"string\"" + " }" + " }," + " \"items-list-property\": {" + " \"type\": \"array\"," + " \"items\": [" + " { \"type\": \"string\" }," + " { \"type\": \"integer\" }" + " ]" + " }" + " }," + " \"additionalProperties\": {" + " \"type\": \"any\"" + " }" + "}", &error)) << error; +} diff --git a/chromium/components/json_schema/json_schema_validator_unittest_base.cc b/chromium/components/json_schema/json_schema_validator_unittest_base.cc new file mode 100644 index 00000000000..2e936a2eb9a --- /dev/null +++ b/chromium/components/json_schema/json_schema_validator_unittest_base.cc @@ -0,0 +1,730 @@ +// 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/json_schema/json_schema_validator_unittest_base.h" + +#include <cfloat> +#include <cmath> +#include <limits> + +#include "base/base_paths.h" +#include "base/file_util.h" +#include "base/json/json_file_value_serializer.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/path_service.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "components/json_schema/json_schema_constants.h" +#include "components/json_schema/json_schema_validator.h" + +namespace schema = json_schema_constants; + +namespace { + +#define TEST_SOURCE base::StringPrintf("%s:%i", __FILE__, __LINE__) + +base::Value* LoadValue(const std::string& filename) { + base::FilePath path; + PathService::Get(base::DIR_SOURCE_ROOT, &path); + path = path.AppendASCII("components") + .AppendASCII("test") + .AppendASCII("data") + .AppendASCII("json_schema") + .AppendASCII(filename); + EXPECT_TRUE(base::PathExists(path)); + + std::string error_message; + JSONFileValueSerializer serializer(path); + base::Value* result = serializer.Deserialize(NULL, &error_message); + if (!result) + ADD_FAILURE() << "Could not parse JSON: " << error_message; + return result; +} + +base::Value* LoadValue(const std::string& filename, base::Value::Type type) { + scoped_ptr<base::Value> result(LoadValue(filename)); + if (!result.get()) + return NULL; + if (!result->IsType(type)) { + ADD_FAILURE() << "Expected type " << type << ", got: " << result->GetType(); + return NULL; + } + return result.release(); +} + +base::ListValue* LoadList(const std::string& filename) { + return static_cast<base::ListValue*>( + LoadValue(filename, base::Value::TYPE_LIST)); +} + +base::DictionaryValue* LoadDictionary(const std::string& filename) { + return static_cast<base::DictionaryValue*>( + LoadValue(filename, base::Value::TYPE_DICTIONARY)); +} + +} // namespace + + +JSONSchemaValidatorTestBase::JSONSchemaValidatorTestBase( + JSONSchemaValidatorTestBase::ValidatorType type) + : type_(type) { +} + +void JSONSchemaValidatorTestBase::RunTests() { + TestComplex(); + TestStringPattern(); + TestEnum(); + TestChoices(); + TestExtends(); + TestObject(); + TestTypeReference(); + TestArrayTuple(); + TestArrayNonTuple(); + TestString(); + TestNumber(); + TestTypeClassifier(); + TestTypes(); +} + +void JSONSchemaValidatorTestBase::TestComplex() { + scoped_ptr<base::DictionaryValue> schema( + LoadDictionary("complex_schema.json")); + scoped_ptr<base::ListValue> instance(LoadList("complex_instance.json")); + + ASSERT_TRUE(schema.get()); + ASSERT_TRUE(instance.get()); + + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Remove(instance->GetSize() - 1, NULL); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Append(new base::DictionaryValue()); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "1", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kNumber, + schema::kObject)); + instance->Remove(instance->GetSize() - 1, NULL); + + base::DictionaryValue* item = NULL; + ASSERT_TRUE(instance->GetDictionary(0, &item)); + item->SetString("url", "xxxxxxxxxxx"); + + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, + "0.url", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kStringMaxLength, "10")); +} + +void JSONSchemaValidatorTestBase::TestStringPattern() { + // Regex patterns not supported in CPP validator. + if (type_ == CPP) + return; + + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kString); + schema->SetString(schema::kPattern, "foo+"); + + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("foo")).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("foooooo")).get(), + schema.get(), NULL); + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("bar")).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kStringPattern, "foo+")); +} + +void JSONSchemaValidatorTestBase::TestEnum() { + scoped_ptr<base::DictionaryValue> schema(LoadDictionary("enum_schema.json")); + + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("foo")).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(false)).get(), + schema.get(), NULL); + + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("42")).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidEnum); + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(base::Value::CreateNullValue()).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidEnum); +} + +void JSONSchemaValidatorTestBase::TestChoices() { + scoped_ptr<base::DictionaryValue> schema( + LoadDictionary("choices_schema.json")); + + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(base::Value::CreateNullValue()).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + + scoped_ptr<base::DictionaryValue> instance(new base::DictionaryValue()); + instance->SetString("foo", "bar"); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("foo")).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidChoice); + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::ListValue()).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidChoice); + + instance->SetInteger("foo", 42); + ExpectNotValid(TEST_SOURCE, + instance.get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidChoice); +} + +void JSONSchemaValidatorTestBase::TestExtends() { + // TODO(aa): JS only +} + +void JSONSchemaValidatorTestBase::TestObject() { + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kObject); + schema->SetString("properties.foo.type", schema::kString); + schema->SetString("properties.bar.type", schema::kInteger); + + scoped_ptr<base::DictionaryValue> instance(new base::DictionaryValue()); + instance->SetString("foo", "foo"); + instance->SetInteger("bar", 42); + + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + instance->SetBoolean("extra", true); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, + "extra", JSONSchemaValidator::kUnexpectedProperty); + + instance->Remove("extra", NULL); + instance->Remove("bar", NULL); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "bar", + JSONSchemaValidator::kObjectPropertyIsRequired); + + instance->SetString("bar", "42"); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "bar", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kInteger, + schema::kString)); + + base::DictionaryValue* additional_properties = new base::DictionaryValue(); + additional_properties->SetString(schema::kType, schema::kAny); + schema->Set(schema::kAdditionalProperties, additional_properties); + + instance->SetInteger("bar", 42); + instance->SetBoolean("extra", true); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + instance->SetString("extra", "foo"); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + additional_properties->SetString(schema::kType, schema::kBoolean); + instance->SetBoolean("extra", true); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + instance->SetString("extra", "foo"); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, + "extra", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kBoolean, + schema::kString)); + + base::DictionaryValue* properties = NULL; + base::DictionaryValue* bar_property = NULL; + ASSERT_TRUE(schema->GetDictionary(schema::kProperties, &properties)); + ASSERT_TRUE(properties->GetDictionary("bar", &bar_property)); + + bar_property->SetBoolean(schema::kOptional, true); + instance->Remove("extra", NULL); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Remove("bar", NULL); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Set("bar", base::Value::CreateNullValue()); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, + "bar", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kInteger, + schema::kNull)); + instance->SetString("bar", "42"); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, + "bar", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kInteger, + schema::kString)); +} + +void JSONSchemaValidatorTestBase::TestTypeReference() { + scoped_ptr<base::ListValue> types(LoadList("reference_types.json")); + ASSERT_TRUE(types.get()); + + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kObject); + schema->SetString("properties.foo.type", schema::kString); + schema->SetString("properties.bar.$ref", "Max10Int"); + schema->SetString("properties.baz.$ref", "MinLengthString"); + + scoped_ptr<base::DictionaryValue> schema_inline(new base::DictionaryValue()); + schema_inline->SetString(schema::kType, schema::kObject); + schema_inline->SetString("properties.foo.type", schema::kString); + schema_inline->SetString("properties.bar.id", "NegativeInt"); + schema_inline->SetString("properties.bar.type", schema::kInteger); + schema_inline->SetInteger("properties.bar.maximum", 0); + schema_inline->SetString("properties.baz.$ref", "NegativeInt"); + + scoped_ptr<base::DictionaryValue> instance(new base::DictionaryValue()); + instance->SetString("foo", "foo"); + instance->SetInteger("bar", 4); + instance->SetString("baz", "ab"); + + scoped_ptr<base::DictionaryValue> instance_inline( + new base::DictionaryValue()); + instance_inline->SetString("foo", "foo"); + instance_inline->SetInteger("bar", -4); + instance_inline->SetInteger("baz", -2); + + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), types.get()); + ExpectValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(), NULL); + + // Validation failure, but successful schema reference. + instance->SetString("baz", "a"); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), types.get(), + "baz", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kStringMinLength, "2")); + + instance_inline->SetInteger("bar", 20); + ExpectNotValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(), NULL, + "bar", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kNumberMaximum, "0")); + + // Remove MinLengthString type. + types->Remove(types->GetSize() - 1, NULL); + instance->SetString("baz", "ab"); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), types.get(), + "bar", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kUnknownTypeReference, + "Max10Int")); + + // Remove internal type "NegativeInt". + schema_inline->Remove("properties.bar", NULL); + instance_inline->Remove("bar", NULL); + ExpectNotValid(TEST_SOURCE, instance_inline.get(), schema_inline.get(), NULL, + "baz", JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kUnknownTypeReference, + "NegativeInt")); +} + +void JSONSchemaValidatorTestBase::TestArrayTuple() { + scoped_ptr<base::DictionaryValue> schema( + LoadDictionary("array_tuple_schema.json")); + ASSERT_TRUE(schema.get()); + + scoped_ptr<base::ListValue> instance(new base::ListValue()); + instance->Append(new base::StringValue("42")); + instance->Append(new base::FundamentalValue(42)); + + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + instance->Append(new base::StringValue("anything")); + ExpectNotValid(TEST_SOURCE, + instance.get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kArrayMaxItems, "2")); + + instance->Remove(1, NULL); + instance->Remove(1, NULL); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "1", + JSONSchemaValidator::kArrayItemRequired); + + instance->Set(0, new base::FundamentalValue(42)); + instance->Append(new base::FundamentalValue(42)); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "0", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kString, + schema::kInteger)); + + base::DictionaryValue* additional_properties = new base::DictionaryValue(); + additional_properties->SetString(schema::kType, schema::kAny); + schema->Set(schema::kAdditionalProperties, additional_properties); + instance->Set(0, new base::StringValue("42")); + instance->Append(new base::StringValue("anything")); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Set(2, new base::ListValue()); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + additional_properties->SetString(schema::kType, schema::kBoolean); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "2", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kBoolean, + schema::kArray)); + instance->Set(2, new base::FundamentalValue(false)); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + base::ListValue* items_schema = NULL; + base::DictionaryValue* item0_schema = NULL; + ASSERT_TRUE(schema->GetList(schema::kItems, &items_schema)); + ASSERT_TRUE(items_schema->GetDictionary(0, &item0_schema)); + item0_schema->SetBoolean(schema::kOptional, true); + instance->Remove(2, NULL); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + // TODO(aa): I think this is inconsistent with the handling of NULL+optional + // for objects. + instance->Set(0, base::Value::CreateNullValue()); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Set(0, new base::FundamentalValue(42)); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "0", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kString, + schema::kInteger)); +} + +void JSONSchemaValidatorTestBase::TestArrayNonTuple() { + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kArray); + schema->SetString("items.type", schema::kString); + schema->SetInteger(schema::kMinItems, 2); + schema->SetInteger(schema::kMaxItems, 3); + + scoped_ptr<base::ListValue> instance(new base::ListValue()); + instance->Append(new base::StringValue("x")); + instance->Append(new base::StringValue("x")); + + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + instance->Append(new base::StringValue("x")); + ExpectValid(TEST_SOURCE, instance.get(), schema.get(), NULL); + + instance->Append(new base::StringValue("x")); + ExpectNotValid(TEST_SOURCE, + instance.get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kArrayMaxItems, "3")); + instance->Remove(1, NULL); + instance->Remove(1, NULL); + instance->Remove(1, NULL); + ExpectNotValid(TEST_SOURCE, + instance.get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kArrayMinItems, "2")); + + instance->Remove(1, NULL); + instance->Append(new base::FundamentalValue(42)); + ExpectNotValid(TEST_SOURCE, instance.get(), schema.get(), NULL, "1", + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, + schema::kString, + schema::kInteger)); +} + +void JSONSchemaValidatorTestBase::TestString() { + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kString); + schema->SetInteger(schema::kMinLength, 1); + schema->SetInteger(schema::kMaxLength, 10); + + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("x")).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>( + new base::StringValue("xxxxxxxxxx")).get(), + schema.get(), NULL); + + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue(std::string())).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kStringMinLength, "1")); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("xxxxxxxxxxx")).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kStringMaxLength, "10")); +} + +void JSONSchemaValidatorTestBase::TestNumber() { + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + schema->SetString(schema::kType, schema::kNumber); + schema->SetInteger(schema::kMinimum, 1); + schema->SetInteger(schema::kMaximum, 100); + schema->SetInteger("maxDecimal", 2); + + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(1)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(50)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(100)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(88.88)).get(), + schema.get(), NULL); + + ExpectNotValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(0.5)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kNumberMinimum, "1")); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(100.1)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kNumberMaximum, "100")); +} + +void JSONSchemaValidatorTestBase::TestTypeClassifier() { + EXPECT_EQ(std::string(schema::kBoolean), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>( + new base::FundamentalValue(true)).get())); + EXPECT_EQ(std::string(schema::kBoolean), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>( + new base::FundamentalValue(false)).get())); + + // It doesn't matter whether the C++ type is 'integer' or 'real'. If the + // number is integral and within the representable range of integers in + // double, it's classified as 'integer'. + EXPECT_EQ(std::string(schema::kInteger), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get())); + EXPECT_EQ(std::string(schema::kInteger), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::FundamentalValue(0)).get())); + EXPECT_EQ(std::string(schema::kInteger), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get())); + EXPECT_EQ(std::string(schema::kInteger), + JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<base::Value>( + new base::FundamentalValue(pow(2.0, DBL_MANT_DIG))).get())); + EXPECT_EQ(std::string(schema::kInteger), + JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<base::Value>( + new base::FundamentalValue(pow(-2.0, DBL_MANT_DIG))).get())); + + // "number" is only used for non-integral numbers, or numbers beyond what + // double can accurately represent. + EXPECT_EQ(std::string(schema::kNumber), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>( + new base::FundamentalValue(88.8)).get())); + EXPECT_EQ(std::string(schema::kNumber), + JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<base::Value>( + new base::FundamentalValue(pow(2.0, DBL_MANT_DIG) * 2)).get())); + EXPECT_EQ(std::string(schema::kNumber), + JSONSchemaValidator::GetJSONSchemaType(scoped_ptr<base::Value>( + new base::FundamentalValue( + pow(-2.0, DBL_MANT_DIG) * 2)).get())); + + EXPECT_EQ(std::string(schema::kString), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::StringValue("foo")).get())); + EXPECT_EQ(std::string(schema::kArray), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::ListValue()).get())); + EXPECT_EQ(std::string(schema::kObject), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(new base::DictionaryValue()).get())); + EXPECT_EQ(std::string(schema::kNull), + JSONSchemaValidator::GetJSONSchemaType( + scoped_ptr<base::Value>(base::Value::CreateNullValue()).get())); +} + +void JSONSchemaValidatorTestBase::TestTypes() { + scoped_ptr<base::DictionaryValue> schema(new base::DictionaryValue()); + + // valid + schema->SetString(schema::kType, schema::kObject); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::DictionaryValue()).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kArray); + ExpectValid(TEST_SOURCE, scoped_ptr<base::Value>(new base::ListValue()).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kString); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("foobar")).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kNumber); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(88.8)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(0)).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kInteger); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(0)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>( + new base::FundamentalValue(pow(2.0, DBL_MANT_DIG))).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>( + new base::FundamentalValue(pow(-2.0, DBL_MANT_DIG))).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kBoolean); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(false)).get(), + schema.get(), NULL); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(true)).get(), + schema.get(), NULL); + + schema->SetString(schema::kType, schema::kNull); + ExpectValid(TEST_SOURCE, + scoped_ptr<base::Value>(base::Value::CreateNullValue()).get(), + schema.get(), NULL); + + // not valid + schema->SetString(schema::kType, schema::kObject); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::ListValue()).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, schema::kObject, schema::kArray)); + + schema->SetString(schema::kType, schema::kObject); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(base::Value::CreateNullValue()).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, schema::kObject, schema::kNull)); + + schema->SetString(schema::kType, schema::kArray); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, schema::kArray, schema::kInteger)); + + schema->SetString(schema::kType, schema::kString); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(42)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage(JSONSchemaValidator::kInvalidType, + schema::kString, + schema::kInteger)); + + schema->SetString(schema::kType, schema::kNumber); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::StringValue("42")).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, schema::kNumber, schema::kString)); + + schema->SetString(schema::kType, schema::kInteger); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(88.8)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::kInvalidTypeIntegerNumber); + + schema->SetString(schema::kType, schema::kBoolean); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(1)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage(JSONSchemaValidator::kInvalidType, + schema::kBoolean, + schema::kInteger)); + + schema->SetString(schema::kType, schema::kNull); + ExpectNotValid( + TEST_SOURCE, + scoped_ptr<base::Value>(new base::FundamentalValue(false)).get(), + schema.get(), + NULL, + std::string(), + JSONSchemaValidator::FormatErrorMessage( + JSONSchemaValidator::kInvalidType, schema::kNull, schema::kBoolean)); +} diff --git a/chromium/components/json_schema/json_schema_validator_unittest_base.h b/chromium/components/json_schema/json_schema_validator_unittest_base.h new file mode 100644 index 00000000000..7b4854e21e8 --- /dev/null +++ b/chromium/components/json_schema/json_schema_validator_unittest_base.h @@ -0,0 +1,63 @@ +// 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_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_ +#define COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_ + +#include "testing/gtest/include/gtest/gtest.h" + +namespace base { +class DictionaryValue; +class ListValue; +class Value; +} + +// Base class for unit tests for JSONSchemaValidator. There is currently only +// one implementation, JSONSchemaValidatorCPPTest. +// +// TODO(aa): Refactor chrome/test/data/json_schema_test.js into +// JSONSchemaValidatorJSTest that inherits from this. +class JSONSchemaValidatorTestBase : public testing::Test { + public: + enum ValidatorType { + CPP = 1, + JS = 2 + }; + + explicit JSONSchemaValidatorTestBase(ValidatorType type); + + void RunTests(); + + protected: + virtual void ExpectValid(const std::string& test_source, + base::Value* instance, + base::DictionaryValue* schema, + base::ListValue* types) = 0; + + virtual void ExpectNotValid(const std::string& test_source, + base::Value* instance, + base::DictionaryValue* schema, + base::ListValue* types, + const std::string& expected_error_path, + const std::string& expected_error_message) = 0; + + private: + void TestComplex(); + void TestStringPattern(); + void TestEnum(); + void TestChoices(); + void TestExtends(); + void TestObject(); + void TestTypeReference(); + void TestArrayTuple(); + void TestArrayNonTuple(); + void TestString(); + void TestNumber(); + void TestTypeClassifier(); + void TestTypes(); + + ValidatorType type_; +}; + +#endif // COMPONENTS_JSON_SCHEMA_JSON_SCHEMA_VALIDATOR_UNITTEST_BASE_H_ diff --git a/chromium/components/nacl.gyp b/chromium/components/nacl.gyp new file mode 100644 index 00000000000..f3ded8354a8 --- /dev/null +++ b/chromium/components/nacl.gyp @@ -0,0 +1,167 @@ +# 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. + +{ + 'variables': { + 'chromium_code': 1, + }, + 'includes': [ + '../native_client/build/untrusted.gypi', + 'nacl/nacl_defines.gypi', + ], + 'target_defaults': { + 'variables': { + 'nacl_target': 0, + }, + 'target_conditions': [ + # This part is shared between the targets defined below. Only files and + # settings relevant for building the Win64 target should be added here. + ['nacl_target==1', { + 'include_dirs': [ + '<(INTERMEDIATE_DIR)', + ], + 'defines': [ + '<@(nacl_defines)', + ], + 'sources': [ + # .cc, .h, and .mm files under nacl that are used on all + # platforms, including both 32-bit and 64-bit Windows. + # Test files are also not included. + 'nacl/loader/nacl_ipc_adapter.cc', + 'nacl/loader/nacl_ipc_adapter.h', + 'nacl/loader/nacl_main.cc', + 'nacl/loader/nacl_main_platform_delegate.h', + 'nacl/loader/nacl_main_platform_delegate_linux.cc', + 'nacl/loader/nacl_main_platform_delegate_mac.mm', + 'nacl/loader/nacl_main_platform_delegate_win.cc', + 'nacl/loader/nacl_listener.cc', + 'nacl/loader/nacl_listener.h', + 'nacl/loader/nacl_validation_db.h', + 'nacl/loader/nacl_validation_query.cc', + 'nacl/loader/nacl_validation_query.h', + ], + # TODO(gregoryd): consider switching NaCl to use Chrome OS defines + 'conditions': [ + ['OS=="win"', { + 'defines': [ + '__STDC_LIMIT_MACROS=1', + ], + 'include_dirs': [ + '<(DEPTH)/third_party/wtl/include', + ], + },], + ['OS=="linux"', { + 'defines': [ + '__STDC_LIMIT_MACROS=1', + ], + 'sources': [ + '../components/nacl/common/nacl_paths.cc', + '../components/nacl/common/nacl_paths.h', + '../components/nacl/zygote/nacl_fork_delegate_linux.cc', + '../components/nacl/zygote/nacl_fork_delegate_linux.h', + ], + },], + ], + }], + ], + }, + 'conditions': [ + ['disable_nacl!=1', { + 'targets': [ + { + 'target_name': 'nacl', + 'type': 'static_library', + 'variables': { + 'nacl_target': 1, + }, + 'dependencies': [ + '../base/base.gyp:base', + '../ipc/ipc.gyp:ipc', + '../ppapi/native_client/src/trusted/plugin/plugin.gyp:ppGoogleNaClPluginChrome', + '../ppapi/ppapi_internal.gyp:ppapi_shared', + '../ppapi/ppapi_internal.gyp:ppapi_ipc', + '../native_client/src/trusted/service_runtime/service_runtime.gyp:sel_main_chrome', + ], + 'conditions': [ + ['disable_nacl_untrusted==0', { + 'dependencies': [ + '../ppapi/native_client/native_client.gyp:nacl_irt', + '../ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_irt_shim.gyp:pnacl_irt_shim', + '../ppapi/native_client/src/untrusted/pnacl_support_extension/pnacl_support_extension.gyp:pnacl_support_extension', + ], + }], + ], + 'direct_dependent_settings': { + 'defines': [ + '<@(nacl_defines)', + ], + }, + }, + ], + 'conditions': [ + ['OS=="win" and target_arch=="ia32"', { + 'targets': [ + { + 'target_name': 'nacl_win64', + 'type': 'static_library', + 'variables': { + 'nacl_target': 1, + }, + 'dependencies': [ + '../native_client/src/trusted/service_runtime/service_runtime.gyp:sel_main_chrome64', + '../ppapi/ppapi_internal.gyp:ppapi_shared_win64', + '../ppapi/ppapi_internal.gyp:ppapi_ipc_win64', + '../components/nacl_common.gyp:nacl_common_win64', + ], + 'export_dependent_settings': [ + '../ppapi/ppapi_internal.gyp:ppapi_ipc_win64', + ], + 'sources': [ + '../components/nacl/broker/nacl_broker_listener.cc', + '../components/nacl/broker/nacl_broker_listener.h', + '../components/nacl/common/nacl_debug_exception_handler_win.cc', + ], + 'include_dirs': [ + '..', + ], + 'defines': [ + '<@(nacl_win64_defines)', + 'COMPILE_CONTENT_STATICALLY', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + 'direct_dependent_settings': { + 'defines': [ + '<@(nacl_defines)', + ], + }, + }, + ], + }], + ], + }, { # else (disable_nacl==1) + 'targets': [ + { + 'target_name': 'nacl', + 'type': 'none', + 'sources': [], + }, + ], + 'conditions': [ + ['OS=="win"', { + 'targets': [ + { + 'target_name': 'nacl_win64', + 'type': 'none', + 'sources': [], + }, + ], + }], + ], + }], + ], +} diff --git a/chromium/components/nacl/OWNERS b/chromium/components/nacl/OWNERS new file mode 100644 index 00000000000..7b60d6b9fc0 --- /dev/null +++ b/chromium/components/nacl/OWNERS @@ -0,0 +1,7 @@ +bradchen@chromium.org +bradnelson@chromium.org +jvoung@chromium.org +mseaborn@chromium.org +noelallen@chromium.org +sehr@chromium.org + diff --git a/chromium/components/nacl/broker/DEPS b/chromium/components/nacl/broker/DEPS new file mode 100644 index 00000000000..cd726c6aae0 --- /dev/null +++ b/chromium/components/nacl/broker/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+content/public/app/startup_helper_win.h", + "+sandbox/win/src", +] diff --git a/chromium/components/nacl/broker/nacl_broker_listener.cc b/chromium/components/nacl/broker/nacl_broker_listener.cc new file mode 100644 index 00000000000..cc365d64a7f --- /dev/null +++ b/chromium/components/nacl/broker/nacl_broker_listener.cc @@ -0,0 +1,129 @@ +// 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/nacl/broker/nacl_broker_listener.h" + +#include "base/base_switches.h" +#include "base/bind.h" +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/path_service.h" +#include "components/nacl/common/nacl_cmd_line.h" +#include "components/nacl/common/nacl_debug_exception_handler_win.h" +#include "components/nacl/common/nacl_messages.h" +#include "components/nacl/common/nacl_switches.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/sandbox_init.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_switches.h" +#include "sandbox/win/src/sandbox_policy.h" + +namespace { + +void SendReply(IPC::Channel* channel, int32 pid, bool result) { + channel->Send(new NaClProcessMsg_DebugExceptionHandlerLaunched(pid, result)); +} + +} // namespace + +NaClBrokerListener::NaClBrokerListener() + : browser_handle_(base::kNullProcessHandle) { +} + +NaClBrokerListener::~NaClBrokerListener() { + base::CloseProcessHandle(browser_handle_); +} + +void NaClBrokerListener::Listen() { + std::string channel_name = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kProcessChannelID); + channel_.reset(new IPC::Channel( + channel_name, IPC::Channel::MODE_CLIENT, this)); + CHECK(channel_->Connect()); + base::MessageLoop::current()->Run(); +} + +// NOTE: changes to this method need to be reviewed by the security team. +void NaClBrokerListener::PreSpawnTarget(sandbox::TargetPolicy* policy, + bool* success) { + // This code is duplicated in chrome_content_browser_client.cc. + + // Allow the server side of a pipe restricted to the "chrome.nacl." + // namespace so that it cannot impersonate other system or other chrome + // service pipes. + sandbox::ResultCode result = policy->AddRule( + sandbox::TargetPolicy::SUBSYS_NAMED_PIPES, + sandbox::TargetPolicy::NAMEDPIPES_ALLOW_ANY, + L"\\\\.\\pipe\\chrome.nacl.*"); + *success = (result == sandbox::SBOX_ALL_OK); +} + +void NaClBrokerListener::OnChannelConnected(int32 peer_pid) { + bool res = base::OpenPrivilegedProcessHandle(peer_pid, &browser_handle_); + CHECK(res); +} + +bool NaClBrokerListener::OnMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(NaClBrokerListener, msg) + IPC_MESSAGE_HANDLER(NaClProcessMsg_LaunchLoaderThroughBroker, + OnLaunchLoaderThroughBroker) + IPC_MESSAGE_HANDLER(NaClProcessMsg_LaunchDebugExceptionHandler, + OnLaunchDebugExceptionHandler) + IPC_MESSAGE_HANDLER(NaClProcessMsg_StopBroker, OnStopBroker) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void NaClBrokerListener::OnChannelError() { + // The browser died unexpectedly, quit to avoid a zombie process. + base::MessageLoop::current()->Quit(); +} + +void NaClBrokerListener::OnLaunchLoaderThroughBroker( + const std::string& loader_channel_id) { + base::ProcessHandle loader_process = 0; + base::ProcessHandle loader_handle_in_browser = 0; + + // Create the path to the nacl broker/loader executable - it's the executable + // this code is running in. + base::FilePath exe_path; + PathService::Get(base::FILE_EXE, &exe_path); + if (!exe_path.empty()) { + CommandLine* cmd_line = new CommandLine(exe_path); + nacl::CopyNaClCommandLineArguments(cmd_line); + + cmd_line->AppendSwitchASCII(switches::kProcessType, + switches::kNaClLoaderProcess); + + cmd_line->AppendSwitchASCII(switches::kProcessChannelID, + loader_channel_id); + + loader_process = content::StartSandboxedProcess(this, cmd_line); + if (loader_process) { + DuplicateHandle(::GetCurrentProcess(), loader_process, + browser_handle_, &loader_handle_in_browser, + PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION , FALSE, 0); + base::CloseProcessHandle(loader_process); + } + } + channel_->Send(new NaClProcessMsg_LoaderLaunched(loader_channel_id, + loader_handle_in_browser)); +} + +void NaClBrokerListener::OnLaunchDebugExceptionHandler( + int32 pid, base::ProcessHandle process_handle, + const std::string& startup_info) { + NaClStartDebugExceptionHandlerThread( + process_handle, startup_info, + base::MessageLoopProxy::current(), + base::Bind(SendReply, channel_.get(), pid)); +} + +void NaClBrokerListener::OnStopBroker() { + base::MessageLoop::current()->Quit(); +} diff --git a/chromium/components/nacl/broker/nacl_broker_listener.h b/chromium/components/nacl/broker/nacl_broker_listener.h new file mode 100644 index 00000000000..0b0fff5cf8b --- /dev/null +++ b/chromium/components/nacl/broker/nacl_broker_listener.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_NACL_BROKER_NACL_BROKER_LISTENER_H_ +#define COMPONENTS_NACL_BROKER_NACL_BROKER_LISTENER_H_ + +#include "base/memory/scoped_ptr.h" +#include "base/process/process.h" +#include "components/nacl/common/nacl_types.h" +#include "content/public/common/sandboxed_process_launcher_delegate.h" +#include "ipc/ipc_listener.h" + +namespace IPC { +class Channel; +} + +// The BrokerThread class represents the thread that handles the messages from +// the browser process and starts NaCl loader processes. +class NaClBrokerListener : public content::SandboxedProcessLauncherDelegate, + public IPC::Listener { + public: + NaClBrokerListener(); + ~NaClBrokerListener(); + + void Listen(); + + // content::SandboxedProcessLauncherDelegate implementation: + virtual void PreSpawnTarget(sandbox::TargetPolicy* policy, + bool* success) OVERRIDE; + + // IPC::Listener implementation. + virtual void OnChannelConnected(int32 peer_pid) OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE; + virtual void OnChannelError() OVERRIDE; + + private: + void OnLaunchLoaderThroughBroker(const std::string& loader_channel_id); + void OnLaunchDebugExceptionHandler(int32 pid, + base::ProcessHandle process_handle, + const std::string& startup_info); + void OnStopBroker(); + + base::ProcessHandle browser_handle_; + scoped_ptr<IPC::Channel> channel_; + + DISALLOW_COPY_AND_ASSIGN(NaClBrokerListener); +}; + +#endif // COMPONENTS_NACL_BROKER_NACL_BROKER_LISTENER_H_ diff --git a/chromium/components/nacl/common/DEPS b/chromium/components/nacl/common/DEPS new file mode 100644 index 00000000000..9583dc6ba82 --- /dev/null +++ b/chromium/components/nacl/common/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+native_client/src", +] diff --git a/chromium/components/nacl/common/OWNERS b/chromium/components/nacl/common/OWNERS new file mode 100644 index 00000000000..6a8067ecf2f --- /dev/null +++ b/chromium/components/nacl/common/OWNERS @@ -0,0 +1,10 @@ +# Changes to IPC messages require a security review to avoid introducing +# new sandbox escapes. +per-file *_messages*.h=set noparent +per-file *_messages*.h=cdn@chromium.org +per-file *_messages*.h=cevans@chromium.org +per-file *_messages*.h=jln@chromium.org +per-file *_messages*.h=jschuh@chromium.org +per-file *_messages*.h=palmer@chromium.org +per-file *_messages*.h=tsepez@chromium.org + diff --git a/chromium/components/nacl/common/nacl_browser_delegate.h b/chromium/components/nacl/common/nacl_browser_delegate.h new file mode 100644 index 00000000000..6cff071716e --- /dev/null +++ b/chromium/components/nacl/common/nacl_browser_delegate.h @@ -0,0 +1,66 @@ +// 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_NACL_COMMON_NACL_BROWSER_DELEGATE_H_ +#define COMPONENTS_NACL_COMMON_NACL_BROWSER_DELEGATE_H_ + +#include <string> + +#include "base/callback_forward.h" + +namespace base { +class FilePath; +} + +namespace ppapi { +namespace host { +class HostFactory; +} +} + +namespace content { +class BrowserPpapiHost; +} + +// Encapsulates the dependencies of NaCl code on chrome/, to avoid a direct +// dependency on chrome/. +class NaClBrowserDelegate { + public: + virtual ~NaClBrowserDelegate() {} + + // Show an infobar to the user. + virtual void ShowNaClInfobar(int render_process_id, int render_view_id, + int error_id) = 0; + // Returns whether dialogs are allowed. This is used to decide if to add the + // command line switch kNoErrorDialogs. + virtual bool DialogsAreSuppressed() = 0; + // Returns true on success, false otherwise. On success |cache_dir| contains + // the cache directory. On failure, it is not changed. + virtual bool GetCacheDirectory(base::FilePath* cache_dir) = 0; + // Returns true on success, false otherwise. On success |plugin_dir| contains + // the directory where the plugins are located. On failure, it is not + // changed. + virtual bool GetPluginDirectory(base::FilePath* plugin_dir) = 0; + // Returns true on success, false otherwise. On success |pnacl_dir| contains + // the directory where the PNaCl files are located. On failure, it is not + // changed. + virtual bool GetPnaclDirectory(base::FilePath* pnacl_dir) = 0; + // Returns true on success, false otherwise. On success |user_dir| contains + // the user data directory. On failure, it is not changed. + virtual bool GetUserDirectory(base::FilePath* user_dir) = 0; + // Returns the version as a string. This string is used to invalidate + // validator cache entries when Chromium is upgraded + virtual std::string GetVersionString() const = 0; + // Returns a HostFactory that hides the details of its embedder. + virtual ppapi::host::HostFactory* CreatePpapiHostFactory( + content::BrowserPpapiHost* ppapi_host) = 0; + // Install PNaCl if this operation is supported. On success, the |installed| + // callback should be called with true, and on failure (or not supported), + // the |installed| callback should be called with false. + // TODO(jvoung): Add the progress callback as well. + virtual void TryInstallPnacl( + const base::Callback<void(bool)>& installed) = 0; +}; + +#endif // COMPONENTS_NACL_COMMON_NACL_BROWSER_DELEGATE_H_ diff --git a/chromium/components/nacl/common/nacl_cmd_line.cc b/chromium/components/nacl/common/nacl_cmd_line.cc new file mode 100644 index 00000000000..d9bbd6557f4 --- /dev/null +++ b/chromium/components/nacl/common/nacl_cmd_line.cc @@ -0,0 +1,36 @@ +// 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 "base/base_switches.h" +#include "base/basictypes.h" +#include "base/command_line.h" +#include "components/nacl/common/nacl_switches.h" +#include "content/public/common/content_switches.h" + +namespace nacl { + +void CopyNaClCommandLineArguments(CommandLine* cmd_line) { + const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); + + // Propagate the following switches to the NaCl loader command line (along + // with any associated values) if present in the browser command line. + // TODO(gregoryd): check which flags of those below can be supported. + static const char* const kSwitchNames[] = { + switches::kNoSandbox, + switches::kDisableBreakpad, + switches::kFullMemoryCrashReport, + switches::kEnableLogging, + switches::kDisableLogging, + switches::kLoggingLevel, + switches::kEnableDCHECK, + switches::kNoErrorDialogs, +#if defined(OS_MACOSX) + switches::kEnableSandboxLogging, +#endif + }; + cmd_line->CopySwitchesFrom(browser_command_line, kSwitchNames, + arraysize(kSwitchNames)); +} + +} // namespace nacl diff --git a/chromium/components/nacl/common/nacl_cmd_line.h b/chromium/components/nacl/common/nacl_cmd_line.h new file mode 100644 index 00000000000..4e4e7536135 --- /dev/null +++ b/chromium/components/nacl/common/nacl_cmd_line.h @@ -0,0 +1,16 @@ +// 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_NACL_COMMON_NACL_CMD_LINE_H_ +#define COMPONENTS_NACL_COMMON_NACL_CMD_LINE_H_ + +class CommandLine; + +namespace nacl { + // Copy all the relevant arguments from the command line of the current + // process to cmd_line that will be used for launching the NaCl loader/broker. + void CopyNaClCommandLineArguments(CommandLine* cmd_line); +} + +#endif // COMPONENTS_NACL_COMMON_NACL_CMD_LINE_H_ diff --git a/chromium/components/nacl/common/nacl_debug_exception_handler_win.cc b/chromium/components/nacl/common/nacl_debug_exception_handler_win.cc new file mode 100644 index 00000000000..f661391c4f4 --- /dev/null +++ b/chromium/components/nacl/common/nacl_debug_exception_handler_win.cc @@ -0,0 +1,78 @@ +// 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/nacl/common/nacl_debug_exception_handler_win.h" + +#include "base/bind.h" +#include "base/threading/platform_thread.h" +#include "base/win/scoped_handle.h" +#include "native_client/src/trusted/service_runtime/win/debug_exception_handler.h" + +namespace { + +class DebugExceptionHandler : public base::PlatformThread::Delegate { + public: + DebugExceptionHandler(base::ProcessHandle nacl_process, + const std::string& startup_info, + base::MessageLoopProxy* message_loop, + const base::Callback<void(bool)>& on_connected) + : nacl_process_(nacl_process), + startup_info_(startup_info), + message_loop_(message_loop), + on_connected_(on_connected) { + } + + virtual void ThreadMain() OVERRIDE { + // In the Windows API, the set of processes being debugged is + // thread-local, so we have to attach to the process (using + // DebugActiveProcess()) on the same thread on which + // NaClDebugExceptionHandlerRun() receives debug events for the + // process. + bool attached = false; + int pid = GetProcessId(nacl_process_); + if (pid == 0) { + LOG(ERROR) << "Invalid process handle"; + } else { + if (!DebugActiveProcess(pid)) { + LOG(ERROR) << "Failed to connect to the process"; + } else { + attached = true; + } + } + message_loop_->PostTask(FROM_HERE, base::Bind(on_connected_, attached)); + + if (attached) { + NaClDebugExceptionHandlerRun( + nacl_process_, + reinterpret_cast<const void*>(startup_info_.data()), + startup_info_.size()); + } + delete this; + } + + private: + base::win::ScopedHandle nacl_process_; + std::string startup_info_; + base::MessageLoopProxy* message_loop_; + base::Callback<void(bool)> on_connected_; + + DISALLOW_COPY_AND_ASSIGN(DebugExceptionHandler); +}; + +} // namespace + +void NaClStartDebugExceptionHandlerThread( + base::ProcessHandle nacl_process, + const std::string& startup_info, + base::MessageLoopProxy* message_loop, + const base::Callback<void(bool)>& on_connected) { + // The new PlatformThread will take ownership of the + // DebugExceptionHandler object, which will delete itself on exit. + DebugExceptionHandler* handler = new DebugExceptionHandler( + nacl_process, startup_info, message_loop, on_connected); + if (!base::PlatformThread::CreateNonJoinable(0, handler)) { + on_connected.Run(false); + delete handler; + } +} diff --git a/chromium/components/nacl/common/nacl_debug_exception_handler_win.h b/chromium/components/nacl/common/nacl_debug_exception_handler_win.h new file mode 100644 index 00000000000..42beefe8514 --- /dev/null +++ b/chromium/components/nacl/common/nacl_debug_exception_handler_win.h @@ -0,0 +1,18 @@ +// 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_NACL_COMMON_NACL_DEBUG_EXCEPTION_HANDLER_WIN_H_ +#define COMPONENTS_NACL_COMMON_NACL_DEBUG_EXCEPTION_HANDLER_WIN_H_ + +#include "base/callback.h" +#include "base/message_loop/message_loop.h" +#include "base/process/process.h" + +void NaClStartDebugExceptionHandlerThread( + base::ProcessHandle nacl_process, + const std::string& startup_info, + base::MessageLoopProxy* message_loop, + const base::Callback<void(bool)>& on_connected); + +#endif // COMPONENTS_NACL_COMMON_NACL_DEBUG_EXCEPTION_HANDLER_WIN_H_ diff --git a/chromium/components/nacl/common/nacl_helper_linux.h b/chromium/components/nacl/common/nacl_helper_linux.h new file mode 100644 index 00000000000..732b21570a9 --- /dev/null +++ b/chromium/components/nacl/common/nacl_helper_linux.h @@ -0,0 +1,42 @@ +// 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_NACL_COMMON_NACL_HELPER_LINUX_H_ +#define COMPONENTS_NACL_COMMON_NACL_HELPER_LINUX_H_ + +// A mini-zygote specifically for Native Client. This file defines +// constants used to implement communication between the nacl_helper +// process and the Chrome zygote. + +// Used by Helper to tell Zygote it has started successfully. +#define kNaClHelperStartupAck "NACLHELPER_OK" +// Used by Zygote to ask Helper to fork a new NaCl loader. +#define kNaClForkRequest "NACLFORK" + +// The next set of constants define global Linux file descriptors. +// For communications between NaCl loader and browser. +// See also content/common/zygote_main_linux.cc and +// http://code.google.com/p/chromium/wiki/LinuxZygote + +// For communications between NaCl loader and zygote. +#define kNaClZygoteDescriptor 3 +// For communications between the NaCl loader process and +// the SUID sandbox. +#define kNaClSandboxDescriptor 5 +// NOTE: kNaClSandboxDescriptor must match +// content/browser/zygote_main_linux.cc +// kMagicSandboxIPCDescriptor. + +// A fork request from the Zygote to the helper includes an array +// of three file descriptors. These constants are used as indicies +// into the array. +// Used to pass in the descriptor for talking to the Browser +#define kNaClBrowserFDIndex 0 +// The next two are used in the protocol for discovering the +// child processes real PID from within the SUID sandbox. See +// http://code.google.com/p/chromium/wiki/LinuxZygote +#define kNaClDummyFDIndex 1 +#define kNaClParentFDIndex 2 + +#endif // COMPONENTS_NACL_COMMON_NACL_HELPER_LINUX_H_ diff --git a/chromium/components/nacl/common/nacl_host_messages.h b/chromium/components/nacl/common/nacl_host_messages.h new file mode 100644 index 00000000000..d9a835ba28c --- /dev/null +++ b/chromium/components/nacl/common/nacl_host_messages.h @@ -0,0 +1,110 @@ +// 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. + +// Multiply-included file, no traditional include guard. + +#include <string> + +#include "base/basictypes.h" +#include "base/process/process.h" +#include "build/build_config.h" +#include "components/nacl/common/nacl_types.h" +#include "components/nacl/common/pnacl_types.h" +#include "content/public/common/common_param_traits.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_platform_file.h" +#include "url/gurl.h" + +#define IPC_MESSAGE_START NaClHostMsgStart + +IPC_STRUCT_TRAITS_BEGIN(nacl::NaClLaunchParams) + IPC_STRUCT_TRAITS_MEMBER(manifest_url) + IPC_STRUCT_TRAITS_MEMBER(render_view_id) + IPC_STRUCT_TRAITS_MEMBER(permission_bits) + IPC_STRUCT_TRAITS_MEMBER(uses_irt) + IPC_STRUCT_TRAITS_MEMBER(enable_dyncode_syscalls) + IPC_STRUCT_TRAITS_MEMBER(enable_exception_handling) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(nacl::NaClLaunchResult) + IPC_STRUCT_TRAITS_MEMBER(imc_channel_handle) + IPC_STRUCT_TRAITS_MEMBER(ipc_channel_handle) + IPC_STRUCT_TRAITS_MEMBER(plugin_pid) + IPC_STRUCT_TRAITS_MEMBER(plugin_child_id) +IPC_STRUCT_TRAITS_END() + +IPC_STRUCT_TRAITS_BEGIN(nacl::PnaclCacheInfo) + IPC_STRUCT_TRAITS_MEMBER(pexe_url) + IPC_STRUCT_TRAITS_MEMBER(abi_version) + IPC_STRUCT_TRAITS_MEMBER(opt_level) + IPC_STRUCT_TRAITS_MEMBER(last_modified) + IPC_STRUCT_TRAITS_MEMBER(etag) +IPC_STRUCT_TRAITS_END() + +// A renderer sends this to the browser process when it wants to start +// a new instance of the Native Client process. The browser will launch +// the process and return an IPC channel handle. This handle will only +// be valid if the NaCl IPC proxy is enabled. +IPC_SYNC_MESSAGE_CONTROL1_2(NaClHostMsg_LaunchNaCl, + nacl::NaClLaunchParams /* launch_params */, + nacl::NaClLaunchResult /* launch_result */, + std::string /* error_message */) + +// A renderer sends this to the browser process when it wants to +// ensure that PNaCl is installed. +IPC_MESSAGE_CONTROL1(NaClHostMsg_EnsurePnaclInstalled, + int /* pp_instance */) + +// The browser replies to the renderer's request to ensure that +// PNaCl is installed. +IPC_MESSAGE_CONTROL2(NaClViewMsg_EnsurePnaclInstalledReply, + int /* pp_instance */, + bool /* success */) + +// A renderer sends this to the browser process when it wants to +// open a file for from the Pnacl component directory. +IPC_SYNC_MESSAGE_CONTROL1_1(NaClHostMsg_GetReadonlyPnaclFD, + std::string /* name of requested PNaCl file */, + IPC::PlatformFileForTransit /* output file */) + +// A renderer sends this to the browser process when it wants to +// create a temporary file. +IPC_SYNC_MESSAGE_CONTROL0_1(NaClHostMsg_NaClCreateTemporaryFile, + IPC::PlatformFileForTransit /* out file */) + +// A renderer sends this to the browser to request a file descriptor for +// a translated nexe. +IPC_MESSAGE_CONTROL3(NaClHostMsg_NexeTempFileRequest, + int /* render_view_id */, + int /* instance */, + nacl::PnaclCacheInfo /* cache info */) + +// The browser replies to a renderer's temp file request with output_file, +// which is either a writeable temp file to use for translation, or a +// read-only file containing the translated nexe from the cache. +IPC_MESSAGE_CONTROL3(NaClViewMsg_NexeTempFileReply, + int /* instance */, + bool /* is_cache_hit */, + IPC::PlatformFileForTransit /* output file */) + +// A renderer sends this to the browser to report that its translation has +// finished and its temp file contains the translated nexe. +IPC_MESSAGE_CONTROL2(NaClHostMsg_ReportTranslationFinished, + int /* instance */, + bool /* success */) + +// A renderer sends this to the browser process to report an error. +IPC_MESSAGE_CONTROL2(NaClHostMsg_NaClErrorStatus, + int /* render_view_id */, + int /* Error ID */) + +// A renderer sends this to the browser process when it wants to +// open a NaCl executable file from an installed application directory. +IPC_SYNC_MESSAGE_CONTROL2_3(NaClHostMsg_OpenNaClExecutable, + int /* render_view_id */, + GURL /* URL of NaCl executable file */, + IPC::PlatformFileForTransit /* output file */, + uint64 /* file_token_lo */, + uint64 /* file_token_hi */) diff --git a/chromium/components/nacl/common/nacl_messages.cc b/chromium/components/nacl/common/nacl_messages.cc new file mode 100644 index 00000000000..21407368aef --- /dev/null +++ b/chromium/components/nacl/common/nacl_messages.cc @@ -0,0 +1,34 @@ +// 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. + +// Get basic type definitions. +#define IPC_MESSAGE_IMPL +#include "components/nacl/common/nacl_messages.h" + +// Generate constructors. +#include "ipc/struct_constructor_macros.h" +#include "components/nacl/common/nacl_messages.h" + +// Generate destructors. +#include "ipc/struct_destructor_macros.h" +#include "components/nacl/common/nacl_messages.h" + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#include "components/nacl/common/nacl_messages.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#include "components/nacl/common/nacl_messages.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#include "components/nacl/common/nacl_messages.h" +} // namespace IPC + diff --git a/chromium/components/nacl/common/nacl_messages.h b/chromium/components/nacl/common/nacl_messages.h new file mode 100644 index 00000000000..068a0f9cc5e --- /dev/null +++ b/chromium/components/nacl/common/nacl_messages.h @@ -0,0 +1,94 @@ +// 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. + +// Defines messages between the browser and NaCl process. + +// Multiply-included message file, no traditional include guard. +#include "base/process/process.h" +#include "components/nacl/common/nacl_types.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_platform_file.h" + +#define IPC_MESSAGE_START NaClMsgStart + +IPC_STRUCT_TRAITS_BEGIN(nacl::NaClStartParams) + IPC_STRUCT_TRAITS_MEMBER(handles) + IPC_STRUCT_TRAITS_MEMBER(debug_stub_server_bound_socket) + IPC_STRUCT_TRAITS_MEMBER(validation_cache_enabled) + IPC_STRUCT_TRAITS_MEMBER(validation_cache_key) + IPC_STRUCT_TRAITS_MEMBER(version) + IPC_STRUCT_TRAITS_MEMBER(enable_exception_handling) + IPC_STRUCT_TRAITS_MEMBER(enable_debug_stub) + IPC_STRUCT_TRAITS_MEMBER(enable_ipc_proxy) + IPC_STRUCT_TRAITS_MEMBER(uses_irt) + IPC_STRUCT_TRAITS_MEMBER(enable_dyncode_syscalls) +IPC_STRUCT_TRAITS_END() + +//----------------------------------------------------------------------------- +// NaClProcess messages +// These are messages sent between the browser and the NaCl process. +// Tells the NaCl process to start. +IPC_MESSAGE_CONTROL1(NaClProcessMsg_Start, + nacl::NaClStartParams /* params */) + +#if defined(OS_WIN) +// Tells the NaCl broker to launch a NaCl loader process. +IPC_MESSAGE_CONTROL1(NaClProcessMsg_LaunchLoaderThroughBroker, + std::string /* channel ID for the loader */) + +// Notify the browser process that the loader was launched successfully. +IPC_MESSAGE_CONTROL2(NaClProcessMsg_LoaderLaunched, + std::string, /* channel ID for the loader */ + base::ProcessHandle /* loader process handle */) + +// Tells the NaCl broker to attach a debug exception handler to the +// given NaCl loader process. +IPC_MESSAGE_CONTROL3(NaClProcessMsg_LaunchDebugExceptionHandler, + int32 /* pid of the NaCl process */, + base::ProcessHandle /* handle of the NaCl process */, + std::string /* NaCl internal process layout info */) + +// Notify the browser process that the broker process finished +// attaching a debug exception handler to the given NaCl loader +// process. +IPC_MESSAGE_CONTROL2(NaClProcessMsg_DebugExceptionHandlerLaunched, + int32 /* pid */, + bool /* success */) + +// Notify the broker that all loader processes have been terminated and it +// should shutdown. +IPC_MESSAGE_CONTROL0(NaClProcessMsg_StopBroker) + +// Used by the NaCl process to request that a Windows debug exception +// handler be attached to it. +IPC_SYNC_MESSAGE_CONTROL1_1(NaClProcessMsg_AttachDebugExceptionHandler, + std::string, /* Internal process info */ + bool /* Result */) +#endif + +// Used by the NaCl process to query a database in the browser. The database +// contains the signatures of previously validated code chunks. +IPC_SYNC_MESSAGE_CONTROL1_1(NaClProcessMsg_QueryKnownToValidate, + std::string, /* A validation signature */ + bool /* Can validation be skipped? */) + +// Used by the NaCl process to add a validation signature to the validation +// database in the browser. +IPC_MESSAGE_CONTROL1(NaClProcessMsg_SetKnownToValidate, + std::string /* A validation signature */) + +// Used by the NaCl process to acquire trusted information about a file directly +// from the browser, including the file's path as well as a fresh version of the +// file handle. +IPC_SYNC_MESSAGE_CONTROL2_2(NaClProcessMsg_ResolveFileToken, + uint64, /* file_token_lo */ + uint64, /* file_token_hi */ + IPC::PlatformFileForTransit, /* fd */ + base::FilePath /* Path opened to get fd */) + +// Notify the browser process that the server side of the PPAPI channel was +// created successfully. +IPC_MESSAGE_CONTROL1(NaClProcessHostMsg_PpapiChannelCreated, + IPC::ChannelHandle /* channel_handle */) diff --git a/chromium/components/nacl/common/nacl_paths.cc b/chromium/components/nacl/common/nacl_paths.cc new file mode 100644 index 00000000000..cb046c23eac --- /dev/null +++ b/chromium/components/nacl/common/nacl_paths.cc @@ -0,0 +1,53 @@ +// 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/nacl/common/nacl_paths.h" + +#include "base/file_util.h" +#include "base/path_service.h" + +namespace { + +#if defined(OS_POSIX) && !defined(OS_MACOSX) +// File name of the nacl_helper and nacl_helper_bootstrap, Linux only. +const base::FilePath::CharType kInternalNaClHelperFileName[] = + FILE_PATH_LITERAL("nacl_helper"); +const base::FilePath::CharType kInternalNaClHelperBootstrapFileName[] = + FILE_PATH_LITERAL("nacl_helper_bootstrap"); +#endif + +} // namespace + +namespace nacl { + +bool PathProvider(int key, base::FilePath* result) { + base::FilePath cur; + switch (key) { +#if defined(OS_LINUX) + case FILE_NACL_HELPER: + if (!PathService::Get(base::DIR_MODULE, &cur)) + return false; + cur = cur.Append(kInternalNaClHelperFileName); + break; + case FILE_NACL_HELPER_BOOTSTRAP: + if (!PathService::Get(base::DIR_MODULE, &cur)) + return false; + cur = cur.Append(kInternalNaClHelperBootstrapFileName); + break; +#endif + default: + return false; + } + + *result = cur; + return true; +} + +// This cannot be done as a static initializer sadly since Visual Studio will +// eliminate this object file if there is no direct entry point into it. +void RegisterPathProvider() { + PathService::RegisterProvider(PathProvider, PATH_START, PATH_END); +} + +} // namespace nacl diff --git a/chromium/components/nacl/common/nacl_paths.h b/chromium/components/nacl/common/nacl_paths.h new file mode 100644 index 00000000000..424d8bdb487 --- /dev/null +++ b/chromium/components/nacl/common/nacl_paths.h @@ -0,0 +1,31 @@ +// 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_NACL_COMMON_NACL_PATHS_H_ +#define COMPONENTS_NACL_COMMON_NACL_PATHS_H_ + +#include "build/build_config.h" + +// This file declares path keys for the chrome module. These can be used with +// the PathService to access various special directories and files. + +namespace nacl { + +enum { + PATH_START = 9000, + +#if defined(OS_LINUX) + FILE_NACL_HELPER = PATH_START, // Full path to Linux nacl_helper executable. + FILE_NACL_HELPER_BOOTSTRAP, // ... and nacl_helper_bootstrap executable. +#endif + + PATH_END +}; + +// Call once to register the provider for the path keys defined above. +void RegisterPathProvider(); + +} // namespace nacl + +#endif // COMPONENTS_NACL_COMMON_NACL_PATHS_H_ diff --git a/chromium/components/nacl/common/nacl_process_type.h b/chromium/components/nacl/common/nacl_process_type.h new file mode 100644 index 00000000000..779cc80546a --- /dev/null +++ b/chromium/components/nacl/common/nacl_process_type.h @@ -0,0 +1,18 @@ +// 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_NACL_COMMON_NACL_PROCESS_TYPE_H_ +#define COMPONENTS_NACL_COMMON_NACL_PROCESS_TYPE_H_ + +#include "content/public/common/process_type.h" + +// Defines the process types that are custom to NaCl. +enum NaClProcessType { + // Start at +1 because we removed an unused value and didn't want to change + // the IDs as they're used in UMA (see the comment for ProcessType). + PROCESS_TYPE_NACL_LOADER = content::PROCESS_TYPE_CONTENT_END + 1, + PROCESS_TYPE_NACL_BROKER, +}; + +#endif // COMPONENTS_NACL_COMMON_NACL_PROCESS_TYPE_H_ diff --git a/chromium/components/nacl/common/nacl_sandbox_type_mac.h b/chromium/components/nacl/common/nacl_sandbox_type_mac.h new file mode 100644 index 00000000000..b1817f30d4e --- /dev/null +++ b/chromium/components/nacl/common/nacl_sandbox_type_mac.h @@ -0,0 +1,16 @@ +// 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_NACL_COMMON_NACL_SANDBOX_TYPE_MAC_H_ +#define COMPONENTS_NACL_COMMON_NACL_SANDBOX_TYPE_MAC_H_ + +#include "content/public/common/sandbox_type_mac.h" + +enum NaClSandboxType { + NACL_SANDBOX_TYPE_FIRST_TYPE = content::SANDBOX_TYPE_AFTER_LAST_TYPE, + + NACL_SANDBOX_TYPE_NACL_LOADER = NACL_SANDBOX_TYPE_FIRST_TYPE, +}; + +#endif // COMPONENTS_NACL_COMMON_NACL_SANDBOX_TYPE_MAC_H_ diff --git a/chromium/components/nacl/common/nacl_switches.cc b/chromium/components/nacl/common/nacl_switches.cc new file mode 100644 index 00000000000..0dfdc949857 --- /dev/null +++ b/chromium/components/nacl/common/nacl_switches.cc @@ -0,0 +1,39 @@ +// 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/nacl/common/nacl_switches.h" + +namespace switches { + +// Enables debugging via RSP over a socket. +const char kEnableNaClDebug[] = "enable-nacl-debug"; + +// Causes the process to run as a NativeClient broker +// (used for launching NaCl loader processes on 64-bit Windows). +const char kNaClBrokerProcess[] = "nacl-broker"; + +// Uses NaCl manifest URL to choose whether NaCl program will be debugged by +// debug stub. +// Switch value format: [!]pattern1,pattern2,...,patternN. Each pattern uses +// the same syntax as patterns in Chrome extension manifest. The only difference +// is that * scheme matches all schemes instead of matching only http and https. +// If the value doesn't start with !, a program will be debugged if manifest URL +// matches any pattern. If the value starts with !, a program will be debugged +// if manifest URL does not match any pattern. +const char kNaClDebugMask[] = "nacl-debug-mask"; + +// Native Client GDB debugger that will be launched automatically when needed. +const char kNaClGdb[] = "nacl-gdb"; + +// GDB script to pass to the nacl-gdb debugger at startup. +const char kNaClGdbScript[] = "nacl-gdb-script"; + +// On POSIX only: the contents of this flag are prepended to the nacl-loader +// command line. Useful values might be "valgrind" or "xterm -e gdb --args". +const char kNaClLoaderCmdPrefix[] = "nacl-loader-cmd-prefix"; + +// Causes the process to run as a NativeClient loader. +const char kNaClLoaderProcess[] = "nacl-loader"; + +} // namespace switches diff --git a/chromium/components/nacl/common/nacl_switches.h b/chromium/components/nacl/common/nacl_switches.h new file mode 100644 index 00000000000..9bc1bcb4697 --- /dev/null +++ b/chromium/components/nacl/common/nacl_switches.h @@ -0,0 +1,24 @@ +// 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. + +// Defines all the command-line switches used by Chrome. + +#ifndef COMPONENTS_NACL_COMMON_NACL_SWITCHES_H_ +#define COMPONENTS_NACL_COMMON_NACL_SWITCHES_H_ + +namespace switches { + +// All switches in alphabetical order. The switches should be documented +// alongside the definition of their values in the .cc file. +extern const char kEnableNaClDebug[]; +extern const char kNaClBrokerProcess[]; +extern const char kNaClDebugMask[]; +extern const char kNaClGdb[]; +extern const char kNaClGdbScript[]; +extern const char kNaClLoaderCmdPrefix[]; +extern const char kNaClLoaderProcess[]; + +} // namespace switches + +#endif // COMPONENTS_NACL_COMMON_NACL_SWITCHES_H_ diff --git a/chromium/components/nacl/common/nacl_types.cc b/chromium/components/nacl/common/nacl_types.cc new file mode 100644 index 00000000000..dea02391125 --- /dev/null +++ b/chromium/components/nacl/common/nacl_types.cc @@ -0,0 +1,77 @@ +// 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/nacl/common/nacl_types.h" +#include "ipc/ipc_platform_file.h" + +namespace nacl { + +NaClStartParams::NaClStartParams() + : validation_cache_enabled(false), + enable_exception_handling(false), + enable_debug_stub(false), + enable_ipc_proxy(false), + uses_irt(false), + enable_dyncode_syscalls(false) { +} + +NaClStartParams::~NaClStartParams() { +} + +NaClLaunchParams::NaClLaunchParams() + : render_view_id(0), + permission_bits(0), + uses_irt(false), + enable_dyncode_syscalls(false), + enable_exception_handling(false) { +} + +NaClLaunchParams::NaClLaunchParams(const std::string& manifest_url, + int render_view_id, + uint32 permission_bits, + bool uses_irt, + bool enable_dyncode_syscalls, + bool enable_exception_handling) + : manifest_url(manifest_url), + render_view_id(render_view_id), + permission_bits(permission_bits), + uses_irt(uses_irt), + enable_dyncode_syscalls(enable_dyncode_syscalls), + enable_exception_handling(enable_exception_handling) { +} + +NaClLaunchParams::NaClLaunchParams(const NaClLaunchParams& l) { + manifest_url = l.manifest_url; + render_view_id = l.render_view_id; + permission_bits = l.permission_bits; + uses_irt = l.uses_irt; + enable_dyncode_syscalls = l.enable_dyncode_syscalls; + enable_exception_handling = l.enable_exception_handling; +} + +NaClLaunchParams::~NaClLaunchParams() { +} + +NaClLaunchResult::NaClLaunchResult() + : imc_channel_handle(IPC::InvalidPlatformFileForTransit()), + ipc_channel_handle(), + plugin_pid(base::kNullProcessId), + plugin_child_id(0) { +} + +NaClLaunchResult::NaClLaunchResult( + FileDescriptor imc_channel_handle, + const IPC::ChannelHandle& ipc_channel_handle, + base::ProcessId plugin_pid, + int plugin_child_id) + : imc_channel_handle(imc_channel_handle), + ipc_channel_handle(ipc_channel_handle), + plugin_pid(plugin_pid), + plugin_child_id(plugin_child_id) { +} + +NaClLaunchResult::~NaClLaunchResult() { +} + +} // namespace nacl diff --git a/chromium/components/nacl/common/nacl_types.h b/chromium/components/nacl/common/nacl_types.h new file mode 100644 index 00000000000..b3fee25c869 --- /dev/null +++ b/chromium/components/nacl/common/nacl_types.h @@ -0,0 +1,101 @@ +// 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_NACL_COMMON_NACL_TYPES_H_ +#define COMPONENTS_NACL_COMMON_NACL_TYPES_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/process/process_handle.h" +#include "build/build_config.h" +#include "ipc/ipc_channel.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#endif + +#if defined(OS_WIN) +#include <windows.h> // for HANDLE +#endif + +// TODO(gregoryd): add a Windows definition for base::FileDescriptor +namespace nacl { + +#if defined(OS_WIN) +typedef HANDLE FileDescriptor; +inline HANDLE ToNativeHandle(const FileDescriptor& desc) { + return desc; +} +#elif defined(OS_POSIX) +typedef base::FileDescriptor FileDescriptor; +inline int ToNativeHandle(const FileDescriptor& desc) { + return desc.fd; +} +#endif + + +// Parameters sent to the NaCl process when we start it. +// +// If you change this, you will also need to update the IPC serialization in +// nacl_messages.h. +struct NaClStartParams { + NaClStartParams(); + ~NaClStartParams(); + + std::vector<FileDescriptor> handles; + FileDescriptor debug_stub_server_bound_socket; + + bool validation_cache_enabled; + std::string validation_cache_key; + // Chrome version string. Sending the version string over IPC avoids linkage + // issues in cases where NaCl is not compiled into the main Chromium + // executable or DLL. + std::string version; + + bool enable_exception_handling; + bool enable_debug_stub; + bool enable_ipc_proxy; + bool uses_irt; + bool enable_dyncode_syscalls; +}; + +// Parameters sent to the browser process to have it launch a NaCl process. +// +// If you change this, you will also need to update the IPC serialization in +// nacl_host_messages.h. +struct NaClLaunchParams { + NaClLaunchParams(); + NaClLaunchParams(const std::string& u, int r, uint32 p, bool uses_irt, + bool enable_dyncode_syscalls, + bool enable_exception_handling); + NaClLaunchParams(const NaClLaunchParams& l); + ~NaClLaunchParams(); + + std::string manifest_url; + int render_view_id; + uint32 permission_bits; + bool uses_irt; + bool enable_dyncode_syscalls; + bool enable_exception_handling; +}; + +struct NaClLaunchResult { + NaClLaunchResult(); + NaClLaunchResult(FileDescriptor imc_channel_handle, + const IPC::ChannelHandle& ipc_channel_handle, + base::ProcessId plugin_pid, + int plugin_child_id); + ~NaClLaunchResult(); + + FileDescriptor imc_channel_handle; + IPC::ChannelHandle ipc_channel_handle; + base::ProcessId plugin_pid; + int plugin_child_id; +}; + +} // namespace nacl + +#endif // COMPONENTS_NACL_COMMON_NACL_TYPES_H_ diff --git a/chromium/components/nacl/common/pnacl_types.cc b/chromium/components/nacl/common/pnacl_types.cc new file mode 100644 index 00000000000..6c43319c6b2 --- /dev/null +++ b/chromium/components/nacl/common/pnacl_types.cc @@ -0,0 +1,28 @@ +// 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/nacl/common/pnacl_types.h" + +namespace nacl { + +PnaclCacheInfo::PnaclCacheInfo() {} +PnaclCacheInfo::~PnaclCacheInfo() {} + +// static +bool PnaclInstallProgress::progress_known(const PnaclInstallProgress& p) { + return p.total_size >= 0; +} + +// static +PnaclInstallProgress PnaclInstallProgress::Unknown() { + PnaclInstallProgress p; + p.current = 0; + // Use -1 to indicate that total is not determined. + // This matches the -1 of the OnURLFetchDownloadProgress interface in + // net/url_request/url_fetcher_delegate.h + p.total_size = -1; + return p; +} + +} // namespace nacl diff --git a/chromium/components/nacl/common/pnacl_types.h b/chromium/components/nacl/common/pnacl_types.h new file mode 100644 index 00000000000..3fc405a980d --- /dev/null +++ b/chromium/components/nacl/common/pnacl_types.h @@ -0,0 +1,48 @@ +// 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_NACL_COMMON_PNACL_TYPES_H_ +#define COMPONENTS_NACL_COMMON_PNACL_TYPES_H_ + +// This file exists (instead of putting this type into nacl_types.h) because +// nacl_types is built into nacl_helper in addition to chrome, and we don't +// want to pull src/url/ into there, since it would be unnecessary bloat. + +#include "base/basictypes.h" +#include "base/time/time.h" +#include "url/gurl.h" + +namespace nacl { +// Cache-related information about pexe files, sent from the plugin/renderer +// to the browser. +// +// If you change this, you will also need to update the IPC serialization in +// nacl_host_messages.h. +struct PnaclCacheInfo { + PnaclCacheInfo(); + ~PnaclCacheInfo(); + GURL pexe_url; + int abi_version; + int opt_level; + base::Time last_modified; + std::string etag; +}; + +// Progress information for PNaCl on-demand installs. +struct PnaclInstallProgress { + int64 current; + int64 total_size; + + // Returns an instance of PnaclInstallProgress where the + // total is marked as unknown. + static PnaclInstallProgress Unknown(); + + // Returns true if the given instance of PnaclInstallProgress has + // an unknown total. + static bool progress_known(const PnaclInstallProgress& p); +}; + +} // namespace nacl + +#endif // COMPONENTS_NACL_COMMON_PNACL_TYPES_H_ diff --git a/chromium/components/nacl/loader/DEPS b/chromium/components/nacl/loader/DEPS new file mode 100644 index 00000000000..aa045563f80 --- /dev/null +++ b/chromium/components/nacl/loader/DEPS @@ -0,0 +1,17 @@ +include_rules = [ + "+components/nacl", + "+content/public/app/startup_helper_win.h", + "+crypto", + "+sandbox/linux/seccomp-bpf", + "+sandbox/linux/services", + "+sandbox/win/src", + "+native_client/src", + "+ppapi/c", # header files only + + # For handle conversion in nacl_ipc_adapter.cc: + "+ppapi/proxy/handle_converter.h", + "+ppapi/proxy/serialized_handle.h", + + # For sending PpapiHostMsg_ChannelCreated in nacl_ipc_adapter.cc: + "+ppapi/proxy/ppapi_messages.h" +] diff --git a/chromium/components/nacl/loader/OWNERS b/chromium/components/nacl/loader/OWNERS new file mode 100644 index 00000000000..9693b6b0b26 --- /dev/null +++ b/chromium/components/nacl/loader/OWNERS @@ -0,0 +1,2 @@ +per-file nacl_sandbox_linux.*=jln@chromium.org +per-file nacl_sandbox_linux.*=mseaborn@chromium.org diff --git a/chromium/components/nacl/loader/nacl_ipc_adapter.cc b/chromium/components/nacl/loader/nacl_ipc_adapter.cc new file mode 100644 index 00000000000..3399bbbf356 --- /dev/null +++ b/chromium/components/nacl/loader/nacl_ipc_adapter.cc @@ -0,0 +1,592 @@ +// 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/nacl/loader/nacl_ipc_adapter.h" + +#include <limits.h> +#include <string.h> + +#include "base/basictypes.h" +#include "base/bind.h" +#include "base/location.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/shared_memory.h" +#include "build/build_config.h" +#include "ipc/ipc_channel.h" +#include "ipc/ipc_platform_file.h" +#include "native_client/src/trusted/desc/nacl_desc_base.h" +#include "native_client/src/trusted/desc/nacl_desc_custom.h" +#include "native_client/src/trusted/desc/nacl_desc_imc_shm.h" +#include "native_client/src/trusted/desc/nacl_desc_io.h" +#include "native_client/src/trusted/desc/nacl_desc_sync_socket.h" +#include "native_client/src/trusted/desc/nacl_desc_wrapper.h" +#include "native_client/src/trusted/service_runtime/include/sys/fcntl.h" +#include "ppapi/c/ppb_file_io.h" +#include "ppapi/proxy/ppapi_messages.h" +#include "ppapi/proxy/serialized_handle.h" + +namespace { + +enum BufferSizeStatus { + // The buffer contains a full message with no extra bytes. + MESSAGE_IS_COMPLETE, + + // The message doesn't fit and the buffer contains only some of it. + MESSAGE_IS_TRUNCATED, + + // The buffer contains a full message + extra data. + MESSAGE_HAS_EXTRA_DATA +}; + +BufferSizeStatus GetBufferStatus(const char* data, size_t len) { + if (len < sizeof(NaClIPCAdapter::NaClMessageHeader)) + return MESSAGE_IS_TRUNCATED; + + const NaClIPCAdapter::NaClMessageHeader* header = + reinterpret_cast<const NaClIPCAdapter::NaClMessageHeader*>(data); + uint32 message_size = + sizeof(NaClIPCAdapter::NaClMessageHeader) + header->payload_size; + + if (len == message_size) + return MESSAGE_IS_COMPLETE; + if (len > message_size) + return MESSAGE_HAS_EXTRA_DATA; + return MESSAGE_IS_TRUNCATED; +} + +// This object allows the NaClDesc to hold a reference to a NaClIPCAdapter and +// forward calls to it. +struct DescThunker { + explicit DescThunker(NaClIPCAdapter* adapter_param) + : adapter(adapter_param) { + } + scoped_refptr<NaClIPCAdapter> adapter; +}; + +NaClIPCAdapter* ToAdapter(void* handle) { + return static_cast<DescThunker*>(handle)->adapter.get(); +} + +// NaClDescCustom implementation. +void NaClDescCustomDestroy(void* handle) { + delete static_cast<DescThunker*>(handle); +} + +ssize_t NaClDescCustomSendMsg(void* handle, const NaClImcTypedMsgHdr* msg, + int /* flags */) { + return static_cast<ssize_t>(ToAdapter(handle)->Send(msg)); +} + +ssize_t NaClDescCustomRecvMsg(void* handle, NaClImcTypedMsgHdr* msg, + int /* flags */) { + return static_cast<ssize_t>(ToAdapter(handle)->BlockingReceive(msg)); +} + +NaClDesc* MakeNaClDescCustom(NaClIPCAdapter* adapter) { + NaClDescCustomFuncs funcs = NACL_DESC_CUSTOM_FUNCS_INITIALIZER; + funcs.Destroy = NaClDescCustomDestroy; + funcs.SendMsg = NaClDescCustomSendMsg; + funcs.RecvMsg = NaClDescCustomRecvMsg; + // NaClDescMakeCustomDesc gives us a reference on the returned NaClDesc. + return NaClDescMakeCustomDesc(new DescThunker(adapter), &funcs); +} + +void DeleteChannel(IPC::Channel* channel) { + delete channel; +} + +// Translates Pepper's read/write open flags into the NaCl equivalents. +// Since the host has already opened the file, flags such as O_CREAT, O_TRUNC, +// and O_EXCL don't make sense, so we filter those out. If no read or write +// flags are set, the function returns NACL_ABI_O_RDONLY as a safe fallback. +int TranslatePepperFileReadWriteOpenFlags(int32_t pp_open_flags) { + bool read = (pp_open_flags & PP_FILEOPENFLAG_READ) != 0; + bool write = (pp_open_flags & PP_FILEOPENFLAG_WRITE) != 0; + bool append = (pp_open_flags & PP_FILEOPENFLAG_APPEND) != 0; + + int nacl_open_flag = NACL_ABI_O_RDONLY; // NACL_ABI_O_RDONLY == 0. + if (read && (write || append)) { + nacl_open_flag = NACL_ABI_O_RDWR; + } else if (write || append) { + nacl_open_flag = NACL_ABI_O_WRONLY; + } else if (!read) { + DLOG(WARNING) << "One of PP_FILEOPENFLAG_READ, PP_FILEOPENFLAG_WRITE, " + << "or PP_FILEOPENFLAG_APPEND should be set."; + } + if (append) + nacl_open_flag |= NACL_ABI_O_APPEND; + + return nacl_open_flag; +} + +class NaClDescWrapper { + public: + explicit NaClDescWrapper(NaClDesc* desc): desc_(desc) {} + ~NaClDescWrapper() { + NaClDescUnref(desc_); + } + + NaClDesc* desc() { return desc_; } + + private: + NaClDesc* desc_; + DISALLOW_COPY_AND_ASSIGN(NaClDescWrapper); +}; + +} // namespace + +class NaClIPCAdapter::RewrittenMessage + : public base::RefCounted<RewrittenMessage> { + public: + RewrittenMessage(); + + bool is_consumed() const { return data_read_cursor_ == data_len_; } + + void SetData(const NaClIPCAdapter::NaClMessageHeader& header, + const void* payload, size_t payload_length); + + int Read(NaClImcTypedMsgHdr* msg); + + void AddDescriptor(NaClDescWrapper* desc) { descs_.push_back(desc); } + + size_t desc_count() const { return descs_.size(); } + + private: + friend class base::RefCounted<RewrittenMessage>; + ~RewrittenMessage() {} + + scoped_ptr<char[]> data_; + size_t data_len_; + + // Offset into data where the next read will happen. This will be equal to + // data_len_ when all data has been consumed. + size_t data_read_cursor_; + + // Wrapped descriptors for transfer to untrusted code. + ScopedVector<NaClDescWrapper> descs_; +}; + +NaClIPCAdapter::RewrittenMessage::RewrittenMessage() + : data_len_(0), + data_read_cursor_(0) { +} + +void NaClIPCAdapter::RewrittenMessage::SetData( + const NaClIPCAdapter::NaClMessageHeader& header, + const void* payload, + size_t payload_length) { + DCHECK(!data_.get() && data_len_ == 0); + size_t header_len = sizeof(NaClIPCAdapter::NaClMessageHeader); + data_len_ = header_len + payload_length; + data_.reset(new char[data_len_]); + + memcpy(data_.get(), &header, sizeof(NaClIPCAdapter::NaClMessageHeader)); + memcpy(&data_[header_len], payload, payload_length); +} + +int NaClIPCAdapter::RewrittenMessage::Read(NaClImcTypedMsgHdr* msg) { + CHECK(data_len_ >= data_read_cursor_); + char* dest_buffer = static_cast<char*>(msg->iov[0].base); + size_t dest_buffer_size = msg->iov[0].length; + size_t bytes_to_write = std::min(dest_buffer_size, + data_len_ - data_read_cursor_); + if (bytes_to_write == 0) + return 0; + + memcpy(dest_buffer, &data_[data_read_cursor_], bytes_to_write); + data_read_cursor_ += bytes_to_write; + + // Once all data has been consumed, transfer any file descriptors. + if (is_consumed()) { + nacl_abi_size_t desc_count = static_cast<nacl_abi_size_t>(descs_.size()); + CHECK(desc_count <= msg->ndesc_length); + msg->ndesc_length = desc_count; + for (nacl_abi_size_t i = 0; i < desc_count; i++) { + // Copy the NaClDesc to the buffer and add a ref so it won't be freed + // when we clear our ScopedVector. + msg->ndescv[i] = descs_[i]->desc(); + NaClDescRef(descs_[i]->desc()); + } + descs_.clear(); + } else { + msg->ndesc_length = 0; + } + return static_cast<int>(bytes_to_write); +} + +NaClIPCAdapter::LockedData::LockedData() + : channel_closed_(false) { +} + +NaClIPCAdapter::LockedData::~LockedData() { +} + +NaClIPCAdapter::IOThreadData::IOThreadData() { +} + +NaClIPCAdapter::IOThreadData::~IOThreadData() { +} + +NaClIPCAdapter::NaClIPCAdapter(const IPC::ChannelHandle& handle, + base::TaskRunner* runner) + : lock_(), + cond_var_(&lock_), + task_runner_(runner), + locked_data_() { + io_thread_data_.channel_.reset( + new IPC::Channel(handle, IPC::Channel::MODE_SERVER, this)); + // Note, we can not PostTask for ConnectChannelOnIOThread here. If we did, + // and that task ran before this constructor completes, the reference count + // would go to 1 and then to 0 because of the Task, before we've been returned + // to the owning scoped_refptr, which is supposed to give us our first + // ref-count. +} + +NaClIPCAdapter::NaClIPCAdapter(scoped_ptr<IPC::Channel> channel, + base::TaskRunner* runner) + : lock_(), + cond_var_(&lock_), + task_runner_(runner), + locked_data_() { + io_thread_data_.channel_ = channel.Pass(); +} + +void NaClIPCAdapter::ConnectChannel() { + task_runner_->PostTask(FROM_HERE, + base::Bind(&NaClIPCAdapter::ConnectChannelOnIOThread, this)); +} + +// Note that this message is controlled by the untrusted code. So we should be +// skeptical of anything it contains and quick to give up if anything is fishy. +int NaClIPCAdapter::Send(const NaClImcTypedMsgHdr* msg) { + if (msg->iov_length != 1) + return -1; + + base::AutoLock lock(lock_); + + const char* input_data = static_cast<char*>(msg->iov[0].base); + size_t input_data_len = msg->iov[0].length; + if (input_data_len > IPC::Channel::kMaximumMessageSize) { + ClearToBeSent(); + return -1; + } + + // current_message[_len] refers to the total input data received so far. + const char* current_message; + size_t current_message_len; + bool did_append_input_data; + if (locked_data_.to_be_sent_.empty()) { + // No accumulated data, we can avoid a copy by referring to the input + // buffer (the entire message fitting in one call is the common case). + current_message = input_data; + current_message_len = input_data_len; + did_append_input_data = false; + } else { + // We've already accumulated some data, accumulate this new data and + // point to the beginning of the buffer. + + // Make sure our accumulated message size doesn't overflow our max. Since + // we know that data_len < max size (checked above) and our current + // accumulated value is also < max size, we just need to make sure that + // 2x max size can never overflow. + COMPILE_ASSERT(IPC::Channel::kMaximumMessageSize < (UINT_MAX / 2), + MaximumMessageSizeWillOverflow); + size_t new_size = locked_data_.to_be_sent_.size() + input_data_len; + if (new_size > IPC::Channel::kMaximumMessageSize) { + ClearToBeSent(); + return -1; + } + + locked_data_.to_be_sent_.append(input_data, input_data_len); + current_message = &locked_data_.to_be_sent_[0]; + current_message_len = locked_data_.to_be_sent_.size(); + did_append_input_data = true; + } + + // Check the total data we've accumulated so far to see if it contains a full + // message. + switch (GetBufferStatus(current_message, current_message_len)) { + case MESSAGE_IS_COMPLETE: { + // Got a complete message, can send it out. This will be the common case. + bool success = SendCompleteMessage(current_message, current_message_len); + ClearToBeSent(); + return success ? static_cast<int>(input_data_len) : -1; + } + case MESSAGE_IS_TRUNCATED: + // For truncated messages, just accumulate the new data (if we didn't + // already do so above) and go back to waiting for more. + if (!did_append_input_data) + locked_data_.to_be_sent_.append(input_data, input_data_len); + return static_cast<int>(input_data_len); + case MESSAGE_HAS_EXTRA_DATA: + default: + // When the plugin gives us too much data, it's an error. + ClearToBeSent(); + return -1; + } +} + +int NaClIPCAdapter::BlockingReceive(NaClImcTypedMsgHdr* msg) { + if (msg->iov_length != 1) + return -1; + + int retval = 0; + { + base::AutoLock lock(lock_); + while (locked_data_.to_be_received_.empty() && + !locked_data_.channel_closed_) + cond_var_.Wait(); + if (locked_data_.channel_closed_) { + retval = -1; + } else { + retval = LockedReceive(msg); + DCHECK(retval > 0); + } + } + cond_var_.Signal(); + return retval; +} + +void NaClIPCAdapter::CloseChannel() { + { + base::AutoLock lock(lock_); + locked_data_.channel_closed_ = true; + } + cond_var_.Signal(); + + task_runner_->PostTask(FROM_HERE, + base::Bind(&NaClIPCAdapter::CloseChannelOnIOThread, this)); +} + +NaClDesc* NaClIPCAdapter::MakeNaClDesc() { + return MakeNaClDescCustom(this); +} + +#if defined(OS_POSIX) +int NaClIPCAdapter::TakeClientFileDescriptor() { + return io_thread_data_.channel_->TakeClientFileDescriptor(); +} +#endif + +bool NaClIPCAdapter::OnMessageReceived(const IPC::Message& msg) { + { + base::AutoLock lock(lock_); + + scoped_refptr<RewrittenMessage> rewritten_msg(new RewrittenMessage); + + typedef std::vector<ppapi::proxy::SerializedHandle> Handles; + Handles handles; + scoped_ptr<IPC::Message> new_msg_ptr; + bool success = locked_data_.handle_converter_.ConvertNativeHandlesToPosix( + msg, &handles, &new_msg_ptr); + if (!success) + return false; + + // Now add any descriptors we found to rewritten_msg. |handles| is usually + // empty, unless we read a message containing a FD or handle. + for (Handles::const_iterator iter = handles.begin(); + iter != handles.end(); + ++iter) { + scoped_ptr<NaClDescWrapper> nacl_desc; + switch (iter->type()) { + case ppapi::proxy::SerializedHandle::SHARED_MEMORY: { + const base::SharedMemoryHandle& shm_handle = iter->shmem(); + uint32_t size = iter->size(); + nacl_desc.reset(new NaClDescWrapper(NaClDescImcShmMake( +#if defined(OS_WIN) + shm_handle, +#else + shm_handle.fd, +#endif + static_cast<size_t>(size)))); + break; + } + case ppapi::proxy::SerializedHandle::SOCKET: { + nacl_desc.reset(new NaClDescWrapper(NaClDescSyncSocketMake( +#if defined(OS_WIN) + iter->descriptor() +#else + iter->descriptor().fd +#endif + ))); + break; + } + case ppapi::proxy::SerializedHandle::CHANNEL_HANDLE: { + // Check that this came from a PpapiMsg_CreateNaClChannel message. + // This code here is only appropriate for that message. + DCHECK(msg.type() == PpapiMsg_CreateNaClChannel::ID); + IPC::ChannelHandle channel_handle = + IPC::Channel::GenerateVerifiedChannelID("nacl"); + scoped_refptr<NaClIPCAdapter> ipc_adapter( + new NaClIPCAdapter(channel_handle, task_runner_.get())); + ipc_adapter->ConnectChannel(); +#if defined(OS_POSIX) + channel_handle.socket = base::FileDescriptor( + ipc_adapter->TakeClientFileDescriptor(), true); +#endif + nacl_desc.reset(new NaClDescWrapper(ipc_adapter->MakeNaClDesc())); + // Send back a message that the channel was created. + scoped_ptr<IPC::Message> response( + new PpapiHostMsg_ChannelCreated(channel_handle)); + task_runner_->PostTask(FROM_HERE, + base::Bind(&NaClIPCAdapter::SendMessageOnIOThread, this, + base::Passed(&response))); + break; + } + case ppapi::proxy::SerializedHandle::FILE: + // IMPORTANT: The NaClDescIoDescFromHandleAllocCtor function creates + // a NaClDesc that checks file flags before reading and writing. This + // is essential since PPB_FileIO now sends a file descriptor to the + // plugin which may have write capabilities. We can't allow the plugin + // to write with it since it could bypass quota checks, which still + // happen in the host. + nacl_desc.reset(new NaClDescWrapper(NaClDescIoDescFromHandleAllocCtor( +#if defined(OS_WIN) + iter->descriptor(), +#else + iter->descriptor().fd, +#endif + TranslatePepperFileReadWriteOpenFlags(iter->open_flag())))); + break; + case ppapi::proxy::SerializedHandle::INVALID: { + // Nothing to do. TODO(dmichael): Should we log this? Or is it + // sometimes okay to pass an INVALID handle? + break; + } + // No default, so the compiler will warn us if new types get added. + } + if (nacl_desc.get()) + rewritten_msg->AddDescriptor(nacl_desc.release()); + } + if (new_msg_ptr && !handles.empty()) + SaveMessage(*new_msg_ptr, rewritten_msg.get()); + else + SaveMessage(msg, rewritten_msg.get()); + } + cond_var_.Signal(); + return true; +} + +void NaClIPCAdapter::OnChannelConnected(int32 peer_pid) { +} + +void NaClIPCAdapter::OnChannelError() { + CloseChannel(); +} + +NaClIPCAdapter::~NaClIPCAdapter() { + // Make sure the channel is deleted on the IO thread. + task_runner_->PostTask(FROM_HERE, + base::Bind(&DeleteChannel, io_thread_data_.channel_.release())); +} + +int NaClIPCAdapter::LockedReceive(NaClImcTypedMsgHdr* msg) { + lock_.AssertAcquired(); + + if (locked_data_.to_be_received_.empty()) + return 0; + scoped_refptr<RewrittenMessage> current = + locked_data_.to_be_received_.front(); + + int retval = current->Read(msg); + + // When a message is entirely consumed, remove if from the waiting queue. + if (current->is_consumed()) + locked_data_.to_be_received_.pop(); + + return retval; +} + +bool NaClIPCAdapter::SendCompleteMessage(const char* buffer, + size_t buffer_len) { + lock_.AssertAcquired(); + // The message will have already been validated, so we know it's large enough + // for our header. + const NaClMessageHeader* header = + reinterpret_cast<const NaClMessageHeader*>(buffer); + + // Length of the message not including the body. The data passed to us by the + // plugin should match that in the message header. This should have already + // been validated by GetBufferStatus. + int body_len = static_cast<int>(buffer_len - sizeof(NaClMessageHeader)); + DCHECK(body_len == static_cast<int>(header->payload_size)); + + // We actually discard the flags and only copy the ones we care about. This + // is just because message doesn't have a constructor that takes raw flags. + scoped_ptr<IPC::Message> msg( + new IPC::Message(header->routing, header->type, + IPC::Message::PRIORITY_NORMAL)); + if (header->flags & IPC::Message::SYNC_BIT) + msg->set_sync(); + if (header->flags & IPC::Message::REPLY_BIT) + msg->set_reply(); + if (header->flags & IPC::Message::REPLY_ERROR_BIT) + msg->set_reply_error(); + if (header->flags & IPC::Message::UNBLOCK_BIT) + msg->set_unblock(true); + + msg->WriteBytes(&buffer[sizeof(NaClMessageHeader)], body_len); + + // Technically we didn't have to do any of the previous work in the lock. But + // sometimes our buffer will point to the to_be_sent_ string which is + // protected by the lock, and it's messier to factor Send() such that it can + // unlock for us. Holding the lock for the message construction, which is + // just some memcpys, shouldn't be a big deal. + lock_.AssertAcquired(); + if (locked_data_.channel_closed_) + return false; // TODO(brettw) clean up handles here when we add support! + + if (msg->is_sync()) { + locked_data_.handle_converter_.RegisterSyncMessageForReply(*msg); + } + // Actual send must be done on the I/O thread. + task_runner_->PostTask(FROM_HERE, + base::Bind(&NaClIPCAdapter::SendMessageOnIOThread, this, + base::Passed(&msg))); + return true; +} + +void NaClIPCAdapter::ClearToBeSent() { + lock_.AssertAcquired(); + + // Don't let the string keep its buffer behind our back. + std::string empty; + locked_data_.to_be_sent_.swap(empty); +} + +void NaClIPCAdapter::ConnectChannelOnIOThread() { + if (!io_thread_data_.channel_->Connect()) + NOTREACHED(); +} + +void NaClIPCAdapter::CloseChannelOnIOThread() { + io_thread_data_.channel_->Close(); +} + +void NaClIPCAdapter::SendMessageOnIOThread(scoped_ptr<IPC::Message> message) { + io_thread_data_.channel_->Send(message.release()); +} + +void NaClIPCAdapter::SaveMessage(const IPC::Message& msg, + RewrittenMessage* rewritten_msg) { + lock_.AssertAcquired(); + // There is some padding in this structure (the "padding" member is 16 + // bits but this then gets padded to 32 bits). We want to be sure not to + // leak data to the untrusted plugin, so zero everything out first. + NaClMessageHeader header; + memset(&header, 0, sizeof(NaClMessageHeader)); + + header.payload_size = static_cast<uint32>(msg.payload_size()); + header.routing = msg.routing_id(); + header.type = msg.type(); + header.flags = msg.flags(); + header.num_fds = static_cast<int>(rewritten_msg->desc_count()); + + rewritten_msg->SetData(header, msg.payload(), msg.payload_size()); + locked_data_.to_be_received_.push(rewritten_msg); +} + +int TranslatePepperFileReadWriteOpenFlagsForTesting(int32_t pp_open_flags) { + return TranslatePepperFileReadWriteOpenFlags(pp_open_flags); +} diff --git a/chromium/components/nacl/loader/nacl_ipc_adapter.h b/chromium/components/nacl/loader/nacl_ipc_adapter.h new file mode 100644 index 00000000000..60ea855a083 --- /dev/null +++ b/chromium/components/nacl/loader/nacl_ipc_adapter.h @@ -0,0 +1,192 @@ +// 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 CHROME_NACL_NACL_IPC_ADAPTER_H_ +#define CHROME_NACL_NACL_IPC_ADAPTER_H_ + +#include <map> +#include <queue> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/memory/shared_memory.h" +#include "base/pickle.h" +#include "base/synchronization/condition_variable.h" +#include "base/synchronization/lock.h" +#include "base/task_runner.h" +#include "ipc/ipc_listener.h" +#include "ppapi/c/pp_stdint.h" +#include "ppapi/proxy/handle_converter.h" + +struct NaClDesc; +struct NaClImcTypedMsgHdr; +struct PP_Size; + +namespace IPC { +class Channel; +struct ChannelHandle; +} + +namespace ppapi { +class HostResource; +} + +// Adapts a Chrome IPC channel to an IPC channel that we expose to Native +// Client. This provides a mapping in both directions, so when IPC messages +// come in from another process, we rewrite them and allow them to be received +// via a recvmsg-like interface in the NaCl code. When NaCl code calls sendmsg, +// we implement that as sending IPC messages on the channel. +// +// This object also provides the necessary logic for rewriting IPC messages. +// NaCl code is platform-independent and runs in a Posix-like enviroment, but +// some formatting in the message and the way handles are transferred varies +// by platform. This class bridges that gap to provide what looks like a +// normal platform-specific IPC implementation to Chrome, and a Posix-like +// version on every platform to NaCl. +// +// This object must be threadsafe since the nacl environment determines which +// thread every function is called on. +class NaClIPCAdapter : public base::RefCountedThreadSafe<NaClIPCAdapter>, + public IPC::Listener { + public: + // Chrome's IPC message format varies by platform, NaCl's does not. In + // particular, the header has some extra fields on Posix platforms. Since + // NaCl is a Posix environment, it gets that version of the header. This + // header is duplicated here so we have a cross-platform definition of the + // header we're exposing to NaCl. +#pragma pack(push, 4) + struct NaClMessageHeader : public Pickle::Header { + int32 routing; + uint32 type; + uint32 flags; + uint16 num_fds; + uint16 pad; + }; +#pragma pack(pop) + + // Creates an adapter, using the thread associated with the given task + // runner for posting messages. In normal use, the task runner will post to + // the I/O thread of the process. + // + // If you use this constructor, you MUST call ConnectChannel after the + // NaClIPCAdapter is constructed, or the NaClIPCAdapter's channel will not be + // connected. + NaClIPCAdapter(const IPC::ChannelHandle& handle, base::TaskRunner* runner); + + // Initializes with a given channel that's already created for testing + // purposes. This function will take ownership of the given channel. + NaClIPCAdapter(scoped_ptr<IPC::Channel> channel, base::TaskRunner* runner); + + // Connect the channel. This must be called after the constructor that accepts + // an IPC::ChannelHandle, and causes the Channel to be connected on the IO + // thread. + void ConnectChannel(); + + // Implementation of sendmsg. Returns the number of bytes written or -1 on + // failure. + int Send(const NaClImcTypedMsgHdr* msg); + + // Implementation of recvmsg. Returns the number of bytes read or -1 on + // failure. This will block until there's an error or there is data to + // read. + int BlockingReceive(NaClImcTypedMsgHdr* msg); + + // Closes the IPC channel. + void CloseChannel(); + + // Make a NaClDesc that refers to this NaClIPCAdapter. Note that the returned + // NaClDesc is reference-counted, and a reference is returned. + NaClDesc* MakeNaClDesc(); + +#if defined(OS_POSIX) + int TakeClientFileDescriptor(); +#endif + + // Listener implementation. + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + virtual void OnChannelConnected(int32 peer_pid) OVERRIDE; + virtual void OnChannelError() OVERRIDE; + + private: + friend class base::RefCountedThreadSafe<NaClIPCAdapter>; + + class RewrittenMessage; + + // This is the data that must only be accessed inside the lock. This struct + // just separates it so it's easier to see. + struct LockedData { + LockedData(); + ~LockedData(); + + // Messages that we have read off of the Chrome IPC channel that are waiting + // to be received by the plugin. + std::queue< scoped_refptr<RewrittenMessage> > to_be_received_; + + ppapi::proxy::HandleConverter handle_converter_; + + // Data that we've queued from the plugin to send, but doesn't consist of a + // full message yet. The calling code can break apart the message into + // smaller pieces, and we need to send the message to the other process in + // one chunk. + // + // The IPC channel always starts a new send() at the beginning of each + // message, so we don't need to worry about arbitrary message boundaries. + std::string to_be_sent_; + + bool channel_closed_; + }; + + // This is the data that must only be accessed on the I/O thread (as defined + // by TaskRunner). This struct just separates it so it's easier to see. + struct IOThreadData { + IOThreadData(); + ~IOThreadData(); + + scoped_ptr<IPC::Channel> channel_; + }; + + virtual ~NaClIPCAdapter(); + + // Returns 0 if nothing is waiting. + int LockedReceive(NaClImcTypedMsgHdr* msg); + + // Sends a message that we know has been completed to the Chrome process. + bool SendCompleteMessage(const char* buffer, size_t buffer_len); + + // Clears the LockedData.to_be_sent_ structure in a way to make sure that + // the memory is deleted. std::string can sometimes hold onto the buffer + // for future use which we don't want. + void ClearToBeSent(); + + void ConnectChannelOnIOThread(); + void CloseChannelOnIOThread(); + void SendMessageOnIOThread(scoped_ptr<IPC::Message> message); + + // Saves the message to forward to NaCl. This method assumes that the caller + // holds the lock for locked_data_. + void SaveMessage(const IPC::Message& message, + RewrittenMessage* rewritten_message); + + base::Lock lock_; + base::ConditionVariable cond_var_; + + scoped_refptr<base::TaskRunner> task_runner_; + + // To be accessed inside of lock_ only. + LockedData locked_data_; + + // To be accessed on the I/O thread (via task runner) only. + IOThreadData io_thread_data_; + + DISALLOW_COPY_AND_ASSIGN(NaClIPCAdapter); +}; + +// Export TranslatePepperFileReadWriteOpenFlags for testing. +int TranslatePepperFileReadWriteOpenFlagsForTesting(int32_t pp_open_flags); + +#endif // CHROME_NACL_NACL_IPC_ADAPTER_H_ diff --git a/chromium/components/nacl/loader/nacl_ipc_adapter_unittest.cc b/chromium/components/nacl/loader/nacl_ipc_adapter_unittest.cc new file mode 100644 index 00000000000..80fd4080d49 --- /dev/null +++ b/chromium/components/nacl/loader/nacl_ipc_adapter_unittest.cc @@ -0,0 +1,357 @@ +// 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/nacl/loader/nacl_ipc_adapter.h" + +#include <string.h> + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/threading/platform_thread.h" +#include "base/threading/simple_thread.h" +#include "ipc/ipc_test_sink.h" +#include "native_client/src/trusted/desc/nacl_desc_custom.h" +#include "native_client/src/trusted/service_runtime/include/sys/fcntl.h" +#include "ppapi/c/ppb_file_io.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class NaClIPCAdapterTest : public testing::Test { + public: + NaClIPCAdapterTest() {} + + // testing::Test implementation. + virtual void SetUp() OVERRIDE { + sink_ = new IPC::TestSink; + + // Takes ownership of the sink_ pointer. Note we provide the current message + // loop instead of using a real IO thread. This should work OK since we do + // not need real IPC for the tests. + adapter_ = new NaClIPCAdapter(scoped_ptr<IPC::Channel>(sink_), + base::MessageLoopProxy::current().get()); + } + virtual void TearDown() OVERRIDE { + sink_ = NULL; // This pointer is actually owned by the IPCAdapter. + adapter_ = NULL; + // The adapter destructor has to post a task to destroy the Channel on the + // IO thread. For the purposes of the test, we just need to make sure that + // task gets run, or it will appear as a leak. + message_loop_.RunUntilIdle(); + } + + protected: + int BlockingReceive(void* buf, size_t buf_size) { + NaClImcMsgIoVec iov = {buf, buf_size}; + NaClImcTypedMsgHdr msg = {&iov, 1}; + return adapter_->BlockingReceive(&msg); + } + + int Send(void* buf, size_t buf_size) { + NaClImcMsgIoVec iov = {buf, buf_size}; + NaClImcTypedMsgHdr msg = {&iov, 1}; + return adapter_->Send(&msg); + } + + base::MessageLoop message_loop_; + + scoped_refptr<NaClIPCAdapter> adapter_; + + // Messages sent from nacl to the adapter end up here. Note that we create + // this pointer and pass ownership of it to the IPC adapter, who will keep + // it alive as long as the adapter is alive. This means that when the + // adapter goes away, this pointer will become invalid. + // + // In real life the adapter needs to take ownership so the channel can be + // destroyed on the right thread. + IPC::TestSink* sink_; +}; + +} // namespace + +// Tests a simple message getting rewritten sent from native code to NaCl. +TEST_F(NaClIPCAdapterTest, SimpleReceiveRewriting) { + int routing_id = 0x89898989; + uint32 type = 0x55555555; + IPC::Message input(routing_id, type, IPC::Message::PRIORITY_NORMAL); + uint32 flags = input.flags(); + + int value = 0x12345678; + input.WriteInt(value); + adapter_->OnMessageReceived(input); + + // Buffer just need to be big enough for our message with one int. + const int kBufSize = 64; + char buf[kBufSize]; + + int bytes_read = BlockingReceive(buf, kBufSize); + EXPECT_EQ(sizeof(NaClIPCAdapter::NaClMessageHeader) + sizeof(int), + static_cast<size_t>(bytes_read)); + + const NaClIPCAdapter::NaClMessageHeader* output_header = + reinterpret_cast<const NaClIPCAdapter::NaClMessageHeader*>(buf); + EXPECT_EQ(sizeof(int), output_header->payload_size); + EXPECT_EQ(routing_id, output_header->routing); + EXPECT_EQ(type, output_header->type); + EXPECT_EQ(flags, output_header->flags); + EXPECT_EQ(0u, output_header->num_fds); + EXPECT_EQ(0u, output_header->pad); + + // Validate the payload. + EXPECT_EQ(value, + *reinterpret_cast<const int*>(&buf[ + sizeof(NaClIPCAdapter::NaClMessageHeader)])); +} + +// Tests a simple message getting rewritten sent from NaCl to native code. +TEST_F(NaClIPCAdapterTest, SendRewriting) { + int routing_id = 0x89898989; + uint32 type = 0x55555555; + int value = 0x12345678; + + // Send a message with one int inside it. + const int buf_size = sizeof(NaClIPCAdapter::NaClMessageHeader) + sizeof(int); + char buf[buf_size] = {0}; + + NaClIPCAdapter::NaClMessageHeader* header = + reinterpret_cast<NaClIPCAdapter::NaClMessageHeader*>(buf); + header->payload_size = sizeof(int); + header->routing = routing_id; + header->type = type; + header->flags = 0; + header->num_fds = 0; + *reinterpret_cast<int*>( + &buf[sizeof(NaClIPCAdapter::NaClMessageHeader)]) = value; + + int result = Send(buf, buf_size); + EXPECT_EQ(buf_size, result); + + // Check that the message came out the other end in the test sink + // (messages are posted, so we have to pump). + message_loop_.RunUntilIdle(); + ASSERT_EQ(1u, sink_->message_count()); + const IPC::Message* msg = sink_->GetMessageAt(0); + + EXPECT_EQ(sizeof(int), msg->payload_size()); + EXPECT_EQ(header->routing, msg->routing_id()); + EXPECT_EQ(header->type, msg->type()); + + // Now test the partial send case. We should be able to break the message + // into two parts and it should still work. + sink_->ClearMessages(); + int first_chunk_size = 7; + result = Send(buf, first_chunk_size); + EXPECT_EQ(first_chunk_size, result); + + // First partial send should not have made any messages. + message_loop_.RunUntilIdle(); + ASSERT_EQ(0u, sink_->message_count()); + + // Second partial send should do the same. + int second_chunk_size = 2; + result = Send(&buf[first_chunk_size], second_chunk_size); + EXPECT_EQ(second_chunk_size, result); + message_loop_.RunUntilIdle(); + ASSERT_EQ(0u, sink_->message_count()); + + // Send the rest of the message in a third chunk. + int third_chunk_size = buf_size - first_chunk_size - second_chunk_size; + result = Send(&buf[first_chunk_size + second_chunk_size], + third_chunk_size); + EXPECT_EQ(third_chunk_size, result); + + // Last send should have generated one message. + message_loop_.RunUntilIdle(); + ASSERT_EQ(1u, sink_->message_count()); + msg = sink_->GetMessageAt(0); + EXPECT_EQ(sizeof(int), msg->payload_size()); + EXPECT_EQ(header->routing, msg->routing_id()); + EXPECT_EQ(header->type, msg->type()); +} + +// Tests when a buffer is too small to receive the entire message. +TEST_F(NaClIPCAdapterTest, PartialReceive) { + int routing_id_1 = 0x89898989; + uint32 type_1 = 0x55555555; + IPC::Message input_1(routing_id_1, type_1, IPC::Message::PRIORITY_NORMAL); + int value_1 = 0x12121212; + input_1.WriteInt(value_1); + adapter_->OnMessageReceived(input_1); + + int routing_id_2 = 0x90909090; + uint32 type_2 = 0x66666666; + IPC::Message input_2(routing_id_2, type_2, IPC::Message::PRIORITY_NORMAL); + int value_2 = 0x23232323; + input_2.WriteInt(value_2); + adapter_->OnMessageReceived(input_2); + + const int kBufSize = 64; + char buf[kBufSize]; + + // Read part of the first message. + int bytes_requested = 7; + int bytes_read = BlockingReceive(buf, bytes_requested); + ASSERT_EQ(bytes_requested, bytes_read); + + // Read the rest, this should give us the rest of the first message only. + bytes_read += BlockingReceive(&buf[bytes_requested], + kBufSize - bytes_requested); + EXPECT_EQ(sizeof(NaClIPCAdapter::NaClMessageHeader) + sizeof(int), + static_cast<size_t>(bytes_read)); + + // Make sure we got the right message. + const NaClIPCAdapter::NaClMessageHeader* output_header = + reinterpret_cast<const NaClIPCAdapter::NaClMessageHeader*>(buf); + EXPECT_EQ(sizeof(int), output_header->payload_size); + EXPECT_EQ(routing_id_1, output_header->routing); + EXPECT_EQ(type_1, output_header->type); + + // Read the second message to make sure we went on to it. + bytes_read = BlockingReceive(buf, kBufSize); + EXPECT_EQ(sizeof(NaClIPCAdapter::NaClMessageHeader) + sizeof(int), + static_cast<size_t>(bytes_read)); + output_header = + reinterpret_cast<const NaClIPCAdapter::NaClMessageHeader*>(buf); + EXPECT_EQ(sizeof(int), output_header->payload_size); + EXPECT_EQ(routing_id_2, output_header->routing); + EXPECT_EQ(type_2, output_header->type); +} + +// Tests sending messages that are too large. We test sends that are too +// small implicitly here and in the success case because in that case it +// succeeds and buffers the data. +TEST_F(NaClIPCAdapterTest, SendOverflow) { + int routing_id = 0x89898989; + uint32 type = 0x55555555; + int value = 0x12345678; + + // Make a message with one int inside it. Reserve some extra space so + // we can test what happens when we send too much data. + const int buf_size = sizeof(NaClIPCAdapter::NaClMessageHeader) + sizeof(int); + const int big_buf_size = buf_size + 4; + char buf[big_buf_size] = {0}; + + NaClIPCAdapter::NaClMessageHeader* header = + reinterpret_cast<NaClIPCAdapter::NaClMessageHeader*>(buf); + header->payload_size = sizeof(int); + header->routing = routing_id; + header->type = type; + header->flags = 0; + header->num_fds = 0; + *reinterpret_cast<int*>( + &buf[sizeof(NaClIPCAdapter::NaClMessageHeader)]) = value; + + // Send too much data and make sure that the send fails. + int result = Send(buf, big_buf_size); + EXPECT_EQ(-1, result); + message_loop_.RunUntilIdle(); + ASSERT_EQ(0u, sink_->message_count()); + + // Send too much data in two chunks and make sure that the send fails. + int first_chunk_size = 7; + result = Send(buf, first_chunk_size); + EXPECT_EQ(first_chunk_size, result); + + // First partial send should not have made any messages. + message_loop_.RunUntilIdle(); + ASSERT_EQ(0u, sink_->message_count()); + + int second_chunk_size = big_buf_size - first_chunk_size; + result = Send(&buf[first_chunk_size], second_chunk_size); + EXPECT_EQ(-1, result); + message_loop_.RunUntilIdle(); + ASSERT_EQ(0u, sink_->message_count()); +} + +// Tests that when the IPC channel reports an error, that waiting reads are +// unblocked and return a -1 error code. +TEST_F(NaClIPCAdapterTest, ReadWithChannelError) { + // Have a background thread that waits a bit and calls the channel error + // handler. This should wake up any waiting threads and immediately return + // -1. There is an inherent race condition in that we can't be sure if the + // other thread is actually waiting when this happens. This is OK, since the + // behavior (which we also explicitly test later) is to return -1 if the + // channel has already had an error when you start waiting. + class MyThread : public base::SimpleThread { + public: + explicit MyThread(NaClIPCAdapter* adapter) + : SimpleThread("NaClIPCAdapterThread"), + adapter_(adapter) {} + virtual void Run() OVERRIDE { + base::PlatformThread::Sleep(base::TimeDelta::FromSeconds(1)); + adapter_->OnChannelError(); + } + private: + scoped_refptr<NaClIPCAdapter> adapter_; + }; + MyThread thread(adapter_.get()); + + // IMPORTANT: do not return early from here down (including ASSERT_*) because + // the thread needs to joined or it will assert. + thread.Start(); + + // Request data. This will normally (modulo races) block until data is + // received or there is an error, and the thread above will wake us up + // after 1s. + const int kBufSize = 64; + char buf[kBufSize]; + int result = BlockingReceive(buf, kBufSize); + EXPECT_EQ(-1, result); + + // Test the "previously had an error" case. BlockingReceive should return + // immediately if there was an error. + result = BlockingReceive(buf, kBufSize); + EXPECT_EQ(-1, result); + + thread.Join(); +} + +// Tests that TranslatePepperFileOpenFlags translates pepper read/write open +// flags into NaCl open flags correctly. +TEST_F(NaClIPCAdapterTest, TranslatePepperFileReadWriteOpenFlags) { + EXPECT_EQ(NACL_ABI_O_RDONLY, + TranslatePepperFileReadWriteOpenFlagsForTesting(PP_FILEOPENFLAG_READ)); + EXPECT_EQ(NACL_ABI_O_WRONLY, + TranslatePepperFileReadWriteOpenFlagsForTesting(PP_FILEOPENFLAG_WRITE)); + EXPECT_EQ(NACL_ABI_O_WRONLY | NACL_ABI_O_APPEND, + TranslatePepperFileReadWriteOpenFlagsForTesting( + PP_FILEOPENFLAG_APPEND)); + EXPECT_EQ(NACL_ABI_O_RDWR, + TranslatePepperFileReadWriteOpenFlagsForTesting( + PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE)); + EXPECT_EQ(NACL_ABI_O_WRONLY | NACL_ABI_O_APPEND, + TranslatePepperFileReadWriteOpenFlagsForTesting( + PP_FILEOPENFLAG_APPEND)); + EXPECT_EQ(NACL_ABI_O_RDWR | NACL_ABI_O_APPEND, + TranslatePepperFileReadWriteOpenFlagsForTesting( + PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_APPEND)); + + // Flags other than PP_FILEOPENFLAG_READ, PP_FILEOPENFLAG_WRITE, and + // PP_FILEOPENFLAG_APPEND are discarded. + EXPECT_EQ(NACL_ABI_O_WRONLY, + TranslatePepperFileReadWriteOpenFlagsForTesting( + PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_CREATE)); + EXPECT_EQ(NACL_ABI_O_WRONLY, + TranslatePepperFileReadWriteOpenFlagsForTesting( + PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_TRUNCATE)); + EXPECT_EQ(NACL_ABI_O_WRONLY, + TranslatePepperFileReadWriteOpenFlagsForTesting( + PP_FILEOPENFLAG_WRITE | PP_FILEOPENFLAG_EXCLUSIVE)); + + // If none of PP_FILEOPENFLAG_READ, PP_FILEOPENFLAG_WRITE, and + // PP_FILEOPENFLAG_APPEND are set, the result should fall back to + // NACL_ABI_O_READONLY. + EXPECT_EQ(NACL_ABI_O_RDONLY, + TranslatePepperFileReadWriteOpenFlagsForTesting(0)); + EXPECT_EQ(NACL_ABI_O_RDONLY, + TranslatePepperFileReadWriteOpenFlagsForTesting( + PP_FILEOPENFLAG_CREATE)); + EXPECT_EQ(NACL_ABI_O_RDONLY, + TranslatePepperFileReadWriteOpenFlagsForTesting( + PP_FILEOPENFLAG_TRUNCATE)); + EXPECT_EQ(NACL_ABI_O_RDONLY, + TranslatePepperFileReadWriteOpenFlagsForTesting( + PP_FILEOPENFLAG_EXCLUSIVE)); +} diff --git a/chromium/components/nacl/loader/nacl_listener.cc b/chromium/components/nacl/loader/nacl_listener.cc new file mode 100644 index 00000000000..cd175dda5f6 --- /dev/null +++ b/chromium/components/nacl/loader/nacl_listener.cc @@ -0,0 +1,336 @@ +// 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/nacl/loader/nacl_listener.h" + +#include <errno.h> +#include <stdlib.h> + +#if defined(OS_POSIX) +#include <unistd.h> +#endif + +#include "base/command_line.h" +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop/message_loop.h" +#include "base/rand_util.h" +#include "components/nacl/common/nacl_messages.h" +#include "components/nacl/loader/nacl_ipc_adapter.h" +#include "components/nacl/loader/nacl_validation_db.h" +#include "components/nacl/loader/nacl_validation_query.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_switches.h" +#include "ipc/ipc_sync_channel.h" +#include "ipc/ipc_sync_message_filter.h" +#include "native_client/src/trusted/service_runtime/sel_main_chrome.h" +#include "native_client/src/trusted/validator/nacl_file_info.h" + +#if defined(OS_POSIX) +#include "base/file_descriptor_posix.h" +#endif + +#if defined(OS_LINUX) +#include "content/public/common/child_process_sandbox_support_linux.h" +#endif + +#if defined(OS_WIN) +#include <fcntl.h> +#include <io.h> + +#include "content/public/common/sandbox_init.h" +#endif + +namespace { +#if defined(OS_MACOSX) + +// On Mac OS X, shm_open() works in the sandbox but does not give us +// an FD that we can map as PROT_EXEC. Rather than doing an IPC to +// get an executable SHM region when CreateMemoryObject() is called, +// we preallocate one on startup, since NaCl's sel_ldr only needs one +// of them. This saves a round trip. + +base::subtle::Atomic32 g_shm_fd = -1; + +int CreateMemoryObject(size_t size, int executable) { + if (executable && size > 0) { + int result_fd = base::subtle::NoBarrier_AtomicExchange(&g_shm_fd, -1); + if (result_fd != -1) { + // ftruncate() is disallowed by the Mac OS X sandbox and + // returns EPERM. Luckily, we can get the same effect with + // lseek() + write(). + if (lseek(result_fd, size - 1, SEEK_SET) == -1) { + LOG(ERROR) << "lseek() failed: " << errno; + return -1; + } + if (write(result_fd, "", 1) != 1) { + LOG(ERROR) << "write() failed: " << errno; + return -1; + } + return result_fd; + } + } + // Fall back to NaCl's default implementation. + return -1; +} + +#elif defined(OS_LINUX) + +int CreateMemoryObject(size_t size, int executable) { + return content::MakeSharedMemorySegmentViaIPC(size, executable); +} + +#elif defined(OS_WIN) + +NaClListener* g_listener; + +// We wrap the function to convert the bool return value to an int. +int BrokerDuplicateHandle(NaClHandle source_handle, + uint32_t process_id, + NaClHandle* target_handle, + uint32_t desired_access, + uint32_t options) { + return content::BrokerDuplicateHandle(source_handle, process_id, + target_handle, desired_access, + options); +} + +int AttachDebugExceptionHandler(const void* info, size_t info_size) { + std::string info_string(reinterpret_cast<const char*>(info), info_size); + bool result = false; + if (!g_listener->Send(new NaClProcessMsg_AttachDebugExceptionHandler( + info_string, &result))) + return false; + return result; +} + +#endif + +} // namespace + +class BrowserValidationDBProxy : public NaClValidationDB { + public: + explicit BrowserValidationDBProxy(NaClListener* listener) + : listener_(listener) { + } + + virtual bool QueryKnownToValidate(const std::string& signature) OVERRIDE { + // Initialize to false so that if the Send fails to write to the return + // value we're safe. For example if the message is (for some reason) + // dispatched as an async message the return parameter will not be written. + bool result = false; + if (!listener_->Send(new NaClProcessMsg_QueryKnownToValidate(signature, + &result))) { + LOG(ERROR) << "Failed to query NaCl validation cache."; + result = false; + } + return result; + } + + virtual void SetKnownToValidate(const std::string& signature) OVERRIDE { + // Caching is optional: NaCl will still work correctly if the IPC fails. + if (!listener_->Send(new NaClProcessMsg_SetKnownToValidate(signature))) { + LOG(ERROR) << "Failed to update NaCl validation cache."; + } + } + + virtual bool ResolveFileToken(struct NaClFileToken* file_token, + int32* fd, std::string* path) OVERRIDE { + *fd = -1; + *path = ""; + if (file_token->lo == 0 && file_token->hi == 0) { + return false; + } + IPC::PlatformFileForTransit ipc_fd = IPC::InvalidPlatformFileForTransit(); + base::FilePath ipc_path; + if (!listener_->Send(new NaClProcessMsg_ResolveFileToken(file_token->lo, + file_token->hi, + &ipc_fd, + &ipc_path))) { + return false; + } + if (ipc_fd == IPC::InvalidPlatformFileForTransit()) { + return false; + } + base::PlatformFile handle = + IPC::PlatformFileForTransitToPlatformFile(ipc_fd); +#if defined(OS_WIN) + // On Windows, valid handles are 32 bit unsigned integers so this is safe. + *fd = reinterpret_cast<uintptr_t>(handle); +#else + *fd = handle; +#endif + // It doesn't matter if the path is invalid UTF8 as long as it's consistent + // and unforgeable. + *path = ipc_path.AsUTF8Unsafe(); + return true; + } + + private: + // The listener never dies, otherwise this might be a dangling reference. + NaClListener* listener_; +}; + + +NaClListener::NaClListener() : shutdown_event_(true, false), + io_thread_("NaCl_IOThread"), +#if defined(OS_LINUX) + prereserved_sandbox_size_(0), +#endif +#if defined(OS_POSIX) + number_of_cores_(-1), // unknown/error +#endif + main_loop_(NULL) { + io_thread_.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); +#if defined(OS_WIN) + DCHECK(g_listener == NULL); + g_listener = this; +#endif +} + +NaClListener::~NaClListener() { + NOTREACHED(); + shutdown_event_.Signal(); +#if defined(OS_WIN) + g_listener = NULL; +#endif +} + +bool NaClListener::Send(IPC::Message* msg) { + DCHECK(main_loop_ != NULL); + if (base::MessageLoop::current() == main_loop_) { + // This thread owns the channel. + return channel_->Send(msg); + } else { + // This thread does not own the channel. + return filter_->Send(msg); + } +} + +void NaClListener::Listen() { + std::string channel_name = + CommandLine::ForCurrentProcess()->GetSwitchValueASCII( + switches::kProcessChannelID); + channel_.reset(new IPC::SyncChannel( + this, io_thread_.message_loop_proxy().get(), &shutdown_event_)); + filter_ = new IPC::SyncMessageFilter(&shutdown_event_); + channel_->AddFilter(filter_.get()); + channel_->Init(channel_name, IPC::Channel::MODE_CLIENT, true); + main_loop_ = base::MessageLoop::current(); + main_loop_->Run(); +} + +bool NaClListener::OnMessageReceived(const IPC::Message& msg) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(NaClListener, msg) + IPC_MESSAGE_HANDLER(NaClProcessMsg_Start, OnStart) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void NaClListener::OnStart(const nacl::NaClStartParams& params) { + struct NaClChromeMainArgs *args = NaClChromeMainArgsCreate(); + if (args == NULL) { + LOG(ERROR) << "NaClChromeMainArgsCreate() failed"; + return; + } + + if (params.enable_ipc_proxy) { + // Create the initial PPAPI IPC channel between the NaCl IRT and the + // browser process. The IRT uses this channel to communicate with the + // browser and to create additional IPC channels to renderer processes. + IPC::ChannelHandle handle = + IPC::Channel::GenerateVerifiedChannelID("nacl"); + scoped_refptr<NaClIPCAdapter> ipc_adapter( + new NaClIPCAdapter(handle, io_thread_.message_loop_proxy().get())); + ipc_adapter->ConnectChannel(); + + // Pass a NaClDesc to the untrusted side. This will hold a ref to the + // NaClIPCAdapter. + args->initial_ipc_desc = ipc_adapter->MakeNaClDesc(); +#if defined(OS_POSIX) + handle.socket = base::FileDescriptor( + ipc_adapter->TakeClientFileDescriptor(), true); +#endif + if (!Send(new NaClProcessHostMsg_PpapiChannelCreated(handle))) + LOG(ERROR) << "Failed to send IPC channel handle to NaClProcessHost."; + } + + std::vector<nacl::FileDescriptor> handles = params.handles; + +#if defined(OS_LINUX) || defined(OS_MACOSX) + args->urandom_fd = dup(base::GetUrandomFD()); + if (args->urandom_fd < 0) { + LOG(ERROR) << "Failed to dup() the urandom FD"; + return; + } + args->number_of_cores = number_of_cores_; + args->create_memory_object_func = CreateMemoryObject; +# if defined(OS_MACOSX) + CHECK(handles.size() >= 1); + g_shm_fd = nacl::ToNativeHandle(handles[handles.size() - 1]); + handles.pop_back(); +# endif +#endif + + if (params.uses_irt) { + CHECK(handles.size() >= 1); + NaClHandle irt_handle = nacl::ToNativeHandle(handles[handles.size() - 1]); + handles.pop_back(); + +#if defined(OS_WIN) + args->irt_fd = _open_osfhandle(reinterpret_cast<intptr_t>(irt_handle), + _O_RDONLY | _O_BINARY); + if (args->irt_fd < 0) { + LOG(ERROR) << "_open_osfhandle() failed"; + return; + } +#else + args->irt_fd = irt_handle; +#endif + } else { + // Otherwise, the IRT handle is not even sent. + args->irt_fd = -1; + } + + if (params.validation_cache_enabled) { + // SHA256 block size. + CHECK_EQ(params.validation_cache_key.length(), (size_t) 64); + // The cache structure is not freed and exists until the NaCl process exits. + args->validation_cache = CreateValidationCache( + new BrowserValidationDBProxy(this), params.validation_cache_key, + params.version); + } + + CHECK(handles.size() == 1); + args->imc_bootstrap_handle = nacl::ToNativeHandle(handles[0]); + args->enable_exception_handling = params.enable_exception_handling; + args->enable_debug_stub = params.enable_debug_stub; + args->enable_dyncode_syscalls = params.enable_dyncode_syscalls; + if (!params.enable_dyncode_syscalls) { + // Bound the initial nexe's code segment size under PNaCl to + // reduce the chance of a code spraying attack succeeding (see + // https://code.google.com/p/nativeclient/issues/detail?id=3572). + // We assume that !params.enable_dyncode_syscalls is synonymous + // with PNaCl. We can't apply this arbitrary limit outside of + // PNaCl because it might break existing NaCl apps, and this limit + // is only useful if the dyncode syscalls are disabled. + args->initial_nexe_max_code_bytes = 32 << 20; // 32 MB + } +#if defined(OS_LINUX) || defined(OS_MACOSX) + args->debug_stub_server_bound_socket_fd = nacl::ToNativeHandle( + params.debug_stub_server_bound_socket); +#endif +#if defined(OS_WIN) + args->broker_duplicate_handle_func = BrokerDuplicateHandle; + args->attach_debug_exception_handler_func = AttachDebugExceptionHandler; +#endif +#if defined(OS_LINUX) + args->prereserved_sandbox_size = prereserved_sandbox_size_; +#endif + NaClChromeMainStart(args); + NOTREACHED(); +} diff --git a/chromium/components/nacl/loader/nacl_listener.h b/chromium/components/nacl/loader/nacl_listener.h new file mode 100644 index 00000000000..df8b04ecaeb --- /dev/null +++ b/chromium/components/nacl/loader/nacl_listener.h @@ -0,0 +1,74 @@ +// 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 CHROME_NACL_NACL_LISTENER_H_ +#define CHROME_NACL_NACL_LISTENER_H_ + +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/waitable_event.h" +#include "base/threading/thread.h" +#include "components/nacl/common/nacl_types.h" +#include "ipc/ipc_listener.h" + +namespace IPC { +class SyncChannel; +class SyncMessageFilter; +} + +// The NaClListener is an IPC channel listener that waits for a +// request to start a NaCl module. +class NaClListener : public IPC::Listener { + public: + NaClListener(); + virtual ~NaClListener(); + // Listen for a request to launch a NaCl module. + void Listen(); + + bool Send(IPC::Message* msg); + +#if defined(OS_LINUX) + void set_prereserved_sandbox_size(size_t prereserved_sandbox_size) { + prereserved_sandbox_size_ = prereserved_sandbox_size; + } +#endif +#if defined(OS_POSIX) + void set_number_of_cores(int number_of_cores) { + number_of_cores_ = number_of_cores; + } +#endif + + private: + void OnStart(const nacl::NaClStartParams& params); + virtual bool OnMessageReceived(const IPC::Message& msg) OVERRIDE; + + // A channel back to the browser. + scoped_ptr<IPC::SyncChannel> channel_; + + // A filter that allows other threads to use the channel. + scoped_refptr<IPC::SyncMessageFilter> filter_; + + base::WaitableEvent shutdown_event_; + base::Thread io_thread_; + +#if defined(OS_LINUX) + size_t prereserved_sandbox_size_; +#endif +#if defined(OS_POSIX) + // The outer sandbox on Linux and OSX prevents + // sysconf(_SC_NPROCESSORS) from working; in Windows, there are no + // problems with invoking GetSystemInfo. Therefore, only in + // OS_POSIX do we need to supply the number of cores into the + // NaClChromeMainArgs object. + int number_of_cores_; +#endif + + // Used to identify what thread we're on. + base::MessageLoop* main_loop_; + + DISALLOW_COPY_AND_ASSIGN(NaClListener); +}; + +#endif // CHROME_NACL_NACL_LISTENER_H_ diff --git a/chromium/components/nacl/loader/nacl_main.cc b/chromium/components/nacl/loader/nacl_main.cc new file mode 100644 index 00000000000..15b6fd2dc8c --- /dev/null +++ b/chromium/components/nacl/loader/nacl_main.cc @@ -0,0 +1,53 @@ +// 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 "build/build_config.h" + +#include "base/command_line.h" +#include "base/message_loop/message_loop.h" +#include "base/power_monitor/power_monitor.h" +#include "base/power_monitor/power_monitor_device_source.h" +#include "base/timer/hi_res_timer_manager.h" +#include "components/nacl/loader/nacl_listener.h" +#include "components/nacl/loader/nacl_main_platform_delegate.h" +#include "content/public/common/content_switches.h" +#include "content/public/common/main_function_params.h" + +// main() routine for the NaCl loader process. +int NaClMain(const content::MainFunctionParams& parameters) { + const CommandLine& parsed_command_line = parameters.command_line; + + // The main thread of the plugin services IO. + base::MessageLoopForIO main_message_loop; + base::PlatformThread::SetName("CrNaClMain"); + + scoped_ptr<base::PowerMonitorSource> power_monitor_source( + new base::PowerMonitorDeviceSource()); + base::PowerMonitor power_monitor(power_monitor_source.Pass()); + base::HighResolutionTimerManager hi_res_timer_manager; + +#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_LINUX) + NaClMainPlatformDelegate platform(parameters); + bool no_sandbox = parsed_command_line.HasSwitch(switches::kNoSandbox); + +#if defined(OS_POSIX) + // The number of cores must be obtained before the invocation of + // platform.EnableSandbox(), so cannot simply be inlined below. + int number_of_cores = sysconf(_SC_NPROCESSORS_ONLN); +#endif + + if (!no_sandbox) { + platform.EnableSandbox(); + } + NaClListener listener; +#if defined(OS_POSIX) + listener.set_number_of_cores(number_of_cores); +#endif + + listener.Listen(); +#else + NOTIMPLEMENTED() << " not implemented startup, plugin startup dialog etc."; +#endif + return 0; +} diff --git a/chromium/components/nacl/loader/nacl_main_platform_delegate.h b/chromium/components/nacl/loader/nacl_main_platform_delegate.h new file mode 100644 index 00000000000..ca740b85bcd --- /dev/null +++ b/chromium/components/nacl/loader/nacl_main_platform_delegate.h @@ -0,0 +1,26 @@ +// 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 CHROME_NACL_NACL_MAIN_PLATFORM_DELEGATE_H_ +#define CHROME_NACL_NACL_MAIN_PLATFORM_DELEGATE_H_ + +#include "base/basictypes.h" +#include "content/public/common/main_function_params.h" + +class NaClMainPlatformDelegate { + public: + explicit NaClMainPlatformDelegate( + const content::MainFunctionParams& parameters); + ~NaClMainPlatformDelegate(); + + // Initiate Lockdown. + void EnableSandbox(); + + private: + const content::MainFunctionParams& parameters_; + + DISALLOW_COPY_AND_ASSIGN(NaClMainPlatformDelegate); +}; + +#endif // CHROME_NACL_NACL_MAIN_PLATFORM_DELEGATE_H_ diff --git a/chromium/components/nacl/loader/nacl_main_platform_delegate_linux.cc b/chromium/components/nacl/loader/nacl_main_platform_delegate_linux.cc new file mode 100644 index 00000000000..cbe886bc006 --- /dev/null +++ b/chromium/components/nacl/loader/nacl_main_platform_delegate_linux.cc @@ -0,0 +1,31 @@ +// 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/nacl/loader/nacl_main_platform_delegate.h" + +NaClMainPlatformDelegate::NaClMainPlatformDelegate( + const content::MainFunctionParams& parameters) + : parameters_(parameters) { +} + +NaClMainPlatformDelegate::~NaClMainPlatformDelegate() { +} + +void NaClMainPlatformDelegate::EnableSandbox() { + // The setuid sandbox is started in the zygote process: zygote_main_linux.cc + // http://code.google.com/p/chromium/wiki/LinuxSUIDSandbox + // + // The seccomp sandbox is started in the renderer. + // http://code.google.com/p/seccompsandbox/ + // seccomp is currently disabled for nacl. + // http://code.google.com/p/chromium/issues/detail?id=59423 + // See the code in chrome/renderer/renderer_main_platform_delegate_linux.cc + // for how to turn seccomp on. + // + // The seccomp sandbox should not be enabled for Native Client until + // all of these issues are fixed: + // http://code.google.com/p/nativeclient/issues/list?q=label:Seccomp + // At best, NaCl will not work. At worst, enabling the seccomp sandbox + // could create a hole in the NaCl sandbox. +} diff --git a/chromium/components/nacl/loader/nacl_main_platform_delegate_mac.mm b/chromium/components/nacl/loader/nacl_main_platform_delegate_mac.mm new file mode 100644 index 00000000000..78fa5390454 --- /dev/null +++ b/chromium/components/nacl/loader/nacl_main_platform_delegate_mac.mm @@ -0,0 +1,26 @@ +// 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/nacl/loader/nacl_main_platform_delegate.h" + +#import <Cocoa/Cocoa.h> +#include "base/files/file_path.h" +#include "base/logging.h" +#include "components/nacl/common/nacl_sandbox_type_mac.h" +#include "components/nacl/common/nacl_switches.h" +#include "content/public/common/sandbox_init.h" + +NaClMainPlatformDelegate::NaClMainPlatformDelegate( + const content::MainFunctionParams& parameters) + : parameters_(parameters) { +} + +NaClMainPlatformDelegate::~NaClMainPlatformDelegate() { +} + +void NaClMainPlatformDelegate::EnableSandbox() { + CHECK(content::InitializeSandbox(NACL_SANDBOX_TYPE_NACL_LOADER, + base::FilePath())) + << "Error initializing sandbox for " << switches::kNaClLoaderProcess; +} diff --git a/chromium/components/nacl/loader/nacl_main_platform_delegate_win.cc b/chromium/components/nacl/loader/nacl_main_platform_delegate_win.cc new file mode 100644 index 00000000000..f530961700f --- /dev/null +++ b/chromium/components/nacl/loader/nacl_main_platform_delegate_win.cc @@ -0,0 +1,31 @@ +// 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/nacl/loader/nacl_main_platform_delegate.h" + +#include "base/logging.h" +#include "sandbox/win/src/sandbox.h" + +NaClMainPlatformDelegate::NaClMainPlatformDelegate( + const content::MainFunctionParams& parameters) + : parameters_(parameters) { +} + +NaClMainPlatformDelegate::~NaClMainPlatformDelegate() { +} + +void NaClMainPlatformDelegate::EnableSandbox() { + sandbox::TargetServices* target_services = + parameters_.sandbox_info->target_services; + + CHECK(target_services) << "NaCl-Win EnableSandbox: No Target Services!"; + // Cause advapi32 to load before the sandbox is turned on. + unsigned int dummy_rand; + rand_s(&dummy_rand); + // Warm up language subsystems before the sandbox is turned on. + ::GetUserDefaultLangID(); + ::GetUserDefaultLCID(); + // Turn the sandbox on. + target_services->LowerToken(); +} diff --git a/chromium/components/nacl/loader/nacl_sandbox_linux.cc b/chromium/components/nacl/loader/nacl_sandbox_linux.cc new file mode 100644 index 00000000000..c93aba7cd86 --- /dev/null +++ b/chromium/components/nacl/loader/nacl_sandbox_linux.cc @@ -0,0 +1,143 @@ +// 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/nacl/loader/nacl_sandbox_linux.h" + +#include <signal.h> +#include <sys/ptrace.h> + +#include "base/callback.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "build/build_config.h" +#include "content/public/common/sandbox_init.h" +#include "sandbox/linux/seccomp-bpf/sandbox_bpf.h" +#include "sandbox/linux/services/linux_syscalls.h" + +using playground2::ErrorCode; +using playground2::Sandbox; + +namespace { + +// On ARM and x86_64, System V shared memory calls have each their own system +// call, while on i386 they are multiplexed. +#if defined(__x86_64__) || defined(__arm__) +bool IsSystemVSharedMemory(int sysno) { + switch (sysno) { + case __NR_shmat: + case __NR_shmctl: + case __NR_shmdt: + case __NR_shmget: + return true; + default: + return false; + } +} +#endif + +#if defined(__i386__) +// Big system V multiplexing system call. +bool IsSystemVIpc(int sysno) { + switch (sysno) { + case __NR_ipc: + return true; + default: + return false; + } +} +#endif + +ErrorCode NaClBpfSandboxPolicy( + playground2::Sandbox* sb, int sysno, void* aux) { + const playground2::BpfSandboxPolicyCallback baseline_policy = + content::GetBpfSandboxBaselinePolicy(); + switch (sysno) { + // TODO(jln): NaCl's GDB debug stub uses the following socket system calls, + // see if it can be restricted a bit. +#if defined(__x86_64__) || defined(__arm__) + // transport_common.cc needs this. + case __NR_accept: + case __NR_setsockopt: +#elif defined(__i386__) + case __NR_socketcall: +#endif + // trusted/service_runtime/linux/thread_suspension.c needs sigwait() and is + // used by NaCl's GDB debug stub. + case __NR_rt_sigtimedwait: +#if defined(__i386__) + // Needed on i386 to set-up the custom segments. + case __NR_modify_ldt: +#endif + // NaClAddrSpaceBeforeAlloc needs prlimit64. + case __NR_prlimit64: + // NaCl uses custom signal stacks. + case __NR_sigaltstack: + // Below is fairly similar to the policy for a Chromium renderer. + // TODO(jln): restrict clone(), ioctl() and prctl(). + case __NR_ioctl: +#if defined(__i386__) || defined(__x86_64__) + case __NR_getrlimit: +#endif +#if defined(__i386__) || defined(__arm__) + case __NR_ugetrlimit: +#endif + // NaCl runtime exposes clock_getres to untrusted code. + case __NR_clock_getres: + case __NR_pread64: + case __NR_pwrite64: + case __NR_sched_get_priority_max: + case __NR_sched_get_priority_min: + case __NR_sched_getaffinity: + case __NR_sched_getparam: + case __NR_sched_getscheduler: + case __NR_sched_setscheduler: + case __NR_setpriority: + case __NR_sysinfo: + // __NR_times needed as clock() is called by CommandBufferHelper, which is + // used by NaCl applications that use Pepper's 3D interfaces. + // See crbug.com/264856 for details. + case __NR_times: + case __NR_uname: + return ErrorCode(ErrorCode::ERR_ALLOWED); + case __NR_ptrace: + return ErrorCode(EPERM); + default: + // TODO(jln): look into getting rid of System V shared memory: + // platform_qualify/linux/sysv_shm_and_mmap.c makes it a requirement, but + // it may not be needed in all cases. Chromium renderers don't need + // System V shared memory on Aura. +#if defined(__x86_64__) || defined(__arm__) + if (IsSystemVSharedMemory(sysno)) + return ErrorCode(ErrorCode::ERR_ALLOWED); +#elif defined(__i386__) + if (IsSystemVIpc(sysno)) + return ErrorCode(ErrorCode::ERR_ALLOWED); +#endif + return baseline_policy.Run(sb, sysno, aux); + } + NOTREACHED(); + // GCC wants this. + return ErrorCode(EPERM); +} + +void RunSandboxSanityChecks() { + errno = 0; + // Make a ptrace request with an invalid PID. + long ptrace_ret = ptrace(PTRACE_PEEKUSER, -1 /* pid */, NULL, NULL); + CHECK_EQ(-1, ptrace_ret); + // Without the sandbox on, this ptrace call would ESRCH instead. + CHECK_EQ(EPERM, errno); +} + +} // namespace + +bool InitializeBpfSandbox() { + bool sandbox_is_initialized = + content::InitializeSandbox(NaClBpfSandboxPolicy); + if (sandbox_is_initialized) { + RunSandboxSanityChecks(); + return true; + } + return false; +} diff --git a/chromium/components/nacl/loader/nacl_sandbox_linux.h b/chromium/components/nacl/loader/nacl_sandbox_linux.h new file mode 100644 index 00000000000..12eea45fec8 --- /dev/null +++ b/chromium/components/nacl/loader/nacl_sandbox_linux.h @@ -0,0 +1,10 @@ +// 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_NACL_LOADER_NACL_SANDBOX_LINUX_H_ +#define COMPONENTS_NACL_LOADER_NACL_SANDBOX_LINUX_H_ + +bool InitializeBpfSandbox(); + +#endif // COMPONENTS_NACL_LOADER_NACL_SANDBOX_LINUX_H_ diff --git a/chromium/components/nacl/loader/nacl_validation_db.h b/chromium/components/nacl/loader/nacl_validation_db.h new file mode 100644 index 00000000000..3f9de423d9c --- /dev/null +++ b/chromium/components/nacl/loader/nacl_validation_db.h @@ -0,0 +1,28 @@ +// 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_NACL_LOADER_NACL_VALIDATION_DB_H_ +#define COMPONENTS_NACL_LOADER_NACL_VALIDATION_DB_H_ + +#include <string> + +#include "base/basictypes.h" + +struct NaClFileToken; + +class NaClValidationDB { + public: + NaClValidationDB() {} + virtual ~NaClValidationDB() {} + + virtual bool QueryKnownToValidate(const std::string& signature) = 0; + virtual void SetKnownToValidate(const std::string& signature) = 0; + virtual bool ResolveFileToken(struct NaClFileToken* file_token, + int32* fd, std::string* path) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(NaClValidationDB); +}; + +#endif // COMPONENTS_NACL_LOADER_NACL_VALIDATION_DB_H_ diff --git a/chromium/components/nacl/loader/nacl_validation_query.cc b/chromium/components/nacl/loader/nacl_validation_query.cc new file mode 100644 index 00000000000..6bd8641460f --- /dev/null +++ b/chromium/components/nacl/loader/nacl_validation_query.cc @@ -0,0 +1,172 @@ +// 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/nacl/loader/nacl_validation_query.h" + +#include "base/logging.h" +#include "components/nacl/loader/nacl_validation_db.h" +#include "crypto/nss_util.h" +#include "native_client/src/include/portability.h" +#include "native_client/src/trusted/validator/nacl_file_info.h" +#include "native_client/src/trusted/validator/validation_cache.h" + +NaClValidationQueryContext::NaClValidationQueryContext( + NaClValidationDB* db, + const std::string& profile_key, + const std::string& nacl_version) + : db_(db), + profile_key_(profile_key), + nacl_version_(nacl_version) { + + // Sanity checks. + CHECK(profile_key.length() >= 8); + CHECK(nacl_version.length() >= 4); +} + +NaClValidationQuery* NaClValidationQueryContext::CreateQuery() { + NaClValidationQuery* query = new NaClValidationQuery(db_, profile_key_); + // Changing the version effectively invalidates existing hashes. + query->AddData(nacl_version_); + return query; +} + +bool NaClValidationQueryContext::ResolveFileToken( + struct NaClFileToken* file_token, + int32* fd, + std::string* path) { + return db_->ResolveFileToken(file_token, fd, path); +} + +NaClValidationQuery::NaClValidationQuery(NaClValidationDB* db, + const std::string& profile_key) + : state_(READY), + hasher_(crypto::HMAC::SHA256), + db_(db), + buffer_length_(0) { + // Without this line on Linux, HMAC::Init will instantiate a singleton that + // in turn attempts to open a file. Disabling this behavior avoids a ~70 ms + // stall the first time HMAC is used. + // This function is also called in nacl_helper_linux.cc, but nacl_helper may + // not be used in all cases. + // TODO(ncbray) remove when nacl_helper becomes the only code path. + // http://code.google.com/p/chromium/issues/detail?id=118263 +#if defined(USE_NSS) + crypto::ForceNSSNoDBInit(); +#endif + CHECK(hasher_.Init(profile_key)); +} + +void NaClValidationQuery::AddData(const char* data, size_t length) { + CHECK(state_ == READY); + CHECK(buffer_length_ <= sizeof(buffer_)); + // Chrome's HMAC class doesn't support incremental signing. Work around + // this by using a (small) temporary buffer to accumulate data. + // Check if there is space in the buffer. + if (buffer_length_ + kDigestLength > sizeof(buffer_)) { + // Hash the buffer to make space. + CompressBuffer(); + } + // Hash the input data into the buffer. Assumes that sizeof(buffer_) >= + // kDigestLength * 2 (the buffer can store at least two digests.) + CHECK(hasher_.Sign(base::StringPiece(data, length), + reinterpret_cast<unsigned char*>(buffer_ + buffer_length_), + kDigestLength)); + buffer_length_ += kDigestLength; +} + +void NaClValidationQuery::AddData(const unsigned char* data, size_t length) { + AddData(reinterpret_cast<const char*>(data), length); +} + +void NaClValidationQuery::AddData(const base::StringPiece& data) { + AddData(data.data(), data.length()); +} + +int NaClValidationQuery::QueryKnownToValidate() { + CHECK(state_ == READY); + // It is suspicious if we have less than a digest's worth of data. + CHECK(buffer_length_ >= kDigestLength); + CHECK(buffer_length_ <= sizeof(buffer_)); + state_ = GET_CALLED; + // Ensure the buffer contains only one digest worth of data. + CompressBuffer(); + return db_->QueryKnownToValidate(std::string(buffer_, buffer_length_)); +} + +void NaClValidationQuery::SetKnownToValidate() { + CHECK(state_ == GET_CALLED); + CHECK(buffer_length_ == kDigestLength); + state_ = SET_CALLED; + db_->SetKnownToValidate(std::string(buffer_, buffer_length_)); +} + +// Reduce the size of the data in the buffer by hashing it and writing it back +// to the buffer. +void NaClValidationQuery::CompressBuffer() { + // Calculate the digest into a temp buffer. It is likely safe to calculate it + // directly back into the buffer, but this is an "accidental" semantic we're + // avoiding depending on. + unsigned char temp[kDigestLength]; + CHECK(hasher_.Sign(base::StringPiece(buffer_, buffer_length_), temp, + kDigestLength)); + memcpy(buffer_, temp, kDigestLength); + buffer_length_ = kDigestLength; +} + +// OO wrappers + +static void* CreateQuery(void* handle) { + return static_cast<NaClValidationQueryContext*>(handle)->CreateQuery(); +} + +static void AddData(void* query, const uint8* data, size_t length) { + static_cast<NaClValidationQuery*>(query)->AddData(data, length); +} + +static int QueryKnownToValidate(void* query) { + return static_cast<NaClValidationQuery*>(query)->QueryKnownToValidate(); +} + +static void SetKnownToValidate(void* query) { + static_cast<NaClValidationQuery*>(query)->SetKnownToValidate(); +} + +static void DestroyQuery(void* query) { + delete static_cast<NaClValidationQuery*>(query); +} + +static int ResolveFileToken(void* handle, struct NaClFileToken* file_token, + int32* fd, char** file_path, + uint32* file_path_length) { + std::string path; + *file_path = NULL; + *file_path_length = 0; + bool ok = static_cast<NaClValidationQueryContext*>(handle)-> + ResolveFileToken(file_token, fd, &path); + if (ok) { + *file_path = static_cast<char*>(malloc(path.length() + 1)); + CHECK(*file_path); + memcpy(*file_path, path.data(), path.length()); + (*file_path)[path.length()] = 0; + *file_path_length = static_cast<uint32>(path.length()); + } + return ok; +} + +struct NaClValidationCache* CreateValidationCache( + NaClValidationDB* db, const std::string& profile_key, + const std::string& nacl_version) { + NaClValidationCache* cache = + static_cast<NaClValidationCache*>(malloc(sizeof(NaClValidationCache))); + // Make sure any fields introduced in a cross-repo change are zeroed. + memset(cache, 0, sizeof(*cache)); + cache->handle = new NaClValidationQueryContext(db, profile_key, nacl_version); + cache->CreateQuery = CreateQuery; + cache->AddData = AddData; + cache->QueryKnownToValidate = QueryKnownToValidate; + cache->SetKnownToValidate = SetKnownToValidate; + cache->DestroyQuery = DestroyQuery; + cache->ResolveFileToken = ResolveFileToken; + return cache; +} diff --git a/chromium/components/nacl/loader/nacl_validation_query.h b/chromium/components/nacl/loader/nacl_validation_query.h new file mode 100644 index 00000000000..19ca8b9bda2 --- /dev/null +++ b/chromium/components/nacl/loader/nacl_validation_query.h @@ -0,0 +1,94 @@ +// 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_NACL_LOADER_NACL_VALIDATION_QUERY_H_ +#define COMPONENTS_NACL_LOADER_NACL_VALIDATION_QUERY_H_ + +#include <string> + +#include "base/basictypes.h" +#include "base/strings/string_piece.h" +#include "crypto/hmac.h" + +struct NaClFileToken; +struct NaClValidationCache; +class NaClValidationDB; +class NaClValidationQuery; + +class NaClValidationQueryContext { + public: + NaClValidationQueryContext(NaClValidationDB* db, + const std::string& profile_key, + const std::string& nacl_version); + + NaClValidationQuery* CreateQuery(); + + bool ResolveFileToken(struct NaClFileToken* file_token, int32* fd, + std::string* path); + + private: + NaClValidationDB* db_; + + // A key used by HMAC that is specific to this installation of Chrome. + std::string profile_key_; + + // Bytes indicating the "version" of the validator being used. This is used + // to implicitly invalidate the cache - changing the version will change the + // hashes that are produced. + std::string nacl_version_; +}; + +class NaClValidationQuery { + public: + // SHA256 digest size. + static const size_t kDigestLength = 32; + + NaClValidationQuery(NaClValidationDB* db, const std::string& profile_key); + + void AddData(const char* data, size_t length); + void AddData(const unsigned char* data, size_t length); + void AddData(const base::StringPiece& data); + + int QueryKnownToValidate(); + + void SetKnownToValidate(); + + private: + enum QueryState { + READY, + GET_CALLED, + SET_CALLED + }; + + // The HMAC interface currently does not support incremental signing. To work + // around this, each piece of data is signed and the signature is added to a + // buffer. If there is not enough space in the buffer to accommodate new + // data, the buffer contents are signed and the new signature replaces the + // contents of the buffer. CompressBuffer performs this operation. In + // affect, a hash tree is constructed to emulate incremental signing. + void CompressBuffer(); + + // Track the state of the query to detect suspicious method calls. + QueryState state_; + + crypto::HMAC hasher_; + NaClValidationDB* db_; + + // The size of buffer_ is a somewhat arbitrary choice. It needs to be at + // at least kDigestLength * 2, but it can be arbitrarily large. In practice + // there are 4 calls to AddData (version, architechture, cpu features, and + // code), so 4 times digest length means the buffer will not need to be + // compressed as an intermediate step in the expected use cases. + char buffer_[kDigestLength * 4]; + size_t buffer_length_; + + DISALLOW_COPY_AND_ASSIGN(NaClValidationQuery); +}; + +// Create a validation cache interface for use by sel_ldr. +struct NaClValidationCache* CreateValidationCache( + NaClValidationDB* db, const std::string& profile_key, + const std::string& nacl_version); + +#endif // COMPONENTS_NACL_LOADER_NACL_VALIDATION_QUERY_H_ diff --git a/chromium/components/nacl/loader/nacl_validation_query_unittest.cc b/chromium/components/nacl/loader/nacl_validation_query_unittest.cc new file mode 100644 index 00000000000..3846effb38e --- /dev/null +++ b/chromium/components/nacl/loader/nacl_validation_query_unittest.cc @@ -0,0 +1,283 @@ +// 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/nacl/loader/nacl_validation_db.h" +#include "components/nacl/loader/nacl_validation_query.h" +#include "testing/gtest/include/gtest/gtest.h" + +// This test makes sure that validation signature generation is performed +// correctly. In effect, this means that we are checking all of the data +// (and no other data) we are passing the signature generator affects the final +// signature. To avoid tying the tests to a particular implementation, each +// test generates two signatures and compares them rather than trying to compare +// against a specified signature. + +namespace { + +const char kKey[] = "bogus key for HMAC..."; +const char kKeyAlt[] = "bogus key for HMAC!!!"; + +const char kVersion[] = "bogus version"; +const char kVersionAlt[] = "bogus!version"; + + +const char kShortData[] = "Short data 1234567890"; +const char kAltShortData[] = "Short!data 1234567890"; + +const char kLongData[] = "Long data." + "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890123456789012345678901234567890" + "1234567890123456789012345678901234567890123456789012345678901234567890"; + +class MockValidationDB : public NaClValidationDB { + public: + MockValidationDB() + : did_query_(false), + did_set_(false), + status_(true) { + } + + virtual bool QueryKnownToValidate(const std::string& signature) OVERRIDE { + // The typecast is needed to work around gtest trying to take the address + // of a constant. + EXPECT_EQ((int) NaClValidationQuery::kDigestLength, + (int) signature.length()); + EXPECT_FALSE(did_query_); + EXPECT_FALSE(did_set_); + did_query_ = true; + memcpy(query_signature_, signature.data(), + NaClValidationQuery::kDigestLength); + return status_; + } + + virtual void SetKnownToValidate(const std::string& signature) OVERRIDE { + // The typecast is needed to work around gtest trying to take the address + // of a constant. + ASSERT_EQ((int) NaClValidationQuery::kDigestLength, + (int) signature.length()); + EXPECT_TRUE(did_query_); + EXPECT_FALSE(did_set_); + did_set_ = true; + memcpy(set_signature_, signature.data(), + NaClValidationQuery::kDigestLength); + // Signatures should be the same. + EXPECT_EQ(0, memcmp(query_signature_, set_signature_, + NaClValidationQuery::kDigestLength)); + } + + virtual bool ResolveFileToken(struct NaClFileToken* file_token, int32* fd, + std::string* path) OVERRIDE { + *fd = -1; + *path = ""; + return false; + } + + bool did_query_; + bool did_set_; + bool status_; + + uint8 query_signature_[NaClValidationQuery::kDigestLength]; + uint8 set_signature_[NaClValidationQuery::kDigestLength]; +}; + +class TestQuery { + public: + TestQuery(const char* key, const char* version) { + db.reset(new MockValidationDB()); + context.reset(new NaClValidationQueryContext(db.get(), key, version)); + query.reset(context->CreateQuery()); + } + + scoped_ptr<MockValidationDB> db; + scoped_ptr<NaClValidationQueryContext> context; + scoped_ptr<NaClValidationQuery> query; +}; + +class NaClValidationQueryTest : public ::testing::Test { + protected: + scoped_ptr<TestQuery> query1; + scoped_ptr<TestQuery> query2; + + virtual void SetUp() { + query1.reset(new TestQuery(kKey, kVersion)); + query2.reset(new TestQuery(kKey, kVersion)); + } + + void AssertQuerySame() { + ASSERT_TRUE(query1->db->did_query_); + ASSERT_TRUE(query2->db->did_query_); + ASSERT_EQ(0, memcmp(query1->db->query_signature_, + query2->db->query_signature_, + NaClValidationQuery::kDigestLength)); + } + + void AssertQueryDifferent() { + ASSERT_TRUE(query1->db->did_query_); + ASSERT_TRUE(query2->db->did_query_); + ASSERT_NE(0, memcmp(query1->db->query_signature_, + query2->db->query_signature_, + NaClValidationQuery::kDigestLength)); + } +}; + +TEST_F(NaClValidationQueryTest, Sanity) { + query1->query->AddData(kShortData, sizeof(kShortData)); + ASSERT_FALSE(query1->db->did_query_); + ASSERT_FALSE(query1->db->did_set_); + ASSERT_EQ(1, query1->query->QueryKnownToValidate()); + ASSERT_TRUE(query1->db->did_query_); + ASSERT_FALSE(query1->db->did_set_); + query1->query->SetKnownToValidate(); + ASSERT_TRUE(query1->db->did_query_); + ASSERT_TRUE(query1->db->did_set_); +} + +TEST_F(NaClValidationQueryTest, ConsistentShort) { + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->QueryKnownToValidate(); + + query2->query->AddData(kShortData, sizeof(kShortData)); + query2->query->QueryKnownToValidate(); + + AssertQuerySame(); +} + +TEST_F(NaClValidationQueryTest, InconsistentShort) { + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->QueryKnownToValidate(); + + query2->query->AddData(kAltShortData, sizeof(kAltShortData)); + query2->query->QueryKnownToValidate(); + + AssertQueryDifferent(); +} + +// Test for a bug caught during development where AddData would accidently +// overwrite previously written data and add uninitialzied memory to the hash. +TEST_F(NaClValidationQueryTest, ConsistentShortBug) { + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->QueryKnownToValidate(); + + query2->query->AddData(kShortData, sizeof(kShortData)); + query2->query->AddData(kShortData, sizeof(kShortData)); + query2->query->QueryKnownToValidate(); + + AssertQuerySame(); +} + +// Test for a bug caught during development where AddData would accidently +// overwrite previously written data and add uninitialzed memory to the hash. +TEST_F(NaClValidationQueryTest, InconsistentShortBug1) { + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->QueryKnownToValidate(); + + query2->query->AddData(kAltShortData, sizeof(kAltShortData)); + query2->query->AddData(kShortData, sizeof(kShortData)); + query2->query->QueryKnownToValidate(); + + AssertQueryDifferent(); +} + +// Make sure we don't ignore the second bit of data. +TEST_F(NaClValidationQueryTest, InconsistentShort2) { + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->QueryKnownToValidate(); + + query2->query->AddData(kShortData, sizeof(kShortData)); + query2->query->AddData(kAltShortData, sizeof(kAltShortData)); + query2->query->QueryKnownToValidate(); + + AssertQueryDifferent(); +} + +TEST_F(NaClValidationQueryTest, InconsistentZeroSizedAdd) { + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->QueryKnownToValidate(); + + query2->query->AddData(kShortData, sizeof(kShortData)); + query2->query->AddData(kShortData, 0); + query2->query->QueryKnownToValidate(); + + AssertQueryDifferent(); +} + +TEST_F(NaClValidationQueryTest, ConsistentZeroSizedAdd) { + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->AddData("a", 0); + query1->query->QueryKnownToValidate(); + + query2->query->AddData(kShortData, sizeof(kShortData)); + query2->query->AddData("b", 0); + query2->query->QueryKnownToValidate(); + + AssertQuerySame(); +} + +TEST_F(NaClValidationQueryTest, ConsistentRepeatedShort) { + for (int i = 0; i < 30; i++) { + query1->query->AddData(kShortData, sizeof(kShortData)); + } + query1->query->QueryKnownToValidate(); + + for (int i = 0; i < 30; i++) { + query2->query->AddData(kShortData, sizeof(kShortData)); + } + query2->query->QueryKnownToValidate(); + + AssertQuerySame(); +} + +TEST_F(NaClValidationQueryTest, ConsistentLong) { + query1->query->AddData(kLongData, sizeof(kLongData)); + query1->query->QueryKnownToValidate(); + + query2->query->AddData(kLongData, sizeof(kLongData)); + query2->query->QueryKnownToValidate(); + + AssertQuerySame(); +} + +TEST_F(NaClValidationQueryTest, ConsistentRepeatedLong) { + for (int i = 0; i < 30; i++) { + query1->query->AddData(kLongData, sizeof(kLongData)); + } + query1->query->QueryKnownToValidate(); + + for (int i = 0; i < 30; i++) { + query2->query->AddData(kLongData, sizeof(kLongData)); + } + query2->query->QueryKnownToValidate(); + + AssertQuerySame(); +} + +TEST_F(NaClValidationQueryTest, PerturbKey) { + query2.reset(new TestQuery(kKeyAlt, kVersion)); + + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->QueryKnownToValidate(); + + query2->query->AddData(kShortData, sizeof(kShortData)); + query2->query->QueryKnownToValidate(); + + AssertQueryDifferent(); +} + +TEST_F(NaClValidationQueryTest, PerturbVersion) { + query2.reset(new TestQuery(kKey, kVersionAlt)); + + query1->query->AddData(kShortData, sizeof(kShortData)); + query1->query->QueryKnownToValidate(); + + query2->query->AddData(kShortData, sizeof(kShortData)); + query2->query->QueryKnownToValidate(); + + AssertQueryDifferent(); +} + +} diff --git a/chromium/components/nacl/nacl_defines.gypi b/chromium/components/nacl/nacl_defines.gypi new file mode 100644 index 00000000000..e00abaa35b1 --- /dev/null +++ b/chromium/components/nacl/nacl_defines.gypi @@ -0,0 +1,61 @@ +# 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. + +{ + 'variables': { + 'conditions': [ + ['OS=="win"', { + 'nacl_defines': [ + 'NACL_WINDOWS=1', + 'NACL_LINUX=0', + 'NACL_OSX=0', + ], + }], + ['OS=="linux"', { + 'nacl_defines': [ + 'NACL_WINDOWS=0', + 'NACL_LINUX=1', + 'NACL_OSX=0', + ], + }], + ['OS=="mac"', { + 'nacl_defines': [ + 'NACL_WINDOWS=0', + 'NACL_LINUX=0', + 'NACL_OSX=1', + ], + }], + # TODO(mcgrathr): This duplicates native_client/build/common.gypi; + # we should figure out a way to unify the settings. + ['target_arch=="ia32"', { + 'nacl_defines': [ + 'NACL_TARGET_SUBARCH=32', + 'NACL_TARGET_ARCH=x86', + 'NACL_BUILD_SUBARCH=32', + 'NACL_BUILD_ARCH=x86', + ], + }], + ['target_arch=="x64"', { + 'nacl_defines': [ + 'NACL_TARGET_SUBARCH=64', + 'NACL_TARGET_ARCH=x86', + 'NACL_BUILD_SUBARCH=64', + 'NACL_BUILD_ARCH=x86', + ], + }], + ['target_arch=="arm"', { + 'nacl_defines': [ + 'NACL_BUILD_ARCH=arm', + 'NACL_BUILD_SUBARCH=32', + 'NACL_TARGET_ARCH=arm', + 'NACL_TARGET_SUBARCH=32', + ], + }], + ['target_arch=="mipsel"', { + 'nacl_defines': [ + ], + }], + ], + } +} diff --git a/chromium/components/nacl/zygote/nacl_fork_delegate_linux.cc b/chromium/components/nacl/zygote/nacl_fork_delegate_linux.cc new file mode 100644 index 00000000000..8445342fdaa --- /dev/null +++ b/chromium/components/nacl/zygote/nacl_fork_delegate_linux.cc @@ -0,0 +1,248 @@ +// 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/nacl/zygote/nacl_fork_delegate_linux.h" + +#include <signal.h> +#include <stdlib.h> +#include <sys/resource.h> +#include <sys/socket.h> + +#include <set> + +#include "base/basictypes.h" +#include "base/command_line.h" +#include "base/cpu.h" +#include "base/files/file_path.h" +#include "base/logging.h" +#include "base/path_service.h" +#include "base/posix/eintr_wrapper.h" +#include "base/posix/unix_domain_socket_linux.h" +#include "base/process/launch.h" +#include "base/third_party/dynamic_annotations/dynamic_annotations.h" +#include "components/nacl/common/nacl_helper_linux.h" +#include "components/nacl/common/nacl_paths.h" +#include "components/nacl/common/nacl_switches.h" +#include "content/public/common/content_switches.h" + +namespace { + +// Note these need to match up with their counterparts in nacl_helper_linux.c +// and nacl_helper_bootstrap_linux.c. +const char kNaClHelperReservedAtZero[] = + "--reserved_at_zero=0xXXXXXXXXXXXXXXXX"; +const char kNaClHelperRDebug[] = "--r_debug=0xXXXXXXXXXXXXXXXX"; + +#if defined(ARCH_CPU_X86) +bool NonZeroSegmentBaseIsSlow() { + base::CPU cpuid; + // Using a non-zero segment base is known to be very slow on Intel + // Atom CPUs. See "Segmentation-based Memory Protection Mechanism + // on Intel Atom Microarchitecture: Coding Optimizations" (Leonardo + // Potenza, Intel). + // + // The following list of CPU model numbers is taken from: + // "Intel 64 and IA-32 Architectures Software Developer's Manual" + // (http://download.intel.com/products/processor/manual/325462.pdf), + // "Table 35-1. CPUID Signature Values of DisplayFamily_DisplayModel" + // (Volume 3C, 35-1), which contains: + // "06_36H - Intel Atom S Processor Family + // 06_1CH, 06_26H, 06_27H, 06_35, 06_36 - Intel Atom Processor Family" + if (cpuid.family() == 6) { + switch (cpuid.model()) { + case 0x1c: + case 0x26: + case 0x27: + case 0x35: + case 0x36: + return true; + } + } + return false; +} +#endif + +} // namespace. + +NaClForkDelegate::NaClForkDelegate() + : status_(kNaClHelperUnused), + fd_(-1) {} + +void NaClForkDelegate::Init(const int sandboxdesc) { + VLOG(1) << "NaClForkDelegate::Init()"; + int fds[2]; + + // Confirm a hard-wired assumption. + // The NaCl constant is from chrome/nacl/nacl_linux_helper.h + DCHECK(kNaClSandboxDescriptor == sandboxdesc); + + CHECK(socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fds) == 0); + base::FileHandleMappingVector fds_to_map; + fds_to_map.push_back(std::make_pair(fds[1], kNaClZygoteDescriptor)); + fds_to_map.push_back(std::make_pair(sandboxdesc, kNaClSandboxDescriptor)); + + // Using nacl_helper_bootstrap is not necessary on x86-64 because + // NaCl's x86-64 sandbox is not zero-address-based. Starting + // nacl_helper through nacl_helper_bootstrap works on x86-64, but it + // leaves nacl_helper_bootstrap mapped at a fixed address at the + // bottom of the address space, which is undesirable because it + // effectively defeats ASLR. +#if defined(ARCH_CPU_X86_64) + bool kUseNaClBootstrap = false; +#elif defined(ARCH_CPU_X86) + // Performance vs. security trade-off: We prefer using a + // non-zero-address-based sandbox on x86-32 because it provides some + // ASLR and so is more secure. However, on Atom CPUs, using a + // non-zero segment base is very slow, so we use a zero-based + // sandbox on those. + bool kUseNaClBootstrap = NonZeroSegmentBaseIsSlow(); +#else + bool kUseNaClBootstrap = true; +#endif + + status_ = kNaClHelperUnused; + base::FilePath helper_exe; + base::FilePath helper_bootstrap_exe; + if (!PathService::Get(nacl::FILE_NACL_HELPER, &helper_exe)) { + status_ = kNaClHelperMissing; + } else if (kUseNaClBootstrap && + !PathService::Get(nacl::FILE_NACL_HELPER_BOOTSTRAP, + &helper_bootstrap_exe)) { + status_ = kNaClHelperBootstrapMissing; + } else if (RunningOnValgrind()) { + status_ = kNaClHelperValgrind; + } else { + CommandLine::StringVector argv_to_launch; + { + CommandLine cmd_line(CommandLine::NO_PROGRAM); + if (kUseNaClBootstrap) + cmd_line.SetProgram(helper_bootstrap_exe); + else + cmd_line.SetProgram(helper_exe); + + // Append any switches that need to be forwarded to the NaCl helper. + static const char* kForwardSwitches[] = { + switches::kDisableSeccompFilterSandbox, + switches::kNoSandbox, + }; + const CommandLine& current_cmd_line = *CommandLine::ForCurrentProcess(); + cmd_line.CopySwitchesFrom(current_cmd_line, kForwardSwitches, + arraysize(kForwardSwitches)); + + // The command line needs to be tightly controlled to use + // |helper_bootstrap_exe|. So from now on, argv_to_launch should be + // modified directly. + argv_to_launch = cmd_line.argv(); + } + if (kUseNaClBootstrap) { + // Arguments to the bootstrap helper which need to be at the start + // of the command line, right after the helper's path. + CommandLine::StringVector bootstrap_prepend; + bootstrap_prepend.push_back(helper_exe.value()); + bootstrap_prepend.push_back(kNaClHelperReservedAtZero); + bootstrap_prepend.push_back(kNaClHelperRDebug); + argv_to_launch.insert(argv_to_launch.begin() + 1, + bootstrap_prepend.begin(), + bootstrap_prepend.end()); + } + base::LaunchOptions options; + options.fds_to_remap = &fds_to_map; + options.clone_flags = CLONE_FS | SIGCHLD; + + // The NaCl processes spawned may need to exceed the ambient soft limit + // on RLIMIT_AS to allocate the untrusted address space and its guard + // regions. The nacl_helper itself cannot just raise its own limit, + // because the existing limit may prevent the initial exec of + // nacl_helper_bootstrap from succeeding, with its large address space + // reservation. + std::set<int> max_these_limits; + max_these_limits.insert(RLIMIT_AS); + options.maximize_rlimits = &max_these_limits; + + if (!base::LaunchProcess(argv_to_launch, options, NULL)) + status_ = kNaClHelperLaunchFailed; + // parent and error cases are handled below + } + if (HANDLE_EINTR(close(fds[1])) != 0) + LOG(ERROR) << "close(fds[1]) failed"; + if (status_ == kNaClHelperUnused) { + const ssize_t kExpectedLength = strlen(kNaClHelperStartupAck); + char buf[kExpectedLength]; + + // Wait for ack from nacl_helper, indicating it is ready to help + const ssize_t nread = HANDLE_EINTR(read(fds[0], buf, sizeof(buf))); + if (nread == kExpectedLength && + memcmp(buf, kNaClHelperStartupAck, nread) == 0) { + // all is well + status_ = kNaClHelperSuccess; + fd_ = fds[0]; + return; + } + + status_ = kNaClHelperAckFailed; + LOG(ERROR) << "Bad NaCl helper startup ack (" << nread << " bytes)"; + } + // TODO(bradchen): Make this LOG(ERROR) when the NaCl helper + // becomes the default. + fd_ = -1; + if (HANDLE_EINTR(close(fds[0])) != 0) + LOG(ERROR) << "close(fds[0]) failed"; +} + +void NaClForkDelegate::InitialUMA(std::string* uma_name, + int* uma_sample, + int* uma_boundary_value) { + *uma_name = "NaCl.Client.Helper.InitState"; + *uma_sample = status_; + *uma_boundary_value = kNaClHelperStatusBoundary; +} + +NaClForkDelegate::~NaClForkDelegate() { + // side effect of close: delegate process will terminate + if (status_ == kNaClHelperSuccess) { + if (HANDLE_EINTR(close(fd_)) != 0) + LOG(ERROR) << "close(fd_) failed"; + } +} + +bool NaClForkDelegate::CanHelp(const std::string& process_type, + std::string* uma_name, + int* uma_sample, + int* uma_boundary_value) { + if (process_type != switches::kNaClLoaderProcess) + return false; + *uma_name = "NaCl.Client.Helper.StateOnFork"; + *uma_sample = status_; + *uma_boundary_value = kNaClHelperStatusBoundary; + return status_ == kNaClHelperSuccess; +} + +pid_t NaClForkDelegate::Fork(const std::vector<int>& fds) { + base::ProcessId naclchild; + VLOG(1) << "NaClForkDelegate::Fork"; + + DCHECK(fds.size() == kNaClParentFDIndex + 1); + if (!UnixDomainSocket::SendMsg(fd_, kNaClForkRequest, + strlen(kNaClForkRequest), fds)) { + LOG(ERROR) << "NaClForkDelegate::Fork: SendMsg failed"; + return -1; + } + int nread = HANDLE_EINTR(read(fd_, &naclchild, sizeof(naclchild))); + if (nread != sizeof(naclchild)) { + LOG(ERROR) << "NaClForkDelegate::Fork: read failed"; + return -1; + } + VLOG(1) << "nacl_child is " << naclchild << " (" << nread << " bytes)"; + return naclchild; +} + +bool NaClForkDelegate::AckChild(const int fd, + const std::string& channel_switch) { + int nwritten = HANDLE_EINTR(write(fd, channel_switch.c_str(), + channel_switch.length())); + if (nwritten != static_cast<int>(channel_switch.length())) { + return false; + } + return true; +} diff --git a/chromium/components/nacl/zygote/nacl_fork_delegate_linux.h b/chromium/components/nacl/zygote/nacl_fork_delegate_linux.h new file mode 100644 index 00000000000..5812f33fe26 --- /dev/null +++ b/chromium/components/nacl/zygote/nacl_fork_delegate_linux.h @@ -0,0 +1,53 @@ +// 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_NACL_ZYGOTE_NACL_FORK_DELEGATE_LINUX_H_ +#define COMPONENTS_NACL_ZYGOTE_NACL_FORK_DELEGATE_LINUX_H_ + +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/public/common/zygote_fork_delegate_linux.h" + +// The NaClForkDelegate is created during Chrome linux zygote +// initialization, and provides "fork()" functionality with +// NaCl specific process characteristics (specifically address +// space layout) as an alternative to forking the zygote. +// A new delegate is passed in as an argument to ZygoteMain(). +class NaClForkDelegate : public content::ZygoteForkDelegate { + public: + NaClForkDelegate(); + virtual ~NaClForkDelegate(); + + virtual void Init(int sandboxdesc) OVERRIDE; + virtual void InitialUMA(std::string* uma_name, + int* uma_sample, + int* uma_boundary_value) OVERRIDE; + virtual bool CanHelp(const std::string& process_type, std::string* uma_name, + int* uma_sample, int* uma_boundary_value) OVERRIDE; + virtual pid_t Fork(const std::vector<int>& fds) OVERRIDE; + virtual bool AckChild(int fd, + const std::string& channel_switch) OVERRIDE; + + private: + // These values are reported via UMA and hence they become permanent + // constants. Old values cannot be reused, only new ones added. + enum NaClHelperStatus { + kNaClHelperUnused = 0, + kNaClHelperMissing = 1, + kNaClHelperBootstrapMissing = 2, + kNaClHelperValgrind = 3, + kNaClHelperLaunchFailed = 4, + kNaClHelperAckFailed = 5, + kNaClHelperSuccess = 6, + kNaClHelperStatusBoundary // Must be one greater than highest value used. + }; + + NaClHelperStatus status_; + int fd_; +}; + +#endif // COMPONENTS_NACL_ZYGOTE_NACL_FORK_DELEGATE_LINUX_H_ diff --git a/chromium/components/nacl_common.gyp b/chromium/components/nacl_common.gyp new file mode 100644 index 00000000000..1364ef2cd64 --- /dev/null +++ b/chromium/components/nacl_common.gyp @@ -0,0 +1,76 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'nacl_switches', + 'type': 'static_library', + 'sources': [ + 'nacl/common/nacl_switches.cc', + 'nacl/common/nacl_switches.h', + ], + 'include_dirs': [ + '..', + ], + }, + { + 'target_name': 'nacl_common', + 'type': 'static_library', + 'sources': [ + 'nacl/common/nacl_cmd_line.cc', + 'nacl/common/nacl_cmd_line.h', + 'nacl/common/nacl_messages.cc', + 'nacl/common/nacl_messages.h', + 'nacl/common/nacl_types.cc', + 'nacl/common/nacl_types.h', + ], + 'include_dirs': [ + '..', + ], + }, + ], + 'conditions': [ + ['OS=="win" and target_arch=="ia32"', { + 'targets': [ + { + 'target_name': 'nacl_switches_win64', + 'type': 'static_library', + 'sources': [ + 'nacl/common/nacl_switches.cc', + 'nacl/common/nacl_switches.h', + ], + 'include_dirs': [ + '..', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + }, + { + 'target_name': 'nacl_common_win64', + 'type': 'static_library', + 'sources': [ + 'nacl/common/nacl_cmd_line.cc', + 'nacl/common/nacl_cmd_line.h', + 'nacl/common/nacl_messages.cc', + 'nacl/common/nacl_messages.h', + 'nacl/common/nacl_types.cc', + 'nacl/common/nacl_types.h', + ], + 'include_dirs': [ + '..', + ], + 'configurations': { + 'Common_Base': { + 'msvs_target_platform': 'x64', + }, + }, + }, + ], + }], + ], +} diff --git a/chromium/components/navigation_interception.gypi b/chromium/components/navigation_interception.gypi new file mode 100644 index 00000000000..993a916a02b --- /dev/null +++ b/chromium/components/navigation_interception.gypi @@ -0,0 +1,80 @@ +# 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. + +{ + 'conditions': [ + ['OS != "ios"', { + 'targets': [ + { + 'target_name': 'navigation_interception', + 'type': 'static_library', + 'defines!': ['CONTENT_IMPLEMENTATION'], + 'dependencies': [ + '../base/base.gyp:base', + '../content/content.gyp:content_browser', + '../content/content.gyp:content_common', + '../net/net.gyp:net', + ], + 'include_dirs': [ + '..', + '../skia/config', + '<(SHARED_INTERMEDIATE_DIR)/navigation_interception', + + ], + 'sources': [ + 'navigation_interception/intercept_navigation_resource_throttle.cc', + 'navigation_interception/intercept_navigation_resource_throttle.h', + 'navigation_interception/navigation_params.h', + 'navigation_interception/navigation_params.cc', + ], + 'conditions': [ + ['OS=="android"', { + 'dependencies': [ + 'navigation_interception_jni_headers', + ], + 'sources': [ + 'navigation_interception/component_jni_registrar.cc', + 'navigation_interception/component_jni_registrar.h', + 'navigation_interception/intercept_navigation_delegate.cc', + 'navigation_interception/intercept_navigation_delegate.h', + 'navigation_interception/navigation_params_android.h', + 'navigation_interception/navigation_params_android.cc', + ], + }], + ], + }, + ], + 'conditions': [ + ['OS=="android"', { + 'targets': [ + { + 'target_name': 'navigation_interception_java', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base', + ], + 'variables': { + 'java_in_dir': 'navigation_interception/android/java', + }, + 'includes': [ '../build/java.gypi' ], + }, + { + 'target_name': 'navigation_interception_jni_headers', + 'type': 'none', + 'sources': [ + 'navigation_interception/android/java/src/org/chromium/components/navigation_interception/InterceptNavigationDelegate.java', + 'navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java', + ], + 'variables': { + 'jni_gen_package': 'navigation_interception', + }, + 'includes': [ '../build/jni_generator.gypi' ], + }, + ], + }], + ], + }], + ], +} diff --git a/chromium/components/navigation_interception/DEPS b/chromium/components/navigation_interception/DEPS new file mode 100644 index 00000000000..cee4d3e1b1c --- /dev/null +++ b/chromium/components/navigation_interception/DEPS @@ -0,0 +1,8 @@ +include_rules = [ + "+jni", + "+net", + + "+content/public/browser", + "+content/public/common", + "+content/public/test", +] diff --git a/chromium/components/navigation_interception/OWNERS b/chromium/components/navigation_interception/OWNERS new file mode 100644 index 00000000000..82ab3736cc6 --- /dev/null +++ b/chromium/components/navigation_interception/OWNERS @@ -0,0 +1,2 @@ +joth@chromium.org +mkosiba@chromium.org diff --git a/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/InterceptNavigationDelegate.java b/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/InterceptNavigationDelegate.java new file mode 100644 index 00000000000..f2144675e1b --- /dev/null +++ b/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/InterceptNavigationDelegate.java @@ -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. + +package org.chromium.components.navigation_interception; + +import org.chromium.base.CalledByNative; + +public interface InterceptNavigationDelegate { + + /** + * This method is called for every top-level navigation within the associated WebContents. + * The method allows the embedder to ignore navigations. This is used on Android to 'convert' + * certain navigations to Intents to 3rd party applications. + * + * @param navigationParams parameters describing the navigation. + * @return true if the navigation should be ignored. + */ + @CalledByNative + boolean shouldIgnoreNavigation(NavigationParams navigationParams); +} diff --git a/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java b/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java new file mode 100644 index 00000000000..cdfd88313f7 --- /dev/null +++ b/chromium/components/navigation_interception/android/java/src/org/chromium/components/navigation_interception/NavigationParams.java @@ -0,0 +1,36 @@ +// 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. + +package org.chromium.components.navigation_interception; + +import org.chromium.base.CalledByNative; + +public class NavigationParams { + // Target url of the navigation. + public final String url; + // True if the the navigation method is "POST". + public final boolean isPost; + // True if the navigation was initiated by the user. + public final boolean hasUserGesture; + // Page transition type (e.g. link / typed). + public final int pageTransitionType; + // Is the navigation a redirect (in which case url is the "target" address). + public final boolean isRedirect; + + public NavigationParams(String url, boolean isPost, boolean hasUserGesture, + int pageTransitionType, boolean isRedirect) { + this.url = url; + this.isPost = isPost; + this.hasUserGesture = hasUserGesture; + this.pageTransitionType = pageTransitionType; + this.isRedirect = isRedirect; + } + + @CalledByNative + public static NavigationParams create(String url, boolean isPost, boolean hasUserGesture, + int pageTransitionType, boolean isRedirect) { + return new NavigationParams(url, isPost, hasUserGesture, pageTransitionType, + isRedirect); + } +} diff --git a/chromium/components/navigation_interception/component_jni_registrar.cc b/chromium/components/navigation_interception/component_jni_registrar.cc new file mode 100644 index 00000000000..9663529d7cc --- /dev/null +++ b/chromium/components/navigation_interception/component_jni_registrar.cc @@ -0,0 +1,24 @@ +// 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/navigation_interception/component_jni_registrar.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "components/navigation_interception/intercept_navigation_delegate.h" +#include "components/navigation_interception/navigation_params_android.h" + +namespace navigation_interception { + +static base::android::RegistrationMethod kComponentRegisteredMethods[] = { + { "InterceptNavigationDelegate", RegisterInterceptNavigationDelegate }, + { "NavigationParams", RegisterNavigationParams }, +}; + +bool RegisterNavigationInterceptionJni(JNIEnv* env) { + return RegisterNativeMethods( + env, kComponentRegisteredMethods, arraysize(kComponentRegisteredMethods)); +} + +} // namespace navigation_interception diff --git a/chromium/components/navigation_interception/component_jni_registrar.h b/chromium/components/navigation_interception/component_jni_registrar.h new file mode 100644 index 00000000000..c79f3778e9f --- /dev/null +++ b/chromium/components/navigation_interception/component_jni_registrar.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_NAVIGATION_INTERCEPTION_COMPONENT_JNI_REGISTRAR_H_ +#define COMPONENTS_NAVIGATION_INTERCEPTION_COMPONENT_JNI_REGISTRAR_H_ + +#include <jni.h> + +namespace navigation_interception { + +// Register all JNI bindings necessary for the navigation_interception +// component. +bool RegisterNavigationInterceptionJni(JNIEnv* env); + +} // namespace navigation_interception + +#endif // COMPONENTS_NAVIGATION_INTERCEPTION_COMPONENT_JNI_REGISTRAR_H_ diff --git a/chromium/components/navigation_interception/intercept_navigation_delegate.cc b/chromium/components/navigation_interception/intercept_navigation_delegate.cc new file mode 100644 index 00000000000..1cbe04c8e7e --- /dev/null +++ b/chromium/components/navigation_interception/intercept_navigation_delegate.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/navigation_interception/intercept_navigation_delegate.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_string.h" +#include "base/callback.h" +#include "components/navigation_interception/intercept_navigation_resource_throttle.h" +#include "components/navigation_interception/navigation_params_android.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/web_contents.h" +#include "jni/InterceptNavigationDelegate_jni.h" +#include "net/url_request/url_request.h" +#include "url/gurl.h" + +using base::android::ConvertUTF8ToJavaString; +using base::android::ScopedJavaLocalRef; +using content::BrowserThread; +using content::PageTransition; +using content::RenderViewHost; +using content::WebContents; + +namespace navigation_interception { + +namespace { + +const void* kInterceptNavigationDelegateUserDataKey = + &kInterceptNavigationDelegateUserDataKey; + +bool CheckIfShouldIgnoreNavigationOnUIThread(RenderViewHost* source, + const NavigationParams& params) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(source); + + WebContents* web_contents = WebContents::FromRenderViewHost(source); + if (!web_contents) + return false; + InterceptNavigationDelegate* intercept_navigation_delegate = + InterceptNavigationDelegate::Get(web_contents); + if (!intercept_navigation_delegate) + return false; + + return intercept_navigation_delegate->ShouldIgnoreNavigation(params); +} + +} // namespace + +// static +void InterceptNavigationDelegate::Associate( + WebContents* web_contents, + scoped_ptr<InterceptNavigationDelegate> delegate) { + web_contents->SetUserData(kInterceptNavigationDelegateUserDataKey, + delegate.release()); +} + +// static +InterceptNavigationDelegate* InterceptNavigationDelegate::Get( + WebContents* web_contents) { + return reinterpret_cast<InterceptNavigationDelegate*>( + web_contents->GetUserData(kInterceptNavigationDelegateUserDataKey)); +} + +// static +content::ResourceThrottle* InterceptNavigationDelegate::CreateThrottleFor( + net::URLRequest* request) { + return new InterceptNavigationResourceThrottle( + request, base::Bind(&CheckIfShouldIgnoreNavigationOnUIThread)); +} + +InterceptNavigationDelegate::InterceptNavigationDelegate( + JNIEnv* env, jobject jdelegate) + : weak_jdelegate_(env, jdelegate) { +} + +InterceptNavigationDelegate::~InterceptNavigationDelegate() { +} + +bool InterceptNavigationDelegate::ShouldIgnoreNavigation( + const NavigationParams& navigation_params) { + if (!navigation_params.url().is_valid()) + return false; + + JNIEnv* env = base::android::AttachCurrentThread(); + ScopedJavaLocalRef<jobject> jdelegate = weak_jdelegate_.get(env); + + if (jdelegate.is_null()) + return false; + + ScopedJavaLocalRef<jobject> jobject_params = + CreateJavaNavigationParams(env, navigation_params); + + return Java_InterceptNavigationDelegate_shouldIgnoreNavigation( + env, + jdelegate.obj(), + jobject_params.obj()); +} + +// Register native methods. + +bool RegisterInterceptNavigationDelegate(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace navigation_interception diff --git a/chromium/components/navigation_interception/intercept_navigation_delegate.h b/chromium/components/navigation_interception/intercept_navigation_delegate.h new file mode 100644 index 00000000000..c70beef978a --- /dev/null +++ b/chromium/components/navigation_interception/intercept_navigation_delegate.h @@ -0,0 +1,69 @@ +// 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_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_DELEGATE_H_ +#define COMPONENTS_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_DELEGATE_H_ + +#include "base/android/jni_helper.h" +#include "base/memory/scoped_ptr.h" +#include "base/supports_user_data.h" +#include "content/public/common/page_transition_types.h" + +class GURL; + +namespace content { +class ResourceThrottle; +class WebContents; +} + +namespace net { +class URLRequest; +} + +namespace navigation_interception { + +class NavigationParams; + +// Native side of the InterceptNavigationDelegate Java interface. +// This is used to create a InterceptNavigationResourceThrottle that calls the +// Java interface method to determine whether a navigation should be ignored or +// not. +// To us this class: +// 1) the Java-side interface implementation must be associated (via the +// Associate method) with a WebContents for which URLRequests are to be +// intercepted, +// 2) the ResourceThrottle obtained via CreateThrottleFor must be associated +// with the URLRequests in the ResourceDispatcherHostDelegate +// implementation. +class InterceptNavigationDelegate : public base::SupportsUserData::Data { + public: + InterceptNavigationDelegate(JNIEnv* env, jobject jdelegate); + virtual ~InterceptNavigationDelegate(); + + // Associates the InterceptNavigationDelegate with a WebContents using the + // SupportsUserData mechanism. + // As implied by the use of scoped_ptr, the WebContents will assume ownership + // of |delegate|. + static void Associate(content::WebContents* web_contents, + scoped_ptr<InterceptNavigationDelegate> delegate); + // Gets the InterceptNavigationDelegate associated with the WebContents, + // can be null. + static InterceptNavigationDelegate* Get(content::WebContents* web_contents); + + // Creates a InterceptNavigationResourceThrottle that will direct all + // callbacks to the InterceptNavigationDelegate. + static content::ResourceThrottle* CreateThrottleFor( + net::URLRequest* request); + + virtual bool ShouldIgnoreNavigation( + const NavigationParams& navigation_params); + private: + JavaObjectWeakGlobalRef weak_jdelegate_; +}; + +bool RegisterInterceptNavigationDelegate(JNIEnv* env); + +} // namespace navigation_interception + +#endif // COMPONENTS_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_DELEGATE_H_ diff --git a/chromium/components/navigation_interception/intercept_navigation_resource_throttle.cc b/chromium/components/navigation_interception/intercept_navigation_resource_throttle.cc new file mode 100644 index 00000000000..73513ef4752 --- /dev/null +++ b/chromium/components/navigation_interception/intercept_navigation_resource_throttle.cc @@ -0,0 +1,140 @@ +// 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/navigation_interception/intercept_navigation_resource_throttle.h" + +#include "components/navigation_interception/navigation_params.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/child_process_security_policy.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_view_host.h" +#include "content/public/browser/resource_controller.h" +#include "content/public/browser/resource_request_info.h" +#include "content/public/common/page_transition_types.h" +#include "content/public/common/referrer.h" +#include "net/http/http_response_headers.h" +#include "net/url_request/url_request.h" + +using content::BrowserThread; +using content::ChildProcessSecurityPolicy; +using content::PageTransition; +using content::Referrer; +using content::RenderViewHost; +using content::ResourceRequestInfo; + +namespace navigation_interception { + +namespace { + +void CheckIfShouldIgnoreNavigationOnUIThread( + int render_process_id, + int render_view_id, + const NavigationParams& navigation_params, + InterceptNavigationResourceThrottle::CheckOnUIThreadCallback + should_ignore_callback, + base::Callback<void(bool)> callback) { + + bool should_ignore_navigation = false; + RenderViewHost* rvh = + RenderViewHost::FromID(render_process_id, render_view_id); + + if (rvh) { + NavigationParams validated_params(navigation_params); + RenderViewHost::FilterURL( + rvh->GetProcess(), false, &validated_params.url()); + + should_ignore_navigation = should_ignore_callback.Run(rvh, + validated_params); + } + + BrowserThread::PostTask( + BrowserThread::IO, + FROM_HERE, + base::Bind(callback, should_ignore_navigation)); +} + +} // namespace + +InterceptNavigationResourceThrottle::InterceptNavigationResourceThrottle( + net::URLRequest* request, + CheckOnUIThreadCallback should_ignore_callback) + : request_(request), + should_ignore_callback_(should_ignore_callback), + weak_ptr_factory_(this) { +} + +InterceptNavigationResourceThrottle::~InterceptNavigationResourceThrottle() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); +} + +void InterceptNavigationResourceThrottle::WillStartRequest(bool* defer) { + *defer = + CheckIfShouldIgnoreNavigation(request_->url(), request_->method(), false); +} + +void InterceptNavigationResourceThrottle::WillRedirectRequest( + const GURL& new_url, + bool* defer) { + *defer = + CheckIfShouldIgnoreNavigation(new_url, GetMethodAfterRedirect(), true); +} + +std::string InterceptNavigationResourceThrottle::GetMethodAfterRedirect() { + net::HttpResponseHeaders* headers = request_->response_headers(); + if (!headers) + return request_->method(); + return net::URLRequest::ComputeMethodForRedirect( + request_->method(), headers->response_code()); +} + +bool InterceptNavigationResourceThrottle::CheckIfShouldIgnoreNavigation( + const GURL& url, + const std::string& method, + bool is_redirect) { + const ResourceRequestInfo* info = ResourceRequestInfo::ForRequest(request_); + if (!info) + return false; + + int render_process_id, render_view_id; + if (!info->GetAssociatedRenderView(&render_process_id, &render_view_id)) + return false; + + NavigationParams navigation_params(url, + Referrer(GURL(request_->referrer()), + info->GetReferrerPolicy()), + info->HasUserGesture(), + method == "POST", + info->GetPageTransition(), + is_redirect); + + BrowserThread::PostTask( + BrowserThread::UI, + FROM_HERE, + base::Bind( + &CheckIfShouldIgnoreNavigationOnUIThread, + render_process_id, + render_view_id, + navigation_params, + should_ignore_callback_, + base::Bind( + &InterceptNavigationResourceThrottle::OnResultObtained, + weak_ptr_factory_.GetWeakPtr()))); + + // Defer request while we wait for the UI thread to check if the navigation + // should be ignored. + return true; +} + +void InterceptNavigationResourceThrottle::OnResultObtained( + bool should_ignore_navigation) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + if (should_ignore_navigation) { + controller()->CancelAndIgnore(); + } else { + controller()->Resume(); + } +} + +} // namespace navigation_interception diff --git a/chromium/components/navigation_interception/intercept_navigation_resource_throttle.h b/chromium/components/navigation_interception/intercept_navigation_resource_throttle.h new file mode 100644 index 00000000000..79ed57e44c1 --- /dev/null +++ b/chromium/components/navigation_interception/intercept_navigation_resource_throttle.h @@ -0,0 +1,62 @@ +// 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_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_RESOURCE_THROTTLE_H_ +#define COMPONENTS_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_RESOURCE_THROTTLE_H_ + +#include <string> + +#include "base/callback.h" +#include "base/memory/weak_ptr.h" +#include "content/public/browser/resource_throttle.h" + +class GURL; + +namespace content { +class RenderViewHost; +} + +namespace net { +class URLRequest; +} + +namespace navigation_interception { + +class NavigationParams; + +// This class allows the provider of the Callback to selectively ignore top +// level navigations. +class InterceptNavigationResourceThrottle : public content::ResourceThrottle { + public: + typedef base::Callback<bool( + content::RenderViewHost* /* source */, + const NavigationParams& /* navigation_params */)> + CheckOnUIThreadCallback; + + InterceptNavigationResourceThrottle( + net::URLRequest* request, + CheckOnUIThreadCallback should_ignore_callback); + virtual ~InterceptNavigationResourceThrottle(); + + // content::ResourceThrottle implementation: + virtual void WillStartRequest(bool* defer) OVERRIDE; + virtual void WillRedirectRequest(const GURL& new_url, bool* defer) OVERRIDE; + + private: + std::string GetMethodAfterRedirect(); + bool CheckIfShouldIgnoreNavigation(const GURL& url, + const std::string& method, + bool is_redirect); + void OnResultObtained(bool should_ignore_navigation); + + net::URLRequest* request_; + CheckOnUIThreadCallback should_ignore_callback_; + base::WeakPtrFactory<InterceptNavigationResourceThrottle> weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(InterceptNavigationResourceThrottle); +}; + +} // namespace navigation_interception + +#endif // COMPONENTS_NAVIGATION_INTERCEPTION_INTERCEPT_NAVIGATION_RESOURCE_THROTTLE_H_ diff --git a/chromium/components/navigation_interception/intercept_navigation_resource_throttle_unittest.cc b/chromium/components/navigation_interception/intercept_navigation_resource_throttle_unittest.cc new file mode 100644 index 00000000000..e1bd566b54e --- /dev/null +++ b/chromium/components/navigation_interception/intercept_navigation_resource_throttle_unittest.cc @@ -0,0 +1,475 @@ +// 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/bind.h" +#include "base/bind_helpers.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "base/run_loop.h" +#include "base/synchronization/waitable_event.h" +#include "components/navigation_interception/intercept_navigation_resource_throttle.h" +#include "components/navigation_interception/navigation_params.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/resource_context.h" +#include "content/public/browser/resource_controller.h" +#include "content/public/browser/resource_dispatcher_host.h" +#include "content/public/browser/resource_dispatcher_host_delegate.h" +#include "content/public/browser/resource_request_info.h" +#include "content/public/browser/resource_throttle.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_delegate.h" +#include "content/public/common/page_transition_types.h" +#include "content/public/test/mock_resource_context.h" +#include "content/public/test/test_renderer_host.h" +#include "net/http/http_response_headers.h" +#include "net/http/http_response_info.h" +#include "net/url_request/url_request.h" +#include "net/url_request/url_request_test_util.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using testing::_; +using testing::Eq; +using testing::Ne; +using testing::Property; +using testing::Return; + +namespace navigation_interception { + +namespace { + +const char kTestUrl[] = "http://www.test.com/"; +const char kUnsafeTestUrl[] = "about:crash"; + +// The MS C++ compiler complains about not being able to resolve which url() +// method (const or non-const) to use if we use the Property matcher to check +// the return value of the NavigationParams::url() method. +// It is possible to suppress the error by specifying the types directly but +// that results in very ugly syntax, which is why these custom matchers are +// used instead. +MATCHER(NavigationParamsUrlIsTest, "") { + return arg.url() == GURL(kTestUrl); +} + +MATCHER(NavigationParamsUrlIsSafe, "") { + return arg.url() != GURL(kUnsafeTestUrl); +} + +} // namespace + + +// MockInterceptCallbackReceiver ---------------------------------------------- + +class MockInterceptCallbackReceiver { + public: + MOCK_METHOD2(ShouldIgnoreNavigation, + bool(content::RenderViewHost* source, + const NavigationParams& navigation_params)); +}; + +// MockResourceController ----------------------------------------------------- +class MockResourceController : public content::ResourceController { + public: + enum Status { + UNKNOWN, + RESUMED, + CANCELLED + }; + + MockResourceController() + : status_(UNKNOWN) { + } + + Status status() const { return status_; } + + // ResourceController: + virtual void Cancel() OVERRIDE { + NOTREACHED(); + } + virtual void CancelAndIgnore() OVERRIDE { + status_ = CANCELLED; + } + virtual void CancelWithError(int error_code) OVERRIDE { + NOTREACHED(); + } + virtual void Resume() OVERRIDE { + DCHECK(status_ == UNKNOWN); + status_ = RESUMED; + } + + private: + Status status_; +}; + +// TestIOThreadState ---------------------------------------------------------- + +enum RedirectMode { + REDIRECT_MODE_NO_REDIRECT, + REDIRECT_MODE_302, +}; + +class TestIOThreadState { + public: + TestIOThreadState(const GURL& url, + int render_process_id, + int render_view_id, + const std::string& request_method, + RedirectMode redirect_mode, + MockInterceptCallbackReceiver* callback_receiver) + : resource_context_(&test_url_request_context_), + request_(url, NULL, resource_context_.GetRequestContext()) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + if (render_process_id != MSG_ROUTING_NONE && + render_view_id != MSG_ROUTING_NONE) { + content::ResourceRequestInfo::AllocateForTesting( + &request_, + ResourceType::MAIN_FRAME, + &resource_context_, + render_process_id, + render_view_id); + } + throttle_.reset(new InterceptNavigationResourceThrottle( + &request_, + base::Bind(&MockInterceptCallbackReceiver::ShouldIgnoreNavigation, + base::Unretained(callback_receiver)))); + throttle_->set_controller_for_testing(&throttle_controller_); + request_.set_method(request_method); + + if (redirect_mode == REDIRECT_MODE_302) { + net::HttpResponseInfo& response_info = + const_cast<net::HttpResponseInfo&>(request_.response_info()); + response_info.headers = new net::HttpResponseHeaders( + "Status: 302 Found\0\0"); + } + } + + void ThrottleWillStartRequest(bool* defer) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + throttle_->WillStartRequest(defer); + } + + void ThrottleWillRedirectRequest(const GURL& new_url, bool* defer) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + throttle_->WillRedirectRequest(new_url, defer); + } + + bool request_resumed() const { + return throttle_controller_.status() == + MockResourceController::RESUMED; + } + + bool request_cancelled() const { + return throttle_controller_.status() == + MockResourceController::CANCELLED; + } + + private: + net::TestURLRequestContext test_url_request_context_; + content::MockResourceContext resource_context_; + net::URLRequest request_; + scoped_ptr<InterceptNavigationResourceThrottle> throttle_; + MockResourceController throttle_controller_; +}; + +// InterceptNavigationResourceThrottleTest ------------------------------------ + +class InterceptNavigationResourceThrottleTest + : public content::RenderViewHostTestHarness { + public: + InterceptNavigationResourceThrottleTest() + : mock_callback_receiver_(new MockInterceptCallbackReceiver()), + io_thread_state_(NULL) { + } + + virtual void SetUp() OVERRIDE { + RenderViewHostTestHarness::SetUp(); + } + + virtual void TearDown() OVERRIDE { + if (web_contents()) + web_contents()->SetDelegate(NULL); + + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind(&base::DeletePointer<TestIOThreadState>, io_thread_state_)); + + RenderViewHostTestHarness::TearDown(); + } + + void SetIOThreadState(TestIOThreadState* io_thread_state) { + io_thread_state_ = io_thread_state; + } + + void RunThrottleWillStartRequestOnIOThread( + const GURL& url, + const std::string& request_method, + RedirectMode redirect_mode, + int render_process_id, + int render_view_id, + bool* defer) { + DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); + TestIOThreadState* io_thread_state = + new TestIOThreadState(url, render_process_id, render_view_id, + request_method, redirect_mode, + mock_callback_receiver_.get()); + + SetIOThreadState(io_thread_state); + + if (redirect_mode == REDIRECT_MODE_NO_REDIRECT) + io_thread_state->ThrottleWillStartRequest(defer); + else + io_thread_state->ThrottleWillRedirectRequest(url, defer); + } + + protected: + enum ShouldIgnoreNavigationCallbackAction { + IgnoreNavigation, + DontIgnoreNavigation + }; + + void SetUpWebContentsDelegateAndDrainRunLoop( + ShouldIgnoreNavigationCallbackAction callback_action, + bool* defer) { + + ON_CALL(*mock_callback_receiver_, ShouldIgnoreNavigation(_, _)) + .WillByDefault(Return(callback_action == IgnoreNavigation)); + EXPECT_CALL(*mock_callback_receiver_, + ShouldIgnoreNavigation(rvh(), NavigationParamsUrlIsTest())) + .Times(1); + + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind( + &InterceptNavigationResourceThrottleTest:: + RunThrottleWillStartRequestOnIOThread, + base::Unretained(this), + GURL(kTestUrl), + "GET", + REDIRECT_MODE_NO_REDIRECT, + web_contents()->GetRenderViewHost()->GetProcess()->GetID(), + web_contents()->GetRenderViewHost()->GetRoutingID(), + base::Unretained(defer))); + + // Wait for the request to finish processing. + base::RunLoop().RunUntilIdle(); + } + + void WaitForPreviouslyScheduledIoThreadWork() { + base::WaitableEvent io_thread_work_done(true, false); + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind( + &base::WaitableEvent::Signal, + base::Unretained(&io_thread_work_done))); + io_thread_work_done.Wait(); + } + + scoped_ptr<MockInterceptCallbackReceiver> mock_callback_receiver_; + TestIOThreadState* io_thread_state_; +}; + +TEST_F(InterceptNavigationResourceThrottleTest, + RequestDeferredAndResumedIfNavigationNotIgnored) { + bool defer = false; + SetUpWebContentsDelegateAndDrainRunLoop(DontIgnoreNavigation, &defer); + + EXPECT_TRUE(defer); + EXPECT_TRUE(io_thread_state_); + EXPECT_TRUE(io_thread_state_->request_resumed()); +} + +TEST_F(InterceptNavigationResourceThrottleTest, + RequestDeferredAndCancelledIfNavigationIgnored) { + bool defer = false; + SetUpWebContentsDelegateAndDrainRunLoop(IgnoreNavigation, &defer); + + EXPECT_TRUE(defer); + EXPECT_TRUE(io_thread_state_); + EXPECT_TRUE(io_thread_state_->request_cancelled()); +} + +TEST_F(InterceptNavigationResourceThrottleTest, + NoCallbackMadeIfContentsDeletedWhileThrottleRunning) { + bool defer = false; + + // The tested scenario is when the WebContents is deleted after the + // ResourceThrottle has finished processing on the IO thread but before the + // UI thread callback has been processed. Since both threads in this test + // are serviced by one message loop, the post order is the execution order. + EXPECT_CALL(*mock_callback_receiver_, + ShouldIgnoreNavigation(_, _)) + .Times(0); + + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind( + &InterceptNavigationResourceThrottleTest:: + RunThrottleWillStartRequestOnIOThread, + base::Unretained(this), + GURL(kTestUrl), + "GET", + REDIRECT_MODE_NO_REDIRECT, + web_contents()->GetRenderViewHost()->GetProcess()->GetID(), + web_contents()->GetRenderViewHost()->GetRoutingID(), + base::Unretained(&defer))); + + content::BrowserThread::PostTask( + content::BrowserThread::UI, + FROM_HERE, + base::Bind( + &RenderViewHostTestHarness::DeleteContents, + base::Unretained(this))); + + // The WebContents will now be deleted and only after that will the UI-thread + // callback posted by the ResourceThrottle be executed. + base::RunLoop().RunUntilIdle(); + + EXPECT_TRUE(defer); + EXPECT_TRUE(io_thread_state_); + EXPECT_TRUE(io_thread_state_->request_resumed()); +} + +TEST_F(InterceptNavigationResourceThrottleTest, + RequestNotDeferredForRequestNotAssociatedWithARenderView) { + bool defer = false; + + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind( + &InterceptNavigationResourceThrottleTest:: + RunThrottleWillStartRequestOnIOThread, + base::Unretained(this), + GURL(kTestUrl), + "GET", + REDIRECT_MODE_NO_REDIRECT, + MSG_ROUTING_NONE, + MSG_ROUTING_NONE, + base::Unretained(&defer))); + + // Wait for the request to finish processing. + base::RunLoop().RunUntilIdle(); + + EXPECT_FALSE(defer); +} + +TEST_F(InterceptNavigationResourceThrottleTest, + CallbackCalledWithFilteredUrl) { + bool defer = false; + + ON_CALL(*mock_callback_receiver_, + ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe())) + .WillByDefault(Return(false)); + EXPECT_CALL(*mock_callback_receiver_, + ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe())) + .Times(1); + + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind( + &InterceptNavigationResourceThrottleTest:: + RunThrottleWillStartRequestOnIOThread, + base::Unretained(this), + GURL(kUnsafeTestUrl), + "GET", + REDIRECT_MODE_NO_REDIRECT, + web_contents()->GetRenderViewHost()->GetProcess()->GetID(), + web_contents()->GetRenderViewHost()->GetRoutingID(), + base::Unretained(&defer))); + + // Wait for the request to finish processing. + base::RunLoop().RunUntilIdle(); +} + +TEST_F(InterceptNavigationResourceThrottleTest, + CallbackIsPostFalseForGet) { + bool defer = false; + + EXPECT_CALL(*mock_callback_receiver_, + ShouldIgnoreNavigation(_, AllOf( + NavigationParamsUrlIsSafe(), + Property(&NavigationParams::is_post, Eq(false))))) + .WillOnce(Return(false)); + + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind( + &InterceptNavigationResourceThrottleTest:: + RunThrottleWillStartRequestOnIOThread, + base::Unretained(this), + GURL(kTestUrl), + "GET", + REDIRECT_MODE_NO_REDIRECT, + web_contents()->GetRenderViewHost()->GetProcess()->GetID(), + web_contents()->GetRenderViewHost()->GetRoutingID(), + base::Unretained(&defer))); + + // Wait for the request to finish processing. + base::RunLoop().RunUntilIdle(); +} + +TEST_F(InterceptNavigationResourceThrottleTest, + CallbackIsPostTrueForPost) { + bool defer = false; + + EXPECT_CALL(*mock_callback_receiver_, + ShouldIgnoreNavigation(_, AllOf( + NavigationParamsUrlIsSafe(), + Property(&NavigationParams::is_post, Eq(true))))) + .WillOnce(Return(false)); + + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind( + &InterceptNavigationResourceThrottleTest:: + RunThrottleWillStartRequestOnIOThread, + base::Unretained(this), + GURL(kTestUrl), + "POST", + REDIRECT_MODE_NO_REDIRECT, + web_contents()->GetRenderViewHost()->GetProcess()->GetID(), + web_contents()->GetRenderViewHost()->GetRoutingID(), + base::Unretained(&defer))); + + // Wait for the request to finish processing. + base::RunLoop().RunUntilIdle(); +} + +TEST_F(InterceptNavigationResourceThrottleTest, + CallbackIsPostFalseForPostConvertedToGetBy302) { + bool defer = false; + + EXPECT_CALL(*mock_callback_receiver_, + ShouldIgnoreNavigation(_, AllOf( + NavigationParamsUrlIsSafe(), + Property(&NavigationParams::is_post, Eq(false))))) + .WillOnce(Return(false)); + + content::BrowserThread::PostTask( + content::BrowserThread::IO, + FROM_HERE, + base::Bind( + &InterceptNavigationResourceThrottleTest:: + RunThrottleWillStartRequestOnIOThread, + base::Unretained(this), + GURL(kTestUrl), + "POST", + REDIRECT_MODE_302, + web_contents()->GetRenderViewHost()->GetProcess()->GetID(), + web_contents()->GetRenderViewHost()->GetRoutingID(), + base::Unretained(&defer))); + + // Wait for the request to finish processing. + base::RunLoop().RunUntilIdle(); +} + +} // namespace navigation_interception diff --git a/chromium/components/navigation_interception/navigation_params.cc b/chromium/components/navigation_interception/navigation_params.cc new file mode 100644 index 00000000000..2bb0e94b58d --- /dev/null +++ b/chromium/components/navigation_interception/navigation_params.cc @@ -0,0 +1,42 @@ +// 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/navigation_interception/navigation_params.h" + +namespace navigation_interception { + +NavigationParams::NavigationParams(const NavigationParams& other) { + Assign(other); +} + +NavigationParams::NavigationParams( + const GURL& url, + const content::Referrer& referrer, + bool has_user_gesture, + bool is_post, + content::PageTransition transition_type, + bool is_redirect) + : url_(url), + referrer_(referrer), + has_user_gesture_(has_user_gesture), + is_post_(is_post), + transition_type_(transition_type), + is_redirect_(is_redirect) { +} + +void NavigationParams::operator=(const NavigationParams& rhs) { + Assign(rhs); +} + +void NavigationParams::Assign(const NavigationParams& other) { + url_ = other.url(); + referrer_ = other.referrer(); + has_user_gesture_ = other.has_user_gesture(); + is_post_ = other.is_post(); + transition_type_ = other.transition_type(); + is_redirect_ = other.is_redirect(); +} + +} // namespace navigation_interception + diff --git a/chromium/components/navigation_interception/navigation_params.h b/chromium/components/navigation_interception/navigation_params.h new file mode 100644 index 00000000000..0144a5af023 --- /dev/null +++ b/chromium/components/navigation_interception/navigation_params.h @@ -0,0 +1,47 @@ +// 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_NAVIGATION_INTERCEPTION_NAVIGATION_PARAMS_H_ +#define COMPONENTS_NAVIGATION_INTERCEPTION_NAVIGATION_PARAMS_H_ + +#include "content/public/common/page_transition_types.h" +#include "content/public/common/page_transition_types.h" +#include "content/public/common/referrer.h" +#include "url/gurl.h" + +namespace navigation_interception { + +class NavigationParams { + public: + NavigationParams(const GURL& url, + const content::Referrer& referrer, + bool has_user_gesture, + bool is_post, + content::PageTransition page_transition_type, + bool is_redirect); + NavigationParams(const NavigationParams& other); + void operator=(const NavigationParams& rhs); + + const GURL& url() const { return url_; } + GURL& url() { return url_; } + const content::Referrer& referrer() const { return referrer_; } + bool has_user_gesture() const { return has_user_gesture_; } + bool is_post() const { return is_post_; } + content::PageTransition transition_type() const { return transition_type_; } + bool is_redirect() const { return is_redirect_; } + + private: + void Assign(const NavigationParams& other); + + GURL url_; + content::Referrer referrer_; + bool has_user_gesture_; + bool is_post_; + content::PageTransition transition_type_; + bool is_redirect_; +}; + +} // namespace navigation_interception + +#endif // COMPONENTS_NAVIGATION_INTERCEPTION_NAVIGATION_PARAMS_H_ diff --git a/chromium/components/navigation_interception/navigation_params_android.cc b/chromium/components/navigation_interception/navigation_params_android.cc new file mode 100644 index 00000000000..769450fa9d2 --- /dev/null +++ b/chromium/components/navigation_interception/navigation_params_android.cc @@ -0,0 +1,34 @@ +// 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/navigation_interception/navigation_params_android.h" + +#include "base/android/jni_string.h" +#include "jni/NavigationParams_jni.h" + +using base::android::ConvertUTF8ToJavaString; + +namespace navigation_interception { + +base::android::ScopedJavaLocalRef<jobject> CreateJavaNavigationParams( + JNIEnv* env, + const NavigationParams& params) { + ScopedJavaLocalRef<jstring> jstring_url = + ConvertUTF8ToJavaString(env, params.url().spec()); + + return Java_NavigationParams_create(env, + jstring_url.obj(), + params.is_post(), + params.has_user_gesture(), + params.transition_type(), + params.is_redirect()); +} + +// Register native methods. + +bool RegisterNavigationParams(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace navigation_interception diff --git a/chromium/components/navigation_interception/navigation_params_android.h b/chromium/components/navigation_interception/navigation_params_android.h new file mode 100644 index 00000000000..004d4927384 --- /dev/null +++ b/chromium/components/navigation_interception/navigation_params_android.h @@ -0,0 +1,22 @@ +// 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_NAVIGATION_INTERCEPTION_NAVIGATION_PARAMS_ANDROID_H_ +#define COMPONENTS_NAVIGATION_INTERCEPTION_NAVIGATION_PARAMS_ANDROID_H_ + +#include "base/android/jni_android.h" +#include "base/android/scoped_java_ref.h" +#include "components/navigation_interception/navigation_params.h" + +namespace navigation_interception { + +base::android::ScopedJavaLocalRef<jobject> CreateJavaNavigationParams( + JNIEnv* env, + const NavigationParams& params); + +bool RegisterNavigationParams(JNIEnv* env); + +} // namespace navigation_interception + +#endif // COMPONENTS_NAVIGATION_INTERCEPTION_NAVIGATION_PARAMS_ANDROID_H_ diff --git a/chromium/components/policy.gypi b/chromium/components/policy.gypi new file mode 100644 index 00000000000..5295ba46e96 --- /dev/null +++ b/chromium/components/policy.gypi @@ -0,0 +1,41 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'policy_component', + 'type': '<(component)', + 'dependencies': [ + '../base/base.gyp:base', + 'json_schema', + ], + 'defines': [ + 'POLICY_COMPONENT_IMPLEMENTATION', + ], + 'include_dirs': [ + '..', + ], + 'conditions': [ + ['configuration_policy==1', { + 'sources': [ + 'policy/core/common/policy_schema.cc', + 'policy/core/common/policy_schema.h', + 'policy/policy_export.h', + ], + }, { # configuration_policy==0 + # The target 'policy_component' always exists. Later it will include + # some stubs when configuration_policy==0. For now this stub file is + # compiled so that an output is produced, otherwise the shared build + # breaks on iOS. + # TODO(joaodasilva): remove this comment and the temporary stub after + # moving one of the real stubs. http://crbug.com/271392 + 'sources': [ + 'policy/stub_to_remove.cc', + ], + }], + ], + }, + ], +} diff --git a/chromium/components/policy/OWNERS b/chromium/components/policy/OWNERS new file mode 100644 index 00000000000..dbccb7c3a0c --- /dev/null +++ b/chromium/components/policy/OWNERS @@ -0,0 +1,6 @@ +mnissler@chromium.org +pastarmovj@chromium.org +joaodasilva@chromium.org +bartfab@chromium.org +atwilson@chromium.org +pneubeck@chromium.org diff --git a/chromium/components/policy/core/common/policy_schema.cc b/chromium/components/policy/core/common/policy_schema.cc new file mode 100644 index 00000000000..8c3145ce43a --- /dev/null +++ b/chromium/components/policy/core/common/policy_schema.cc @@ -0,0 +1,244 @@ +// 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/policy/core/common/policy_schema.h" + +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/stl_util.h" +#include "components/json_schema/json_schema_constants.h" +#include "components/json_schema/json_schema_validator.h" + +namespace policy { + +namespace { + +const char kJSONSchemaVersion[] = "http://json-schema.org/draft-03/schema#"; + +// Describes the properties of a TYPE_DICTIONARY policy schema. +class DictionaryPolicySchema : public PolicySchema { + public: + static scoped_ptr<PolicySchema> Parse(const base::DictionaryValue& schema, + std::string* error); + + virtual ~DictionaryPolicySchema(); + + virtual const PolicySchemaMap* GetProperties() const OVERRIDE; + virtual const PolicySchema* GetSchemaForAdditionalProperties() const OVERRIDE; + + private: + DictionaryPolicySchema(); + + PolicySchemaMap properties_; + scoped_ptr<PolicySchema> additional_properties_; + + DISALLOW_COPY_AND_ASSIGN(DictionaryPolicySchema); +}; + +// Describes the items of a TYPE_LIST policy schema. +class ListPolicySchema : public PolicySchema { + public: + static scoped_ptr<PolicySchema> Parse(const base::DictionaryValue& schema, + std::string* error); + + virtual ~ListPolicySchema(); + + virtual const PolicySchema* GetSchemaForItems() const OVERRIDE; + + private: + ListPolicySchema(); + + scoped_ptr<PolicySchema> items_schema_; + + DISALLOW_COPY_AND_ASSIGN(ListPolicySchema); +}; + +bool SchemaTypeToValueType(const std::string& type_string, + base::Value::Type* type) { + // Note: "any" is not an accepted type. + static const struct { + const char* schema_type; + base::Value::Type value_type; + } kSchemaToValueTypeMap[] = { + { json_schema_constants::kArray, base::Value::TYPE_LIST }, + { json_schema_constants::kBoolean, base::Value::TYPE_BOOLEAN }, + { json_schema_constants::kInteger, base::Value::TYPE_INTEGER }, + { json_schema_constants::kNull, base::Value::TYPE_NULL }, + { json_schema_constants::kNumber, base::Value::TYPE_DOUBLE }, + { json_schema_constants::kObject, base::Value::TYPE_DICTIONARY }, + { json_schema_constants::kString, base::Value::TYPE_STRING }, + }; + for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSchemaToValueTypeMap); ++i) { + if (kSchemaToValueTypeMap[i].schema_type == type_string) { + *type = kSchemaToValueTypeMap[i].value_type; + return true; + } + } + return false; +} + +scoped_ptr<PolicySchema> ParseSchema(const base::DictionaryValue& schema, + std::string* error) { + std::string type_string; + if (!schema.GetString(json_schema_constants::kType, &type_string)) { + *error = "The schema type must be declared."; + return scoped_ptr<PolicySchema>(); + } + + base::Value::Type type = base::Value::TYPE_NULL; + if (!SchemaTypeToValueType(type_string, &type)) { + *error = "The \"any\" type can't be used."; + return scoped_ptr<PolicySchema>(); + } + + switch (type) { + case base::Value::TYPE_DICTIONARY: + return DictionaryPolicySchema::Parse(schema, error); + case base::Value::TYPE_LIST: + return ListPolicySchema::Parse(schema, error); + default: + return make_scoped_ptr(new PolicySchema(type)); + } +} + +DictionaryPolicySchema::DictionaryPolicySchema() + : PolicySchema(base::Value::TYPE_DICTIONARY) {} + +DictionaryPolicySchema::~DictionaryPolicySchema() { + STLDeleteValues(&properties_); +} + +const PolicySchemaMap* DictionaryPolicySchema::GetProperties() const { + return &properties_; +} + +const PolicySchema* + DictionaryPolicySchema::GetSchemaForAdditionalProperties() const { + return additional_properties_.get(); +} + +// static +scoped_ptr<PolicySchema> DictionaryPolicySchema::Parse( + const base::DictionaryValue& schema, + std::string* error) { + scoped_ptr<DictionaryPolicySchema> dict_schema(new DictionaryPolicySchema()); + + const base::DictionaryValue* dict = NULL; + const base::DictionaryValue* properties = NULL; + if (schema.GetDictionary(json_schema_constants::kProperties, &properties)) { + for (base::DictionaryValue::Iterator it(*properties); + !it.IsAtEnd(); it.Advance()) { + // This should have been verified by the JSONSchemaValidator. + CHECK(it.value().GetAsDictionary(&dict)); + scoped_ptr<PolicySchema> sub_schema = ParseSchema(*dict, error); + if (!sub_schema) + return scoped_ptr<PolicySchema>(); + dict_schema->properties_[it.key()] = sub_schema.release(); + } + } + + if (schema.GetDictionary(json_schema_constants::kAdditionalProperties, + &dict)) { + scoped_ptr<PolicySchema> sub_schema = ParseSchema(*dict, error); + if (!sub_schema) + return scoped_ptr<PolicySchema>(); + dict_schema->additional_properties_ = sub_schema.Pass(); + } + + return dict_schema.PassAs<PolicySchema>(); +} + +ListPolicySchema::ListPolicySchema() + : PolicySchema(base::Value::TYPE_LIST) {} + +ListPolicySchema::~ListPolicySchema() {} + +const PolicySchema* ListPolicySchema::GetSchemaForItems() const { + return items_schema_.get(); +} + +scoped_ptr<PolicySchema> ListPolicySchema::Parse( + const base::DictionaryValue& schema, + std::string* error) { + const base::DictionaryValue* dict = NULL; + if (!schema.GetDictionary(json_schema_constants::kItems, &dict)) { + *error = "Arrays must declare a single schema for their items."; + return scoped_ptr<PolicySchema>(); + } + scoped_ptr<PolicySchema> items_schema = ParseSchema(*dict, error); + if (!items_schema) + return scoped_ptr<PolicySchema>(); + + scoped_ptr<ListPolicySchema> list_schema(new ListPolicySchema()); + list_schema->items_schema_ = items_schema.Pass(); + return list_schema.PassAs<PolicySchema>(); +} + +} // namespace + +PolicySchema::PolicySchema(base::Value::Type type) + : type_(type) {} + +PolicySchema::~PolicySchema() {} + +const PolicySchemaMap* PolicySchema::GetProperties() const { + NOTREACHED(); + return NULL; +} + +const PolicySchema* PolicySchema::GetSchemaForAdditionalProperties() const { + NOTREACHED(); + return NULL; +} + +const PolicySchema* PolicySchema::GetSchemaForProperty( + const std::string& key) const { + const PolicySchemaMap* properties = GetProperties(); + PolicySchemaMap::const_iterator it = properties->find(key); + return it == properties->end() ? GetSchemaForAdditionalProperties() + : it->second; +} + +const PolicySchema* PolicySchema::GetSchemaForItems() const { + NOTREACHED(); + return NULL; +} + +// static +scoped_ptr<PolicySchema> PolicySchema::Parse(const std::string& content, + std::string* error) { + // Validate as a generic JSON schema. + scoped_ptr<base::DictionaryValue> dict = + JSONSchemaValidator::IsValidSchema(content, error); + if (!dict) + return scoped_ptr<PolicySchema>(); + + // Validate the schema version. + std::string string_value; + if (!dict->GetString(json_schema_constants::kSchema, &string_value) || + string_value != kJSONSchemaVersion) { + *error = "Must declare JSON Schema v3 version in \"$schema\"."; + return scoped_ptr<PolicySchema>(); + } + + // Validate the main type. + if (!dict->GetString(json_schema_constants::kType, &string_value) || + string_value != json_schema_constants::kObject) { + *error = + "The main schema must have a type attribute with \"object\" value."; + return scoped_ptr<PolicySchema>(); + } + + // Checks for invalid attributes at the top-level. + if (dict->HasKey(json_schema_constants::kAdditionalProperties) || + dict->HasKey(json_schema_constants::kPatternProperties)) { + *error = "\"additionalProperties\" and \"patternProperties\" are not " + "supported at the main schema."; + return scoped_ptr<PolicySchema>(); + } + + return ParseSchema(*dict, error); +} + +} // namespace policy diff --git a/chromium/components/policy/core/common/policy_schema.h b/chromium/components/policy/core/common/policy_schema.h new file mode 100644 index 00000000000..c48ee6fc2b9 --- /dev/null +++ b/chromium/components/policy/core/common/policy_schema.h @@ -0,0 +1,70 @@ +// 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_POLICY_CORE_COMMON_POLICY_SCHEMA_H_ +#define COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEMA_H_ + +#include <map> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "components/policy/policy_export.h" + +namespace policy { + +class PolicySchema; +typedef std::map<std::string, PolicySchema*> PolicySchemaMap; + +// Maps known policy keys to their expected types, and recursively describes +// the known keys within dictionary or list types. +class POLICY_EXPORT PolicySchema { + public: + + // Parses |schema| as a JSON v3 schema, and additionally verifies that: + // - the version is JSON schema v3; + // - the top-level entry is of type "object"; + // - the top-level object doesn't contain "additionalProperties" nor + // "patternProperties"; + // - each "property" maps to a schema with one "type"; + // - the type "any" is not used. + // If all the checks pass then the parsed PolicySchema is returned; otherwise + // returns NULL. + static scoped_ptr<PolicySchema> Parse(const std::string& schema, + std::string* error); + + explicit PolicySchema(base::Value::Type type); + virtual ~PolicySchema(); + + // Returns the expected type for this policy. At the top-level PolicySchema + // this is always TYPE_DICTIONARY. + base::Value::Type type() const { return type_; } + + // It is invalid to call these methods when type() is not TYPE_DICTIONARY. + // + // GetProperties() returns a map of the known property names to their schemas; + // the map is never NULL. + // GetSchemaForAdditionalProperties() returns the schema that should be used + // for keys not found in the map, and may be NULL. + // GetSchemaForProperty() is a utility method that combines both, returning + // the mapped schema if found in GetProperties(), otherwise returning + // GetSchemaForAdditionalProperties(). + virtual const PolicySchemaMap* GetProperties() const; + virtual const PolicySchema* GetSchemaForAdditionalProperties() const; + const PolicySchema* GetSchemaForProperty(const std::string& key) const; + + // It is invalid to call this method when type() is not TYPE_LIST. + // Returns the type of the entries of this "array", which is never NULL. + virtual const PolicySchema* GetSchemaForItems() const; + + private: + const base::Value::Type type_; + + DISALLOW_COPY_AND_ASSIGN(PolicySchema); +}; + +} // namespace policy + +#endif // COMPONENTS_POLICY_CORE_COMMON_POLICY_SCHEMA_H_ diff --git a/chromium/components/policy/core/common/policy_schema_unittest.cc b/chromium/components/policy/core/common/policy_schema_unittest.cc new file mode 100644 index 00000000000..bfbdd652159 --- /dev/null +++ b/chromium/components/policy/core/common/policy_schema_unittest.cc @@ -0,0 +1,193 @@ +// 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/policy/core/common/policy_schema.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace policy { + +namespace { + +#define SCHEMA_VERSION "\"$schema\":\"http://json-schema.org/draft-03/schema#\"" +#define OBJECT_TYPE "\"type\":\"object\"" + +bool ParseFails(const std::string& content) { + std::string error; + scoped_ptr<PolicySchema> schema = PolicySchema::Parse(content, &error); + EXPECT_TRUE(schema || !error.empty()); + return !schema; +} + +} // namespace + +TEST(PolicySchemaTest, MinimalSchema) { + EXPECT_FALSE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE + "}")); +} + +TEST(PolicySchemaTest, InvalidSchemas) { + EXPECT_TRUE(ParseFails("")); + EXPECT_TRUE(ParseFails("omg")); + EXPECT_TRUE(ParseFails("\"omg\"")); + EXPECT_TRUE(ParseFails("123")); + EXPECT_TRUE(ParseFails("[]")); + EXPECT_TRUE(ParseFails("null")); + EXPECT_TRUE(ParseFails("{}")); + EXPECT_TRUE(ParseFails("{" SCHEMA_VERSION "}")); + EXPECT_TRUE(ParseFails("{" OBJECT_TYPE "}")); + + EXPECT_TRUE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"additionalProperties\": { \"type\":\"object\" }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"patternProperties\": { \"a+b*\": { \"type\": \"object\" } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": \"bogus\" } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": [\"string\", \"number\"] } }" + "}")); + + EXPECT_TRUE(ParseFails( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"properties\": { \"Policy\": { \"type\": \"any\" } }" + "}")); +} + +TEST(PolicySchemaTest, ValidSchema) { + std::string error; + scoped_ptr<PolicySchema> schema = PolicySchema::Parse( + "{" + SCHEMA_VERSION "," + OBJECT_TYPE "," + "\"properties\": {" + " \"Boolean\": { \"type\": \"boolean\" }," + " \"Integer\": { \"type\": \"integer\" }," + " \"Null\": { \"type\": \"null\" }," + " \"Number\": { \"type\": \"number\" }," + " \"String\": { \"type\": \"string\" }," + " \"Array\": {" + " \"type\": \"array\"," + " \"items\": { \"type\": \"string\" }" + " }," + " \"ArrayOfObjects\": {" + " \"type\": \"array\"," + " \"items\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"one\": { \"type\": \"string\" }," + " \"two\": { \"type\": \"integer\" }" + " }" + " }" + " }," + " \"ArrayOfArray\": {" + " \"type\": \"array\"," + " \"items\": {" + " \"type\": \"array\"," + " \"items\": { \"type\": \"string\" }" + " }" + " }," + " \"Object\": {" + " \"type\": \"object\"," + " \"properties\": {" + " \"one\": { \"type\": \"boolean\" }," + " \"two\": { \"type\": \"integer\" }" + " }," + " \"additionalProperties\": { \"type\": \"string\" }" + " }" + "}" + "}", &error); + ASSERT_TRUE(schema) << error; + + ASSERT_EQ(base::Value::TYPE_DICTIONARY, schema->type()); + EXPECT_FALSE(schema->GetSchemaForProperty("invalid")); + + const PolicySchema* sub = schema->GetSchemaForProperty("Boolean"); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_BOOLEAN, sub->type()); + + sub = schema->GetSchemaForProperty("Integer"); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_INTEGER, sub->type()); + + sub = schema->GetSchemaForProperty("Null"); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_NULL, sub->type()); + + sub = schema->GetSchemaForProperty("Number"); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_DOUBLE, sub->type()); + sub = schema->GetSchemaForProperty("String"); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_STRING, sub->type()); + + sub = schema->GetSchemaForProperty("Array"); + ASSERT_TRUE(sub); + ASSERT_EQ(base::Value::TYPE_LIST, sub->type()); + sub = sub->GetSchemaForItems(); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_STRING, sub->type()); + + sub = schema->GetSchemaForProperty("ArrayOfObjects"); + ASSERT_TRUE(sub); + ASSERT_EQ(base::Value::TYPE_LIST, sub->type()); + sub = sub->GetSchemaForItems(); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_DICTIONARY, sub->type()); + const PolicySchema* subsub = sub->GetSchemaForProperty("one"); + ASSERT_TRUE(subsub); + EXPECT_EQ(base::Value::TYPE_STRING, subsub->type()); + subsub = sub->GetSchemaForProperty("two"); + ASSERT_TRUE(subsub); + EXPECT_EQ(base::Value::TYPE_INTEGER, subsub->type()); + subsub = sub->GetSchemaForProperty("invalid"); + EXPECT_FALSE(subsub); + + sub = schema->GetSchemaForProperty("ArrayOfArray"); + ASSERT_TRUE(sub); + ASSERT_EQ(base::Value::TYPE_LIST, sub->type()); + sub = sub->GetSchemaForItems(); + ASSERT_TRUE(sub); + ASSERT_EQ(base::Value::TYPE_LIST, sub->type()); + sub = sub->GetSchemaForItems(); + ASSERT_TRUE(sub); + EXPECT_EQ(base::Value::TYPE_STRING, sub->type()); + + sub = schema->GetSchemaForProperty("Object"); + ASSERT_TRUE(sub); + ASSERT_EQ(base::Value::TYPE_DICTIONARY, sub->type()); + subsub = sub->GetSchemaForProperty("one"); + ASSERT_TRUE(subsub); + EXPECT_EQ(base::Value::TYPE_BOOLEAN, subsub->type()); + subsub = sub->GetSchemaForProperty("two"); + ASSERT_TRUE(subsub); + EXPECT_EQ(base::Value::TYPE_INTEGER, subsub->type()); + subsub = sub->GetSchemaForProperty("undeclared"); + ASSERT_TRUE(subsub); + EXPECT_EQ(base::Value::TYPE_STRING, subsub->type()); +} + +} // namespace policy diff --git a/chromium/components/policy/policy_export.h b/chromium/components/policy/policy_export.h new file mode 100644 index 00000000000..47376acaa85 --- /dev/null +++ b/chromium/components/policy/policy_export.h @@ -0,0 +1,29 @@ +// 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_POLICY_POLICY_EXPORT_H_ +#define COMPONENTS_POLICY_POLICY_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(POLICY_COMPONENT_IMPLEMENTATION) +#define POLICY_EXPORT __declspec(dllexport) +#else +#define POLICY_EXPORT __declspec(dllimport) +#endif // defined(BASE_PREFS_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(POLICY_COMPONENT_IMPLEMENTATION) +#define POLICY_EXPORT __attribute__((visibility("default"))) +#else +#define POLICY_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define POLICY_EXPORT +#endif + +#endif // COMPONENTS_POLICY_POLICY_EXPORT_H_ diff --git a/chromium/components/policy/stub_to_remove.cc b/chromium/components/policy/stub_to_remove.cc new file mode 100644 index 00000000000..6e352ace9cd --- /dev/null +++ b/chromium/components/policy/stub_to_remove.cc @@ -0,0 +1,6 @@ +// 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. + +// TODO(joaodasilva): remove this file and update the comment on policy.gypi. +// http://crbug.com/271392 diff --git a/chromium/components/sessions.gypi b/chromium/components/sessions.gypi new file mode 100644 index 00000000000..401015eea04 --- /dev/null +++ b/chromium/components/sessions.gypi @@ -0,0 +1,64 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'sessions', + 'type': '<(component)', + 'dependencies': [ + '../base/base.gyp:base', + '../content/content.gyp:content_browser', + '../skia/skia.gyp:skia', + '../third_party/protobuf/protobuf.gyp:protobuf_lite', + '../url/url.gyp:url_lib', + ], + 'include_dirs': [ + '..', + ], + 'defines': [ + 'SESSIONS_IMPLEMENTATION', + ], + 'sources': [ + 'sessions/serialized_navigation_entry.cc', + 'sessions/serialized_navigation_entry.h', + ], + 'conditions': [ + ['OS != "ios" and chrome_multiple_dll != 1', { + 'dependencies': [ + '../webkit/support/webkit_support.gyp:glue', + ] + }], + ['android_webview_build == 0', { + 'dependencies': [ + '../sync/sync.gyp:sync', + ] + }], + ], + }, + { + 'target_name': 'sessions_test_support', + 'type': 'static_library', + 'defines!': ['SESSIONS_IMPLEMENTATION'], + 'dependencies': [ + '../skia/skia.gyp:skia', + '../testing/gtest.gyp:gtest', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'sessions/serialized_navigation_entry_test_helper.cc', + 'sessions/serialized_navigation_entry_test_helper.h', + ], + 'conditions': [ + ['android_webview_build == 0', { + 'dependencies': [ + '../sync/sync.gyp:sync', + ] + }], + ], + }, + ], +} diff --git a/chromium/components/sessions/DEPS b/chromium/components/sessions/DEPS new file mode 100644 index 00000000000..fd2f190d0a5 --- /dev/null +++ b/chromium/components/sessions/DEPS @@ -0,0 +1,7 @@ +include_rules = [ + "+content/public/browser", + "+content/public/common", + "+sync", + "+third_party/WebKit/public/platform", + "+webkit", +] diff --git a/chromium/components/sessions/OWNERS b/chromium/components/sessions/OWNERS new file mode 100644 index 00000000000..73674b786cb --- /dev/null +++ b/chromium/components/sessions/OWNERS @@ -0,0 +1,2 @@ +marja@chromium.org +sky@chromium.org diff --git a/chromium/components/sessions/serialized_navigation_entry.cc b/chromium/components/sessions/serialized_navigation_entry.cc new file mode 100644 index 00000000000..5e4183cf5d2 --- /dev/null +++ b/chromium/components/sessions/serialized_navigation_entry.cc @@ -0,0 +1,475 @@ +// 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/sessions/serialized_navigation_entry.h" + +#include "base/pickle.h" +#include "base/strings/utf_string_conversions.h" +#include "content/public/browser/favicon_status.h" +#include "content/public/browser/navigation_controller.h" +#include "content/public/browser/navigation_entry.h" +#include "sync/protocol/session_specifics.pb.h" +#include "sync/util/time.h" +#include "third_party/WebKit/public/platform/WebReferrerPolicy.h" + +using content::NavigationEntry; + +namespace sessions { + +const char kSearchTermsKey[] = "search_terms"; + +SerializedNavigationEntry::SerializedNavigationEntry() + : index_(-1), + unique_id_(0), + transition_type_(content::PAGE_TRANSITION_TYPED), + has_post_data_(false), + post_id_(-1), + is_overriding_user_agent_(false), + blocked_state_(STATE_INVALID) {} + +SerializedNavigationEntry::~SerializedNavigationEntry() {} + +// static +SerializedNavigationEntry SerializedNavigationEntry::FromNavigationEntry( + int index, + const NavigationEntry& entry) { + SerializedNavigationEntry navigation; + navigation.index_ = index; + navigation.unique_id_ = entry.GetUniqueID(); + navigation.referrer_ = entry.GetReferrer(); + navigation.virtual_url_ = entry.GetVirtualURL(); + navigation.title_ = entry.GetTitle(); + navigation.page_state_ = entry.GetPageState(); + navigation.transition_type_ = entry.GetTransitionType(); + navigation.has_post_data_ = entry.GetHasPostData(); + navigation.post_id_ = entry.GetPostID(); + navigation.original_request_url_ = entry.GetOriginalRequestURL(); + navigation.is_overriding_user_agent_ = entry.GetIsOverridingUserAgent(); + navigation.timestamp_ = entry.GetTimestamp(); + // If you want to navigate a named frame in Chrome, you will first need to + // add support for persisting it. It is currently only used for layout tests. + CHECK(entry.GetFrameToNavigate().empty()); + entry.GetExtraData(kSearchTermsKey, &navigation.search_terms_); + if (entry.GetFavicon().valid) + navigation.favicon_url_ = entry.GetFavicon().url; + + return navigation; +} + +SerializedNavigationEntry SerializedNavigationEntry::FromSyncData( + int index, + const sync_pb::TabNavigation& sync_data) { + SerializedNavigationEntry navigation; + navigation.index_ = index; + navigation.unique_id_ = sync_data.unique_id(); + navigation.referrer_ = + content::Referrer(GURL(sync_data.referrer()), + WebKit::WebReferrerPolicyDefault); + navigation.virtual_url_ = GURL(sync_data.virtual_url()); + navigation.title_ = UTF8ToUTF16(sync_data.title()); + navigation.page_state_ = + content::PageState::CreateFromEncodedData(sync_data.state()); + + uint32 transition = 0; + if (sync_data.has_page_transition()) { + switch (sync_data.page_transition()) { + case sync_pb::SyncEnums_PageTransition_LINK: + transition = content::PAGE_TRANSITION_LINK; + break; + case sync_pb::SyncEnums_PageTransition_TYPED: + transition = content::PAGE_TRANSITION_TYPED; + break; + case sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK: + transition = content::PAGE_TRANSITION_AUTO_BOOKMARK; + break; + case sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME: + transition = content::PAGE_TRANSITION_AUTO_SUBFRAME; + break; + case sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME: + transition = content::PAGE_TRANSITION_MANUAL_SUBFRAME; + break; + case sync_pb::SyncEnums_PageTransition_GENERATED: + transition = content::PAGE_TRANSITION_GENERATED; + break; + case sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL: + transition = content::PAGE_TRANSITION_AUTO_TOPLEVEL; + break; + case sync_pb::SyncEnums_PageTransition_FORM_SUBMIT: + transition = content::PAGE_TRANSITION_FORM_SUBMIT; + break; + case sync_pb::SyncEnums_PageTransition_RELOAD: + transition = content::PAGE_TRANSITION_RELOAD; + break; + case sync_pb::SyncEnums_PageTransition_KEYWORD: + transition = content::PAGE_TRANSITION_KEYWORD; + break; + case sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED: + transition = + content::PAGE_TRANSITION_KEYWORD_GENERATED; + break; + default: + transition = content::PAGE_TRANSITION_LINK; + break; + } + } + + if (sync_data.has_redirect_type()) { + switch (sync_data.redirect_type()) { + case sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT: + transition |= content::PAGE_TRANSITION_CLIENT_REDIRECT; + break; + case sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT: + transition |= content::PAGE_TRANSITION_SERVER_REDIRECT; + break; + } + } + if (sync_data.navigation_forward_back()) + transition |= content::PAGE_TRANSITION_FORWARD_BACK; + if (sync_data.navigation_from_address_bar()) + transition |= content::PAGE_TRANSITION_FROM_ADDRESS_BAR; + if (sync_data.navigation_home_page()) + transition |= content::PAGE_TRANSITION_HOME_PAGE; + if (sync_data.navigation_chain_start()) + transition |= content::PAGE_TRANSITION_CHAIN_START; + if (sync_data.navigation_chain_end()) + transition |= content::PAGE_TRANSITION_CHAIN_END; + + navigation.transition_type_ = + static_cast<content::PageTransition>(transition); + + navigation.timestamp_ = base::Time(); + navigation.search_terms_ = UTF8ToUTF16(sync_data.search_terms()); + if (sync_data.has_favicon_url()) + navigation.favicon_url_ = GURL(sync_data.favicon_url()); + + // We shouldn't sync session data for managed users down at the moment. + DCHECK(!sync_data.has_blocked_state()); + DCHECK_EQ(0, sync_data.content_pack_categories_size()); + + return navigation; +} + +namespace { + +// Helper used by SerializedNavigationEntry::WriteToPickle(). It writes |str| to +// |pickle|, if and only if |str| fits within (|max_bytes| - +// |*bytes_written|). |bytes_written| is incremented to reflect the +// data written. +// +// TODO(akalin): Unify this with the same function in +// base_session_service.cc. +void WriteStringToPickle(Pickle* pickle, + int* bytes_written, + int max_bytes, + const std::string& str) { + int num_bytes = str.size() * sizeof(char); + if (*bytes_written + num_bytes < max_bytes) { + *bytes_written += num_bytes; + pickle->WriteString(str); + } else { + pickle->WriteString(std::string()); + } +} + +// string16 version of WriteStringToPickle. +// +// TODO(akalin): Unify this, too. +void WriteString16ToPickle(Pickle* pickle, + int* bytes_written, + int max_bytes, + const string16& str) { + int num_bytes = str.size() * sizeof(char16); + if (*bytes_written + num_bytes < max_bytes) { + *bytes_written += num_bytes; + pickle->WriteString16(str); + } else { + pickle->WriteString16(string16()); + } +} + +// A mask used for arbitrary boolean values needed to represent a +// NavigationEntry. Currently only contains HAS_POST_DATA. +// +// NOTE(akalin): We may want to just serialize |has_post_data_| +// directly. Other bools (|is_overriding_user_agent_|) haven't been +// added to this mask. +enum TypeMask { + HAS_POST_DATA = 1 +}; + +} // namespace + +// Pickle order: +// +// index_ +// virtual_url_ +// title_ +// page_state_ +// transition_type_ +// +// Added on later: +// +// type_mask (has_post_data_) +// referrer_ +// original_request_url_ +// is_overriding_user_agent_ +// timestamp_ +// search_terms_ + +void SerializedNavigationEntry::WriteToPickle(int max_size, + Pickle* pickle) const { + pickle->WriteInt(index_); + + int bytes_written = 0; + + WriteStringToPickle(pickle, &bytes_written, max_size, + virtual_url_.spec()); + + WriteString16ToPickle(pickle, &bytes_written, max_size, title_); + + content::PageState page_state = page_state_; + if (has_post_data_) + page_state = page_state.RemovePasswordData(); + + WriteStringToPickle(pickle, &bytes_written, max_size, + page_state.ToEncodedData()); + + pickle->WriteInt(transition_type_); + + const int type_mask = has_post_data_ ? HAS_POST_DATA : 0; + pickle->WriteInt(type_mask); + + WriteStringToPickle( + pickle, &bytes_written, max_size, + referrer_.url.is_valid() ? referrer_.url.spec() : std::string()); + + pickle->WriteInt(referrer_.policy); + + // Save info required to override the user agent. + WriteStringToPickle( + pickle, &bytes_written, max_size, + original_request_url_.is_valid() ? + original_request_url_.spec() : std::string()); + pickle->WriteBool(is_overriding_user_agent_); + pickle->WriteInt64(timestamp_.ToInternalValue()); + + WriteString16ToPickle(pickle, &bytes_written, max_size, search_terms_); +} + +bool SerializedNavigationEntry::ReadFromPickle(PickleIterator* iterator) { + *this = SerializedNavigationEntry(); + std::string virtual_url_spec, page_state_data; + int transition_type_int = 0; + if (!iterator->ReadInt(&index_) || + !iterator->ReadString(&virtual_url_spec) || + !iterator->ReadString16(&title_) || + !iterator->ReadString(&page_state_data) || + !iterator->ReadInt(&transition_type_int)) + return false; + virtual_url_ = GURL(virtual_url_spec); + page_state_ = content::PageState::CreateFromEncodedData(page_state_data); + transition_type_ = static_cast<content::PageTransition>(transition_type_int); + + // type_mask did not always exist in the written stream. As such, we + // don't fail if it can't be read. + int type_mask = 0; + bool has_type_mask = iterator->ReadInt(&type_mask); + + if (has_type_mask) { + has_post_data_ = type_mask & HAS_POST_DATA; + // the "referrer" property was added after type_mask to the written + // stream. As such, we don't fail if it can't be read. + std::string referrer_spec; + if (!iterator->ReadString(&referrer_spec)) + referrer_spec = std::string(); + // The "referrer policy" property was added even later, so we fall back to + // the default policy if the property is not present. + int policy_int; + WebKit::WebReferrerPolicy policy; + if (iterator->ReadInt(&policy_int)) + policy = static_cast<WebKit::WebReferrerPolicy>(policy_int); + else + policy = WebKit::WebReferrerPolicyDefault; + referrer_ = content::Referrer(GURL(referrer_spec), policy); + + // If the original URL can't be found, leave it empty. + std::string original_request_url_spec; + if (!iterator->ReadString(&original_request_url_spec)) + original_request_url_spec = std::string(); + original_request_url_ = GURL(original_request_url_spec); + + // Default to not overriding the user agent if we don't have info. + if (!iterator->ReadBool(&is_overriding_user_agent_)) + is_overriding_user_agent_ = false; + + int64 timestamp_internal_value = 0; + if (iterator->ReadInt64(×tamp_internal_value)) { + timestamp_ = base::Time::FromInternalValue(timestamp_internal_value); + } else { + timestamp_ = base::Time(); + } + + // If the search terms field can't be found, leave it empty. + if (!iterator->ReadString16(&search_terms_)) + search_terms_.clear(); + } + + return true; +} + +scoped_ptr<NavigationEntry> SerializedNavigationEntry::ToNavigationEntry( + int page_id, + content::BrowserContext* browser_context) const { + scoped_ptr<NavigationEntry> entry( + content::NavigationController::CreateNavigationEntry( + virtual_url_, + referrer_, + // Use a transition type of reload so that we don't incorrectly + // increase the typed count. + content::PAGE_TRANSITION_RELOAD, + false, + // The extra headers are not sync'ed across sessions. + std::string(), + browser_context)); + + entry->SetTitle(title_); + entry->SetPageState(page_state_); + entry->SetPageID(page_id); + entry->SetHasPostData(has_post_data_); + entry->SetPostID(post_id_); + entry->SetOriginalRequestURL(original_request_url_); + entry->SetIsOverridingUserAgent(is_overriding_user_agent_); + entry->SetTimestamp(timestamp_); + entry->SetExtraData(kSearchTermsKey, search_terms_); + + // These fields should have default values. + DCHECK_EQ(STATE_INVALID, blocked_state_); + DCHECK_EQ(0u, content_pack_categories_.size()); + + return entry.Pass(); +} + +// TODO(zea): perhaps sync state (scroll position, form entries, etc.) as well? +// See http://crbug.com/67068. +sync_pb::TabNavigation SerializedNavigationEntry::ToSyncData() const { + sync_pb::TabNavigation sync_data; + sync_data.set_virtual_url(virtual_url_.spec()); + // FIXME(zea): Support referrer policy? + sync_data.set_referrer(referrer_.url.spec()); + sync_data.set_title(UTF16ToUTF8(title_)); + + // Page transition core. + COMPILE_ASSERT(content::PAGE_TRANSITION_LAST_CORE == + content::PAGE_TRANSITION_KEYWORD_GENERATED, + PageTransitionCoreBounds); + switch (PageTransitionStripQualifier(transition_type_)) { + case content::PAGE_TRANSITION_LINK: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_LINK); + break; + case content::PAGE_TRANSITION_TYPED: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_TYPED); + break; + case content::PAGE_TRANSITION_AUTO_BOOKMARK: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_AUTO_BOOKMARK); + break; + case content::PAGE_TRANSITION_AUTO_SUBFRAME: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME); + break; + case content::PAGE_TRANSITION_MANUAL_SUBFRAME: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_MANUAL_SUBFRAME); + break; + case content::PAGE_TRANSITION_GENERATED: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_GENERATED); + break; + case content::PAGE_TRANSITION_AUTO_TOPLEVEL: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_AUTO_TOPLEVEL); + break; + case content::PAGE_TRANSITION_FORM_SUBMIT: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_FORM_SUBMIT); + break; + case content::PAGE_TRANSITION_RELOAD: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_RELOAD); + break; + case content::PAGE_TRANSITION_KEYWORD: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_KEYWORD); + break; + case content::PAGE_TRANSITION_KEYWORD_GENERATED: + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_KEYWORD_GENERATED); + break; + default: + NOTREACHED(); + } + + // Page transition qualifiers. + if (PageTransitionIsRedirect(transition_type_)) { + if (transition_type_ & content::PAGE_TRANSITION_CLIENT_REDIRECT) { + sync_data.set_redirect_type( + sync_pb::SyncEnums_PageTransitionRedirectType_CLIENT_REDIRECT); + } else if (transition_type_ & content::PAGE_TRANSITION_SERVER_REDIRECT) { + sync_data.set_redirect_type( + sync_pb::SyncEnums_PageTransitionRedirectType_SERVER_REDIRECT); + } + } + sync_data.set_navigation_forward_back( + (transition_type_ & content::PAGE_TRANSITION_FORWARD_BACK) != 0); + sync_data.set_navigation_from_address_bar( + (transition_type_ & content::PAGE_TRANSITION_FROM_ADDRESS_BAR) != 0); + sync_data.set_navigation_home_page( + (transition_type_ & content::PAGE_TRANSITION_HOME_PAGE) != 0); + sync_data.set_navigation_chain_start( + (transition_type_ & content::PAGE_TRANSITION_CHAIN_START) != 0); + sync_data.set_navigation_chain_end( + (transition_type_ & content::PAGE_TRANSITION_CHAIN_END) != 0); + + sync_data.set_unique_id(unique_id_); + sync_data.set_timestamp_msec(syncer::TimeToProtoTime(timestamp_)); + // The full-resolution timestamp works as a global ID. + sync_data.set_global_id(timestamp_.ToInternalValue()); + + sync_data.set_search_terms(UTF16ToUTF8(search_terms_)); + + if (favicon_url_.is_valid()) + sync_data.set_favicon_url(favicon_url_.spec()); + + if (blocked_state_ != STATE_INVALID) { + sync_data.set_blocked_state( + static_cast<sync_pb::TabNavigation_BlockedState>(blocked_state_)); + } + + for (std::set<std::string>::const_iterator it = + content_pack_categories_.begin(); + it != content_pack_categories_.end(); ++it) { + sync_data.add_content_pack_categories(*it); + } + + return sync_data; +} + +// static +std::vector<NavigationEntry*> SerializedNavigationEntry::ToNavigationEntries( + const std::vector<SerializedNavigationEntry>& navigations, + content::BrowserContext* browser_context) { + int page_id = 0; + std::vector<NavigationEntry*> entries; + for (std::vector<SerializedNavigationEntry>::const_iterator + it = navigations.begin(); it != navigations.end(); ++it) { + entries.push_back( + it->ToNavigationEntry(page_id, browser_context).release()); + ++page_id; + } + return entries; +} + +} // namespace sessions diff --git a/chromium/components/sessions/serialized_navigation_entry.h b/chromium/components/sessions/serialized_navigation_entry.h new file mode 100644 index 00000000000..e5813dce0d8 --- /dev/null +++ b/chromium/components/sessions/serialized_navigation_entry.h @@ -0,0 +1,160 @@ +// 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_SESSIONS_SERIALIZED_NAVIGATION_ENTRY_H_ +#define COMPONENTS_SESSIONS_SERIALIZED_NAVIGATION_ENTRY_H_ + +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/string16.h" +#include "base/time/time.h" +#include "components/sessions/sessions_export.h" +#include "content/public/common/page_state.h" +#include "content/public/common/page_transition_types.h" +#include "content/public/common/referrer.h" +#include "url/gurl.h" + +class Pickle; +class PickleIterator; + +namespace content { +class BrowserContext; +class NavigationEntry; +} + +namespace sync_pb { +class TabNavigation; +} + +namespace sessions { + +class SerializedNavigationEntryTestHelper; + +// The key used to store search terms data in the NavigationEntry. +SESSIONS_EXPORT extern const char kSearchTermsKey[]; + +// SerializedNavigationEntry is a "freeze-dried" version of NavigationEntry. It +// contains the data needed to restore a NavigationEntry during session restore +// and tab restore, and it can also be pickled and unpickled. It is also +// convertible to a sync protocol buffer for session syncing. +// +// Default copy constructor and assignment operator welcome. +class SESSIONS_EXPORT SerializedNavigationEntry { + public: + enum BlockedState { + STATE_INVALID = 0, + STATE_ALLOWED = 1, + STATE_BLOCKED = 2, + }; + + // Creates an invalid (index < 0) SerializedNavigationEntry. + SerializedNavigationEntry(); + ~SerializedNavigationEntry(); + + // Construct a SerializedNavigationEntry for a particular index from the given + // NavigationEntry. + static SerializedNavigationEntry FromNavigationEntry( + int index, + const content::NavigationEntry& entry); + + // Construct a SerializedNavigationEntry for a particular index from a sync + // protocol buffer. Note that the sync protocol buffer doesn't contain all + // SerializedNavigationEntry fields. Also, the timestamp of the returned + // SerializedNavigationEntry is nulled out, as we assume that the protocol + // buffer is from a foreign session. + static SerializedNavigationEntry FromSyncData( + int index, + const sync_pb::TabNavigation& sync_data); + + // Note that not all SerializedNavigationEntry fields are preserved. + // |max_size| is the max number of bytes to write. + void WriteToPickle(int max_size, Pickle* pickle) const; + bool ReadFromPickle(PickleIterator* iterator); + + // Convert this SerializedNavigationEntry into a NavigationEntry with the + // given page ID and context. The NavigationEntry will have a transition type + // of PAGE_TRANSITION_RELOAD and a new unique ID. + scoped_ptr<content::NavigationEntry> ToNavigationEntry( + int page_id, + content::BrowserContext* browser_context) const; + + // Convert this navigation into its sync protocol buffer equivalent. Note + // that the protocol buffer doesn't contain all SerializedNavigationEntry + // fields. + sync_pb::TabNavigation ToSyncData() const; + + // The index in the NavigationController. This SerializedNavigationEntry is + // valid only when the index is non-negative. + int index() const { return index_; } + void set_index(int index) { index_ = index; } + + // Accessors for some fields taken from NavigationEntry. + int unique_id() const { return unique_id_; } + const GURL& virtual_url() const { return virtual_url_; } + const string16& title() const { return title_; } + const content::PageState& page_state() const { return page_state_; } + const string16& search_terms() const { return search_terms_; } + const GURL& favicon_url() const { return favicon_url_; } + const content::Referrer& referrer() const { return referrer_; } + content::PageTransition transition_type() const { + return transition_type_; + } + bool has_post_data() const { return has_post_data_; } + int64 post_id() const { return post_id_; } + const GURL& original_request_url() const { return original_request_url_; } + bool is_overriding_user_agent() const { return is_overriding_user_agent_; } + base::Time timestamp() const { return timestamp_; } + + BlockedState blocked_state() { return blocked_state_; } + void set_blocked_state(BlockedState blocked_state) { + blocked_state_ = blocked_state; + } + std::set<std::string> content_pack_categories() { + return content_pack_categories_; + } + void set_content_pack_categories( + const std::set<std::string>& content_pack_categories) { + content_pack_categories_ = content_pack_categories; + } + + // Converts a set of SerializedNavigationEntrys into a list of + // NavigationEntrys with sequential page IDs and the given context. The caller + // owns the returned NavigationEntrys. + static std::vector<content::NavigationEntry*> ToNavigationEntries( + const std::vector<SerializedNavigationEntry>& navigations, + content::BrowserContext* browser_context); + + private: + friend class SerializedNavigationEntryTestHelper; + + // Index in the NavigationController. + int index_; + + // Member variables corresponding to NavigationEntry fields. + int unique_id_; + content::Referrer referrer_; + GURL virtual_url_; + string16 title_; + content::PageState page_state_; + content::PageTransition transition_type_; + bool has_post_data_; + int64 post_id_; + GURL original_request_url_; + bool is_overriding_user_agent_; + base::Time timestamp_; + string16 search_terms_; + GURL favicon_url_; + + // Additional information. + BlockedState blocked_state_; + std::set<std::string> content_pack_categories_; +}; + +} // namespace sessions + +#endif // COMPONENTS_SESSIONS_SERIALIZED_NAVIGATION_ENTRY_H_ diff --git a/chromium/components/sessions/serialized_navigation_entry_test_helper.cc b/chromium/components/sessions/serialized_navigation_entry_test_helper.cc new file mode 100644 index 00000000000..f4a7e2eba6a --- /dev/null +++ b/chromium/components/sessions/serialized_navigation_entry_test_helper.cc @@ -0,0 +1,84 @@ +// 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/sessions/serialized_navigation_entry_test_helper.h" + +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "components/sessions/serialized_navigation_entry.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/platform/WebReferrerPolicy.h" +#include "url/gurl.h" + +namespace sessions { + +// static +void SerializedNavigationEntryTestHelper::ExpectNavigationEquals( + const SerializedNavigationEntry& expected, + const SerializedNavigationEntry& actual) { + EXPECT_EQ(expected.referrer_.url, actual.referrer_.url); + EXPECT_EQ(expected.referrer_.policy, actual.referrer_.policy); + EXPECT_EQ(expected.virtual_url_, actual.virtual_url_); + EXPECT_EQ(expected.title_, actual.title_); + EXPECT_EQ(expected.page_state_, actual.page_state_); + EXPECT_EQ(expected.transition_type_, actual.transition_type_); + EXPECT_EQ(expected.has_post_data_, actual.has_post_data_); + EXPECT_EQ(expected.original_request_url_, actual.original_request_url_); + EXPECT_EQ(expected.is_overriding_user_agent_, + actual.is_overriding_user_agent_); +} + +// static +SerializedNavigationEntry SerializedNavigationEntryTestHelper::CreateNavigation( + const std::string& virtual_url, + const std::string& title) { + SerializedNavigationEntry navigation; + navigation.index_ = 0; + navigation.referrer_ = + content::Referrer(GURL("http://www.referrer.com"), + WebKit::WebReferrerPolicyDefault); + navigation.virtual_url_ = GURL(virtual_url); + navigation.title_ = UTF8ToUTF16(title); + navigation.page_state_ = + content::PageState::CreateFromEncodedData("fake_state"); + navigation.timestamp_ = base::Time::Now(); + return navigation; +} + +// static +void SerializedNavigationEntryTestHelper::SetPageState( + const content::PageState& page_state, + SerializedNavigationEntry* navigation) { + navigation->page_state_ = page_state; +} + +// static +void SerializedNavigationEntryTestHelper::SetHasPostData( + bool has_post_data, + SerializedNavigationEntry* navigation) { + navigation->has_post_data_ = has_post_data; +} + +// static +void SerializedNavigationEntryTestHelper::SetOriginalRequestURL( + const GURL& original_request_url, + SerializedNavigationEntry* navigation) { + navigation->original_request_url_ = original_request_url; +} + +// static +void SerializedNavigationEntryTestHelper::SetIsOverridingUserAgent( + bool is_overriding_user_agent, + SerializedNavigationEntry* navigation) { + navigation->is_overriding_user_agent_ = is_overriding_user_agent; +} + +// static +void SerializedNavigationEntryTestHelper::SetTimestamp( + base::Time timestamp, + SerializedNavigationEntry* navigation) { + navigation->timestamp_ = timestamp; +} + +} // namespace sessions diff --git a/chromium/components/sessions/serialized_navigation_entry_test_helper.h b/chromium/components/sessions/serialized_navigation_entry_test_helper.h new file mode 100644 index 00000000000..a653f22d67e --- /dev/null +++ b/chromium/components/sessions/serialized_navigation_entry_test_helper.h @@ -0,0 +1,63 @@ +// 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_SESSIONS_SESSION_TYPES_TEST_HELPER_H_ +#define COMPONENTS_SESSIONS_SESSION_TYPES_TEST_HELPER_H_ + +#include <string> + +#include "base/basictypes.h" +#include "content/public/common/page_transition_types.h" + +class GURL; + +namespace base { +class Time; +} + +namespace content { +class PageState; +struct Referrer; +} + +namespace sessions { + +class SerializedNavigationEntry; + +// Set of test functions to manipulate a SerializedNavigationEntry. +class SerializedNavigationEntryTestHelper { + public: + // Compares the two entries. This uses EXPECT_XXX on each member, if your test + // needs to stop after this wrap calls to this in EXPECT_NO_FATAL_FAILURE. + static void ExpectNavigationEquals(const SerializedNavigationEntry& expected, + const SerializedNavigationEntry& actual); + + // Create a SerializedNavigationEntry with the given URL and title and some + // common values for the other fields. + static SerializedNavigationEntry CreateNavigation( + const std::string& virtual_url, + const std::string& title); + + static void SetPageState(const content::PageState& page_state, + SerializedNavigationEntry* navigation); + + static void SetHasPostData(bool has_post_data, + SerializedNavigationEntry* navigation); + + static void SetOriginalRequestURL(const GURL& original_request_url, + SerializedNavigationEntry* navigation); + + static void SetIsOverridingUserAgent(bool is_overriding_user_agent, + SerializedNavigationEntry* navigation); + + static void SetTimestamp(base::Time timestamp, + SerializedNavigationEntry* navigation); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(SerializedNavigationEntryTestHelper); +}; + +} // sessions + +#endif // COMPONENTS_SESSIONS_SESSION_TYPES_TEST_HELPER_H_ diff --git a/chromium/components/sessions/serialized_navigation_entry_unittest.cc b/chromium/components/sessions/serialized_navigation_entry_unittest.cc new file mode 100644 index 00000000000..5f052fe23e7 --- /dev/null +++ b/chromium/components/sessions/serialized_navigation_entry_unittest.cc @@ -0,0 +1,290 @@ +// 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/sessions/serialized_navigation_entry.h" + +#include <cstddef> +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "base/pickle.h" +#include "base/strings/string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "content/public/browser/favicon_status.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/common/page_transition_types.h" +#include "content/public/common/referrer.h" +#include "sync/protocol/session_specifics.pb.h" +#include "sync/util/time.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "third_party/WebKit/public/platform/WebReferrerPolicy.h" +#include "url/gurl.h" + +namespace sessions { +namespace { + +const int kIndex = 3; +const int kUniqueID = 50; +const content::Referrer kReferrer = + content::Referrer(GURL("http://www.referrer.com"), + WebKit::WebReferrerPolicyAlways); +const GURL kVirtualURL("http://www.virtual-url.com"); +const string16 kTitle = ASCIIToUTF16("title"); +const content::PageState kPageState = + content::PageState::CreateFromEncodedData("page state"); +const content::PageTransition kTransitionType = + static_cast<content::PageTransition>( + content::PAGE_TRANSITION_AUTO_SUBFRAME | + content::PAGE_TRANSITION_HOME_PAGE | + content::PAGE_TRANSITION_CLIENT_REDIRECT); +const bool kHasPostData = true; +const int64 kPostID = 100; +const GURL kOriginalRequestURL("http://www.original-request.com"); +const bool kIsOverridingUserAgent = true; +const base::Time kTimestamp = syncer::ProtoTimeToTime(100); +const string16 kSearchTerms = ASCIIToUTF16("my search terms"); +const GURL kFaviconURL("http://virtual-url.com/favicon.ico"); + +const int kPageID = 10; + +// Create a NavigationEntry from the constants above. +scoped_ptr<content::NavigationEntry> MakeNavigationEntryForTest() { + scoped_ptr<content::NavigationEntry> navigation_entry( + content::NavigationEntry::Create()); + navigation_entry->SetReferrer(kReferrer); + navigation_entry->SetVirtualURL(kVirtualURL); + navigation_entry->SetTitle(kTitle); + navigation_entry->SetPageState(kPageState); + navigation_entry->SetTransitionType(kTransitionType); + navigation_entry->SetHasPostData(kHasPostData); + navigation_entry->SetPostID(kPostID); + navigation_entry->SetOriginalRequestURL(kOriginalRequestURL); + navigation_entry->SetIsOverridingUserAgent(kIsOverridingUserAgent); + navigation_entry->SetTimestamp(kTimestamp); + navigation_entry->SetExtraData(kSearchTermsKey, kSearchTerms); + navigation_entry->GetFavicon().valid = true; + navigation_entry->GetFavicon().url = kFaviconURL; + return navigation_entry.Pass(); +} + +// Create a sync_pb::TabNavigation from the constants above. +sync_pb::TabNavigation MakeSyncDataForTest() { + sync_pb::TabNavigation sync_data; + sync_data.set_virtual_url(kVirtualURL.spec()); + sync_data.set_referrer(kReferrer.url.spec()); + sync_data.set_title(UTF16ToUTF8(kTitle)); + sync_data.set_state(kPageState.ToEncodedData()); + sync_data.set_page_transition( + sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME); + sync_data.set_unique_id(kUniqueID); + sync_data.set_timestamp_msec(syncer::TimeToProtoTime(kTimestamp)); + sync_data.set_redirect_type(sync_pb::SyncEnums::CLIENT_REDIRECT); + sync_data.set_navigation_home_page(true); + sync_data.set_search_terms(UTF16ToUTF8(kSearchTerms)); + sync_data.set_favicon_url(kFaviconURL.spec()); + return sync_data; +} + +// Create a default SerializedNavigationEntry. All its fields should be +// initialized to their respective default values. +TEST(SerializedNavigationEntryTest, DefaultInitializer) { + const SerializedNavigationEntry navigation; + EXPECT_EQ(-1, navigation.index()); + EXPECT_EQ(0, navigation.unique_id()); + EXPECT_EQ(GURL(), navigation.referrer().url); + EXPECT_EQ(WebKit::WebReferrerPolicyDefault, navigation.referrer().policy); + EXPECT_EQ(GURL(), navigation.virtual_url()); + EXPECT_TRUE(navigation.title().empty()); + EXPECT_FALSE(navigation.page_state().IsValid()); + EXPECT_EQ(content::PAGE_TRANSITION_TYPED, navigation.transition_type()); + EXPECT_FALSE(navigation.has_post_data()); + EXPECT_EQ(-1, navigation.post_id()); + EXPECT_EQ(GURL(), navigation.original_request_url()); + EXPECT_FALSE(navigation.is_overriding_user_agent()); + EXPECT_TRUE(navigation.timestamp().is_null()); + EXPECT_TRUE(navigation.search_terms().empty()); + EXPECT_FALSE(navigation.favicon_url().is_valid()); +} + +// Create a SerializedNavigationEntry from a NavigationEntry. All its fields +// should match the NavigationEntry's. +TEST(SerializedNavigationEntryTest, FromNavigationEntry) { + const scoped_ptr<content::NavigationEntry> navigation_entry( + MakeNavigationEntryForTest()); + + const SerializedNavigationEntry& navigation = + SerializedNavigationEntry::FromNavigationEntry(kIndex, *navigation_entry); + + EXPECT_EQ(kIndex, navigation.index()); + + EXPECT_EQ(navigation_entry->GetUniqueID(), navigation.unique_id()); + EXPECT_EQ(kReferrer.url, navigation.referrer().url); + EXPECT_EQ(kReferrer.policy, navigation.referrer().policy); + EXPECT_EQ(kVirtualURL, navigation.virtual_url()); + EXPECT_EQ(kTitle, navigation.title()); + EXPECT_EQ(kPageState, navigation.page_state()); + EXPECT_EQ(kTransitionType, navigation.transition_type()); + EXPECT_EQ(kHasPostData, navigation.has_post_data()); + EXPECT_EQ(kPostID, navigation.post_id()); + EXPECT_EQ(kOriginalRequestURL, navigation.original_request_url()); + EXPECT_EQ(kIsOverridingUserAgent, navigation.is_overriding_user_agent()); + EXPECT_EQ(kTimestamp, navigation.timestamp()); + EXPECT_EQ(kFaviconURL, navigation.favicon_url()); +} + +// Create a SerializedNavigationEntry from a sync_pb::TabNavigation. All its +// fields should match the protocol buffer's if it exists there, and +// sbould be set to the default value otherwise. +TEST(SerializedNavigationEntryTest, FromSyncData) { + const sync_pb::TabNavigation sync_data = MakeSyncDataForTest(); + + const SerializedNavigationEntry& navigation = + SerializedNavigationEntry::FromSyncData(kIndex, sync_data); + + EXPECT_EQ(kIndex, navigation.index()); + EXPECT_EQ(kUniqueID, navigation.unique_id()); + EXPECT_EQ(kReferrer.url, navigation.referrer().url); + EXPECT_EQ(WebKit::WebReferrerPolicyDefault, navigation.referrer().policy); + EXPECT_EQ(kVirtualURL, navigation.virtual_url()); + EXPECT_EQ(kTitle, navigation.title()); + EXPECT_EQ(kPageState, navigation.page_state()); + EXPECT_EQ(kTransitionType, navigation.transition_type()); + EXPECT_FALSE(navigation.has_post_data()); + EXPECT_EQ(-1, navigation.post_id()); + EXPECT_EQ(GURL(), navigation.original_request_url()); + EXPECT_FALSE(navigation.is_overriding_user_agent()); + EXPECT_TRUE(navigation.timestamp().is_null()); + EXPECT_EQ(kSearchTerms, navigation.search_terms()); + EXPECT_EQ(kFaviconURL, navigation.favicon_url()); +} + +// Create a SerializedNavigationEntry, pickle it, then create another one by +// unpickling. The new one should match the old one except for fields +// that aren't pickled, which should be set to default values. +TEST(SerializedNavigationEntryTest, Pickle) { + const SerializedNavigationEntry& old_navigation = + SerializedNavigationEntry::FromNavigationEntry( + kIndex, *MakeNavigationEntryForTest()); + + Pickle pickle; + old_navigation.WriteToPickle(30000, &pickle); + + SerializedNavigationEntry new_navigation; + PickleIterator pickle_iterator(pickle); + EXPECT_TRUE(new_navigation.ReadFromPickle(&pickle_iterator)); + + EXPECT_EQ(kIndex, new_navigation.index()); + + EXPECT_EQ(0, new_navigation.unique_id()); + EXPECT_EQ(kReferrer.url, new_navigation.referrer().url); + EXPECT_EQ(kReferrer.policy, new_navigation.referrer().policy); + EXPECT_EQ(kVirtualURL, new_navigation.virtual_url()); + EXPECT_EQ(kTitle, new_navigation.title()); + EXPECT_FALSE(new_navigation.page_state().IsValid()); + EXPECT_EQ(kTransitionType, new_navigation.transition_type()); + EXPECT_EQ(kHasPostData, new_navigation.has_post_data()); + EXPECT_EQ(-1, new_navigation.post_id()); + EXPECT_EQ(kOriginalRequestURL, new_navigation.original_request_url()); + EXPECT_EQ(kIsOverridingUserAgent, new_navigation.is_overriding_user_agent()); + EXPECT_EQ(kTimestamp, new_navigation.timestamp()); + EXPECT_EQ(kSearchTerms, new_navigation.search_terms()); +} + +// Create a NavigationEntry, then create another one by converting to +// a SerializedNavigationEntry and back. The new one should match the old one +// except for fields that aren't preserved, which should be set to +// expected values. +TEST(SerializedNavigationEntryTest, ToNavigationEntry) { + const scoped_ptr<content::NavigationEntry> old_navigation_entry( + MakeNavigationEntryForTest()); + + const SerializedNavigationEntry& navigation = + SerializedNavigationEntry::FromNavigationEntry(kIndex, + *old_navigation_entry); + + const scoped_ptr<content::NavigationEntry> new_navigation_entry( + navigation.ToNavigationEntry(kPageID, NULL)); + + EXPECT_EQ(kReferrer.url, new_navigation_entry->GetReferrer().url); + EXPECT_EQ(kReferrer.policy, new_navigation_entry->GetReferrer().policy); + EXPECT_EQ(kVirtualURL, new_navigation_entry->GetVirtualURL()); + EXPECT_EQ(kTitle, new_navigation_entry->GetTitle()); + EXPECT_EQ(kPageState, new_navigation_entry->GetPageState()); + EXPECT_EQ(kPageID, new_navigation_entry->GetPageID()); + EXPECT_EQ(content::PAGE_TRANSITION_RELOAD, + new_navigation_entry->GetTransitionType()); + EXPECT_EQ(kHasPostData, new_navigation_entry->GetHasPostData()); + EXPECT_EQ(kPostID, new_navigation_entry->GetPostID()); + EXPECT_EQ(kOriginalRequestURL, + new_navigation_entry->GetOriginalRequestURL()); + EXPECT_EQ(kIsOverridingUserAgent, + new_navigation_entry->GetIsOverridingUserAgent()); + string16 search_terms; + new_navigation_entry->GetExtraData(kSearchTermsKey, &search_terms); + EXPECT_EQ(kSearchTerms, search_terms); +} + +// Create a NavigationEntry, convert it to a SerializedNavigationEntry, then +// create a sync protocol buffer from it. The protocol buffer should +// have matching fields to the NavigationEntry (when applicable). +TEST(SerializedNavigationEntryTest, ToSyncData) { + const scoped_ptr<content::NavigationEntry> navigation_entry( + MakeNavigationEntryForTest()); + + const SerializedNavigationEntry& navigation = + SerializedNavigationEntry::FromNavigationEntry(kIndex, *navigation_entry); + + const sync_pb::TabNavigation sync_data = navigation.ToSyncData(); + + EXPECT_EQ(kVirtualURL.spec(), sync_data.virtual_url()); + EXPECT_EQ(kReferrer.url.spec(), sync_data.referrer()); + EXPECT_EQ(kTitle, ASCIIToUTF16(sync_data.title())); + EXPECT_TRUE(sync_data.state().empty()); + EXPECT_EQ(sync_pb::SyncEnums_PageTransition_AUTO_SUBFRAME, + sync_data.page_transition()); + EXPECT_TRUE(sync_data.has_redirect_type()); + EXPECT_EQ(navigation_entry->GetUniqueID(), sync_data.unique_id()); + EXPECT_EQ(syncer::TimeToProtoTime(kTimestamp), sync_data.timestamp_msec()); + EXPECT_EQ(kTimestamp.ToInternalValue(), sync_data.global_id()); + EXPECT_EQ(kFaviconURL.spec(), sync_data.favicon_url()); +} + +// Ensure all transition types and qualifiers are converted to/from the sync +// SerializedNavigationEntry representation properly. +TEST(SerializedNavigationEntryTest, TransitionTypes) { + scoped_ptr<content::NavigationEntry> navigation_entry( + MakeNavigationEntryForTest()); + for (uint32 core_type = content::PAGE_TRANSITION_LINK; + core_type != content::PAGE_TRANSITION_LAST_CORE; ++core_type) { + // Because qualifier is a uint32, left shifting will eventually overflow + // and hit zero again. SERVER_REDIRECT, as the last qualifier and also + // in place of the sign bit, is therefore the last transition before + // breaking. + for (uint32 qualifier = content::PAGE_TRANSITION_FORWARD_BACK; + qualifier != 0; qualifier <<= 1) { + if (qualifier == 0x08000000) + continue; // 0x08000000 is not a valid qualifier. + content::PageTransition transition = + static_cast<content::PageTransition>(core_type | qualifier); + + navigation_entry->SetTransitionType(transition); + const SerializedNavigationEntry& navigation = + SerializedNavigationEntry::FromNavigationEntry(kIndex, + *navigation_entry); + const sync_pb::TabNavigation& sync_data = navigation.ToSyncData(); + const SerializedNavigationEntry& constructed_nav = + SerializedNavigationEntry::FromSyncData(kIndex, sync_data); + const content::PageTransition constructed_transition = + constructed_nav.transition_type(); + + EXPECT_EQ(transition, constructed_transition); + } + } +} + +} // namespace +} // namespace sessions diff --git a/chromium/components/sessions/sessions_export.h b/chromium/components/sessions/sessions_export.h new file mode 100644 index 00000000000..66ac82270bc --- /dev/null +++ b/chromium/components/sessions/sessions_export.h @@ -0,0 +1,29 @@ +// 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_SESSIONS_SESSIONS_EXPORT_H_ +#define COMPONENTS_SESSIONS_SESSIONS_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(SESSIONS_IMPLEMENTATION) +#define SESSIONS_EXPORT __declspec(dllexport) +#else +#define SESSIONS_EXPORT __declspec(dllimport) +#endif // defined(BASE_PREFS_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(SESSIONS_IMPLEMENTATION) +#define SESSIONS_EXPORT __attribute__((visibility("default"))) +#else +#define SESSIONS_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define SESSIONS_EXPORT +#endif + +#endif // COMPONENTS_SESSIONS_SESSIONS_EXPORT_H_ diff --git a/chromium/components/strings/component_strings_am.xtb b/chromium/components/strings/component_strings_am.xtb new file mode 100644 index 00000000000..6c985cb8784 --- /dev/null +++ b/chromium/components/strings/component_strings_am.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="am"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_ar.xtb b/chromium/components/strings/component_strings_ar.xtb new file mode 100644 index 00000000000..e8b5562299a --- /dev/null +++ b/chromium/components/strings/component_strings_ar.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ar"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_bg.xtb b/chromium/components/strings/component_strings_bg.xtb new file mode 100644 index 00000000000..ebab4736462 --- /dev/null +++ b/chromium/components/strings/component_strings_bg.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="bg"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_bn.xtb b/chromium/components/strings/component_strings_bn.xtb new file mode 100644 index 00000000000..a66cc1fcd8f --- /dev/null +++ b/chromium/components/strings/component_strings_bn.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="bn"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_ca.xtb b/chromium/components/strings/component_strings_ca.xtb new file mode 100644 index 00000000000..1438d89736b --- /dev/null +++ b/chromium/components/strings/component_strings_ca.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ca"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_cs.xtb b/chromium/components/strings/component_strings_cs.xtb new file mode 100644 index 00000000000..2d95130b933 --- /dev/null +++ b/chromium/components/strings/component_strings_cs.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="cs"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_da.xtb b/chromium/components/strings/component_strings_da.xtb new file mode 100644 index 00000000000..751fa4a8b98 --- /dev/null +++ b/chromium/components/strings/component_strings_da.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="da"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_de.xtb b/chromium/components/strings/component_strings_de.xtb new file mode 100644 index 00000000000..91de7f5115b --- /dev/null +++ b/chromium/components/strings/component_strings_de.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="de"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_el.xtb b/chromium/components/strings/component_strings_el.xtb new file mode 100644 index 00000000000..6e5e7d8157a --- /dev/null +++ b/chromium/components/strings/component_strings_el.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="el"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_en-GB.xtb b/chromium/components/strings/component_strings_en-GB.xtb new file mode 100644 index 00000000000..0fb2133a32a --- /dev/null +++ b/chromium/components/strings/component_strings_en-GB.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="en-GB"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_es-419.xtb b/chromium/components/strings/component_strings_es-419.xtb new file mode 100644 index 00000000000..2fe4770c0d6 --- /dev/null +++ b/chromium/components/strings/component_strings_es-419.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="es-419"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_es.xtb b/chromium/components/strings/component_strings_es.xtb new file mode 100644 index 00000000000..64022ecf43c --- /dev/null +++ b/chromium/components/strings/component_strings_es.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="es"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_et.xtb b/chromium/components/strings/component_strings_et.xtb new file mode 100644 index 00000000000..5244dfd4bfd --- /dev/null +++ b/chromium/components/strings/component_strings_et.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="et"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_fa.xtb b/chromium/components/strings/component_strings_fa.xtb new file mode 100644 index 00000000000..18626f8a294 --- /dev/null +++ b/chromium/components/strings/component_strings_fa.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fa"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_fi.xtb b/chromium/components/strings/component_strings_fi.xtb new file mode 100644 index 00000000000..4691cd56eaf --- /dev/null +++ b/chromium/components/strings/component_strings_fi.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fi"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_fil.xtb b/chromium/components/strings/component_strings_fil.xtb new file mode 100644 index 00000000000..443630e740a --- /dev/null +++ b/chromium/components/strings/component_strings_fil.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fil"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_fr.xtb b/chromium/components/strings/component_strings_fr.xtb new file mode 100644 index 00000000000..63026a3ad03 --- /dev/null +++ b/chromium/components/strings/component_strings_fr.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="fr"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_gu.xtb b/chromium/components/strings/component_strings_gu.xtb new file mode 100644 index 00000000000..7d8a4df1b08 --- /dev/null +++ b/chromium/components/strings/component_strings_gu.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="gu"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_hi.xtb b/chromium/components/strings/component_strings_hi.xtb new file mode 100644 index 00000000000..a6ddd5d3f83 --- /dev/null +++ b/chromium/components/strings/component_strings_hi.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="hi"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_hr.xtb b/chromium/components/strings/component_strings_hr.xtb new file mode 100644 index 00000000000..26f99d08e94 --- /dev/null +++ b/chromium/components/strings/component_strings_hr.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="hr"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_hu.xtb b/chromium/components/strings/component_strings_hu.xtb new file mode 100644 index 00000000000..7ef9a5e0bbe --- /dev/null +++ b/chromium/components/strings/component_strings_hu.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="hu"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_id.xtb b/chromium/components/strings/component_strings_id.xtb new file mode 100644 index 00000000000..aa34783a27c --- /dev/null +++ b/chromium/components/strings/component_strings_id.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="id"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_it.xtb b/chromium/components/strings/component_strings_it.xtb new file mode 100644 index 00000000000..a6ac8d46363 --- /dev/null +++ b/chromium/components/strings/component_strings_it.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="it"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_iw.xtb b/chromium/components/strings/component_strings_iw.xtb new file mode 100644 index 00000000000..86b55334c0b --- /dev/null +++ b/chromium/components/strings/component_strings_iw.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="iw"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_ja.xtb b/chromium/components/strings/component_strings_ja.xtb new file mode 100644 index 00000000000..23139e6dd1d --- /dev/null +++ b/chromium/components/strings/component_strings_ja.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ja"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_kn.xtb b/chromium/components/strings/component_strings_kn.xtb new file mode 100644 index 00000000000..cc3643a208f --- /dev/null +++ b/chromium/components/strings/component_strings_kn.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="kn"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_ko.xtb b/chromium/components/strings/component_strings_ko.xtb new file mode 100644 index 00000000000..e0fc3707e1b --- /dev/null +++ b/chromium/components/strings/component_strings_ko.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ko"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_lt.xtb b/chromium/components/strings/component_strings_lt.xtb new file mode 100644 index 00000000000..5804ae2a34c --- /dev/null +++ b/chromium/components/strings/component_strings_lt.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="lt"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_lv.xtb b/chromium/components/strings/component_strings_lv.xtb new file mode 100644 index 00000000000..a0a1c477043 --- /dev/null +++ b/chromium/components/strings/component_strings_lv.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="lv"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_ml.xtb b/chromium/components/strings/component_strings_ml.xtb new file mode 100644 index 00000000000..f7db3152e4c --- /dev/null +++ b/chromium/components/strings/component_strings_ml.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ml"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_mr.xtb b/chromium/components/strings/component_strings_mr.xtb new file mode 100644 index 00000000000..098d29c9921 --- /dev/null +++ b/chromium/components/strings/component_strings_mr.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="mr"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_ms.xtb b/chromium/components/strings/component_strings_ms.xtb new file mode 100644 index 00000000000..1fb470abf06 --- /dev/null +++ b/chromium/components/strings/component_strings_ms.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ms"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_nl.xtb b/chromium/components/strings/component_strings_nl.xtb new file mode 100644 index 00000000000..e78241066f9 --- /dev/null +++ b/chromium/components/strings/component_strings_nl.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="nl"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_no.xtb b/chromium/components/strings/component_strings_no.xtb new file mode 100644 index 00000000000..913638ba4e9 --- /dev/null +++ b/chromium/components/strings/component_strings_no.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="no"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_pl.xtb b/chromium/components/strings/component_strings_pl.xtb new file mode 100644 index 00000000000..4519e3de389 --- /dev/null +++ b/chromium/components/strings/component_strings_pl.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="pl"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_pt-BR.xtb b/chromium/components/strings/component_strings_pt-BR.xtb new file mode 100644 index 00000000000..e95eb56bb7b --- /dev/null +++ b/chromium/components/strings/component_strings_pt-BR.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="pt-BR"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_pt-PT.xtb b/chromium/components/strings/component_strings_pt-PT.xtb new file mode 100644 index 00000000000..1dcf557a081 --- /dev/null +++ b/chromium/components/strings/component_strings_pt-PT.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="pt-PT"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_ro.xtb b/chromium/components/strings/component_strings_ro.xtb new file mode 100644 index 00000000000..9e434933f16 --- /dev/null +++ b/chromium/components/strings/component_strings_ro.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ro"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_ru.xtb b/chromium/components/strings/component_strings_ru.xtb new file mode 100644 index 00000000000..c4a621b9fdd --- /dev/null +++ b/chromium/components/strings/component_strings_ru.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ru"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_sk.xtb b/chromium/components/strings/component_strings_sk.xtb new file mode 100644 index 00000000000..00750d31cd0 --- /dev/null +++ b/chromium/components/strings/component_strings_sk.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sk"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_sl.xtb b/chromium/components/strings/component_strings_sl.xtb new file mode 100644 index 00000000000..489b7e46ba0 --- /dev/null +++ b/chromium/components/strings/component_strings_sl.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sl"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_sr.xtb b/chromium/components/strings/component_strings_sr.xtb new file mode 100644 index 00000000000..38f6f354db5 --- /dev/null +++ b/chromium/components/strings/component_strings_sr.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sr"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_sv.xtb b/chromium/components/strings/component_strings_sv.xtb new file mode 100644 index 00000000000..ddea3dcce5d --- /dev/null +++ b/chromium/components/strings/component_strings_sv.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sv"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_sw.xtb b/chromium/components/strings/component_strings_sw.xtb new file mode 100644 index 00000000000..b7750886d22 --- /dev/null +++ b/chromium/components/strings/component_strings_sw.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="sw"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_ta.xtb b/chromium/components/strings/component_strings_ta.xtb new file mode 100644 index 00000000000..ef90687762c --- /dev/null +++ b/chromium/components/strings/component_strings_ta.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="ta"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_te.xtb b/chromium/components/strings/component_strings_te.xtb new file mode 100644 index 00000000000..48c714ba18f --- /dev/null +++ b/chromium/components/strings/component_strings_te.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="te"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_th.xtb b/chromium/components/strings/component_strings_th.xtb new file mode 100644 index 00000000000..fae31966a2f --- /dev/null +++ b/chromium/components/strings/component_strings_th.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="th"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_tr.xtb b/chromium/components/strings/component_strings_tr.xtb new file mode 100644 index 00000000000..9a299515338 --- /dev/null +++ b/chromium/components/strings/component_strings_tr.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="tr"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_uk.xtb b/chromium/components/strings/component_strings_uk.xtb new file mode 100644 index 00000000000..f0db52c6692 --- /dev/null +++ b/chromium/components/strings/component_strings_uk.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="uk"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_vi.xtb b/chromium/components/strings/component_strings_vi.xtb new file mode 100644 index 00000000000..b2957daa924 --- /dev/null +++ b/chromium/components/strings/component_strings_vi.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="vi"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_zh-CN.xtb b/chromium/components/strings/component_strings_zh-CN.xtb new file mode 100644 index 00000000000..26e8b409cd0 --- /dev/null +++ b/chromium/components/strings/component_strings_zh-CN.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="zh-CN"> +</translationbundle> diff --git a/chromium/components/strings/component_strings_zh-TW.xtb b/chromium/components/strings/component_strings_zh-TW.xtb new file mode 100644 index 00000000000..935ef485a1c --- /dev/null +++ b/chromium/components/strings/component_strings_zh-TW.xtb @@ -0,0 +1,4 @@ +<?xml version="1.0" ?> +<!DOCTYPE translationbundle> +<translationbundle lang="zh-TW"> +</translationbundle> diff --git a/chromium/components/tools/metrics/browser_components_metrics.py b/chromium/components/tools/metrics/browser_components_metrics.py new file mode 100755 index 00000000000..725295377ae --- /dev/null +++ b/chromium/components/tools/metrics/browser_components_metrics.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# 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. + +"""Generates the metrics collected weekly for the Browser Components project. + +See +http://www.chromium.org/developers/design-documents/browser-components +for details. +""" + +import os +import sys + + +# This is done so that we can import checkdeps. If not invoked as +# main, our user must ensure it is in PYTHONPATH. +if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), '..', '..', '..', + 'tools', 'checkdeps')) + + +import count_ifdefs +import checkdeps +import results + + +# Preprocessor pattern to find OS_XYZ defines. +PREPROCESSOR_PATTERN = 'OS_[A-Z]+' + + +class BrowserComponentsMetricsGenerator(object): + def __init__(self, checkout_root): + self.checkout_root = checkout_root + self.chrome_browser = os.path.join(checkout_root, 'chrome', 'browser') + + def CountIfdefs(self, skip_tests): + return count_ifdefs.CountIfdefs( + PREPROCESSOR_PATTERN, self.chrome_browser, skip_tests) + + def CountViolations(self, skip_tests): + deps_checker = checkdeps.DepsChecker(self.checkout_root, + ignore_temp_rules=True, + skip_tests=skip_tests) + deps_checker.results_formatter = results.CountViolationsFormatter() + deps_checker.CheckDirectory(os.path.join('chrome', 'browser')) + return int(deps_checker.results_formatter.GetResults()) + + +def main(): + generator = BrowserComponentsMetricsGenerator( + os.path.join(os.path.dirname(__file__), '..', '..', '..')) + + print "All metrics are for chrome/browser.\n" + print "OS ifdefs, all: %d" % generator.CountIfdefs(False) + print "OS ifdefs, -tests: %d" % generator.CountIfdefs(True) + print ("Intended DEPS violations, all: %d" % + generator.CountViolations(False)) + print "Intended DEPS violations, -tests: %d" % generator.CountViolations(True) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/chromium/components/tools/metrics/count_ifdefs.py b/chromium/components/tools/metrics/count_ifdefs.py new file mode 100755 index 00000000000..2e3c2adf02d --- /dev/null +++ b/chromium/components/tools/metrics/count_ifdefs.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python +# 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. + +"""Counts the number of #if or #ifdef lines containing at least one +preprocessor token that is a full match for the given pattern, in the +given directory. +""" + + +import optparse +import os +import re +import sys + + +# Filename extensions we know will be handled by the C preprocessor. +# We ignore files not matching these. +CPP_EXTENSIONS = [ + '.h', + '.cc', + '.m', + '.mm', +] + + +def _IsTestFile(filename): + """Does a rudimentary check to try to skip test files; this could be + improved but is good enough for basic metrics generation. + """ + return re.match('(test|mock|dummy)_.*|.*_[a-z]*test\.(h|cc|mm)', filename) + + +def CountIfdefs(token_pattern, directory, skip_tests=False): + """Returns the number of lines in files in |directory| and its + subdirectories that have an extension from |CPP_EXTENSIONS| and are + an #if or #ifdef line with a preprocessor token fully matching + the string |token_pattern|. + + If |skip_tests| is true, a best effort is made to ignore test files. + """ + token_line_re = re.compile(r'^#if(def)?.*\b(%s)\b.*$' % token_pattern) + count = 0 + for root, dirs, files in os.walk(directory): + for filename in files: + if os.path.splitext(filename)[1] in CPP_EXTENSIONS: + if not skip_tests or not _IsTestFile(filename): + with open(os.path.join(root, filename)) as f: + for line in f: + line = line.strip() + if token_line_re.match(line): + count += 1 + return count + + +def PrintUsage(): + print "Usage: %s [--skip-tests] TOKEN_PATTERN DIRECTORY" % sys.argv[0] + + +def main(): + option_parser = optparse.OptionParser() + option_parser.add_option('', '--skip-tests', action='store_true', + dest='skip_tests', default=False, + help='Skip test files.') + options, args = option_parser.parse_args() + + if len(args) < 2: + PrintUsage() + return 1 + else: + print CountIfdefs(args[0], args[1], options.skip_tests) + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/chromium/components/tools/metrics/count_ifdefs_unittest.py b/chromium/components/tools/metrics/count_ifdefs_unittest.py new file mode 100755 index 00000000000..9b8c3229a0f --- /dev/null +++ b/chromium/components/tools/metrics/count_ifdefs_unittest.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# 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. + +"""Tests for count_ifdefs. +""" + +import os +import unittest + +import count_ifdefs + + +class CountIfdefsTest(unittest.TestCase): + + def setUp(self): + self.root = os.path.join(os.path.dirname(__file__), 'testdata') + + def testNormal(self): + count = count_ifdefs.CountIfdefs('OS_[A-Z]+', self.root) + self.failUnless(count == 6) + + def testSkipTests(self): + count = count_ifdefs.CountIfdefs('OS_[A-Z]+', self.root, True) + self.failUnless(count == 4) + + +if __name__ == '__main__': + unittest.main() diff --git a/chromium/components/tools/metrics/testdata/foo.cc b/chromium/components/tools/metrics/testdata/foo.cc new file mode 100644 index 00000000000..bd3d3c716d2 --- /dev/null +++ b/chromium/components/tools/metrics/testdata/foo.cc @@ -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. + +// Should not match +#ifndef FOO_OS_ANDROID_BLAT +#define FOO_OS_ANDROID_BLAT + +#if defined(OS_ANDROID) + +#if defined(OS_ANDROID) || defined(OS_IOS) || defined(OS_CHROMEOS) + +#if !defined(OS_CAT) + +#if defined(OS_WIN) + +#endif // !OS_ANDROID && !OS_IOS +#endif // OS_CAT + +#endif // FOO_OS_ANDROID_BLAT diff --git a/chromium/components/tools/metrics/testdata/foo_ignored.txt b/chromium/components/tools/metrics/testdata/foo_ignored.txt new file mode 100644 index 00000000000..2b186f93da4 --- /dev/null +++ b/chromium/components/tools/metrics/testdata/foo_ignored.txt @@ -0,0 +1,4 @@ +#if defined(OS_ANDROID) + +#if defined(OS_WIN) + diff --git a/chromium/components/tools/metrics/testdata/subdir/foo_test.mm b/chromium/components/tools/metrics/testdata/subdir/foo_test.mm new file mode 100644 index 00000000000..adf1f891ea9 --- /dev/null +++ b/chromium/components/tools/metrics/testdata/subdir/foo_test.mm @@ -0,0 +1,8 @@ +// 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. + +#if defined(OS_ANDROID) + +#if defined(OS_WIN) + diff --git a/chromium/components/tracing.gyp b/chromium/components/tracing.gyp new file mode 100644 index 00000000000..fe7e588f3ff --- /dev/null +++ b/chromium/components/tracing.gyp @@ -0,0 +1,31 @@ +# 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 is intentionally a gyp file rather than a gypi for dependencies +# reasons. The other gypi files include content.gyp and content_common depends +# on this, thus if you try to rename this to gypi and include it in +# components.gyp, you will get a circular dependency error. +{ + 'targets' : [ + { + 'target_name': 'tracing', + 'type': 'static_library', + 'defines!': ['CONTENT_IMPLEMENTATION'], + 'dependencies': [ + '../base/base.gyp:base', + '../ipc/ipc.gyp:ipc', + ], + 'include_dirs': [ + '..', + ], + 'sources': [ + 'tracing/child_trace_message_filter.cc', + 'tracing/child_trace_message_filter.h', + 'tracing/tracing_messages.cc', + 'tracing/tracing_messages.h', + ], + }, + ], +} diff --git a/chromium/components/tracing/DEPS b/chromium/components/tracing/DEPS new file mode 100644 index 00000000000..bd05a3eaf55 --- /dev/null +++ b/chromium/components/tracing/DEPS @@ -0,0 +1,8 @@ +include_rules = [ + "+base", + "+ipc", + + # This component will be compiled into NaCl, so it shouldn't depend on + # anything in content. + "-content", +] diff --git a/chromium/components/tracing/OWNERS b/chromium/components/tracing/OWNERS new file mode 100644 index 00000000000..9201ab2fca0 --- /dev/null +++ b/chromium/components/tracing/OWNERS @@ -0,0 +1,12 @@ +jbauman@chromium.org +nduca@chromium.org + +# Changes to IPC messages require a security review to avoid introducing +# new sandbox escapes. +per-file *_messages*.h=set noparent +per-file *_messages*.h=cdn@chromium.org +per-file *_messages*.h=cevans@chromium.org +per-file *_messages*.h=jln@chromium.org +per-file *_messages*.h=jschuh@chromium.org +per-file *_messages*.h=palmer@chromium.org +per-file *_messages*.h=tsepez@chromium.org diff --git a/chromium/components/tracing/child_trace_message_filter.cc b/chromium/components/tracing/child_trace_message_filter.cc new file mode 100644 index 00000000000..154903a15d0 --- /dev/null +++ b/chromium/components/tracing/child_trace_message_filter.cc @@ -0,0 +1,118 @@ +// 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/tracing/child_trace_message_filter.h" + +#include "base/debug/trace_event.h" +#include "base/message_loop/message_loop_proxy.h" +#include "components/tracing/tracing_messages.h" + +using base::debug::TraceLog; + +namespace tracing { + +ChildTraceMessageFilter::ChildTraceMessageFilter( + base::MessageLoopProxy* ipc_message_loop) + : channel_(NULL), + ipc_message_loop_(ipc_message_loop) {} + +void ChildTraceMessageFilter::OnFilterAdded(IPC::Channel* channel) { + channel_ = channel; + TraceLog::GetInstance()->SetNotificationCallback( + base::Bind(&ChildTraceMessageFilter::OnTraceNotification, this)); + channel_->Send(new TracingHostMsg_ChildSupportsTracing()); +} + +void ChildTraceMessageFilter::OnFilterRemoved() { + TraceLog::GetInstance()->SetNotificationCallback( + TraceLog::NotificationCallback()); +} + +bool ChildTraceMessageFilter::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(ChildTraceMessageFilter, message) + IPC_MESSAGE_HANDLER(TracingMsg_BeginTracing, OnBeginTracing) + IPC_MESSAGE_HANDLER(TracingMsg_EndTracing, OnEndTracing) + IPC_MESSAGE_HANDLER(TracingMsg_GetTraceBufferPercentFull, + OnGetTraceBufferPercentFull) + IPC_MESSAGE_HANDLER(TracingMsg_SetWatchEvent, OnSetWatchEvent) + IPC_MESSAGE_HANDLER(TracingMsg_CancelWatchEvent, OnCancelWatchEvent) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +ChildTraceMessageFilter::~ChildTraceMessageFilter() {} + +void ChildTraceMessageFilter::OnBeginTracing( + const std::string& category_filter_str, + base::TimeTicks browser_time, + int options) { +#if defined(__native_client__) + // NaCl and system times are offset by a bit, so subtract some time from + // the captured timestamps. The value might be off by a bit due to messaging + // latency. + base::TimeDelta time_offset = base::TimeTicks::NowFromSystemTraceTime() - + browser_time; + TraceLog::GetInstance()->SetTimeOffset(time_offset); +#endif + TraceLog::GetInstance()->SetEnabled( + base::debug::CategoryFilter(category_filter_str), + static_cast<base::debug::TraceLog::Options>(options)); +} + +void ChildTraceMessageFilter::OnEndTracing() { + TraceLog::GetInstance()->SetDisabled(); + + // Flush will generate one or more callbacks to OnTraceDataCollected. It's + // important that the last OnTraceDataCollected gets called before + // EndTracingAck below. We are already on the IO thread, so the + // OnTraceDataCollected calls will not be deferred. + TraceLog::GetInstance()->Flush( + base::Bind(&ChildTraceMessageFilter::OnTraceDataCollected, this)); + + std::vector<std::string> category_groups; + TraceLog::GetInstance()->GetKnownCategoryGroups(&category_groups); + channel_->Send(new TracingHostMsg_EndTracingAck(category_groups)); +} + +void ChildTraceMessageFilter::OnGetTraceBufferPercentFull() { + float bpf = TraceLog::GetInstance()->GetBufferPercentFull(); + + channel_->Send(new TracingHostMsg_TraceBufferPercentFullReply(bpf)); +} + +void ChildTraceMessageFilter::OnSetWatchEvent(const std::string& category_name, + const std::string& event_name) { + TraceLog::GetInstance()->SetWatchEvent(category_name.c_str(), + event_name.c_str()); +} + +void ChildTraceMessageFilter::OnCancelWatchEvent() { + TraceLog::GetInstance()->CancelWatchEvent(); +} + +void ChildTraceMessageFilter::OnTraceDataCollected( + const scoped_refptr<base::RefCountedString>& events_str_ptr) { + if (!ipc_message_loop_->BelongsToCurrentThread()) { + ipc_message_loop_->PostTask(FROM_HERE, + base::Bind(&ChildTraceMessageFilter::OnTraceDataCollected, this, + events_str_ptr)); + return; + } + channel_->Send(new TracingHostMsg_TraceDataCollected( + events_str_ptr->data())); +} + +void ChildTraceMessageFilter::OnTraceNotification(int notification) { + if (!ipc_message_loop_->BelongsToCurrentThread()) { + ipc_message_loop_->PostTask(FROM_HERE, + base::Bind(&ChildTraceMessageFilter::OnTraceNotification, this, + notification)); + return; + } + channel_->Send(new TracingHostMsg_TraceNotification(notification)); +} + +} // namespace tracing diff --git a/chromium/components/tracing/child_trace_message_filter.h b/chromium/components/tracing/child_trace_message_filter.h new file mode 100644 index 00000000000..9631ced7b34 --- /dev/null +++ b/chromium/components/tracing/child_trace_message_filter.h @@ -0,0 +1,55 @@ +// 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_TRACING_CHILD_TRACE_MESSAGE_FILTER_H_ +#define COMPONENTS_TRACING_CHILD_TRACE_MESSAGE_FILTER_H_ + +#include "base/bind.h" +#include "base/memory/ref_counted_memory.h" +#include "ipc/ipc_channel_proxy.h" + +namespace base { +class MessageLoopProxy; +} + +namespace tracing { + +// This class sends and receives trace messages on child processes. +class ChildTraceMessageFilter : public IPC::ChannelProxy::MessageFilter { + public: + explicit ChildTraceMessageFilter(base::MessageLoopProxy* ipc_message_loop); + + // IPC::ChannelProxy::MessageFilter implementation. + virtual void OnFilterAdded(IPC::Channel* channel) OVERRIDE; + virtual void OnFilterRemoved() OVERRIDE; + virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; + + protected: + virtual ~ChildTraceMessageFilter(); + + private: + // Message handlers. + void OnBeginTracing(const std::string& category_filter_str, + base::TimeTicks browser_time, + int mode); + void OnEndTracing(); + void OnGetTraceBufferPercentFull(); + void OnSetWatchEvent(const std::string& category_name, + const std::string& event_name); + void OnCancelWatchEvent(); + + // Callback from trace subsystem. + void OnTraceDataCollected( + const scoped_refptr<base::RefCountedString>& events_str_ptr); + void OnTraceNotification(int notification); + + IPC::Channel* channel_; + base::MessageLoopProxy* ipc_message_loop_; + + DISALLOW_COPY_AND_ASSIGN(ChildTraceMessageFilter); +}; + +} // namespace tracing + +#endif // COMPONENTS_TRACING_CHILD_TRACE_MESSAGE_FILTER_H_ diff --git a/chromium/components/tracing/tracing_messages.cc b/chromium/components/tracing/tracing_messages.cc new file mode 100644 index 00000000000..3d7637ae6b7 --- /dev/null +++ b/chromium/components/tracing/tracing_messages.cc @@ -0,0 +1,34 @@ +// Copyright (c) 2010 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. + +// Get basic type definitions. +#define IPC_MESSAGE_IMPL +#include "components/tracing/tracing_messages.h" + +// Generate constructors. +#include "ipc/struct_constructor_macros.h" +#include "components/tracing/tracing_messages.h" + +// Generate destructors. +#include "ipc/struct_destructor_macros.h" +#include "components/tracing/tracing_messages.h" + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#include "components/tracing/tracing_messages.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#include "components/tracing/tracing_messages.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#include "components/tracing/tracing_messages.h" +} // namespace IPC + diff --git a/chromium/components/tracing/tracing_messages.h b/chromium/components/tracing/tracing_messages.h new file mode 100644 index 00000000000..9beb7e1ec37 --- /dev/null +++ b/chromium/components/tracing/tracing_messages.h @@ -0,0 +1,56 @@ +// 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. + +// Multiply-included message header, no traditional include guard. +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/sync_socket.h" +#include "ipc/ipc_channel_handle.h" +#include "ipc/ipc_message_macros.h" +#include "ipc/ipc_message_utils.h" +#include "ipc/ipc_platform_file.h" + +#define IPC_MESSAGE_START TracingMsgStart + +// Sent to all child processes to enable trace event recording. +IPC_MESSAGE_CONTROL3(TracingMsg_BeginTracing, + std::string /* category_filter_str */, + base::TimeTicks /* browser_time */, + int /* base::debug::TraceLog::Options */) + +// Sent to all child processes to disable trace event recording. +IPC_MESSAGE_CONTROL0(TracingMsg_EndTracing) + +// Sent to all child processes to get trace buffer fullness. +IPC_MESSAGE_CONTROL0(TracingMsg_GetTraceBufferPercentFull) + +// Sent to all child processes to set watch event. +IPC_MESSAGE_CONTROL2(TracingMsg_SetWatchEvent, + std::string /* category_name */, + std::string /* event_name */) + +// Sent to all child processes to clear watch event. +IPC_MESSAGE_CONTROL0(TracingMsg_CancelWatchEvent) + +// Notify the browser that this child process supports tracing. +IPC_MESSAGE_CONTROL0(TracingHostMsg_ChildSupportsTracing) + +// Reply from child processes acking ChildProcessMsg_TraceChangeStatus(false). +IPC_MESSAGE_CONTROL1(TracingHostMsg_EndTracingAck, + std::vector<std::string> /* known_categories */) + +// Sent if the trace buffer becomes full. +IPC_MESSAGE_CONTROL1(TracingHostMsg_TraceNotification, + int /* base::debug::TraceLog::Notification */) + +// Child processes send trace data back in JSON chunks. +IPC_MESSAGE_CONTROL1(TracingHostMsg_TraceDataCollected, + std::string /*json trace data*/) + +// Reply to TracingMsg_GetTraceBufferPercentFull. +IPC_MESSAGE_CONTROL1(TracingHostMsg_TraceBufferPercentFullReply, + float /*trace buffer percent full*/) + diff --git a/chromium/components/tracing_untrusted.gyp b/chromium/components/tracing_untrusted.gyp new file mode 100644 index 00000000000..f2215de6a1b --- /dev/null +++ b/chromium/components/tracing_untrusted.gyp @@ -0,0 +1,42 @@ +# 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. + +{ + 'includes': [ + '../build/common_untrusted.gypi', + ], + 'conditions': [ + ['disable_nacl==0 and disable_nacl_untrusted==0', { + 'targets': [ + { + 'target_name': 'tracing_untrusted', + 'type': 'none', + 'defines!': ['CONTENT_IMPLEMENTATION'], + 'dependencies': [ + '../base/base_untrusted.gyp:base_untrusted', + '../native_client/tools.gyp:prep_toolchain', + '../ipc/ipc.gyp:ipc', + ], + 'include_dirs': [ + '..', + ], + 'variables': { + 'nacl_untrusted_build': 1, + 'nlib_target': 'libtracing_untrusted.a', + 'build_glibc': 0, + 'build_newlib': 1, + 'build_irt': 1, + }, + 'sources': [ + 'tracing/child_trace_message_filter.cc', + 'tracing/child_trace_message_filter.h', + 'tracing/tracing_messages.cc', + 'tracing/tracing_messages.h', + ], + }, + ], + }], + ], +} diff --git a/chromium/components/user_prefs.gypi b/chromium/components/user_prefs.gypi new file mode 100644 index 00000000000..a556e708ce3 --- /dev/null +++ b/chromium/components/user_prefs.gypi @@ -0,0 +1,32 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'user_prefs', + 'type': '<(component)', + 'dependencies': [ + '../base/base.gyp:base', + '../base/base.gyp:base_prefs', + '../base/third_party/dynamic_annotations/dynamic_annotations.gyp:dynamic_annotations', + '../content/content.gyp:content_browser', + '../ui/ui.gyp:ui', + ], + 'include_dirs': [ + '..', + ], + 'defines': [ + 'USER_PREFS_IMPLEMENTATION', + ], + 'sources': [ + 'user_prefs/pref_registry_syncable.cc', + 'user_prefs/pref_registry_syncable.h', + 'user_prefs/user_prefs.cc', + 'user_prefs/user_prefs.h', + 'user_prefs/user_prefs_export.h', + ], + }, + ], +} diff --git a/chromium/components/user_prefs/DEPS b/chromium/components/user_prefs/DEPS new file mode 100644 index 00000000000..c36190b429d --- /dev/null +++ b/chromium/components/user_prefs/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+content/public/browser", + "+ui/base", +] diff --git a/chromium/components/user_prefs/OWNERS b/chromium/components/user_prefs/OWNERS new file mode 100644 index 00000000000..024da1cb54c --- /dev/null +++ b/chromium/components/user_prefs/OWNERS @@ -0,0 +1,4 @@ +battre@chromium.org +bauerb@chromium.org +mnissler@chromium.org +pam@chromium.org diff --git a/chromium/components/user_prefs/README b/chromium/components/user_prefs/README new file mode 100644 index 00000000000..a7ca50936d2 --- /dev/null +++ b/chromium/components/user_prefs/README @@ -0,0 +1,8 @@ +The //components/user_pref component provides: + +a) The UserPrefs class, where components dependent on looking up +preferences associated with users can look them up by +content::BrowserContext. + +b) A place for PrefRegistrySyncable to live, where it can be used by +components that need to register preferences associated with users. diff --git a/chromium/components/user_prefs/pref_registry_syncable.cc b/chromium/components/user_prefs/pref_registry_syncable.cc new file mode 100644 index 00000000000..4ffd6393ba4 --- /dev/null +++ b/chromium/components/user_prefs/pref_registry_syncable.cc @@ -0,0 +1,225 @@ +// 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/user_prefs/pref_registry_syncable.h" + +#include "base/files/file_path.h" +#include "base/prefs/default_pref_store.h" +#include "base/strings/string_number_conversions.h" +#include "base/values.h" +#include "ui/base/l10n/l10n_util.h" + +namespace user_prefs { + +namespace { + +// A helper function for RegisterLocalized*Pref that creates a Value* +// based on a localized resource. Because we control the values in a +// locale dll, this should always return a Value of the appropriate +// type. +base::Value* CreateLocaleDefaultValue(base::Value::Type type, + int message_id) { + const std::string resource_string = l10n_util::GetStringUTF8(message_id); + DCHECK(!resource_string.empty()); + switch (type) { + case Value::TYPE_BOOLEAN: { + if ("true" == resource_string) + return Value::CreateBooleanValue(true); + if ("false" == resource_string) + return Value::CreateBooleanValue(false); + break; + } + + case Value::TYPE_INTEGER: { + int val; + base::StringToInt(resource_string, &val); + return Value::CreateIntegerValue(val); + } + + case Value::TYPE_DOUBLE: { + double val; + base::StringToDouble(resource_string, &val); + return Value::CreateDoubleValue(val); + } + + case Value::TYPE_STRING: { + return Value::CreateStringValue(resource_string); + } + + default: { + NOTREACHED() << + "list and dictionary types cannot have default locale values"; + } + } + NOTREACHED(); + return Value::CreateNullValue(); +} + +} // namespace + +PrefRegistrySyncable::PrefRegistrySyncable() { +} + +PrefRegistrySyncable::~PrefRegistrySyncable() { +} + +const PrefRegistrySyncable::PrefToStatus& +PrefRegistrySyncable::syncable_preferences() const { + return syncable_preferences_; +} + +void PrefRegistrySyncable::SetSyncableRegistrationCallback( + const SyncableRegistrationCallback& cb) { + callback_ = cb; +} + +void PrefRegistrySyncable::RegisterBooleanPref(const char* path, + bool default_value, + PrefSyncStatus sync_status) { + RegisterSyncablePreference(path, + Value::CreateBooleanValue(default_value), + sync_status); +} + +void PrefRegistrySyncable::RegisterIntegerPref(const char* path, + int default_value, + PrefSyncStatus sync_status) { + RegisterSyncablePreference(path, + Value::CreateIntegerValue(default_value), + sync_status); +} + +void PrefRegistrySyncable::RegisterDoublePref(const char* path, + double default_value, + PrefSyncStatus sync_status) { + RegisterSyncablePreference(path, + Value::CreateDoubleValue(default_value), + sync_status); +} + +void PrefRegistrySyncable::RegisterStringPref(const char* path, + const std::string& default_value, + PrefSyncStatus sync_status) { + RegisterSyncablePreference(path, + Value::CreateStringValue(default_value), + sync_status); +} + +void PrefRegistrySyncable::RegisterFilePathPref( + const char* path, + const base::FilePath& default_value, + PrefSyncStatus sync_status) { + RegisterSyncablePreference(path, + Value::CreateStringValue(default_value.value()), + sync_status); +} + +void PrefRegistrySyncable::RegisterListPref(const char* path, + PrefSyncStatus sync_status) { + RegisterSyncablePreference(path, new ListValue(), sync_status); +} + +void PrefRegistrySyncable::RegisterListPref(const char* path, + ListValue* default_value, + PrefSyncStatus sync_status) { + RegisterSyncablePreference(path, default_value, sync_status); +} + +void PrefRegistrySyncable::RegisterDictionaryPref(const char* path, + PrefSyncStatus sync_status) { + RegisterSyncablePreference(path, new DictionaryValue(), sync_status); +} + +void PrefRegistrySyncable::RegisterDictionaryPref( + const char* path, + DictionaryValue* default_value, + PrefSyncStatus sync_status) { + RegisterSyncablePreference(path, default_value, sync_status); +} + +void PrefRegistrySyncable::RegisterLocalizedBooleanPref( + const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status) { + RegisterSyncablePreference( + path, + CreateLocaleDefaultValue(Value::TYPE_BOOLEAN, locale_default_message_id), + sync_status); +} + +void PrefRegistrySyncable::RegisterLocalizedIntegerPref( + const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status) { + RegisterSyncablePreference( + path, + CreateLocaleDefaultValue(Value::TYPE_INTEGER, locale_default_message_id), + sync_status); +} + +void PrefRegistrySyncable::RegisterLocalizedDoublePref( + const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status) { + RegisterSyncablePreference( + path, + CreateLocaleDefaultValue(Value::TYPE_DOUBLE, locale_default_message_id), + sync_status); +} + +void PrefRegistrySyncable::RegisterLocalizedStringPref( + const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status) { + RegisterSyncablePreference( + path, + CreateLocaleDefaultValue(Value::TYPE_STRING, locale_default_message_id), + sync_status); +} + +void PrefRegistrySyncable::RegisterInt64Pref( + const char* path, + int64 default_value, + PrefSyncStatus sync_status) { + RegisterSyncablePreference( + path, + Value::CreateStringValue(base::Int64ToString(default_value)), + sync_status); +} + +void PrefRegistrySyncable::RegisterUint64Pref( + const char* path, + uint64 default_value, + PrefSyncStatus sync_status) { + RegisterSyncablePreference( + path, + Value::CreateStringValue(base::Uint64ToString(default_value)), + sync_status); +} + +void PrefRegistrySyncable::RegisterSyncablePreference( + const char* path, + base::Value* default_value, + PrefSyncStatus sync_status) { + PrefRegistry::RegisterPreference(path, default_value); + + if (sync_status == PrefRegistrySyncable::SYNCABLE_PREF || + sync_status == PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF) { + syncable_preferences_[path] = sync_status; + + if (!callback_.is_null()) + callback_.Run(path, sync_status); + } +} + +scoped_refptr<PrefRegistrySyncable> PrefRegistrySyncable::ForkForIncognito() { + // TODO(joi): We can directly reuse the same PrefRegistry once + // PrefService no longer registers for callbacks on registration and + // unregistration. + scoped_refptr<PrefRegistrySyncable> registry(new PrefRegistrySyncable()); + registry->defaults_ = defaults_; + return registry; +} + +} // namespace user_prefs diff --git a/chromium/components/user_prefs/pref_registry_syncable.h b/chromium/components/user_prefs/pref_registry_syncable.h new file mode 100644 index 00000000000..9134d0b09d2 --- /dev/null +++ b/chromium/components/user_prefs/pref_registry_syncable.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_USER_PREFS_PREF_REGISTRY_SYNCABLE_H_ +#define COMPONENTS_USER_PREFS_PREF_REGISTRY_SYNCABLE_H_ + +#include <set> +#include <string> + +#include "base/prefs/pref_registry.h" +#include "components/user_prefs/user_prefs_export.h" + +namespace base { +class DictionaryValue; +class FilePath; +class ListValue; +class Value; +} + +namespace user_prefs { + +// A PrefRegistry that forces users to choose whether each registered +// preference is syncable or not. +// +// Classes or components that want to register such preferences should +// define a static function named RegisterUserPrefs that takes a +// PrefRegistrySyncable*, and the top-level application using the +// class or embedding the component should call this function at an +// appropriate time before the PrefService for these preferences is +// constructed. See e.g. chrome/browser/prefs/browser_prefs.cc which +// does this for Chrome. +class USER_PREFS_EXPORT PrefRegistrySyncable : public PrefRegistry { + public: + // Enum used when registering preferences to determine if it should + // be synced or not. Syncable priority preferences are preferences that are + // never encrypted and are synced before other datatypes. Because they're + // never encrypted, on first sync, they can be synced down before the user + // is prompted for a passphrase. + enum PrefSyncStatus { + UNSYNCABLE_PREF, + SYNCABLE_PREF, + SYNCABLE_PRIORITY_PREF, + }; + + typedef + base::Callback<void(const char* path, const PrefSyncStatus sync_status)> + SyncableRegistrationCallback; + + PrefRegistrySyncable(); + + typedef std::map<std::string, PrefSyncStatus> PrefToStatus; + + // Retrieve the set of syncable preferences currently registered. + const PrefToStatus& syncable_preferences() const; + + // Exactly one callback can be set for the event of a syncable + // preference being registered. It will be fired after the + // registration has occurred. + // + // Calling this method after a callback has already been set will + // make the object forget the previous callback and use the new one + // instead. + void SetSyncableRegistrationCallback(const SyncableRegistrationCallback& cb); + + void RegisterBooleanPref(const char* path, + bool default_value, + PrefSyncStatus sync_status); + void RegisterIntegerPref(const char* path, + int default_value, + PrefSyncStatus sync_status); + void RegisterDoublePref(const char* path, + double default_value, + PrefSyncStatus sync_status); + void RegisterStringPref(const char* path, + const std::string& default_value, + PrefSyncStatus sync_status); + void RegisterFilePathPref(const char* path, + const base::FilePath& default_value, + PrefSyncStatus sync_status); + void RegisterListPref(const char* path, + PrefSyncStatus sync_status); + void RegisterDictionaryPref(const char* path, + PrefSyncStatus sync_status); + void RegisterListPref(const char* path, + base::ListValue* default_value, + PrefSyncStatus sync_status); + void RegisterDictionaryPref(const char* path, + base::DictionaryValue* default_value, + PrefSyncStatus sync_status); + void RegisterLocalizedBooleanPref(const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status); + void RegisterLocalizedIntegerPref(const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status); + void RegisterLocalizedDoublePref(const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status); + void RegisterLocalizedStringPref(const char* path, + int locale_default_message_id, + PrefSyncStatus sync_status); + void RegisterInt64Pref(const char* path, + int64 default_value, + PrefSyncStatus sync_status); + void RegisterUint64Pref(const char* path, + uint64 default_value, + PrefSyncStatus sync_status); + + // Returns a new PrefRegistrySyncable that uses the same defaults + // store. + scoped_refptr<PrefRegistrySyncable> ForkForIncognito(); + + private: + virtual ~PrefRegistrySyncable(); + + void RegisterSyncablePreference(const char* path, + base::Value* default_value, + PrefSyncStatus sync_status); + + SyncableRegistrationCallback callback_; + + // Contains the names of all registered preferences that are syncable. + PrefToStatus syncable_preferences_; + + DISALLOW_COPY_AND_ASSIGN(PrefRegistrySyncable); +}; + +} // namespace user_prefs + +#endif // COMPONENTS_USER_PREFS_PREF_REGISTRY_SYNCABLE_H_ diff --git a/chromium/components/user_prefs/user_prefs.cc b/chromium/components/user_prefs/user_prefs.cc new file mode 100644 index 00000000000..8ecdfe4bc93 --- /dev/null +++ b/chromium/components/user_prefs/user_prefs.cc @@ -0,0 +1,47 @@ +// 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/user_prefs/user_prefs.h" + +#include "base/logging.h" +#include "base/memory/singleton.h" +#include "base/prefs/pref_service.h" +#include "content/public/browser/browser_context.h" + +namespace user_prefs { + +namespace { + +void* UserDataKey() { + // We just need a unique constant. Use the address of a static that + // COMDAT folding won't touch in an optimizing linker. + static int data_key = 0; + return reinterpret_cast<void*>(&data_key); +} + +} // namespace + +// static +PrefService* UserPrefs::Get(content::BrowserContext* context) { + DCHECK(context); + DCHECK(context->GetUserData(UserDataKey())); + return static_cast<UserPrefs*>( + context->GetUserData(UserDataKey()))->prefs_; +} + +// static +void UserPrefs::Set(content::BrowserContext* context, PrefService* prefs) { + DCHECK(context); + DCHECK(prefs); + DCHECK(!context->GetUserData(UserDataKey())); + context->SetUserData(UserDataKey(), new UserPrefs(prefs)); +} + +UserPrefs::UserPrefs(PrefService* prefs) : prefs_(prefs) { +} + +UserPrefs::~UserPrefs() { +} + +} // namespace user_prefs diff --git a/chromium/components/user_prefs/user_prefs.h b/chromium/components/user_prefs/user_prefs.h new file mode 100644 index 00000000000..90bf9009399 --- /dev/null +++ b/chromium/components/user_prefs/user_prefs.h @@ -0,0 +1,48 @@ +// 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_USER_PREFS_USER_PREFS_H_ +#define COMPONENTS_USER_PREFS_USER_PREFS_H_ + +#include "base/basictypes.h" +#include "base/supports_user_data.h" +#include "components/user_prefs/user_prefs_export.h" + +class PrefService; + +namespace content { +class BrowserContext; +} + +namespace user_prefs { + +// Components may use preferences associated with a given user. These +// hang off of content::BrowserContext and can be retrieved using +// UserPrefs::Get(). +// +// It is up to the embedder to create and own the PrefService and +// attach it to BrowserContext using the UserPrefs::Set() function. +class USER_PREFS_EXPORT UserPrefs : public base::SupportsUserData::Data { + public: + // Retrieves the PrefService for a given BrowserContext, or NULL if + // none is attached. + static PrefService* Get(content::BrowserContext* context); + + // Hangs the specified |prefs| off of |context|. Should be called + // only once per BrowserContext. + static void Set(content::BrowserContext* context, PrefService* prefs); + + private: + explicit UserPrefs(PrefService* prefs); + virtual ~UserPrefs(); + + // Non-owning; owned by embedder. + PrefService* prefs_; + + DISALLOW_COPY_AND_ASSIGN(UserPrefs); +}; + +} // namespace user_prefs + +#endif // COMPONENTS_USER_PREFS_USER_PREFS_H_ diff --git a/chromium/components/user_prefs/user_prefs_export.h b/chromium/components/user_prefs/user_prefs_export.h new file mode 100644 index 00000000000..3d20222a2b9 --- /dev/null +++ b/chromium/components/user_prefs/user_prefs_export.h @@ -0,0 +1,29 @@ +// 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_USER_PREFS_USER_PREFS_EXPORT_H_ +#define COMPONENTS_USER_PREFS_USER_PREFS_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(USER_PREFS_IMPLEMENTATION) +#define USER_PREFS_EXPORT __declspec(dllexport) +#else +#define USER_PREFS_EXPORT __declspec(dllimport) +#endif // defined(BASE_PREFS_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(USER_PREFS_IMPLEMENTATION) +#define USER_PREFS_EXPORT __attribute__((visibility("default"))) +#else +#define USER_PREFS_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define USER_PREFS_EXPORT +#endif + +#endif // COMPONENTS_USER_PREFS_USER_PREFS_EXPORT_H_ diff --git a/chromium/components/visitedlink.gypi b/chromium/components/visitedlink.gypi new file mode 100644 index 00000000000..bca40f2f9b5 --- /dev/null +++ b/chromium/components/visitedlink.gypi @@ -0,0 +1,66 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'visitedlink_common', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../content/content.gyp:content_common', + '../ipc/ipc.gyp:ipc', + '../url/url.gyp:url_lib', + ], + 'sources': [ + 'visitedlink/common/visitedlink_common.cc', + 'visitedlink/common/visitedlink_common.h', + 'visitedlink/common/visitedlink_message_generator.cc', + 'visitedlink/common/visitedlink_message_generator.h', + 'visitedlink/common/visitedlink_messages.h', + ], + }, + { + 'target_name': 'visitedlink_browser', + 'type': 'static_library', + 'include_dirs': [ + '../skia/config', + ], + 'dependencies': [ + 'visitedlink_common', + '../base/base.gyp:base', + '../content/content.gyp:content_browser', + '../content/content.gyp:content_common', + ], + 'sources': [ + 'visitedlink/browser/visitedlink_delegate.h', + 'visitedlink/browser/visitedlink_event_listener.cc', + 'visitedlink/browser/visitedlink_event_listener.h', + 'visitedlink/browser/visitedlink_master.cc', + 'visitedlink/browser/visitedlink_master.h', + ], + } + ], + 'conditions': [ + ['OS != "ios"', { + 'targets': [ + { + 'target_name': 'visitedlink_renderer', + 'type': 'static_library', + 'dependencies': [ + 'visitedlink_common', + '../base/base.gyp:base', + '../content/content.gyp:content_common', + '../content/content.gyp:content_renderer', + '../third_party/WebKit/public/blink.gyp:blink', + ], + 'sources': [ + 'visitedlink/renderer/visitedlink_slave.cc', + 'visitedlink/renderer/visitedlink_slave.h', + ], + }, + ], + }], + ], +} diff --git a/chromium/components/visitedlink/OWNERS b/chromium/components/visitedlink/OWNERS new file mode 100644 index 00000000000..b221410c272 --- /dev/null +++ b/chromium/components/visitedlink/OWNERS @@ -0,0 +1,2 @@ +brettw@chromium.org +sky@chromium.org diff --git a/chromium/components/visitedlink/browser/DEPS b/chromium/components/visitedlink/browser/DEPS new file mode 100644 index 00000000000..1c35d9ca694 --- /dev/null +++ b/chromium/components/visitedlink/browser/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+content/public/browser", +] diff --git a/chromium/components/visitedlink/browser/visitedlink_delegate.h b/chromium/components/visitedlink/browser/visitedlink_delegate.h new file mode 100644 index 00000000000..6adf7de5718 --- /dev/null +++ b/chromium/components/visitedlink/browser/visitedlink_delegate.h @@ -0,0 +1,51 @@ +// 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_VISITEDLINK_BROWSER_VISITEDLINK_DELEGATE_H_ +#define COMPONENTS_VISITEDLINK_BROWSER_VISITEDLINK_DELEGATE_H_ + +#include "base/memory/ref_counted.h" + +class GURL; + +namespace content { +class BrowserContext; +} + +namespace visitedlink { + +// Delegate class that clients of VisitedLinkMaster must implement. +class VisitedLinkDelegate { + public: + // See RebuildTable. + class URLEnumerator : public base::RefCountedThreadSafe<URLEnumerator> { + public: + // Call this with each URL to rebuild the table. + virtual void OnURL(const GURL& url) = 0; + + // This must be called by Delegate after RebuildTable is called. |success| + // indicates all URLs have been returned successfully. The URLEnumerator + // object cannot be used by the delegate after this call. + virtual void OnComplete(bool success) = 0; + + protected: + virtual ~URLEnumerator() {} + + private: + friend class base::RefCountedThreadSafe<URLEnumerator>; + }; + + // Delegate class is responsible for persisting the list of visited URLs + // across browser runs. This is called by VisitedLinkMaster to repopulate + // its internal table. Note that methods on enumerator can be called on any + // thread but the delegate is responsible for synchronizating the calls. + virtual void RebuildTable(const scoped_refptr<URLEnumerator>& enumerator) = 0; + + protected: + virtual ~VisitedLinkDelegate() {} +}; + +} // namespace visitedlink + +#endif // COMPONENTS_VISITEDLINK_BROWSER_VISITEDLINK_DELEGATE_H_ diff --git a/chromium/components/visitedlink/browser/visitedlink_event_listener.cc b/chromium/components/visitedlink/browser/visitedlink_event_listener.cc new file mode 100644 index 00000000000..a96a0e3aa55 --- /dev/null +++ b/chromium/components/visitedlink/browser/visitedlink_event_listener.cc @@ -0,0 +1,219 @@ +// 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/visitedlink/browser/visitedlink_event_listener.h" + +#include "base/memory/shared_memory.h" +#include "components/visitedlink/browser/visitedlink_delegate.h" +#include "components/visitedlink/common/visitedlink_messages.h" +#include "content/public/browser/notification_service.h" +#include "content/public/browser/notification_types.h" +#include "content/public/browser/render_process_host.h" +#include "content/public/browser/render_widget_host.h" + +using base::Time; +using base::TimeDelta; +using content::RenderWidgetHost; + +namespace { + +// The amount of time we wait to accumulate visited link additions. +const int kCommitIntervalMs = 100; + +// Size of the buffer after which individual link updates deemed not warranted +// and the overall update should be used instead. +const unsigned kVisitedLinkBufferThreshold = 50; + +} // namespace + +namespace visitedlink { + +// This class manages buffering and sending visited link hashes (fingerprints) +// to renderer based on widget visibility. +// As opposed to the VisitedLinkEventListener, which coalesces to +// reduce the rate of messages being sent to render processes, this class +// ensures that the updates occur only when explicitly requested. This is +// used for RenderProcessHostImpl to only send Add/Reset link events to the +// renderers when their tabs are visible and the corresponding RenderViews are +// created. +class VisitedLinkUpdater { + public: + explicit VisitedLinkUpdater(int render_process_id) + : reset_needed_(false), render_process_id_(render_process_id) { + } + + // Informs the renderer about a new visited link table. + void SendVisitedLinkTable(base::SharedMemory* table_memory) { + content::RenderProcessHost* process = + content::RenderProcessHost::FromID(render_process_id_); + if (!process) + return; // Happens in tests + base::SharedMemoryHandle handle_for_process; + table_memory->ShareToProcess(process->GetHandle(), &handle_for_process); + if (base::SharedMemory::IsHandleValid(handle_for_process)) + process->Send(new ChromeViewMsg_VisitedLink_NewTable( + handle_for_process)); + } + + // Buffers |links| to update, but doesn't actually relay them. + void AddLinks(const VisitedLinkCommon::Fingerprints& links) { + if (reset_needed_) + return; + + if (pending_.size() + links.size() > kVisitedLinkBufferThreshold) { + // Once the threshold is reached, there's no need to store pending visited + // link updates -- we opt for resetting the state for all links. + AddReset(); + return; + } + + pending_.insert(pending_.end(), links.begin(), links.end()); + } + + // Tells the updater that sending individual link updates is no longer + // necessary and the visited state for all links should be reset. + void AddReset() { + reset_needed_ = true; + pending_.clear(); + } + + // Sends visited link update messages: a list of links whose visited state + // changed or reset of visited state for all links. + void Update() { + content::RenderProcessHost* process = + content::RenderProcessHost::FromID(render_process_id_); + if (!process) + return; // Happens in tests + + if (!process->VisibleWidgetCount()) + return; + + if (reset_needed_) { + process->Send(new ChromeViewMsg_VisitedLink_Reset()); + reset_needed_ = false; + return; + } + + if (pending_.empty()) + return; + + process->Send(new ChromeViewMsg_VisitedLink_Add(pending_)); + + pending_.clear(); + } + + private: + bool reset_needed_; + int render_process_id_; + VisitedLinkCommon::Fingerprints pending_; +}; + +VisitedLinkEventListener::VisitedLinkEventListener( + VisitedLinkMaster* master, + content::BrowserContext* browser_context) + : master_(master), + browser_context_(browser_context) { + registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED, + content::NotificationService::AllBrowserContextsAndSources()); + registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED, + content::NotificationService::AllBrowserContextsAndSources()); + registrar_.Add(this, content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED, + content::NotificationService::AllBrowserContextsAndSources()); +} + +VisitedLinkEventListener::~VisitedLinkEventListener() { + if (!pending_visited_links_.empty()) + pending_visited_links_.clear(); +} + +void VisitedLinkEventListener::NewTable(base::SharedMemory* table_memory) { + if (!table_memory) + return; + + // Send to all RenderProcessHosts. + for (Updaters::iterator i = updaters_.begin(); i != updaters_.end(); ++i) { + // Make sure to not send to incognito renderers. + content::RenderProcessHost* process = + content::RenderProcessHost::FromID(i->first); + if (!process) + continue; + + i->second->SendVisitedLinkTable(table_memory); + } +} + +void VisitedLinkEventListener::Add(VisitedLinkMaster::Fingerprint fingerprint) { + pending_visited_links_.push_back(fingerprint); + + if (!coalesce_timer_.IsRunning()) { + coalesce_timer_.Start(FROM_HERE, + TimeDelta::FromMilliseconds(kCommitIntervalMs), this, + &VisitedLinkEventListener::CommitVisitedLinks); + } +} + +void VisitedLinkEventListener::Reset() { + pending_visited_links_.clear(); + coalesce_timer_.Stop(); + + for (Updaters::iterator i = updaters_.begin(); i != updaters_.end(); ++i) { + i->second->AddReset(); + i->second->Update(); + } +} + +void VisitedLinkEventListener::CommitVisitedLinks() { + // Send to all RenderProcessHosts. + for (Updaters::iterator i = updaters_.begin(); i != updaters_.end(); ++i) { + i->second->AddLinks(pending_visited_links_); + i->second->Update(); + } + + pending_visited_links_.clear(); +} + +void VisitedLinkEventListener::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + switch (type) { + case content::NOTIFICATION_RENDERER_PROCESS_CREATED: { + content::RenderProcessHost* process = + content::Source<content::RenderProcessHost>(source).ptr(); + if (browser_context_ != process->GetBrowserContext()) + return; + + // Happens on browser start up. + if (!master_->shared_memory()) + return; + + updaters_[process->GetID()] = + make_linked_ptr(new VisitedLinkUpdater(process->GetID())); + updaters_[process->GetID()]->SendVisitedLinkTable( + master_->shared_memory()); + break; + } + case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: { + content::RenderProcessHost* process = + content::Source<content::RenderProcessHost>(source).ptr(); + if (updaters_.count(process->GetID())) { + updaters_.erase(process->GetID()); + } + break; + } + case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: { + RenderWidgetHost* widget = + content::Source<RenderWidgetHost>(source).ptr(); + int child_id = widget->GetProcess()->GetID(); + if (updaters_.count(child_id)) + updaters_[child_id]->Update(); + break; + } + default: + NOTREACHED(); + break; + } +} + +} // namespace visitedlink diff --git a/chromium/components/visitedlink/browser/visitedlink_event_listener.h b/chromium/components/visitedlink/browser/visitedlink_event_listener.h new file mode 100644 index 00000000000..c2de5d205d0 --- /dev/null +++ b/chromium/components/visitedlink/browser/visitedlink_event_listener.h @@ -0,0 +1,70 @@ +// 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_VISITEDLINK_BROWSER_VISITEDLINK_EVENT_LISTENER_H_ +#define COMPONENTS_VISITEDLINK_BROWSER_VISITEDLINK_EVENT_LISTENER_H_ + +#include <map> + +#include "base/memory/linked_ptr.h" +#include "base/timer/timer.h" +#include "components/visitedlink/browser/visitedlink_master.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" + +namespace base { +class SharedMemory; +} + +namespace content { +class BrowserContext; +} + +namespace visitedlink { + +class VisitedLinkUpdater; + +// VisitedLinkEventListener broadcasts link coloring database updates to all +// processes. It also coalesces the updates to avoid excessive broadcasting of +// messages to the renderers. +class VisitedLinkEventListener : public VisitedLinkMaster::Listener, + public content::NotificationObserver { + public: + VisitedLinkEventListener(VisitedLinkMaster* master, + content::BrowserContext* browser_context); + virtual ~VisitedLinkEventListener(); + + virtual void NewTable(base::SharedMemory* table_memory) OVERRIDE; + virtual void Add(VisitedLinkMaster::Fingerprint fingerprint) OVERRIDE; + virtual void Reset() OVERRIDE; + + private: + void CommitVisitedLinks(); + + // content::NotificationObserver implementation. + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + base::OneShotTimer<VisitedLinkEventListener> coalesce_timer_; + VisitedLinkCommon::Fingerprints pending_visited_links_; + + content::NotificationRegistrar registrar_; + + // Map between renderer child ids and their VisitedLinkUpdater. + typedef std::map<int, linked_ptr<VisitedLinkUpdater> > Updaters; + Updaters updaters_; + + VisitedLinkMaster* master_; + + // Used to filter RENDERER_PROCESS_CREATED notifications to renderers that + // belong to this BrowserContext. + content::BrowserContext* browser_context_; + + DISALLOW_COPY_AND_ASSIGN(VisitedLinkEventListener); +}; + +} // namespace visitedlink + +#endif // COMPONENTS_VISITEDLINK_BROWSER_VISITEDLINK_EVENT_LISTENER_H_ diff --git a/chromium/components/visitedlink/browser/visitedlink_master.cc b/chromium/components/visitedlink/browser/visitedlink_master.cc new file mode 100644 index 00000000000..7cf68911af7 --- /dev/null +++ b/chromium/components/visitedlink/browser/visitedlink_master.cc @@ -0,0 +1,989 @@ +// 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/visitedlink/browser/visitedlink_master.h" + +#if defined(OS_WIN) +#include <windows.h> +#include <io.h> +#include <shlobj.h> +#endif // defined(OS_WIN) +#include <stdio.h> + +#include <algorithm> + +#include "base/bind.h" +#include "base/bind_helpers.h" +#include "base/containers/stack_container.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/rand_util.h" +#include "base/strings/string_util.h" +#include "base/threading/thread_restrictions.h" +#include "components/visitedlink/browser/visitedlink_delegate.h" +#include "components/visitedlink/browser/visitedlink_event_listener.h" +#include "content/public/browser/browser_context.h" +#include "content/public/browser/browser_thread.h" +#include "url/gurl.h" + +using content::BrowserThread; +using file_util::ScopedFILE; +using file_util::OpenFile; +using file_util::TruncateFile; + +namespace visitedlink { + +const int32 VisitedLinkMaster::kFileHeaderSignatureOffset = 0; +const int32 VisitedLinkMaster::kFileHeaderVersionOffset = 4; +const int32 VisitedLinkMaster::kFileHeaderLengthOffset = 8; +const int32 VisitedLinkMaster::kFileHeaderUsedOffset = 12; +const int32 VisitedLinkMaster::kFileHeaderSaltOffset = 16; + +const int32 VisitedLinkMaster::kFileCurrentVersion = 3; + +// the signature at the beginning of the URL table = "VLnk" (visited links) +const int32 VisitedLinkMaster::kFileSignature = 0x6b6e4c56; +const size_t VisitedLinkMaster::kFileHeaderSize = + kFileHeaderSaltOffset + LINK_SALT_LENGTH; + +// This value should also be the same as the smallest size in the lookup +// table in NewTableSizeForCount (prime number). +const unsigned VisitedLinkMaster::kDefaultTableSize = 16381; + +const size_t VisitedLinkMaster::kBigDeleteThreshold = 64; + +namespace { + +// Fills the given salt structure with some quasi-random values +// It is not necessary to generate a cryptographically strong random string, +// only that it be reasonably different for different users. +void GenerateSalt(uint8 salt[LINK_SALT_LENGTH]) { + DCHECK_EQ(LINK_SALT_LENGTH, 8) << "This code assumes the length of the salt"; + uint64 randval = base::RandUint64(); + memcpy(salt, &randval, 8); +} + +// Opens file on a background thread to not block UI thread. +void AsyncOpen(FILE** file, const base::FilePath& filename) { + *file = OpenFile(filename, "wb+"); + DLOG_IF(ERROR, !(*file)) << "Failed to open file " << filename.value(); +} + +// Returns true if the write was complete. +static bool WriteToFile(FILE* file, + off_t offset, + const void* data, + size_t data_len) { + if (fseek(file, offset, SEEK_SET) != 0) + return false; // Don't write to an invalid part of the file. + + size_t num_written = fwrite(data, 1, data_len, file); + + // The write may not make it to the kernel (stdlib may buffer the write) + // until the next fseek/fclose call. If we crash, it's easy for our used + // item count to be out of sync with the number of hashes we write. + // Protect against this by calling fflush. + int ret = fflush(file); + DCHECK_EQ(0, ret); + return num_written == data_len; +} + +// This task executes on a background thread and executes a write. This +// prevents us from blocking the UI thread doing I/O. Double pointer to FILE +// is used because file may still not be opened by the time of scheduling +// the task for execution. +void AsyncWrite(FILE** file, int32 offset, const std::string& data) { + if (*file) + WriteToFile(*file, offset, data.data(), data.size()); +} + +// Truncates the file to the current position asynchronously on a background +// thread. Double pointer to FILE is used because file may still not be opened +// by the time of scheduling the task for execution. +void AsyncTruncate(FILE** file) { + if (*file) + base::IgnoreResult(TruncateFile(*file)); +} + +// Closes the file on a background thread and releases memory used for storage +// of FILE* value. Double pointer to FILE is used because file may still not +// be opened by the time of scheduling the task for execution. +void AsyncClose(FILE** file) { + if (*file) + base::IgnoreResult(fclose(*file)); + free(file); +} + +} // namespace + +// TableBuilder --------------------------------------------------------------- + +// How rebuilding from history works +// --------------------------------- +// +// We mark that we're rebuilding from history by setting the table_builder_ +// member in VisitedLinkMaster to the TableBuilder we create. This builder +// will be called on the history thread by the history system for every URL +// in the database. +// +// The builder will store the fingerprints for those URLs, and then marshalls +// back to the main thread where the VisitedLinkMaster will be notified. The +// master then replaces its table with a new table containing the computed +// fingerprints. +// +// The builder must remain active while the history system is using it. +// Sometimes, the master will be deleted before the rebuild is complete, in +// which case it notifies the builder via DisownMaster(). The builder will +// delete itself once rebuilding is complete, and not execute any callback. +class VisitedLinkMaster::TableBuilder + : public VisitedLinkDelegate::URLEnumerator { + public: + TableBuilder(VisitedLinkMaster* master, + const uint8 salt[LINK_SALT_LENGTH]); + + // Called on the main thread when the master is being destroyed. This will + // prevent a crash when the query completes and the master is no longer + // around. We can not actually do anything but mark this fact, since the + // table will be being rebuilt simultaneously on the other thread. + void DisownMaster(); + + // VisitedLinkDelegate::URLEnumerator + virtual void OnURL(const GURL& url) OVERRIDE; + virtual void OnComplete(bool succeed) OVERRIDE; + + private: + virtual ~TableBuilder() {} + + // OnComplete mashals to this function on the main thread to do the + // notification. + void OnCompleteMainThread(); + + // Owner of this object. MAY ONLY BE ACCESSED ON THE MAIN THREAD! + VisitedLinkMaster* master_; + + // Indicates whether the operation has failed or not. + bool success_; + + // Salt for this new table. + uint8 salt_[LINK_SALT_LENGTH]; + + // Stores the fingerprints we computed on the background thread. + VisitedLinkCommon::Fingerprints fingerprints_; + + DISALLOW_COPY_AND_ASSIGN(TableBuilder); +}; + +// VisitedLinkMaster ---------------------------------------------------------- + +VisitedLinkMaster::VisitedLinkMaster(content::BrowserContext* browser_context, + VisitedLinkDelegate* delegate, + bool persist_to_disk) + : browser_context_(browser_context), + delegate_(delegate), + listener_(new VisitedLinkEventListener(this, browser_context)), + persist_to_disk_(persist_to_disk) { + InitMembers(); +} + +VisitedLinkMaster::VisitedLinkMaster(Listener* listener, + VisitedLinkDelegate* delegate, + bool persist_to_disk, + bool suppress_rebuild, + const base::FilePath& filename, + int32 default_table_size) + : browser_context_(NULL), + delegate_(delegate), + persist_to_disk_(persist_to_disk) { + listener_.reset(listener); + DCHECK(listener_.get()); + InitMembers(); + + database_name_override_ = filename; + table_size_override_ = default_table_size; + suppress_rebuild_ = suppress_rebuild; +} + +VisitedLinkMaster::~VisitedLinkMaster() { + if (table_builder_.get()) { + // Prevent the table builder from calling us back now that we're being + // destroyed. Note that we DON'T delete the object, since the history + // system is still writing into it. When that is complete, the table + // builder will destroy itself when it finds we are gone. + table_builder_->DisownMaster(); + } + FreeURLTable(); + // FreeURLTable() will schedule closing of the file and deletion of |file_|. + // So nothing should be done here. +} + +void VisitedLinkMaster::InitMembers() { + file_ = NULL; + shared_memory_ = NULL; + shared_memory_serial_ = 0; + used_items_ = 0; + table_size_override_ = 0; + suppress_rebuild_ = false; + sequence_token_ = BrowserThread::GetBlockingPool()->GetSequenceToken(); + +#ifndef NDEBUG + posted_asynchronous_operation_ = false; +#endif +} + +bool VisitedLinkMaster::Init() { + // We probably shouldn't be loading this from the UI thread, + // but it does need to happen early on in startup. + // http://code.google.com/p/chromium/issues/detail?id=24163 + base::ThreadRestrictions::ScopedAllowIO allow_io; + + if (persist_to_disk_) { + if (InitFromFile()) + return true; + } + return InitFromScratch(suppress_rebuild_); +} + +VisitedLinkMaster::Hash VisitedLinkMaster::TryToAddURL(const GURL& url) { + // Extra check that we are not incognito. This should not happen. + // TODO(boliu): Move this check to HistoryService when IsOffTheRecord is + // removed from BrowserContext. + if (browser_context_ && browser_context_->IsOffTheRecord()) { + NOTREACHED(); + return null_hash_; + } + + if (!url.is_valid()) + return null_hash_; // Don't add invalid URLs. + + Fingerprint fingerprint = ComputeURLFingerprint(url.spec().data(), + url.spec().size(), + salt_); + if (table_builder_.get()) { + // If we have a pending delete for this fingerprint, cancel it. + std::set<Fingerprint>::iterator found = + deleted_since_rebuild_.find(fingerprint); + if (found != deleted_since_rebuild_.end()) + deleted_since_rebuild_.erase(found); + + // A rebuild is in progress, save this addition in the temporary list so + // it can be added once rebuild is complete. + added_since_rebuild_.insert(fingerprint); + } + + // If the table is "full", we don't add URLs and just drop them on the floor. + // This can happen if we get thousands of new URLs and something causes + // the table resizing to fail. This check prevents a hang in that case. Note + // that this is *not* the resize limit, this is just a sanity check. + if (used_items_ / 8 > table_length_ / 10) + return null_hash_; // Table is more than 80% full. + + return AddFingerprint(fingerprint, true); +} + +void VisitedLinkMaster::PostIOTask(const tracked_objects::Location& from_here, + const base::Closure& task) { + DCHECK(persist_to_disk_); + BrowserThread::GetBlockingPool()->PostSequencedWorkerTask(sequence_token_, + from_here, task); +} + +void VisitedLinkMaster::AddURL(const GURL& url) { + Hash index = TryToAddURL(url); + if (!table_builder_.get() && index != null_hash_) { + // Not rebuilding, so we want to keep the file on disk up-to-date. + if (persist_to_disk_) { + WriteUsedItemCountToFile(); + WriteHashRangeToFile(index, index); + } + ResizeTableIfNecessary(); + } +} + +void VisitedLinkMaster::AddURLs(const std::vector<GURL>& url) { + for (std::vector<GURL>::const_iterator i = url.begin(); + i != url.end(); ++i) { + Hash index = TryToAddURL(*i); + if (!table_builder_.get() && index != null_hash_) + ResizeTableIfNecessary(); + } + + // Keeps the file on disk up-to-date. + if (!table_builder_.get() && persist_to_disk_) + WriteFullTable(); +} + +void VisitedLinkMaster::DeleteAllURLs() { + // Any pending modifications are invalid. + added_since_rebuild_.clear(); + deleted_since_rebuild_.clear(); + + // Clear the hash table. + used_items_ = 0; + memset(hash_table_, 0, this->table_length_ * sizeof(Fingerprint)); + + // Resize it if it is now too empty. Resize may write the new table out for + // us, otherwise, schedule writing the new table to disk ourselves. + if (!ResizeTableIfNecessary() && persist_to_disk_) + WriteFullTable(); + + listener_->Reset(); +} + +VisitedLinkDelegate* VisitedLinkMaster::GetDelegate() { + return delegate_; +} + +void VisitedLinkMaster::DeleteURLs(URLIterator* urls) { + if (!urls->HasNextURL()) + return; + + listener_->Reset(); + + if (table_builder_.get()) { + // A rebuild is in progress, save this deletion in the temporary list so + // it can be added once rebuild is complete. + while (urls->HasNextURL()) { + const GURL& url(urls->NextURL()); + if (!url.is_valid()) + continue; + + Fingerprint fingerprint = + ComputeURLFingerprint(url.spec().data(), url.spec().size(), salt_); + deleted_since_rebuild_.insert(fingerprint); + + // If the URL was just added and now we're deleting it, it may be in the + // list of things added since the last rebuild. Delete it from that list. + std::set<Fingerprint>::iterator found = + added_since_rebuild_.find(fingerprint); + if (found != added_since_rebuild_.end()) + added_since_rebuild_.erase(found); + + // Delete the URLs from the in-memory table, but don't bother writing + // to disk since it will be replaced soon. + DeleteFingerprint(fingerprint, false); + } + return; + } + + // Compute the deleted URLs' fingerprints and delete them + std::set<Fingerprint> deleted_fingerprints; + while (urls->HasNextURL()) { + const GURL& url(urls->NextURL()); + if (!url.is_valid()) + continue; + deleted_fingerprints.insert( + ComputeURLFingerprint(url.spec().data(), url.spec().size(), salt_)); + } + DeleteFingerprintsFromCurrentTable(deleted_fingerprints); +} + +// See VisitedLinkCommon::IsVisited which should be in sync with this algorithm +VisitedLinkMaster::Hash VisitedLinkMaster::AddFingerprint( + Fingerprint fingerprint, + bool send_notifications) { + if (!hash_table_ || table_length_ == 0) { + NOTREACHED(); // Not initialized. + return null_hash_; + } + + Hash cur_hash = HashFingerprint(fingerprint); + Hash first_hash = cur_hash; + while (true) { + Fingerprint cur_fingerprint = FingerprintAt(cur_hash); + if (cur_fingerprint == fingerprint) + return null_hash_; // This fingerprint is already in there, do nothing. + + if (cur_fingerprint == null_fingerprint_) { + // End of probe sequence found, insert here. + hash_table_[cur_hash] = fingerprint; + used_items_++; + // If allowed, notify listener that a new visited link was added. + if (send_notifications) + listener_->Add(fingerprint); + return cur_hash; + } + + // Advance in the probe sequence. + cur_hash = IncrementHash(cur_hash); + if (cur_hash == first_hash) { + // This means that we've wrapped around and are about to go into an + // infinite loop. Something was wrong with the hashtable resizing + // logic, so stop here. + NOTREACHED(); + return null_hash_; + } + } +} + +void VisitedLinkMaster::DeleteFingerprintsFromCurrentTable( + const std::set<Fingerprint>& fingerprints) { + bool bulk_write = (fingerprints.size() > kBigDeleteThreshold); + + // Delete the URLs from the table. + for (std::set<Fingerprint>::const_iterator i = fingerprints.begin(); + i != fingerprints.end(); ++i) + DeleteFingerprint(*i, !bulk_write); + + // These deleted fingerprints may make us shrink the table. + if (ResizeTableIfNecessary()) + return; // The resize function wrote the new table to disk for us. + + // Nobody wrote this out for us, write the full file to disk. + if (bulk_write && persist_to_disk_) + WriteFullTable(); +} + +bool VisitedLinkMaster::DeleteFingerprint(Fingerprint fingerprint, + bool update_file) { + if (!hash_table_ || table_length_ == 0) { + NOTREACHED(); // Not initialized. + return false; + } + if (!IsVisited(fingerprint)) + return false; // Not in the database to delete. + + // First update the header used count. + used_items_--; + if (update_file && persist_to_disk_) + WriteUsedItemCountToFile(); + + Hash deleted_hash = HashFingerprint(fingerprint); + + // Find the range of "stuff" in the hash table that is adjacent to this + // fingerprint. These are things that could be affected by the change in + // the hash table. Since we use linear probing, anything after the deleted + // item up until an empty item could be affected. + Hash end_range = deleted_hash; + while (true) { + Hash next_hash = IncrementHash(end_range); + if (next_hash == deleted_hash) + break; // We wrapped around and the whole table is full. + if (!hash_table_[next_hash]) + break; // Found the last spot. + end_range = next_hash; + } + + // We could get all fancy and move the affected fingerprints around, but + // instead we just remove them all and re-add them (minus our deleted one). + // This will mean there's a small window of time where the affected links + // won't be marked visited. + base::StackVector<Fingerprint, 32> shuffled_fingerprints; + Hash stop_loop = IncrementHash(end_range); // The end range is inclusive. + for (Hash i = deleted_hash; i != stop_loop; i = IncrementHash(i)) { + if (hash_table_[i] != fingerprint) { + // Don't save the one we're deleting! + shuffled_fingerprints->push_back(hash_table_[i]); + + // This will balance the increment of this value in AddFingerprint below + // so there is no net change. + used_items_--; + } + hash_table_[i] = null_fingerprint_; + } + + if (!shuffled_fingerprints->empty()) { + // Need to add the new items back. + for (size_t i = 0; i < shuffled_fingerprints->size(); i++) + AddFingerprint(shuffled_fingerprints[i], false); + } + + // Write the affected range to disk [deleted_hash, end_range]. + if (update_file && persist_to_disk_) + WriteHashRangeToFile(deleted_hash, end_range); + + return true; +} + +void VisitedLinkMaster::WriteFullTable() { + // This function can get called when the file is open, for example, when we + // resize the table. We must handle this case and not try to reopen the file, + // since there may be write operations pending on the file I/O thread. + // + // Note that once we start writing, we do not delete on error. This means + // there can be a partial file, but the short file will be detected next time + // we start, and will be replaced. + // + // This might possibly get corrupted if we crash in the middle of writing. + // We should pick up the most common types of these failures when we notice + // that the file size is different when we load it back in, and then we will + // regenerate the table. + DCHECK(persist_to_disk_); + + if (!file_) { + file_ = static_cast<FILE**>(calloc(1, sizeof(*file_))); + base::FilePath filename; + GetDatabaseFileName(&filename); + PostIOTask(FROM_HERE, base::Bind(&AsyncOpen, file_, filename)); + } + + // Write the new header. + int32 header[4]; + header[0] = kFileSignature; + header[1] = kFileCurrentVersion; + header[2] = table_length_; + header[3] = used_items_; + WriteToFile(file_, 0, header, sizeof(header)); + WriteToFile(file_, sizeof(header), salt_, LINK_SALT_LENGTH); + + // Write the hash data. + WriteToFile(file_, kFileHeaderSize, + hash_table_, table_length_ * sizeof(Fingerprint)); + + // The hash table may have shrunk, so make sure this is the end. + PostIOTask(FROM_HERE, base::Bind(&AsyncTruncate, file_)); +} + +bool VisitedLinkMaster::InitFromFile() { + DCHECK(file_ == NULL); + DCHECK(persist_to_disk_); + + base::FilePath filename; + GetDatabaseFileName(&filename); + ScopedFILE file_closer(OpenFile(filename, "rb+")); + if (!file_closer.get()) + return false; + + int32 num_entries, used_count; + if (!ReadFileHeader(file_closer.get(), &num_entries, &used_count, salt_)) + return false; // Header isn't valid. + + // Allocate and read the table. + if (!CreateURLTable(num_entries, false)) + return false; + if (!ReadFromFile(file_closer.get(), kFileHeaderSize, + hash_table_, num_entries * sizeof(Fingerprint))) { + FreeURLTable(); + return false; + } + used_items_ = used_count; + +#ifndef NDEBUG + DebugValidate(); +#endif + + file_ = static_cast<FILE**>(malloc(sizeof(*file_))); + *file_ = file_closer.release(); + return true; +} + +bool VisitedLinkMaster::InitFromScratch(bool suppress_rebuild) { + int32 table_size = kDefaultTableSize; + if (table_size_override_) + table_size = table_size_override_; + + // The salt must be generated before the table so that it can be copied to + // the shared memory. + GenerateSalt(salt_); + if (!CreateURLTable(table_size, true)) + return false; + +#ifndef NDEBUG + DebugValidate(); +#endif + + if (suppress_rebuild && persist_to_disk_) { + // When we disallow rebuilds (normally just unit tests), just use the + // current empty table. + WriteFullTable(); + return true; + } + + // This will build the table from history. On the first run, history will + // be empty, so this will be correct. This will also write the new table + // to disk. We don't want to save explicitly here, since the rebuild may + // not complete, leaving us with an empty but valid visited link database. + // In the future, we won't know we need to try rebuilding again. + return RebuildTableFromDelegate(); +} + +bool VisitedLinkMaster::ReadFileHeader(FILE* file, + int32* num_entries, + int32* used_count, + uint8 salt[LINK_SALT_LENGTH]) { + DCHECK(persist_to_disk_); + + // Get file size. + // Note that there is no need to seek back to the original location in the + // file since ReadFromFile() [which is the next call accessing the file] + // seeks before reading. + if (fseek(file, 0, SEEK_END) == -1) + return false; + size_t file_size = ftell(file); + + if (file_size <= kFileHeaderSize) + return false; + + uint8 header[kFileHeaderSize]; + if (!ReadFromFile(file, 0, &header, kFileHeaderSize)) + return false; + + // Verify the signature. + int32 signature; + memcpy(&signature, &header[kFileHeaderSignatureOffset], sizeof(signature)); + if (signature != kFileSignature) + return false; + + // Verify the version is up-to-date. As with other read errors, a version + // mistmatch will trigger a rebuild of the database from history, which will + // have the effect of migrating the database. + int32 version; + memcpy(&version, &header[kFileHeaderVersionOffset], sizeof(version)); + if (version != kFileCurrentVersion) + return false; // Bad version. + + // Read the table size and make sure it matches the file size. + memcpy(num_entries, &header[kFileHeaderLengthOffset], sizeof(*num_entries)); + if (*num_entries * sizeof(Fingerprint) + kFileHeaderSize != file_size) + return false; // Bad size. + + // Read the used item count. + memcpy(used_count, &header[kFileHeaderUsedOffset], sizeof(*used_count)); + if (*used_count > *num_entries) + return false; // Bad used item count; + + // Read the salt. + memcpy(salt, &header[kFileHeaderSaltOffset], LINK_SALT_LENGTH); + + // This file looks OK from the header's perspective. + return true; +} + +bool VisitedLinkMaster::GetDatabaseFileName(base::FilePath* filename) { + if (!database_name_override_.empty()) { + // use this filename, the directory must exist + *filename = database_name_override_; + return true; + } + + if (!browser_context_ || browser_context_->GetPath().empty()) + return false; + + base::FilePath profile_dir = browser_context_->GetPath(); + *filename = profile_dir.Append(FILE_PATH_LITERAL("Visited Links")); + return true; +} + +// Initializes the shared memory structure. The salt should already be filled +// in so that it can be written to the shared memory +bool VisitedLinkMaster::CreateURLTable(int32 num_entries, bool init_to_empty) { + // The table is the size of the table followed by the entries. + uint32 alloc_size = num_entries * sizeof(Fingerprint) + sizeof(SharedHeader); + + // Create the shared memory object. + shared_memory_ = new base::SharedMemory(); + if (!shared_memory_) + return false; + + if (!shared_memory_->CreateAndMapAnonymous(alloc_size)) { + delete shared_memory_; + shared_memory_ = NULL; + return false; + } + + if (init_to_empty) { + memset(shared_memory_->memory(), 0, alloc_size); + used_items_ = 0; + } + table_length_ = num_entries; + + // Save the header for other processes to read. + SharedHeader* header = static_cast<SharedHeader*>(shared_memory_->memory()); + header->length = table_length_; + memcpy(header->salt, salt_, LINK_SALT_LENGTH); + + // Our table pointer is just the data immediately following the size. + hash_table_ = reinterpret_cast<Fingerprint*>( + static_cast<char*>(shared_memory_->memory()) + sizeof(SharedHeader)); + + return true; +} + +bool VisitedLinkMaster::BeginReplaceURLTable(int32 num_entries) { + base::SharedMemory *old_shared_memory = shared_memory_; + Fingerprint* old_hash_table = hash_table_; + int32 old_table_length = table_length_; + if (!CreateURLTable(num_entries, true)) { + // Try to put back the old state. + shared_memory_ = old_shared_memory; + hash_table_ = old_hash_table; + table_length_ = old_table_length; + return false; + } + +#ifndef NDEBUG + DebugValidate(); +#endif + + return true; +} + +void VisitedLinkMaster::FreeURLTable() { + if (shared_memory_) { + delete shared_memory_; + shared_memory_ = NULL; + } + if (!persist_to_disk_ || !file_) + return; + PostIOTask(FROM_HERE, base::Bind(&AsyncClose, file_)); + // AsyncClose() will close the file and free the memory pointed by |file_|. + file_ = NULL; +} + +bool VisitedLinkMaster::ResizeTableIfNecessary() { + DCHECK(table_length_ > 0) << "Must have a table"; + + // Load limits for good performance/space. We are pretty conservative about + // keeping the table not very full. This is because we use linear probing + // which increases the likelihood of clumps of entries which will reduce + // performance. + const float max_table_load = 0.5f; // Grow when we're > this full. + const float min_table_load = 0.2f; // Shrink when we're < this full. + + float load = ComputeTableLoad(); + if (load < max_table_load && + (table_length_ <= static_cast<float>(kDefaultTableSize) || + load > min_table_load)) + return false; + + // Table needs to grow or shrink. + int new_size = NewTableSizeForCount(used_items_); + DCHECK(new_size > used_items_); + DCHECK(load <= min_table_load || new_size > table_length_); + ResizeTable(new_size); + return true; +} + +void VisitedLinkMaster::ResizeTable(int32 new_size) { + DCHECK(shared_memory_ && shared_memory_->memory() && hash_table_); + shared_memory_serial_++; + +#ifndef NDEBUG + DebugValidate(); +#endif + + base::SharedMemory* old_shared_memory = shared_memory_; + Fingerprint* old_hash_table = hash_table_; + int32 old_table_length = table_length_; + if (!BeginReplaceURLTable(new_size)) + return; + + // Now we have two tables, our local copy which is the old one, and the new + // one loaded into this object where we need to copy the data. + for (int32 i = 0; i < old_table_length; i++) { + Fingerprint cur = old_hash_table[i]; + if (cur) + AddFingerprint(cur, false); + } + + // On error unmapping, just forget about it since we can't do anything + // else to release it. + delete old_shared_memory; + + // Send an update notification to all child processes so they read the new + // table. + listener_->NewTable(shared_memory_); + +#ifndef NDEBUG + DebugValidate(); +#endif + + // The new table needs to be written to disk. + if (persist_to_disk_) + WriteFullTable(); +} + +uint32 VisitedLinkMaster::NewTableSizeForCount(int32 item_count) const { + // These table sizes are selected to be the maximum prime number less than + // a "convenient" multiple of 1K. + static const int table_sizes[] = { + 16381, // 16K = 16384 <- don't shrink below this table size + // (should be == default_table_size) + 32767, // 32K = 32768 + 65521, // 64K = 65536 + 130051, // 128K = 131072 + 262127, // 256K = 262144 + 524269, // 512K = 524288 + 1048549, // 1M = 1048576 + 2097143, // 2M = 2097152 + 4194301, // 4M = 4194304 + 8388571, // 8M = 8388608 + 16777199, // 16M = 16777216 + 33554347}; // 32M = 33554432 + + // Try to leave the table 33% full. + int desired = item_count * 3; + + // Find the closest prime. + for (size_t i = 0; i < arraysize(table_sizes); i ++) { + if (table_sizes[i] > desired) + return table_sizes[i]; + } + + // Growing very big, just approximate a "good" number, not growing as much + // as normal. + return item_count * 2 - 1; +} + +// See the TableBuilder definition in the header file for how this works. +bool VisitedLinkMaster::RebuildTableFromDelegate() { + DCHECK(!table_builder_.get()); + + // TODO(brettw) make sure we have reasonable salt! + table_builder_ = new TableBuilder(this, salt_); + delegate_->RebuildTable(table_builder_); + return true; +} + +// See the TableBuilder declaration above for how this works. +void VisitedLinkMaster::OnTableRebuildComplete( + bool success, + const std::vector<Fingerprint>& fingerprints) { + if (success) { + // Replace the old table with a new blank one. + shared_memory_serial_++; + + // We are responsible for freeing it AFTER it has been replaced if + // replacement succeeds. + base::SharedMemory* old_shared_memory = shared_memory_; + + int new_table_size = NewTableSizeForCount( + static_cast<int>(fingerprints.size() + added_since_rebuild_.size())); + if (BeginReplaceURLTable(new_table_size)) { + // Free the old table. + delete old_shared_memory; + + // Add the stored fingerprints to the hash table. + for (size_t i = 0; i < fingerprints.size(); i++) + AddFingerprint(fingerprints[i], false); + + // Also add anything that was added while we were asynchronously + // generating the new table. + for (std::set<Fingerprint>::iterator i = added_since_rebuild_.begin(); + i != added_since_rebuild_.end(); ++i) + AddFingerprint(*i, false); + added_since_rebuild_.clear(); + + // Now handle deletions. + DeleteFingerprintsFromCurrentTable(deleted_since_rebuild_); + deleted_since_rebuild_.clear(); + + // Send an update notification to all child processes. + listener_->NewTable(shared_memory_); + + if (persist_to_disk_) + WriteFullTable(); + } + } + table_builder_ = NULL; // Will release our reference to the builder. + + // Notify the unit test that the rebuild is complete (will be NULL in prod.) + if (!rebuild_complete_task_.is_null()) { + rebuild_complete_task_.Run(); + rebuild_complete_task_.Reset(); + } +} + +void VisitedLinkMaster::WriteToFile(FILE** file, + off_t offset, + void* data, + int32 data_size) { + DCHECK(persist_to_disk_); +#ifndef NDEBUG + posted_asynchronous_operation_ = true; +#endif + PostIOTask(FROM_HERE, + base::Bind(&AsyncWrite, file, offset, + std::string(static_cast<const char*>(data), data_size))); +} + +void VisitedLinkMaster::WriteUsedItemCountToFile() { + DCHECK(persist_to_disk_); + if (!file_) + return; // See comment on the file_ variable for why this might happen. + WriteToFile(file_, kFileHeaderUsedOffset, &used_items_, sizeof(used_items_)); +} + +void VisitedLinkMaster::WriteHashRangeToFile(Hash first_hash, Hash last_hash) { + DCHECK(persist_to_disk_); + + if (!file_) + return; // See comment on the file_ variable for why this might happen. + if (last_hash < first_hash) { + // Handle wraparound at 0. This first write is first_hash->EOF + WriteToFile(file_, first_hash * sizeof(Fingerprint) + kFileHeaderSize, + &hash_table_[first_hash], + (table_length_ - first_hash + 1) * sizeof(Fingerprint)); + + // Now do 0->last_lash. + WriteToFile(file_, kFileHeaderSize, hash_table_, + (last_hash + 1) * sizeof(Fingerprint)); + } else { + // Normal case, just write the range. + WriteToFile(file_, first_hash * sizeof(Fingerprint) + kFileHeaderSize, + &hash_table_[first_hash], + (last_hash - first_hash + 1) * sizeof(Fingerprint)); + } +} + +bool VisitedLinkMaster::ReadFromFile(FILE* file, + off_t offset, + void* data, + size_t data_size) { + DCHECK(persist_to_disk_); +#ifndef NDEBUG + // Since this function is synchronous, we require that no asynchronous + // operations could possibly be pending. + DCHECK(!posted_asynchronous_operation_); +#endif + + if (fseek(file, offset, SEEK_SET) != 0) + return false; + + size_t num_read = fread(data, 1, data_size, file); + return num_read == data_size; +} + +// VisitedLinkTableBuilder ---------------------------------------------------- + +VisitedLinkMaster::TableBuilder::TableBuilder( + VisitedLinkMaster* master, + const uint8 salt[LINK_SALT_LENGTH]) + : master_(master), + success_(true) { + fingerprints_.reserve(4096); + memcpy(salt_, salt, LINK_SALT_LENGTH * sizeof(uint8)); +} + +// TODO(brettw): Do we want to try to cancel the request if this happens? It +// could delay shutdown if there are a lot of URLs. +void VisitedLinkMaster::TableBuilder::DisownMaster() { + master_ = NULL; +} + +void VisitedLinkMaster::TableBuilder::OnURL(const GURL& url) { + if (!url.is_empty()) { + fingerprints_.push_back(VisitedLinkMaster::ComputeURLFingerprint( + url.spec().data(), url.spec().length(), salt_)); + } +} + +void VisitedLinkMaster::TableBuilder::OnComplete(bool success) { + success_ = success; + DLOG_IF(WARNING, !success) << "Unable to rebuild visited links"; + + // Marshal to the main thread to notify the VisitedLinkMaster that the + // rebuild is complete. + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&TableBuilder::OnCompleteMainThread, this)); +} + +void VisitedLinkMaster::TableBuilder::OnCompleteMainThread() { + if (master_) + master_->OnTableRebuildComplete(success_, fingerprints_); +} + +} // namespace visitedlink diff --git a/chromium/components/visitedlink/browser/visitedlink_master.h b/chromium/components/visitedlink/browser/visitedlink_master.h new file mode 100644 index 00000000000..aff4e498a7c --- /dev/null +++ b/chromium/components/visitedlink/browser/visitedlink_master.h @@ -0,0 +1,445 @@ +// 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_VISITEDLINK_BROWSER_VISITEDLINK_MASTER_H_ +#define COMPONENTS_VISITEDLINK_BROWSER_VISITEDLINK_MASTER_H_ + +#if defined(OS_WIN) +#include <windows.h> +#endif +#include <set> +#include <vector> + +#include "base/callback.h" +#include "base/callback_forward.h" +#include "base/files/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/shared_memory.h" +#include "base/threading/sequenced_worker_pool.h" +#include "components/visitedlink/common/visitedlink_common.h" + +#if defined(UNIT_TEST) || defined(PERF_TEST) || !defined(NDEBUG) +#include "base/logging.h" +#endif + +class GURL; + +namespace content { +class BrowserContext; +} + +namespace visitedlink { + +class VisitedLinkDelegate; + +// Controls the link coloring database. The master controls all writing to the +// database as well as disk I/O. There should be only one master. +// +// This class will defer writing operations to the file thread. This means that +// class destruction, the file may still be open since operations are pending on +// another thread. +class VisitedLinkMaster : public VisitedLinkCommon { + public: + // Listens to the link coloring database events. The master is given this + // event as a constructor argument and dispatches events using it. + class Listener { + public: + virtual ~Listener() {} + + // Called when link coloring database has been created or replaced. The + // argument is the new table handle. + virtual void NewTable(base::SharedMemory*) = 0; + + // Called when new link has been added. The argument is the fingerprint + // (hash) of the link. + virtual void Add(Fingerprint fingerprint) = 0; + + // Called when link coloring state has been reset. This may occur when + // entire or parts of history were deleted. + virtual void Reset() = 0; + }; + + VisitedLinkMaster(content::BrowserContext* browser_context, + VisitedLinkDelegate* delegate, + bool persist_to_disk); + + // In unit test mode, we allow the caller to optionally specify the database + // filename so that it can be run from a unit test. The directory where this + // file resides must exist in this mode. You can also specify the default + // table size to test table resizing. If this parameter is 0, we will use the + // defaults. + // + // In the unit test mode, we also allow the caller to provide a history + // service pointer (the history service can't be fetched from the browser + // process when we're in unit test mode). This can be NULL to try to access + // the main version, which will probably fail (which can be good for testing + // this failure mode). + // + // When |suppress_rebuild| is set, we'll not attempt to load data from + // history if the file can't be loaded. This should generally be set for + // testing except when you want to test the rebuild process explicitly. + VisitedLinkMaster(Listener* listener, + VisitedLinkDelegate* delegate, + bool persist_to_disk, + bool suppress_rebuild, + const base::FilePath& filename, + int32 default_table_size); + virtual ~VisitedLinkMaster(); + + // Must be called immediately after object creation. Nothing else will work + // until this is called. Returns true on success, false means that this + // object won't work. + bool Init(); + + base::SharedMemory* shared_memory() { return shared_memory_; } + + // Adds a URL to the table. + void AddURL(const GURL& url); + + // Adds a set of URLs to the table. + void AddURLs(const std::vector<GURL>& url); + + // See DeleteURLs. + class URLIterator { + public: + // HasNextURL must return true when this is called. Returns the next URL + // then advances the iterator. Note that the returned reference is only + // valid until the next call of NextURL. + virtual const GURL& NextURL() = 0; + + // Returns true if still has URLs to be iterated. + virtual bool HasNextURL() const = 0; + + protected: + virtual ~URLIterator() {} + }; + + // Deletes the specified URLs from |rows| from the table. + void DeleteURLs(URLIterator* iterator); + + // Clears the visited links table by deleting the file from disk. Used as + // part of history clearing. + void DeleteAllURLs(); + + // Returns the Delegate of this Master. + VisitedLinkDelegate* GetDelegate(); + +#if defined(UNIT_TEST) || !defined(NDEBUG) || defined(PERF_TEST) + // This is a debugging function that can be called to double-check internal + // data structures. It will assert if the check fails. + void DebugValidate(); + + // Sets a task to execute when the next rebuild from history is complete. + // This is used by unit tests to wait for the rebuild to complete before + // they continue. The pointer will be owned by this object after the call. + void set_rebuild_complete_task(const base::Closure& task) { + DCHECK(rebuild_complete_task_.is_null()); + rebuild_complete_task_ = task; + } + + // returns the number of items in the table for testing verification + int32 GetUsedCount() const { + return used_items_; + } + + // Returns the listener. + VisitedLinkMaster::Listener* GetListener() const { + return listener_.get(); + } + + // Call to cause the entire database file to be re-written from scratch + // to disk. Used by the performance tester. + void RewriteFile() { + WriteFullTable(); + } +#endif + + private: + FRIEND_TEST_ALL_PREFIXES(VisitedLinkTest, Delete); + FRIEND_TEST_ALL_PREFIXES(VisitedLinkTest, BigDelete); + FRIEND_TEST_ALL_PREFIXES(VisitedLinkTest, BigImport); + + // Object to rebuild the table on the history thread (see the .cc file). + class TableBuilder; + + // Byte offsets of values in the header. + static const int32 kFileHeaderSignatureOffset; + static const int32 kFileHeaderVersionOffset; + static const int32 kFileHeaderLengthOffset; + static const int32 kFileHeaderUsedOffset; + static const int32 kFileHeaderSaltOffset; + + // The signature at the beginning of a file. + static const int32 kFileSignature; + + // version of the file format this module currently uses + static const int32 kFileCurrentVersion; + + // Bytes in the file header, including the salt. + static const size_t kFileHeaderSize; + + // When creating a fresh new table, we use this many entries. + static const unsigned kDefaultTableSize; + + // When the user is deleting a boatload of URLs, we don't really want to do + // individual writes for each of them. When the count exceeds this threshold, + // we will write the whole table to disk at once instead of individual items. + static const size_t kBigDeleteThreshold; + + // Backend for the constructors initializing the members. + void InitMembers(); + + // If a rebuild is in progress, we save the URL in the temporary list. + // Otherwise, we add this to the table. Returns the index of the + // inserted fingerprint or null_hash_ on failure. + Hash TryToAddURL(const GURL& url); + + // File I/O functions + // ------------------ + // These functions are only called if |persist_to_disk_| is true. + + // Posts the given task to the blocking worker pool with our options. + void PostIOTask(const tracked_objects::Location& from_here, + const base::Closure& task); + + // Writes the entire table to disk. It will leave the table file open and + // the handle to it will be stored in file_. + void WriteFullTable(); + + // Try to load the table from the database file. If the file doesn't exist or + // is corrupt, this will return failure. + bool InitFromFile(); + + // Reads the header of the link coloring database from disk. Assumes the + // file pointer is at the beginning of the file and that there are no pending + // asynchronous I/O operations. + // + // Returns true on success and places the size of the table in num_entries + // and the number of nonzero fingerprints in used_count. This will fail if + // the version of the file is not the current version of the database. + bool ReadFileHeader(FILE* hfile, int32* num_entries, int32* used_count, + uint8 salt[LINK_SALT_LENGTH]); + + // Fills *filename with the name of the link database filename + bool GetDatabaseFileName(base::FilePath* filename); + + // Wrapper around Window's WriteFile using asynchronous I/O. This will proxy + // the write to a background thread. + void WriteToFile(FILE** hfile, off_t offset, void* data, int32 data_size); + + // Helper function to schedule and asynchronous write of the used count to + // disk (this is a common operation). + void WriteUsedItemCountToFile(); + + // Helper function to schedule an asynchronous write of the given range of + // hash functions to disk. The range is inclusive on both ends. The range can + // wrap around at 0 and this function will handle it. + void WriteHashRangeToFile(Hash first_hash, Hash last_hash); + + // Synchronous read from the file. Assumes there are no pending asynchronous + // I/O functions. Returns true if the entire buffer was successfully filled. + bool ReadFromFile(FILE* hfile, off_t offset, void* data, size_t data_size); + + // General table handling + // ---------------------- + + // Called to add a fingerprint to the table. If |send_notifications| is true + // and the item is added successfully, Listener::Add will be invoked. + // Returns the index of the inserted fingerprint or null_hash_ if there was a + // duplicate and this item was skippped. + Hash AddFingerprint(Fingerprint fingerprint, bool send_notifications); + + // Deletes all fingerprints from the given vector from the current hash table + // and syncs it to disk if there are changes. This does not update the + // deleted_since_rebuild_ list, the caller must update this itself if there + // is an update pending. + void DeleteFingerprintsFromCurrentTable( + const std::set<Fingerprint>& fingerprints); + + // Removes the indicated fingerprint from the table. If the update_file flag + // is set, the changes will also be written to disk. Returns true if the + // fingerprint was deleted, false if it was not in the table to delete. + bool DeleteFingerprint(Fingerprint fingerprint, bool update_file); + + // Creates a new empty table, call if InitFromFile() fails. Normally, when + // |suppress_rebuild| is false, the table will be rebuilt from history, + // keeping us in sync. When |suppress_rebuild| is true, the new table will be + // empty and we will not consult history. This is used when clearing the + // database and for unit tests. + bool InitFromScratch(bool suppress_rebuild); + + // Allocates the Fingerprint structure and length. When init_to_empty is set, + // the table will be filled with 0s and used_items_ will be set to 0 as well. + // If the flag is not set, these things are untouched and it is the + // responsibility of the caller to fill them (like when we are reading from + // a file). + bool CreateURLTable(int32 num_entries, bool init_to_empty); + + // A wrapper for CreateURLTable, this will allocate a new table, initialized + // to empty. The caller is responsible for saving the shared memory pointer + // and handles before this call (they will be replaced with new ones) and + // releasing them later. This is designed for callers that make a new table + // and then copy values from the old table to the new one, then release the + // old table. + // + // Returns true on success. On failure, the old table will be restored. The + // caller should not attemp to release the pointer/handle in this case. + bool BeginReplaceURLTable(int32 num_entries); + + // unallocates the Fingerprint table + void FreeURLTable(); + + // For growing the table. ResizeTableIfNecessary will check to see if the + // table should be resized and calls ResizeTable if needed. Returns true if + // we decided to resize the table. + bool ResizeTableIfNecessary(); + + // Resizes the table (growing or shrinking) as necessary to accomodate the + // current count. + void ResizeTable(int32 new_size); + + // Returns the desired table size for |item_count| URLs. + uint32 NewTableSizeForCount(int32 item_count) const; + + // Computes the table load as fraction. For example, if 1/4 of the entries are + // full, this value will be 0.25 + float ComputeTableLoad() const { + return static_cast<float>(used_items_) / static_cast<float>(table_length_); + } + + // Initializes a rebuild of the visited link database based on the browser + // history. This will set table_builder_ while working, and there should not + // already be a rebuild in place when called. See the definition for more + // details on how this works. + // + // Returns true on success. Failure means we're not attempting to rebuild + // the database because something failed. + bool RebuildTableFromDelegate(); + + // Callback that the table rebuilder uses when the rebuild is complete. + // |success| is true if the fingerprint generation succeeded, in which case + // |fingerprints| will contain the computed fingerprints. On failure, there + // will be no fingerprints. + void OnTableRebuildComplete(bool success, + const std::vector<Fingerprint>& fingerprints); + + // Increases or decreases the given hash value by one, wrapping around as + // necessary. Used for probing. + inline Hash IncrementHash(Hash hash) { + if (hash >= table_length_ - 1) + return 0; // Wrap around. + return hash + 1; + } + inline Hash DecrementHash(Hash hash) { + if (hash <= 0) + return table_length_ - 1; // Wrap around. + return hash - 1; + } + +#ifndef NDEBUG + // Indicates whether any asynchronous operation has ever been completed. + // We do some synchronous reads that require that no asynchronous operations + // are pending, yet we don't track whether they have been completed. This + // flag is a sanity check that these reads only happen before any + // asynchronous writes have been fired. + bool posted_asynchronous_operation_; +#endif + + // Reference to the browser context that this object belongs to + // (it knows the path to where the data is stored) + content::BrowserContext* browser_context_; + + // Client owns the delegate and is responsible for it being valid through + // the life time this VisitedLinkMaster. + VisitedLinkDelegate* delegate_; + + // VisitedLinkEventListener to handle incoming events. + scoped_ptr<Listener> listener_; + + // Lazily initialized sequence token for posting file tasks. + base::SequencedWorkerPool::SequenceToken sequence_token_; + + // When non-NULL, indicates we are in database rebuild mode and points to + // the class collecting fingerprint information from the history system. + // The pointer is owned by this class, but it must remain valid while the + // history query is running. We must only delete it when the query is done. + scoped_refptr<TableBuilder> table_builder_; + + // Indicates URLs added and deleted since we started rebuilding the table. + std::set<Fingerprint> added_since_rebuild_; + std::set<Fingerprint> deleted_since_rebuild_; + + // TODO(brettw) Support deletion, we need to track whether anything was + // deleted during the rebuild here. Then we should delete any of these + // entries from the complete table later. + // std::vector<Fingerprint> removed_since_rebuild_; + + // The currently open file with the table in it. This may be NULL if we're + // rebuilding and haven't written a new version yet or if |persist_to_disk_| + // is false. Writing to the file may be safely ignored in this case. Also + // |file_| may be non-NULL but point to a NULL pointer. That would mean that + // opening of the file is already scheduled in a background thread and any + // writing to the file can also be scheduled to the background thread as it's + // guaranteed to be executed after the opening. + // The class owns both the |file_| pointer and the pointer pointed + // by |*file_|. + FILE** file_; + + // If true, will try to persist the hash table to disk. Will rebuild from + // VisitedLinkDelegate::RebuildTable if there are disk corruptions. + bool persist_to_disk_; + + // Shared memory consists of a SharedHeader followed by the table. + base::SharedMemory *shared_memory_; + + // When we generate new tables, we increment the serial number of the + // shared memory object. + int32 shared_memory_serial_; + + // Number of non-empty items in the table, used to compute fullness. + int32 used_items_; + + // Testing values ----------------------------------------------------------- + // + // The following fields exist for testing purposes. They are not used in + // release builds. It'd be nice to eliminate them in release builds, but we + // don't want to change the signature of the object between the unit test and + // regular builds. Instead, we just have "default" values that these take + // in release builds that give "regular" behavior. + + // Overridden database file name for testing + base::FilePath database_name_override_; + + // When nonzero, overrides the table size for new databases for testing + int32 table_size_override_; + + // When set, indicates the task that should be run after the next rebuild from + // history is complete. + base::Closure rebuild_complete_task_; + + // Set to prevent us from attempting to rebuild the database from global + // history if we have an error opening the file. This is used for testing, + // will be false in production. + bool suppress_rebuild_; + + DISALLOW_COPY_AND_ASSIGN(VisitedLinkMaster); +}; + +// NOTE: These methods are defined inline here, so we can share the compilation +// of visitedlink_master.cc between the browser and the unit/perf tests. + +#if defined(UNIT_TEST) || defined(PERF_TEST) || !defined(NDEBUG) +inline void VisitedLinkMaster::DebugValidate() { + int32 used_count = 0; + for (int32 i = 0; i < table_length_; i++) { + if (hash_table_[i]) + used_count++; + } + DCHECK_EQ(used_count, used_items_); +} +#endif + +} // namespace visitedlink + +#endif // COMPONENTS_VISITEDLINK_BROWSER_VISITEDLINK_MASTER_H_ diff --git a/chromium/components/visitedlink/common/DEPS b/chromium/components/visitedlink/common/DEPS new file mode 100644 index 00000000000..d5923ee96cb --- /dev/null +++ b/chromium/components/visitedlink/common/DEPS @@ -0,0 +1,3 @@ +include_rules = [ + "+content/public/common", +] diff --git a/chromium/components/visitedlink/common/OWNERS b/chromium/components/visitedlink/common/OWNERS new file mode 100644 index 00000000000..fbe7a20d52f --- /dev/null +++ b/chromium/components/visitedlink/common/OWNERS @@ -0,0 +1,9 @@ +# Changes to IPC messages require a security review to avoid introducing +# new sandbox escapes. +per-file *_messages*.h=set noparent +per-file *_messages*.h=cdn@chromium.org +per-file *_messages*.h=cevans@chromium.org +per-file *_messages*.h=jln@chromium.org +per-file *_messages*.h=jschuh@chromium.org +per-file *_messages*.h=palmer@chromium.org +per-file *_messages*.h=tsepez@chromium.org diff --git a/chromium/components/visitedlink/common/visitedlink_common.cc b/chromium/components/visitedlink/common/visitedlink_common.cc new file mode 100644 index 00000000000..7576231bc05 --- /dev/null +++ b/chromium/components/visitedlink/common/visitedlink_common.cc @@ -0,0 +1,102 @@ +// 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/visitedlink/common/visitedlink_common.h" + +#include <string.h> // for memset() + +#include "base/logging.h" +#include "base/md5.h" +#include "url/gurl.h" + +namespace visitedlink { + +const VisitedLinkCommon::Fingerprint VisitedLinkCommon::null_fingerprint_ = 0; +const VisitedLinkCommon::Hash VisitedLinkCommon::null_hash_ = -1; + +VisitedLinkCommon::VisitedLinkCommon() + : hash_table_(NULL), + table_length_(0) { + memset(salt_, 0, sizeof(salt_)); +} + +VisitedLinkCommon::~VisitedLinkCommon() { +} + +// FIXME: this uses linear probing, it should be replaced with quadratic +// probing or something better. See VisitedLinkMaster::AddFingerprint +bool VisitedLinkCommon::IsVisited(const char* canonical_url, + size_t url_len) const { + if (url_len == 0) + return false; + if (!hash_table_ || table_length_ == 0) + return false; + return IsVisited(ComputeURLFingerprint(canonical_url, url_len)); +} + +bool VisitedLinkCommon::IsVisited(const GURL& url) const { + return IsVisited(url.spec().data(), url.spec().size()); +} + +bool VisitedLinkCommon::IsVisited(Fingerprint fingerprint) const { + // Go through the table until we find the item or an empty spot (meaning it + // wasn't found). This loop will terminate as long as the table isn't full, + // which should be enforced by AddFingerprint. + Hash first_hash = HashFingerprint(fingerprint); + Hash cur_hash = first_hash; + while (true) { + Fingerprint cur_fingerprint = FingerprintAt(cur_hash); + if (cur_fingerprint == null_fingerprint_) + return false; // End of probe sequence found. + if (cur_fingerprint == fingerprint) + return true; // Found a match. + + // This spot was taken, but not by the item we're looking for, search in + // the next position. + cur_hash++; + if (cur_hash == table_length_) + cur_hash = 0; + if (cur_hash == first_hash) { + // Wrapped around and didn't find an empty space, this means we're in an + // infinite loop because AddFingerprint didn't do its job resizing. + NOTREACHED(); + return false; + } + } +} + +// Uses the top 64 bits of the MD5 sum of the canonical URL as the fingerprint, +// this is as random as any other subset of the MD5SUM. +// +// FIXME: this uses the MD5SUM of the 16-bit character version. For systems +// where wchar_t is not 16 bits (Linux uses 32 bits, I think), this will not be +// compatable. We should define explicitly what should happen here across +// platforms, and convert if necessary (probably to UTF-16). + +// static +VisitedLinkCommon::Fingerprint VisitedLinkCommon::ComputeURLFingerprint( + const char* canonical_url, + size_t url_len, + const uint8 salt[LINK_SALT_LENGTH]) { + DCHECK(url_len > 0) << "Canonical URLs should not be empty"; + + base::MD5Context ctx; + base::MD5Init(&ctx); + base::MD5Update(&ctx, base::StringPiece(reinterpret_cast<const char*>(salt), + LINK_SALT_LENGTH)); + base::MD5Update(&ctx, base::StringPiece(canonical_url, url_len)); + + base::MD5Digest digest; + base::MD5Final(&digest, &ctx); + + // This is the same as "return *(Fingerprint*)&digest.a;" but if we do that + // direct cast the alignment could be wrong, and we can't access a 64-bit int + // on arbitrary alignment on some processors. This reinterpret_casts it + // down to a char array of the same size as fingerprint, and then does the + // bit cast, which amounts to a memcpy. This does not handle endian issues. + return bit_cast<Fingerprint, uint8[8]>( + *reinterpret_cast<uint8(*)[8]>(&digest.a)); +} + +} // namespace visitedlink diff --git a/chromium/components/visitedlink/common/visitedlink_common.h b/chromium/components/visitedlink/common/visitedlink_common.h new file mode 100644 index 00000000000..37afe1d1575 --- /dev/null +++ b/chromium/components/visitedlink/common/visitedlink_common.h @@ -0,0 +1,138 @@ +// Copyright (c) 2006-2008 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_VISITEDLINK_COMMON_VISITEDLINK_COMMON_H_ +#define COMPONENTS_VISITEDLINK_COMMON_VISITEDLINK_COMMON_H_ + +#include <vector> + +#include "base/basictypes.h" + +class GURL; + +namespace visitedlink { + +// number of bytes in the salt +#define LINK_SALT_LENGTH 8 + +// A multiprocess-safe database of the visited links for the browser. There +// should be exactly one process that has write access (implemented by +// VisitedLinkMaster), while all other processes should be read-only +// (implemented by VisitedLinkSlave). These other processes add links by calling +// the writer process to add them for it. The writer may also notify the readers +// to replace their table when the table is resized. +// +// IPC is not implemented in these classes. This is done through callback +// functions supplied by the creator of these objects to allow more flexibility, +// especially for testing. +// +// This class defines the common base for these others. We implement accessors +// for looking things up in the hash table, and for computing hash values and +// fingerprints. Both the master and the slave inherit from this, and add their +// own code to set up and change these values as their design requires. The +// slave pretty much just sets up the shared memory and saves the pointer. The +// master does a lot of work to manage the table, reading and writing it to and +// from disk, and resizing it when it gets too full. +// +// To ask whether a page is in history, we compute a 64-bit fingerprint of the +// URL. This URL is hashed and we see if it is in the URL hashtable. If it is, +// we consider it visited. Otherwise, it is unvisited. Note that it is possible +// to get collisions, which is the penalty for not storing all URL strings in +// memory (which could get to be more than we want to have in memory). We use +// a salt value for the links on one computer so that an attacker can not +// manually create a link that causes a collision. +class VisitedLinkCommon { + public: + // A number that identifies the URL. + typedef uint64 Fingerprint; + typedef std::vector<Fingerprint> Fingerprints; + + // A hash value of a fingerprint + typedef int32 Hash; + + // A fingerprint or hash value that does not exist + static const Fingerprint null_fingerprint_; + static const Hash null_hash_; + + VisitedLinkCommon(); + virtual ~VisitedLinkCommon(); + + // Returns the fingerprint for the given URL. + Fingerprint ComputeURLFingerprint(const char* canonical_url, + size_t url_len) const { + return ComputeURLFingerprint(canonical_url, url_len, salt_); + } + + // Looks up the given key in the table. The fingerprint for the URL is + // computed if you call one with the string argument. Returns true if found. + // Does not modify the hastable. + bool IsVisited(const char* canonical_url, size_t url_len) const; + bool IsVisited(const GURL& url) const; + bool IsVisited(Fingerprint fingerprint) const; + +#ifdef UNIT_TEST + // Returns statistics about DB usage + void GetUsageStatistics(int32* table_size, + VisitedLinkCommon::Fingerprint** fingerprints) { + *table_size = table_length_; + *fingerprints = hash_table_; + } +#endif + + protected: + // This structure is at the beginning of the shared memory so that the slaves + // can get stats on the table + struct SharedHeader { + // see goes into table_length_ + uint32 length; + + // goes into salt_ + uint8 salt[LINK_SALT_LENGTH]; + }; + + // Returns the fingerprint at the given index into the URL table. This + // function should be called instead of accessing the table directly to + // contain endian issues. + Fingerprint FingerprintAt(int32 table_offset) const { + if (!hash_table_) + return null_fingerprint_; + return hash_table_[table_offset]; + } + + // Computes the fingerprint of the given canonical URL. It is static so the + // same algorithm can be re-used by the table rebuilder, so you will have to + // pass the salt as a parameter. See the non-static version above if you + // want to use the current class' salt. + static Fingerprint ComputeURLFingerprint(const char* canonical_url, + size_t url_len, + const uint8 salt[LINK_SALT_LENGTH]); + + // Computes the hash value of the given fingerprint, this is used as a lookup + // into the hashtable. + static Hash HashFingerprint(Fingerprint fingerprint, int32 table_length) { + if (table_length == 0) + return null_hash_; + return static_cast<Hash>(fingerprint % table_length); + } + // Uses the current hashtable. + Hash HashFingerprint(Fingerprint fingerprint) const { + return HashFingerprint(fingerprint, table_length_); + } + + // pointer to the first item + VisitedLinkCommon::Fingerprint* hash_table_; + + // the number of items in the hash table + int32 table_length_; + + // salt used for each URL when computing the fingerprint + uint8 salt_[LINK_SALT_LENGTH]; + + private: + DISALLOW_COPY_AND_ASSIGN(VisitedLinkCommon); +}; + +} // namespace visitedlink + +#endif // COMPONENTS_VISITEDLINK_COMMON_VISITEDLINK_COMMON_H_ diff --git a/chromium/components/visitedlink/common/visitedlink_message_generator.cc b/chromium/components/visitedlink/common/visitedlink_message_generator.cc new file mode 100644 index 00000000000..29d31916e7f --- /dev/null +++ b/chromium/components/visitedlink/common/visitedlink_message_generator.cc @@ -0,0 +1,34 @@ +// 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. + +// Get basic type definitions. +#define IPC_MESSAGE_IMPL +#include "components/visitedlink/common/visitedlink_message_generator.h" + +// Generate constructors. +#include "ipc/struct_constructor_macros.h" +#include "components/visitedlink/common/visitedlink_message_generator.h" + +// Generate destructors. +#include "ipc/struct_destructor_macros.h" +#include "components/visitedlink/common/visitedlink_message_generator.h" + +// Generate param traits write methods. +#include "ipc/param_traits_write_macros.h" +namespace IPC { +#include "components/visitedlink/common/visitedlink_message_generator.h" +} // namespace IPC + +// Generate param traits read methods. +#include "ipc/param_traits_read_macros.h" +namespace IPC { +#include "components/visitedlink/common/visitedlink_message_generator.h" +} // namespace IPC + +// Generate param traits log methods. +#include "ipc/param_traits_log_macros.h" +namespace IPC { +#include "components/visitedlink/common/visitedlink_message_generator.h" +} // namespace IPC + diff --git a/chromium/components/visitedlink/common/visitedlink_message_generator.h b/chromium/components/visitedlink/common/visitedlink_message_generator.h new file mode 100644 index 00000000000..e4a496bafb3 --- /dev/null +++ b/chromium/components/visitedlink/common/visitedlink_message_generator.h @@ -0,0 +1,7 @@ +// 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. + +// Multiply-included file, no traditional include guard. + +#include "components/visitedlink/common/visitedlink_messages.h" diff --git a/chromium/components/visitedlink/common/visitedlink_messages.h b/chromium/components/visitedlink/common/visitedlink_messages.h new file mode 100644 index 00000000000..c0877765f12 --- /dev/null +++ b/chromium/components/visitedlink/common/visitedlink_messages.h @@ -0,0 +1,29 @@ +// 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. + +// Multiply-included file, no traditional include guard. +#include <vector> + +#include "base/basictypes.h" +#include "base/memory/shared_memory.h" +#include "content/public/common/common_param_traits_macros.h" +#include "ipc/ipc_message_macros.h" + +#define IPC_MESSAGE_START VisitedLinkMsgStart + +// History system notification that the visited link database has been +// replaced. It has one SharedMemoryHandle argument consisting of the table +// handle. This handle is valid in the context of the renderer +IPC_MESSAGE_CONTROL1(ChromeViewMsg_VisitedLink_NewTable, + base::SharedMemoryHandle) + +// History system notification that a link has been added and the link +// coloring state for the given hash must be re-calculated. +IPC_MESSAGE_CONTROL1(ChromeViewMsg_VisitedLink_Add, std::vector<uint64>) + +// History system notification that one or more history items have been +// deleted, which at this point means that all link coloring state must be +// re-calculated. +IPC_MESSAGE_CONTROL0(ChromeViewMsg_VisitedLink_Reset) + diff --git a/chromium/components/visitedlink/renderer/DEPS b/chromium/components/visitedlink/renderer/DEPS new file mode 100644 index 00000000000..ad0391cf78b --- /dev/null +++ b/chromium/components/visitedlink/renderer/DEPS @@ -0,0 +1,5 @@ +include_rules = [ + "+content/public/renderer", + "+third_party/WebKit/public/platform", + "+third_party/WebKit/public/web", +] diff --git a/chromium/components/visitedlink/renderer/visitedlink_slave.cc b/chromium/components/visitedlink/renderer/visitedlink_slave.cc new file mode 100644 index 00000000000..7d3e32123a5 --- /dev/null +++ b/chromium/components/visitedlink/renderer/visitedlink_slave.cc @@ -0,0 +1,92 @@ +// Copyright (c) 2006-2008 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/visitedlink/renderer/visitedlink_slave.h" + +#include "base/logging.h" +#include "base/memory/shared_memory.h" +#include "components/visitedlink/common/visitedlink_messages.h" +#include "third_party/WebKit/public/web/WebView.h" + +using WebKit::WebView; + +namespace visitedlink { + +VisitedLinkSlave::VisitedLinkSlave() : shared_memory_(NULL) {} + +VisitedLinkSlave::~VisitedLinkSlave() { + FreeTable(); +} + +bool VisitedLinkSlave::OnControlMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(VisitedLinkSlave, message) + IPC_MESSAGE_HANDLER(ChromeViewMsg_VisitedLink_NewTable, + OnUpdateVisitedLinks) + IPC_MESSAGE_HANDLER(ChromeViewMsg_VisitedLink_Add, OnAddVisitedLinks) + IPC_MESSAGE_HANDLER(ChromeViewMsg_VisitedLink_Reset, OnResetVisitedLinks) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +// This function's job is to initialize the table with the given +// shared memory handle. This memory is mapped into the process. +void VisitedLinkSlave::OnUpdateVisitedLinks(base::SharedMemoryHandle table) { + DCHECK(base::SharedMemory::IsHandleValid(table)) << "Bad table handle"; + // since this function may be called again to change the table, we may need + // to free old objects + FreeTable(); + DCHECK(shared_memory_ == NULL && hash_table_ == NULL); + + // create the shared memory object + shared_memory_ = new base::SharedMemory(table, true); + if (!shared_memory_) + return; + + // map the header into our process so we can see how long the rest is, + // and set the salt + if (!shared_memory_->Map(sizeof(SharedHeader))) + return; + SharedHeader* header = + static_cast<SharedHeader*>(shared_memory_->memory()); + DCHECK(header); + int32 table_len = header->length; + memcpy(salt_, header->salt, sizeof(salt_)); + shared_memory_->Unmap(); + + // now do the whole table because we know the length + if (!shared_memory_->Map(sizeof(SharedHeader) + + table_len * sizeof(Fingerprint))) { + shared_memory_->Close(); + return; + } + + // commit the data + DCHECK(shared_memory_->memory()); + hash_table_ = reinterpret_cast<Fingerprint*>( + static_cast<char*>(shared_memory_->memory()) + sizeof(SharedHeader)); + table_length_ = table_len; +} + +void VisitedLinkSlave::OnAddVisitedLinks( + const VisitedLinkSlave::Fingerprints& fingerprints) { + for (size_t i = 0; i < fingerprints.size(); ++i) + WebView::updateVisitedLinkState(fingerprints[i]); +} + +void VisitedLinkSlave::OnResetVisitedLinks() { + WebView::resetVisitedLinkState(); +} + +void VisitedLinkSlave::FreeTable() { + if (shared_memory_) { + delete shared_memory_; + shared_memory_ = NULL; + } + hash_table_ = NULL; + table_length_ = 0; +} + +} // namespace visitedlink diff --git a/chromium/components/visitedlink/renderer/visitedlink_slave.h b/chromium/components/visitedlink/renderer/visitedlink_slave.h new file mode 100644 index 00000000000..425c03e1be7 --- /dev/null +++ b/chromium/components/visitedlink/renderer/visitedlink_slave.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_VISITEDLINK_RENDERER_VISITEDLINK_SLAVE_H_ +#define COMPONENTS_VISITEDLINK_RENDERER_VISITEDLINK_SLAVE_H_ + +#include "base/compiler_specific.h" +#include "base/memory/shared_memory.h" +#include "components/visitedlink/common/visitedlink_common.h" +#include "content/public/renderer/render_process_observer.h" + +namespace visitedlink { + +// Reads the link coloring database provided by the master. There can be any +// number of slaves reading the same database. +class VisitedLinkSlave : public VisitedLinkCommon, + public content::RenderProcessObserver { + public: + VisitedLinkSlave(); + virtual ~VisitedLinkSlave(); + + // RenderProcessObserver implementation. + virtual bool OnControlMessageReceived(const IPC::Message& message) OVERRIDE; + + // Message handlers. + void OnUpdateVisitedLinks(base::SharedMemoryHandle table); + void OnAddVisitedLinks(const VisitedLinkSlave::Fingerprints& fingerprints); + void OnResetVisitedLinks(); + + private: + void FreeTable(); + + // shared memory consists of a SharedHeader followed by the table + base::SharedMemory* shared_memory_; + + DISALLOW_COPY_AND_ASSIGN(VisitedLinkSlave); +}; + +} // namespace visitedlink + +#endif // COMPONENTS_VISITEDLINK_RENDERER_VISITEDLINK_SLAVE_H_ diff --git a/chromium/components/web_contents_delegate_android.gypi b/chromium/components/web_contents_delegate_android.gypi new file mode 100644 index 00000000000..0a8b845dbb8 --- /dev/null +++ b/chromium/components/web_contents_delegate_android.gypi @@ -0,0 +1,64 @@ +# 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. + +{ + 'conditions': [ + ['OS=="android"', { + 'targets': [ + { + 'target_name': 'web_contents_delegate_android', + 'type': 'static_library', + 'dependencies': [ + '../base/base.gyp:base', + '../content/content.gyp:content_browser', + '../content/content.gyp:content_common', + '../net/net.gyp:net', + '../skia/skia.gyp:skia', + '../ui/ui.gyp:ui', + '../webkit/support/webkit_support.gyp:glue', + 'web_contents_delegate_android_jni_headers', + ], + 'include_dirs': [ + '..', + '../skia/config', + '<(SHARED_INTERMEDIATE_DIR)/web_contents_delegate_android', + ], + 'sources': [ + 'web_contents_delegate_android/color_chooser_android.cc', + 'web_contents_delegate_android/color_chooser_android.h', + 'web_contents_delegate_android/component_jni_registrar.cc', + 'web_contents_delegate_android/component_jni_registrar.h', + 'web_contents_delegate_android/web_contents_delegate_android.cc', + 'web_contents_delegate_android/web_contents_delegate_android.h', + ], + }, + { + 'target_name': 'web_contents_delegate_android_java', + 'type': 'none', + 'dependencies': [ + '../base/base.gyp:base', + '../content/content.gyp:content_java', + ], + 'variables': { + 'java_in_dir': 'web_contents_delegate_android/android/java', + }, + 'includes': [ '../build/java.gypi' ], + }, + { + 'target_name': 'web_contents_delegate_android_jni_headers', + 'type': 'none', + 'sources': [ + 'web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java', + 'web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/WebContentsDelegateAndroid.java', + ], + 'variables': { + 'jni_gen_package': 'web_contents_delegate_android', + }, + 'includes': [ '../build/jni_generator.gypi' ], + }, + ], + }], + ], +} diff --git a/chromium/components/web_contents_delegate_android/DEPS b/chromium/components/web_contents_delegate_android/DEPS new file mode 100644 index 00000000000..647531f7476 --- /dev/null +++ b/chromium/components/web_contents_delegate_android/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+content/public/browser", + "+jni", + "+ui/base", + "+ui/gfx", +] diff --git a/chromium/components/web_contents_delegate_android/OWNERS b/chromium/components/web_contents_delegate_android/OWNERS new file mode 100644 index 00000000000..f9dca5cf635 --- /dev/null +++ b/chromium/components/web_contents_delegate_android/OWNERS @@ -0,0 +1 @@ +joth@chromium.org diff --git a/chromium/components/web_contents_delegate_android/android/DEPS b/chromium/components/web_contents_delegate_android/android/DEPS new file mode 100644 index 00000000000..45a78e4e2ce --- /dev/null +++ b/chromium/components/web_contents_delegate_android/android/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "+content/public/android/java", + "+ui/android/java", +] diff --git a/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java b/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java new file mode 100644 index 00000000000..babf91ea359 --- /dev/null +++ b/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/ColorChooserAndroid.java @@ -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. + +package org.chromium.components.web_contents_delegate_android; + +import android.content.Context; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.content.browser.ContentViewCore; +import org.chromium.ui.ColorPickerDialog; +import org.chromium.ui.OnColorChangedListener; + +/** + * ColorChooserAndroid communicates with the java ColorPickerDialog and the + * native color_chooser_android.cc + */ +@JNINamespace("web_contents_delegate_android") +public class ColorChooserAndroid { + private final ColorPickerDialog mDialog; + private final int mNativeColorChooserAndroid; + + private ColorChooserAndroid(int nativeColorChooserAndroid, + Context context, int initialColor) { + OnColorChangedListener listener = new OnColorChangedListener() { + @Override + public void onColorChanged(int color) { + mDialog.dismiss(); + nativeOnColorChosen(mNativeColorChooserAndroid, color); + } + }; + + mNativeColorChooserAndroid = nativeColorChooserAndroid; + mDialog = new ColorPickerDialog(context, listener, initialColor); + } + + private void openColorChooser() { + mDialog.show(); + } + + @CalledByNative + public void closeColorChooser() { + mDialog.dismiss(); + } + + @CalledByNative + public static ColorChooserAndroid createColorChooserAndroid( + int nativeColorChooserAndroid, + ContentViewCore contentViewCore, + int initialColor) { + ColorChooserAndroid chooser = new ColorChooserAndroid(nativeColorChooserAndroid, + contentViewCore.getContext(), initialColor); + chooser.openColorChooser(); + return chooser; + } + + // Implemented in color_chooser_android.cc + private native void nativeOnColorChosen(int nativeColorChooserAndroid, int color); +} diff --git a/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/WebContentsDelegateAndroid.java b/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/WebContentsDelegateAndroid.java new file mode 100644 index 00000000000..c09cb89629c --- /dev/null +++ b/chromium/components/web_contents_delegate_android/android/java/src/org/chromium/components/web_contents_delegate_android/WebContentsDelegateAndroid.java @@ -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. + +package org.chromium.components.web_contents_delegate_android; + +import android.graphics.Rect; +import android.view.KeyEvent; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.content.browser.ContentViewCore; + +/** + * Java peer of the native class of the same name. + */ +@JNINamespace("web_contents_delegate_android") +public class WebContentsDelegateAndroid { + + // Equivalent of WebCore::WebConsoleMessage::LevelTip. + public static final int LOG_LEVEL_TIP = 0; + // Equivalent of WebCore::WebConsoleMessage::LevelLog. + public static final int LOG_LEVEL_LOG = 1; + // Equivalent of WebCore::WebConsoleMessage::LevelWarning. + public static final int LOG_LEVEL_WARNING = 2; + // Equivalent of WebCore::WebConsoleMessage::LevelError. + public static final int LOG_LEVEL_ERROR = 3; + + // Flags passed to the WebContentsDelegateAndroid.navigationStateChanged to tell it + // what has changed. Should match the values in invalidate_type.h. + // Equivalent of InvalidateTypes::INVALIDATE_TYPE_URL. + public static final int INVALIDATE_TYPE_URL = 1 << 0; + // Equivalent of InvalidateTypes::INVALIDATE_TYPE_TAB. + public static final int INVALIDATE_TYPE_TAB = 1 << 1; + // Equivalent of InvalidateTypes::INVALIDATE_TYPE_LOAD. + public static final int INVALIDATE_TYPE_LOAD = 1 << 2; + // Equivalent of InvalidateTypes::INVALIDATE_TYPE_PAGE_ACTIONS. + public static final int INVALIDATE_TYPE_PAGE_ACTIONS = 1 << 3; + // Equivalent of InvalidateTypes::INVALIDATE_TYPE_TITLE. + public static final int INVALIDATE_TYPE_TITLE = 1 << 4; + + // The most recent load progress callback received from WebContents, as a percentage. + // Initialize to 100 to indicate that we're not in a loading state. + private int mMostRecentProgress = 100; + + public int getMostRecentProgress() { + return mMostRecentProgress; + } + + /** + * @param disposition The new tab disposition as per the constants in + * org.chromium.ui.WindowOpenDisposition (See window_open_disposition_list.h + * for the enumeration definitions). + */ + @CalledByNative + public void openNewTab(String url, String extraHeaders, byte[] postData, int disposition) { + } + + @CalledByNative + public boolean addNewContents(int nativeSourceWebContents, int nativeWebContents, + int disposition, Rect initialPosition, boolean userGesture) { + return false; + } + + @CalledByNative + public void activateContents() { + } + + @CalledByNative + public void closeContents() { + } + + @CalledByNative + public void onLoadStarted() { + } + + @CalledByNative + public void onLoadStopped() { + } + + @CalledByNative + public void navigationStateChanged(int flags) { + } + + @SuppressWarnings("unused") + @CalledByNative + private final void notifyLoadProgressChanged(double progress) { + mMostRecentProgress = (int) (100.0 * progress); + onLoadProgressChanged(mMostRecentProgress); + } + + /** + * @param progress The load progress [0, 100] for the current web contents. + */ + public void onLoadProgressChanged(int progress) { + } + + /** + * Signaled when the renderer has been deemed to be unresponsive. + */ + @CalledByNative + public void rendererUnresponsive() { + } + + /** + * Signaled when the render has been deemed to be responsive. + */ + @CalledByNative + public void rendererResponsive() { + } + + @CalledByNative + public void onUpdateUrl(String url) { + } + + @CalledByNative + public boolean takeFocus(boolean reverse) { + return false; + } + + @CalledByNative + public void handleKeyboardEvent(KeyEvent event) { + // TODO(bulach): we probably want to re-inject the KeyEvent back into + // the system. Investigate if this is at all possible. + } + + /** + * Report a JavaScript console message. + * + * @param level message level. One of WebContentsDelegateAndroid.LOG_LEVEL*. + * @param message the error message. + * @param lineNumber the line number int the source file at which the error is reported. + * @param sourceId the name of the source file that caused the error. + * @return true if the client will handle logging the message. + */ + @CalledByNative + public boolean addMessageToConsole(int level, String message, int lineNumber, + String sourceId) { + return false; + } + + /** + * Report a form resubmission. The overwriter of this function should eventually call + * either of ContentViewCore.ContinuePendingReload or ContentViewCore.CancelPendingReload. + */ + @CalledByNative + public void showRepostFormWarningDialog(ContentViewCore contentViewCore) { + } + + @CalledByNative + public void toggleFullscreenModeForTab(boolean enterFullscreen) { + } + + @CalledByNative + public boolean isFullscreenForTabOrPending() { + return false; + } + + /** + * Called from WebKit to request that the top controls be shown or hidden. + * The implementation should call ContentViewCore.showTopControls to actually + * show or hide the top controls. + */ + @CalledByNative + public void didProgrammaticallyScroll(int scrollX, int scrollY) { + } +} diff --git a/chromium/components/web_contents_delegate_android/color_chooser_android.cc b/chromium/components/web_contents_delegate_android/color_chooser_android.cc new file mode 100644 index 00000000000..4eb3d03d704 --- /dev/null +++ b/chromium/components/web_contents_delegate_android/color_chooser_android.cc @@ -0,0 +1,57 @@ +// 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/web_contents_delegate_android/color_chooser_android.h" + +#include "content/public/browser/android/content_view_core.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_contents_view.h" +#include "jni/ColorChooserAndroid_jni.h" + +namespace web_contents_delegate_android { + +ColorChooserAndroid::ColorChooserAndroid(content::WebContents* web_contents, + SkColor initial_color) + : web_contents_(web_contents) { + JNIEnv* env = AttachCurrentThread(); + content::ContentViewCore* content_view_core = + content::ContentViewCore::FromWebContents(web_contents); + DCHECK(content_view_core); + + j_color_chooser_.Reset(Java_ColorChooserAndroid_createColorChooserAndroid( + env, + reinterpret_cast<intptr_t>(this), + content_view_core->GetJavaObject().obj(), + initial_color)); +} + +ColorChooserAndroid::~ColorChooserAndroid() { +} + +void ColorChooserAndroid::End() { + if (!j_color_chooser_.is_null()) { + JNIEnv* env = AttachCurrentThread(); + Java_ColorChooserAndroid_closeColorChooser(env, j_color_chooser_.obj()); + } +} + +void ColorChooserAndroid::SetSelectedColor(SkColor color) { + // Not implemented since the color is set on the java side only, in theory + // it can be set from JS which would override the user selection but + // we don't support that for now. +} + +void ColorChooserAndroid::OnColorChosen(JNIEnv* env, jobject obj, jint color) { + web_contents_->DidChooseColorInColorChooser(color); + web_contents_->DidEndColorChooser(); +} + +// ---------------------------------------------------------------------------- +// Native JNI methods +// ---------------------------------------------------------------------------- +bool RegisterColorChooserAndroid(JNIEnv* env) { + return RegisterNativesImpl(env); +} + +} // namespace web_contents_delegate_android diff --git a/chromium/components/web_contents_delegate_android/color_chooser_android.h b/chromium/components/web_contents_delegate_android/color_chooser_android.h new file mode 100644 index 00000000000..8ab332603a8 --- /dev/null +++ b/chromium/components/web_contents_delegate_android/color_chooser_android.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_WEB_CONTENTS_DELEGATE_ANDROID_COLOR_CHOOSER_ANDROID_H_ +#define COMPONENTS_WEB_CONTENTS_DELEGATE_ANDROID_COLOR_CHOOSER_ANDROID_H_ + +#include "base/android/jni_android.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/public/browser/color_chooser.h" + +using base::android::AttachCurrentThread; +using base::android::ScopedJavaLocalRef; + +namespace content { +class WebContents; +} + +namespace web_contents_delegate_android { + +// Glues the Java (ColorPickerChooser.java) picker with the native part. +class ColorChooserAndroid : public content::ColorChooser { + public: + ColorChooserAndroid(content::WebContents* tab, + SkColor initial_color); + virtual ~ColorChooserAndroid(); + + void OnColorChosen(JNIEnv* env, jobject obj, jint color); + + // ColorChooser interface + virtual void End() OVERRIDE; + virtual void SetSelectedColor(SkColor color) OVERRIDE; + + private: + base::android::ScopedJavaGlobalRef<jobject> j_color_chooser_; + + // The web contents invoking the color chooser. No ownership. because it will + // outlive this class. + content::WebContents* web_contents_; + + DISALLOW_COPY_AND_ASSIGN(ColorChooserAndroid); +}; + +// Native JNI methods +bool RegisterColorChooserAndroid(JNIEnv* env); + +} // namespace web_contents_delegate_android + +#endif // COMPONENTS_WEB_CONTENTS_DELEGATE_ANDROID_COLOR_CHOOSER_ANDROID_H_ diff --git a/chromium/components/web_contents_delegate_android/component_jni_registrar.cc b/chromium/components/web_contents_delegate_android/component_jni_registrar.cc new file mode 100644 index 00000000000..b5c5e1590e8 --- /dev/null +++ b/chromium/components/web_contents_delegate_android/component_jni_registrar.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/web_contents_delegate_android/component_jni_registrar.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_registrar.h" +#include "components/web_contents_delegate_android/color_chooser_android.h" +#include "components/web_contents_delegate_android/web_contents_delegate_android.h" + +namespace web_contents_delegate_android { + +static base::android::RegistrationMethod kComponentRegisteredMethods[] = { + { "ColorChooserAndroid", RegisterColorChooserAndroid }, + { "WebContentsDelegateAndroid", RegisterWebContentsDelegateAndroid }, +}; + +bool RegisterWebContentsDelegateAndroidJni(JNIEnv* env) { + return RegisterNativeMethods(env, + kComponentRegisteredMethods, arraysize(kComponentRegisteredMethods)); +} + +} // namespace web_contents_delegate_android + diff --git a/chromium/components/web_contents_delegate_android/component_jni_registrar.h b/chromium/components/web_contents_delegate_android/component_jni_registrar.h new file mode 100644 index 00000000000..203585d4972 --- /dev/null +++ b/chromium/components/web_contents_delegate_android/component_jni_registrar.h @@ -0,0 +1,19 @@ +// 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_WEB_CONTENTS_DELEGATE_ANDROID_COMPONENT_JNI_REGISTRAR_H_ +#define COMPONENTS_WEB_CONTENTS_DELEGATE_ANDROID_COMPONENT_JNI_REGISTRAR_H_ + +#include <jni.h> + +namespace web_contents_delegate_android { + +// Register all JNI bindings necessary for the web_contents_delegate_android +// component. +bool RegisterWebContentsDelegateAndroidJni(JNIEnv* env); + +} // namespace web_contents_delegate_android + +#endif // COMPONENTS_WEB_CONTENTS_DELEGATE_ANDROID_COMPONENT_JNI_REGISTRAR_H_ + diff --git a/chromium/components/web_contents_delegate_android/web_contents_delegate_android.cc b/chromium/components/web_contents_delegate_android/web_contents_delegate_android.cc new file mode 100644 index 00000000000..8f599a16b03 --- /dev/null +++ b/chromium/components/web_contents_delegate_android/web_contents_delegate_android.cc @@ -0,0 +1,361 @@ +// 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/web_contents_delegate_android/web_contents_delegate_android.h" + +#include <android/keycodes.h> + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "components/web_contents_delegate_android/color_chooser_android.h" +#include "content/public/browser/android/content_view_core.h" +#include "content/public/browser/color_chooser.h" +#include "content/public/browser/invalidate_type.h" +#include "content/public/browser/native_web_keyboard_event.h" +#include "content/public/browser/page_navigator.h" +#include "content/public/browser/render_widget_host_view.h" +#include "content/public/browser/web_contents.h" +#include "content/public/common/page_transition_types.h" +#include "content/public/common/referrer.h" +#include "jni/WebContentsDelegateAndroid_jni.h" +#include "ui/base/window_open_disposition.h" +#include "ui/gfx/rect.h" +#include "url/gurl.h" + +using base::android::AttachCurrentThread; +using base::android::ConvertUTF8ToJavaString; +using base::android::ConvertUTF16ToJavaString; +using base::android::HasClass; +using base::android::ScopedJavaLocalRef; +using content::ColorChooser; +using content::WebContents; +using content::WebContentsDelegate; + +namespace web_contents_delegate_android { + +WebContentsDelegateAndroid::WebContentsDelegateAndroid(JNIEnv* env, jobject obj) + : weak_java_delegate_(env, obj) { +} + +WebContentsDelegateAndroid::~WebContentsDelegateAndroid() { +} + +ScopedJavaLocalRef<jobject> +WebContentsDelegateAndroid::GetJavaDelegate(JNIEnv* env) const { + return weak_java_delegate_.get(env); +} + +// ---------------------------------------------------------------------------- +// WebContentsDelegate methods +// ---------------------------------------------------------------------------- + +ColorChooser* WebContentsDelegateAndroid::OpenColorChooser(WebContents* source, + SkColor color) { + return new ColorChooserAndroid(source, color); +} + +// OpenURLFromTab() will be called when we're performing a browser-intiated +// navigation. The most common scenario for this is opening new tabs (see +// RenderViewImpl::decidePolicyForNavigation for more details). +WebContents* WebContentsDelegateAndroid::OpenURLFromTab( + WebContents* source, + const content::OpenURLParams& params) { + const GURL& url = params.url; + WindowOpenDisposition disposition = params.disposition; + content::PageTransition transition( + PageTransitionFromInt(params.transition)); + + if (!source || (disposition != CURRENT_TAB && + disposition != NEW_FOREGROUND_TAB && + disposition != NEW_BACKGROUND_TAB && + disposition != OFF_THE_RECORD)) { + NOTIMPLEMENTED(); + return NULL; + } + + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return WebContentsDelegate::OpenURLFromTab(source, params); + + if (disposition == NEW_FOREGROUND_TAB || + disposition == NEW_BACKGROUND_TAB || + disposition == OFF_THE_RECORD) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jstring> java_url = + ConvertUTF8ToJavaString(env, url.spec()); + ScopedJavaLocalRef<jstring> extra_headers = + ConvertUTF8ToJavaString(env, params.extra_headers); + ScopedJavaLocalRef<jbyteArray> post_data; + if (params.uses_post && + params.browser_initiated_post_data.get() && + params.browser_initiated_post_data.get()->size()) { + post_data = base::android::ToJavaByteArray( + env, + reinterpret_cast<const uint8*>( + params.browser_initiated_post_data.get()->front()), + params.browser_initiated_post_data.get()->size()); + } + Java_WebContentsDelegateAndroid_openNewTab(env, + obj.obj(), + java_url.obj(), + extra_headers.obj(), + post_data.obj(), + disposition); + return NULL; + } + + source->GetController().LoadURL(url, params.referrer, transition, + std::string()); + return source; +} + +void WebContentsDelegateAndroid::NavigationStateChanged( + const WebContents* source, unsigned changed_flags) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + Java_WebContentsDelegateAndroid_navigationStateChanged( + env, + obj.obj(), + changed_flags); +} + +void WebContentsDelegateAndroid::AddNewContents( + WebContents* source, + WebContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture, + bool* was_blocked) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + bool handled = false; + if (!obj.is_null()) { + handled = Java_WebContentsDelegateAndroid_addNewContents( + env, + obj.obj(), + reinterpret_cast<jint>(source), + reinterpret_cast<jint>(new_contents), + static_cast<jint>(disposition), + NULL, + user_gesture); + } + if (!handled) + delete new_contents; +} + +void WebContentsDelegateAndroid::ActivateContents(WebContents* contents) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + Java_WebContentsDelegateAndroid_activateContents(env, obj.obj()); +} + +void WebContentsDelegateAndroid::DeactivateContents(WebContents* contents) { + // On desktop the current window is deactivated here, bringing the next window + // to focus. Not implemented on Android. +} + +void WebContentsDelegateAndroid::LoadingStateChanged(WebContents* source) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + bool has_stopped = source == NULL || !source->IsLoading(); + + if (has_stopped) + Java_WebContentsDelegateAndroid_onLoadStopped(env, obj.obj()); + else + Java_WebContentsDelegateAndroid_onLoadStarted(env, obj.obj()); +} + +void WebContentsDelegateAndroid::LoadProgressChanged(WebContents* source, + double progress) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + Java_WebContentsDelegateAndroid_notifyLoadProgressChanged( + env, + obj.obj(), + progress); +} + +void WebContentsDelegateAndroid::RendererUnresponsive(WebContents* source) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + Java_WebContentsDelegateAndroid_rendererUnresponsive(env, obj.obj()); +} + +void WebContentsDelegateAndroid::RendererResponsive(WebContents* source) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + Java_WebContentsDelegateAndroid_rendererResponsive(env, obj.obj()); +} + +void WebContentsDelegateAndroid::CloseContents(WebContents* source) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + Java_WebContentsDelegateAndroid_closeContents(env, obj.obj()); +} + +void WebContentsDelegateAndroid::MoveContents(WebContents* source, + const gfx::Rect& pos) { + // Do nothing. +} + +bool WebContentsDelegateAndroid::AddMessageToConsole( + WebContents* source, + int32 level, + const base::string16& message, + int32 line_no, + const base::string16& source_id) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return WebContentsDelegate::AddMessageToConsole(source, level, message, + line_no, source_id); + ScopedJavaLocalRef<jstring> jmessage(ConvertUTF16ToJavaString(env, message)); + ScopedJavaLocalRef<jstring> jsource_id( + ConvertUTF16ToJavaString(env, source_id)); + int jlevel = WEB_CONTENTS_DELEGATE_LOG_LEVEL_DEBUG; + switch (level) { + case logging::LOG_VERBOSE: + jlevel = WEB_CONTENTS_DELEGATE_LOG_LEVEL_DEBUG; + break; + case logging::LOG_INFO: + jlevel = WEB_CONTENTS_DELEGATE_LOG_LEVEL_LOG; + break; + case logging::LOG_WARNING: + jlevel = WEB_CONTENTS_DELEGATE_LOG_LEVEL_WARNING; + break; + case logging::LOG_ERROR: + jlevel = WEB_CONTENTS_DELEGATE_LOG_LEVEL_ERROR; + break; + default: + NOTREACHED(); + } + return Java_WebContentsDelegateAndroid_addMessageToConsole( + env, + GetJavaDelegate(env).obj(), + jlevel, + jmessage.obj(), + line_no, + jsource_id.obj()); +} + +// This is either called from TabContents::DidNavigateMainFramePostCommit() with +// an empty GURL or responding to RenderViewHost::OnMsgUpateTargetURL(). In +// Chrome, the latter is not always called, especially not during history +// navigation. So we only handle the first case and pass the source TabContents' +// url to Java to update the UI. +void WebContentsDelegateAndroid::UpdateTargetURL(WebContents* source, + int32 page_id, + const GURL& url) { + if (!url.is_empty()) + return; + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + ScopedJavaLocalRef<jstring> java_url = + ConvertUTF8ToJavaString(env, source->GetURL().spec()); + Java_WebContentsDelegateAndroid_onUpdateUrl(env, + obj.obj(), + java_url.obj()); +} + +void WebContentsDelegateAndroid::HandleKeyboardEvent( + WebContents* source, + const content::NativeWebKeyboardEvent& event) { + jobject key_event = event.os_event; + if (key_event) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + Java_WebContentsDelegateAndroid_handleKeyboardEvent( + env, obj.obj(), key_event); + } +} + +bool WebContentsDelegateAndroid::TakeFocus(WebContents* source, bool reverse) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return WebContentsDelegate::TakeFocus(source, reverse); + return Java_WebContentsDelegateAndroid_takeFocus( + env, obj.obj(), reverse); +} + +void WebContentsDelegateAndroid::ShowRepostFormWarningDialog( + WebContents* source) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + ScopedJavaLocalRef<jobject> content_view_core = + content::ContentViewCore::FromWebContents(source)->GetJavaObject(); + if (content_view_core.is_null()) + return; + Java_WebContentsDelegateAndroid_showRepostFormWarningDialog(env, obj.obj(), + content_view_core.obj()); +} + +void WebContentsDelegateAndroid::ToggleFullscreenModeForTab( + WebContents* web_contents, + bool enter_fullscreen) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + Java_WebContentsDelegateAndroid_toggleFullscreenModeForTab( + env, obj.obj(), enter_fullscreen); +} + +bool WebContentsDelegateAndroid::IsFullscreenForTabOrPending( + const WebContents* web_contents) const { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return false; + return Java_WebContentsDelegateAndroid_isFullscreenForTabOrPending( + env, obj.obj()); +} + +void WebContentsDelegateAndroid::DidProgrammaticallyScroll( + WebContents* web_contents, const gfx::Vector2d& scroll_point) { + JNIEnv* env = AttachCurrentThread(); + ScopedJavaLocalRef<jobject> obj = GetJavaDelegate(env); + if (obj.is_null()) + return; + Java_WebContentsDelegateAndroid_didProgrammaticallyScroll( + env, obj.obj(), scroll_point.x(), scroll_point.y()); +} + +// ---------------------------------------------------------------------------- +// Native JNI methods +// ---------------------------------------------------------------------------- + +// Register native methods + +bool RegisterWebContentsDelegateAndroid(JNIEnv* env) { + if (!HasClass(env, kWebContentsDelegateAndroidClassPath)) { + DLOG(ERROR) << "Unable to find class WebContentsDelegateAndroid!"; + return false; + } + return RegisterNativesImpl(env); +} + +} // namespace web_contents_delegate_android diff --git a/chromium/components/web_contents_delegate_android/web_contents_delegate_android.h b/chromium/components/web_contents_delegate_android/web_contents_delegate_android.h new file mode 100644 index 00000000000..68bca15cf44 --- /dev/null +++ b/chromium/components/web_contents_delegate_android/web_contents_delegate_android.h @@ -0,0 +1,112 @@ +// 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_WEB_CONTENTS_DELEGATE_ANDROID_WEB_CONTENTS_DELEGATE_ANDROID_H_ +#define COMPONENTS_WEB_CONTENTS_DELEGATE_ANDROID_WEB_CONTENTS_DELEGATE_ANDROID_H_ + +#include "base/android/jni_helper.h" +#include "base/android/scoped_java_ref.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "content/public/browser/web_contents_delegate.h" +#include "ui/gfx/vector2d.h" + +class GURL; + +namespace content { +class WebContents; +class WebContentsDelegate; +struct NativeWebKeyboardEvent; +struct OpenURLParams; +} + +namespace web_contents_delegate_android { + +enum WebContentsDelegateLogLevel { + // Equivalent of WebCore::WebConsoleMessage::LevelDebug. + WEB_CONTENTS_DELEGATE_LOG_LEVEL_DEBUG = 0, + // Equivalent of WebCore::WebConsoleMessage::LevelLog. + WEB_CONTENTS_DELEGATE_LOG_LEVEL_LOG = 1, + // Equivalent of WebCore::WebConsoleMessage::LevelWarning. + WEB_CONTENTS_DELEGATE_LOG_LEVEL_WARNING = 2, + // Equivalent of WebCore::WebConsoleMessage::LevelError. + WEB_CONTENTS_DELEGATE_LOG_LEVEL_ERROR = 3, +}; + + +// Native underpinnings of WebContentsDelegateAndroid.java. Provides a default +// delegate for WebContents to forward calls to the java peer. The embedding +// application may subclass and override methods on either the C++ or Java side +// as required. +class WebContentsDelegateAndroid : public content::WebContentsDelegate { + public: + WebContentsDelegateAndroid(JNIEnv* env, jobject obj); + virtual ~WebContentsDelegateAndroid(); + + // Binds this WebContentsDelegateAndroid to the passed WebContents instance, + // such that when that WebContents is destroyed, this + // WebContentsDelegateAndroid instance will be destroyed too. + void SetOwnerWebContents(content::WebContents* contents); + + // Overridden from WebContentsDelegate: + virtual content::WebContents* OpenURLFromTab( + content::WebContents* source, + const content::OpenURLParams& params) OVERRIDE; + virtual content::ColorChooser* OpenColorChooser(content::WebContents* source, + SkColor color) OVERRIDE; + virtual void NavigationStateChanged(const content::WebContents* source, + unsigned changed_flags) OVERRIDE; + virtual void AddNewContents(content::WebContents* source, + content::WebContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture, + bool* was_blocked) OVERRIDE; + virtual void ActivateContents(content::WebContents* contents) OVERRIDE; + virtual void DeactivateContents(content::WebContents* contents) OVERRIDE; + virtual void LoadingStateChanged(content::WebContents* source) OVERRIDE; + virtual void LoadProgressChanged(content::WebContents* source, + double load_progress) OVERRIDE; + virtual void RendererUnresponsive(content::WebContents* source) OVERRIDE; + virtual void RendererResponsive(content::WebContents* source) OVERRIDE; + virtual void CloseContents(content::WebContents* source) OVERRIDE; + virtual void MoveContents(content::WebContents* source, + const gfx::Rect& pos) OVERRIDE; + virtual bool AddMessageToConsole(content::WebContents* source, + int32 level, + const base::string16& message, + int32 line_no, + const base::string16& source_id) OVERRIDE; + virtual void UpdateTargetURL(content::WebContents* source, + int32 page_id, + const GURL& url) OVERRIDE; + virtual void HandleKeyboardEvent( + content::WebContents* source, + const content::NativeWebKeyboardEvent& event) OVERRIDE; + virtual bool TakeFocus(content::WebContents* source, bool reverse) OVERRIDE; + virtual void ShowRepostFormWarningDialog( + content::WebContents* source) OVERRIDE; + virtual void ToggleFullscreenModeForTab(content::WebContents* web_contents, + bool enter_fullscreen) OVERRIDE; + virtual bool IsFullscreenForTabOrPending( + const content::WebContents* web_contents) const OVERRIDE; + virtual void DidProgrammaticallyScroll( + content::WebContents* web_contents, + const gfx::Vector2d& scroll_point) OVERRIDE; + + protected: + base::android::ScopedJavaLocalRef<jobject> GetJavaDelegate(JNIEnv* env) const; + + private: + // We depend on the java side user of WebContentDelegateAndroid to hold a + // strong reference to that object as long as they want to receive callbacks + // on it. Using a weak ref here allows it to be correctly GCed. + JavaObjectWeakGlobalRef weak_java_delegate_; +}; + +bool RegisterWebContentsDelegateAndroid(JNIEnv* env); + +} // namespace web_contents_delegate_android + +#endif // COMPONENTS_WEB_CONTENTS_DELEGATE_ANDROID_WEB_CONTENTS_DELEGATE_ANDROID_H_ diff --git a/chromium/components/web_modal.gypi b/chromium/components/web_modal.gypi new file mode 100644 index 00000000000..f2db7d4cb83 --- /dev/null +++ b/chromium/components/web_modal.gypi @@ -0,0 +1,33 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'web_modal', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../content/content.gyp:content_browser', + '../skia/skia.gyp:skia', + ], + 'defines': [ + 'WEB_MODAL_IMPLEMENTATION', + ], + 'sources': [ + 'web_modal/native_web_contents_modal_dialog.h', + 'web_modal/native_web_contents_modal_dialog_manager.h', + 'web_modal/web_contents_modal_dialog_host.cc', + 'web_modal/web_contents_modal_dialog_host.h', + 'web_modal/web_contents_modal_dialog_manager.cc', + 'web_modal/web_contents_modal_dialog_manager.h', + 'web_modal/web_contents_modal_dialog_manager_delegate.cc', + 'web_modal/web_contents_modal_dialog_manager_delegate.h', + ], + }, + ], +} diff --git a/chromium/components/web_modal/DEPS b/chromium/components/web_modal/DEPS new file mode 100644 index 00000000000..3539cc43913 --- /dev/null +++ b/chromium/components/web_modal/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+content/public/browser", + "+content/public/test", + "+net/base", + "+ui/gfx", +] diff --git a/chromium/components/web_modal/native_web_contents_modal_dialog.h b/chromium/components/web_modal/native_web_contents_modal_dialog.h new file mode 100644 index 00000000000..266e073ab9c --- /dev/null +++ b/chromium/components/web_modal/native_web_contents_modal_dialog.h @@ -0,0 +1,22 @@ +// 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_WEB_MODAL_NATIVE_WEB_CONTENTS_MODAL_DIALOG_H_ +#define COMPONENTS_WEB_MODAL_NATIVE_WEB_CONTENTS_MODAL_DIALOG_H_ + +#include "ui/gfx/native_widget_types.h" + +namespace web_modal { + +#if defined(OS_MACOSX) +// Use a void* since none of the gfx::Native* types are suitable for +// representing the web contents modal dialog under Cocoa. +typedef void* NativeWebContentsModalDialog; +#else +typedef gfx::NativeView NativeWebContentsModalDialog; +#endif + +} // namespace web_modal + +#endif // COMPONENTS_WEB_MODAL_NATIVE_WEB_CONTENTS_MODAL_DIALOG_H_ diff --git a/chromium/components/web_modal/native_web_contents_modal_dialog_manager.h b/chromium/components/web_modal/native_web_contents_modal_dialog_manager.h new file mode 100644 index 00000000000..7145a08c088 --- /dev/null +++ b/chromium/components/web_modal/native_web_contents_modal_dialog_manager.h @@ -0,0 +1,66 @@ +// 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_WEB_MODAL_NATIVE_WEB_CONTENTS_MODAL_DIALOG_MANAGER_H_ +#define COMPONENTS_WEB_MODAL_NATIVE_WEB_CONTENTS_MODAL_DIALOG_MANAGER_H_ + +#include "components/web_modal/native_web_contents_modal_dialog.h" + +namespace content { +class WebContents; +} // namespace content + +namespace web_modal { + +// Interface from NativeWebContentsModalDialogManager to +// WebContentsModalDialogManager. +class NativeWebContentsModalDialogManagerDelegate { + public: + NativeWebContentsModalDialogManagerDelegate() {} + virtual ~NativeWebContentsModalDialogManagerDelegate() {} + + virtual content::WebContents* GetWebContents() const = 0; + virtual void WillClose(NativeWebContentsModalDialog dialog) = 0; + + private: + DISALLOW_COPY_AND_ASSIGN(NativeWebContentsModalDialogManagerDelegate); +}; + +// Provides an interface for platform-specific UI implementation for the web +// contents modal dialog. +class NativeWebContentsModalDialogManager { + public: + virtual ~NativeWebContentsModalDialogManager() {} + + // Starts management of the modal aspects of the dialog. This function should + // also register to be notified when the dialog is closing, so that it can + // notify the manager. + virtual void ManageDialog(NativeWebContentsModalDialog dialog) = 0; + + // Makes the web contents modal dialog visible. Only one web contents modal + // dialog is shown at a time per tab. + virtual void ShowDialog(NativeWebContentsModalDialog dialog) = 0; + + // Hides the web contents modal dialog without closing it. + virtual void HideDialog(NativeWebContentsModalDialog dialog) = 0; + + // Closes the web contents modal dialog. + virtual void CloseDialog(NativeWebContentsModalDialog dialog) = 0; + + // Sets focus on the web contents modal dialog. + virtual void FocusDialog(NativeWebContentsModalDialog dialog) = 0; + + // Runs a pulse animation for the web contents modal dialog. + virtual void PulseDialog(NativeWebContentsModalDialog dialog) = 0; + + protected: + NativeWebContentsModalDialogManager() {} + + private: + DISALLOW_COPY_AND_ASSIGN(NativeWebContentsModalDialogManager); +}; + +} // namespace web_modal + +#endif // COMPONENTS_WEB_MODAL_NATIVE_WEB_CONTENTS_MODAL_DIALOG_MANAGER_H_ diff --git a/chromium/components/web_modal/web_contents_modal_dialog_host.cc b/chromium/components/web_modal/web_contents_modal_dialog_host.cc new file mode 100644 index 00000000000..1dcaa00508e --- /dev/null +++ b/chromium/components/web_modal/web_contents_modal_dialog_host.cc @@ -0,0 +1,21 @@ +// 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/web_modal/web_contents_modal_dialog_host.h" + +namespace web_modal { + +WebContentsModalDialogHostObserver::~WebContentsModalDialogHostObserver() { +} + +WebContentsModalDialogHostObserver::WebContentsModalDialogHostObserver() { +} + +WebContentsModalDialogHost::~WebContentsModalDialogHost() { +} + +WebContentsModalDialogHost::WebContentsModalDialogHost() { +} + +} // namespace web_modal diff --git a/chromium/components/web_modal/web_contents_modal_dialog_host.h b/chromium/components/web_modal/web_contents_modal_dialog_host.h new file mode 100644 index 00000000000..499925cac21 --- /dev/null +++ b/chromium/components/web_modal/web_contents_modal_dialog_host.h @@ -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. + +#ifndef COMPONENTS_WEB_MODAL_WEB_CONTENTS_MODAL_DIALOG_HOST_H_ +#define COMPONENTS_WEB_MODAL_WEB_CONTENTS_MODAL_DIALOG_HOST_H_ + +#include "ui/gfx/native_widget_types.h" +#include "ui/gfx/point.h" +#include "ui/gfx/size.h" + +namespace web_modal { + +// Observer to be implemented to update web contents modal dialogs when the host +// indicates their position needs to be changed. +class WebContentsModalDialogHostObserver { + public: + virtual ~WebContentsModalDialogHostObserver(); + + virtual void OnPositionRequiresUpdate() = 0; + + protected: + WebContentsModalDialogHostObserver(); + + private: + DISALLOW_COPY_AND_ASSIGN(WebContentsModalDialogHostObserver); +}; + +// Interface for supporting positioning of web contents modal dialogs over a +// window/widget. +class WebContentsModalDialogHost { + public: + virtual ~WebContentsModalDialogHost(); + + // Returns the view against which the dialog is positioned and parented. + virtual gfx::NativeView GetHostView() const = 0; + // Gets the position for the dialog in coordinates relative to the host + // view. + virtual gfx::Point GetDialogPosition(const gfx::Size& size) = 0; + + // Add/remove observer. + virtual void AddObserver(WebContentsModalDialogHostObserver* observer) = 0; + virtual void RemoveObserver(WebContentsModalDialogHostObserver* observer) = 0; + + protected: + WebContentsModalDialogHost(); + + private: + DISALLOW_COPY_AND_ASSIGN(WebContentsModalDialogHost); +}; + +} // namespace web_modal + +#endif // COMPONENTS_WEB_MODAL_WEB_CONTENTS_MODAL_DIALOG_HOST_H_ diff --git a/chromium/components/web_modal/web_contents_modal_dialog_manager.cc b/chromium/components/web_modal/web_contents_modal_dialog_manager.cc new file mode 100644 index 00000000000..0e07f8dd1d9 --- /dev/null +++ b/chromium/components/web_modal/web_contents_modal_dialog_manager.cc @@ -0,0 +1,149 @@ +// 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/web_modal/web_contents_modal_dialog_manager.h" + +#include "components/web_modal/web_contents_modal_dialog_manager_delegate.h" +#include "content/public/browser/navigation_details.h" +#include "content/public/browser/navigation_entry.h" +#include "content/public/browser/notification_details.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 "content/public/browser/web_contents_view.h" +#include "net/base/registry_controlled_domains/registry_controlled_domain.h" + +using content::WebContents; + +DEFINE_WEB_CONTENTS_USER_DATA_KEY(web_modal::WebContentsModalDialogManager); + +namespace web_modal { + +WebContentsModalDialogManager::~WebContentsModalDialogManager() { + DCHECK(child_dialogs_.empty()); +} + +void WebContentsModalDialogManager::ShowDialog( + NativeWebContentsModalDialog dialog) { + child_dialogs_.push_back(dialog); + + native_manager_->ManageDialog(dialog); + + if (child_dialogs_.size() == 1) { + if (delegate_ && delegate_->IsWebContentsVisible(web_contents())) + native_manager_->ShowDialog(dialog); + BlockWebContentsInteraction(true); + } +} + +bool WebContentsModalDialogManager::IsShowingDialog() const { + return !child_dialogs_.empty(); +} + +void WebContentsModalDialogManager::FocusTopmostDialog() { + DCHECK(!child_dialogs_.empty()); + native_manager_->FocusDialog(child_dialogs_.front()); +} + +content::WebContents* WebContentsModalDialogManager::GetWebContents() const { + return web_contents(); +} + +void WebContentsModalDialogManager::WillClose( + NativeWebContentsModalDialog dialog) { + WebContentsModalDialogList::iterator i( + std::find(child_dialogs_.begin(), child_dialogs_.end(), dialog)); + + // The Views tab contents modal dialog calls WillClose twice. Ignore the + // second invocation. + if (i == child_dialogs_.end()) + return; + + bool removed_topmost_dialog = i == child_dialogs_.begin(); + child_dialogs_.erase(i); + if (!child_dialogs_.empty() && removed_topmost_dialog && + !closing_all_dialogs_) + native_manager_->ShowDialog(child_dialogs_.front()); + + BlockWebContentsInteraction(!child_dialogs_.empty()); +} + +void WebContentsModalDialogManager::Observe( + int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) { + DCHECK(type == content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED); + + if (child_dialogs_.empty()) + return; + + bool visible = *content::Details<bool>(details).ptr(); + if (visible) + native_manager_->ShowDialog(child_dialogs_.front()); + else + native_manager_->HideDialog(child_dialogs_.front()); +} + +WebContentsModalDialogManager::WebContentsModalDialogManager( + content::WebContents* web_contents) + : content::WebContentsObserver(web_contents), + delegate_(NULL), + native_manager_(CreateNativeManager(this)), + closing_all_dialogs_(false) { + DCHECK(native_manager_); + registrar_.Add(this, + content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, + content::Source<content::WebContents>(web_contents)); +} + +void WebContentsModalDialogManager::BlockWebContentsInteraction(bool blocked) { + WebContents* contents = web_contents(); + if (!contents) { + // The WebContents has already disconnected. + return; + } + + // RenderViewHost may be NULL during shutdown. + content::RenderViewHost* host = contents->GetRenderViewHost(); + if (host) + host->SetIgnoreInputEvents(blocked); + if (delegate_) + delegate_->SetWebContentsBlocked(contents, blocked); +} + +void WebContentsModalDialogManager::CloseAllDialogs() { + closing_all_dialogs_ = true; + + // Clear out any dialogs since we are leaving this page entirely. + while (!child_dialogs_.empty()) + native_manager_->CloseDialog(child_dialogs_.front()); + + closing_all_dialogs_ = false; +} + +void WebContentsModalDialogManager::DidNavigateMainFrame( + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params) { + // Close constrained windows if necessary. + if (!net::registry_controlled_domains::SameDomainOrHost( + details.previous_url, details.entry->GetURL(), + net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES)) + CloseAllDialogs(); +} + +void WebContentsModalDialogManager::DidGetIgnoredUIEvent() { + if (!child_dialogs_.empty()) + native_manager_->FocusDialog(child_dialogs_.front()); +} + +void WebContentsModalDialogManager::WebContentsDestroyed(WebContents* tab) { + // First cleanly close all child dialogs. + // TODO(mpcomplete): handle case if MaybeCloseChildWindows() already asked + // some of these to close. CloseAllDialogs is async, so it might get called + // twice before it runs. + CloseAllDialogs(); +} + +} // namespace web_modal diff --git a/chromium/components/web_modal/web_contents_modal_dialog_manager.h b/chromium/components/web_modal/web_contents_modal_dialog_manager.h new file mode 100644 index 00000000000..e2548c04e5c --- /dev/null +++ b/chromium/components/web_modal/web_contents_modal_dialog_manager.h @@ -0,0 +1,116 @@ +// 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_WEB_MODAL_WEB_CONTENTS_MODAL_DIALOG_MANAGER_H_ +#define COMPONENTS_WEB_MODAL_WEB_CONTENTS_MODAL_DIALOG_MANAGER_H_ + +#include <deque> + +#include "base/memory/scoped_ptr.h" +#include "components/web_modal/native_web_contents_modal_dialog_manager.h" +#include "content/public/browser/notification_observer.h" +#include "content/public/browser/notification_registrar.h" +#include "content/public/browser/web_contents_observer.h" +#include "content/public/browser/web_contents_user_data.h" +#include "ui/gfx/native_widget_types.h" + +namespace web_modal { + +class WebContentsModalDialogManagerDelegate; + +// Per-WebContents class to manage WebContents-modal dialogs. +class WebContentsModalDialogManager + : public NativeWebContentsModalDialogManagerDelegate, + public content::WebContentsObserver, + public content::WebContentsUserData<WebContentsModalDialogManager>, + public content::NotificationObserver { + public: + virtual ~WebContentsModalDialogManager(); + + WebContentsModalDialogManagerDelegate* delegate() const { return delegate_; } + void set_delegate(WebContentsModalDialogManagerDelegate* d) { delegate_ = d; } + + static NativeWebContentsModalDialogManager* CreateNativeManager( + NativeWebContentsModalDialogManagerDelegate* native_delegate); + + // Shows the dialog as a web contents modal dialog. The dialog will notify via + // WillClose() when it is being destroyed. + void ShowDialog(NativeWebContentsModalDialog dialog); + + // Returns true if a dialog is currently being shown. + bool IsShowingDialog() const; + + // Focus the topmost modal dialog. IsShowingDialog() must be true when + // calling this function. + void FocusTopmostDialog(); + + // Overriden from NativeWebContentsModalDialogManagerDelegate: + virtual content::WebContents* GetWebContents() const OVERRIDE; + // Called when a WebContentsModalDialogs we own is about to be closed. + virtual void WillClose(NativeWebContentsModalDialog dialog) OVERRIDE; + + // content::NotificationObserver overrides + virtual void Observe(int type, + const content::NotificationSource& source, + const content::NotificationDetails& details) OVERRIDE; + + // For testing. + class TestApi { + public: + explicit TestApi(WebContentsModalDialogManager* manager) + : manager_(manager) {} + + void CloseAllDialogs() { manager_->CloseAllDialogs(); } + void ResetNativeManager(NativeWebContentsModalDialogManager* delegate) { + manager_->native_manager_.reset(delegate); + } + + private: + WebContentsModalDialogManager* manager_; + + DISALLOW_COPY_AND_ASSIGN(TestApi); + }; + + private: + explicit WebContentsModalDialogManager(content::WebContents* web_contents); + friend class content::WebContentsUserData<WebContentsModalDialogManager>; + + typedef std::deque<NativeWebContentsModalDialog> WebContentsModalDialogList; + + // Blocks/unblocks interaction with renderer process. + void BlockWebContentsInteraction(bool blocked); + + bool IsWebContentsVisible() const; + + // Closes all WebContentsModalDialogs. + void CloseAllDialogs(); + + // Overridden from content::WebContentsObserver: + virtual void DidNavigateMainFrame( + const content::LoadCommittedDetails& details, + const content::FrameNavigateParams& params) OVERRIDE; + virtual void DidGetIgnoredUIEvent() OVERRIDE; + virtual void WebContentsDestroyed(content::WebContents* tab) OVERRIDE; + + // Delegate for notifying our owner about stuff. Not owned by us. + WebContentsModalDialogManagerDelegate* delegate_; + + // Delegate for native UI-specific functions on the dialog. + scoped_ptr<NativeWebContentsModalDialogManager> native_manager_; + + // All active dialogs. + WebContentsModalDialogList child_dialogs_; + + // True while closing the dialogs on WebContents close. + bool closing_all_dialogs_; + + // A scoped container for notification registries. + content::NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(WebContentsModalDialogManager); +}; + +} // namespace web_modal + +#endif // COMPONENTS_WEB_MODAL_WEB_CONTENTS_MODAL_DIALOG_MANAGER_H_ diff --git a/chromium/components/web_modal/web_contents_modal_dialog_manager_delegate.cc b/chromium/components/web_modal/web_contents_modal_dialog_manager_delegate.cc new file mode 100644 index 00000000000..25a777cf4f5 --- /dev/null +++ b/chromium/components/web_modal/web_contents_modal_dialog_manager_delegate.cc @@ -0,0 +1,28 @@ +// 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/web_modal/web_contents_modal_dialog_manager_delegate.h" + +#include <string.h> + +namespace web_modal { + +void WebContentsModalDialogManagerDelegate::SetWebContentsBlocked( + content::WebContents* web_contents, bool blocked) { +} + +WebContentsModalDialogHost* + WebContentsModalDialogManagerDelegate::GetWebContentsModalDialogHost() { + return NULL; +} + +bool WebContentsModalDialogManagerDelegate::IsWebContentsVisible( + content::WebContents* web_contents) { + return true; +} + +WebContentsModalDialogManagerDelegate::~WebContentsModalDialogManagerDelegate( +) {} + +} // namespace web_modal diff --git a/chromium/components/web_modal/web_contents_modal_dialog_manager_delegate.h b/chromium/components/web_modal/web_contents_modal_dialog_manager_delegate.h new file mode 100644 index 00000000000..76f74354de5 --- /dev/null +++ b/chromium/components/web_modal/web_contents_modal_dialog_manager_delegate.h @@ -0,0 +1,42 @@ +// 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_WEB_MODAL_WEB_CONTENTS_MODAL_DIALOG_MANAGER_DELEGATE_H_ +#define COMPONENTS_WEB_MODAL_WEB_CONTENTS_MODAL_DIALOG_MANAGER_DELEGATE_H_ + +namespace content { +class WebContents; +} + +namespace gfx { +class Point; +} + +namespace web_modal { + +class WebContentsModalDialogHost; + +class WebContentsModalDialogManagerDelegate { + public: + // Changes the blocked state of |web_contents|. WebContentses are considered + // blocked while displaying a web contents modal dialog. During that time + // renderer host will ignore any UI interaction within WebContents outside of + // the currently displaying dialog. + virtual void SetWebContentsBlocked(content::WebContents* web_contents, + bool blocked); + + // Returns the WebContentsModalDialogHost for use in positioning web contents + // modal dialogs within the browser window. + virtual WebContentsModalDialogHost* GetWebContentsModalDialogHost(); + + // Returns whether the WebContents is currently visible or not. + virtual bool IsWebContentsVisible(content::WebContents* web_contents); + + protected: + virtual ~WebContentsModalDialogManagerDelegate(); +}; + +} // namespace web_modal + +#endif // COMPONENTS_WEB_MODAL_WEB_CONTENTS_MODAL_DIALOG_MANAGER_DELEGATE_H_ diff --git a/chromium/components/web_modal/web_contents_modal_dialog_manager_unittest.cc b/chromium/components/web_modal/web_contents_modal_dialog_manager_unittest.cc new file mode 100644 index 00000000000..1268896d6af --- /dev/null +++ b/chromium/components/web_modal/web_contents_modal_dialog_manager_unittest.cc @@ -0,0 +1,81 @@ +// 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/web_modal/native_web_contents_modal_dialog_manager.h" +#include "components/web_modal/web_contents_modal_dialog_manager.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/test/test_renderer_host.h" +#include "testing/gtest/include/gtest/gtest.h" + +using content::BrowserThread; + +namespace web_modal { + +class WebContentsModalDialogManagerTest + : public content::RenderViewHostTestHarness { + public: + virtual void SetUp() { + content::RenderViewHostTestHarness::SetUp(); + WebContentsModalDialogManager::CreateForWebContents(web_contents()); + } +}; + +class NativeWebContentsModalDialogManagerCloseTest + : public NativeWebContentsModalDialogManager { + public: + NativeWebContentsModalDialogManagerCloseTest( + NativeWebContentsModalDialogManagerDelegate* delegate) + : delegate_(delegate) {} + virtual void ManageDialog(NativeWebContentsModalDialog dialog) OVERRIDE { + } + virtual void ShowDialog(NativeWebContentsModalDialog dialog) OVERRIDE { + } + virtual void HideDialog(NativeWebContentsModalDialog dialog) OVERRIDE { + } + virtual void CloseDialog(NativeWebContentsModalDialog dialog) OVERRIDE { + delegate_->WillClose(dialog); + close_count++; + } + virtual void FocusDialog(NativeWebContentsModalDialog dialog) OVERRIDE { + } + virtual void PulseDialog(NativeWebContentsModalDialog dialog) OVERRIDE { + } + + int close_count; + NativeWebContentsModalDialogManagerDelegate* delegate_; +}; + +NativeWebContentsModalDialogManager* WebContentsModalDialogManager:: +CreateNativeManager( + NativeWebContentsModalDialogManagerDelegate* native_delegate) { + return new NativeWebContentsModalDialogManagerCloseTest(native_delegate); +} + +TEST_F(WebContentsModalDialogManagerTest, WebContentsModalDialogs) { + WebContentsModalDialogManager* web_contents_modal_dialog_manager = + WebContentsModalDialogManager::FromWebContents(web_contents()); + WebContentsModalDialogManager::TestApi test_api( + web_contents_modal_dialog_manager); + + NativeWebContentsModalDialogManagerCloseTest* native_manager = + new NativeWebContentsModalDialogManagerCloseTest( + web_contents_modal_dialog_manager); + native_manager->close_count = 0; + + test_api.ResetNativeManager(native_manager); + + const int kWindowCount = 4; + for (int i = 0; i < kWindowCount; i++) + // WebContentsModalDialogManager treats the NativeWebContentsModalDialog as + // an opaque type, so creating fake NativeWebContentsModalDialogs using + // reinterpret_cast is valid. + web_contents_modal_dialog_manager->ShowDialog( + reinterpret_cast<NativeWebContentsModalDialog>(i)); + EXPECT_EQ(native_manager->close_count, 0); + + test_api.CloseAllDialogs(); + EXPECT_EQ(native_manager->close_count, kWindowCount); +} + +} // namespace web_modal diff --git a/chromium/components/webdata.gypi b/chromium/components/webdata.gypi new file mode 100644 index 00000000000..f728c211010 --- /dev/null +++ b/chromium/components/webdata.gypi @@ -0,0 +1,75 @@ +# 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. + +{ + 'targets': [ + { + 'target_name': 'encryptor', + 'type': 'static_library', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../crypto/crypto.gyp:crypto', + ], + 'sources': [ + 'webdata/encryptor/encryptor.h', + 'webdata/encryptor/encryptor_mac.mm', + 'webdata/encryptor/encryptor_password_mac.h', + 'webdata/encryptor/encryptor_password_mac.mm', + 'webdata/encryptor/encryptor_posix.cc', + 'webdata/encryptor/encryptor_win.cc', + 'webdata/encryptor/ie7_password.cc', + 'webdata/encryptor/ie7_password.h', + ], + 'conditions': [ + ['OS!="win"', { + 'sources!': [ + 'webdata/encryptor/ie7_password.cc' + ], + }], + ['OS=="mac"', { + 'sources!': [ + 'webdata/encryptor/encryptor_posix.cc', + ], + }], + ], + }, + { + 'target_name': 'webdata_common', + 'type': '<(component)', + 'include_dirs': [ + '..', + ], + 'dependencies': [ + '../base/base.gyp:base', + '../content/content.gyp:content_browser', + '../sql/sql.gyp:sql', + ], + 'defines': [ + 'WEBDATA_IMPLEMENTATION', + ], + 'sources': [ + 'webdata/common/web_database.cc', + 'webdata/common/web_database.h', + 'webdata/common/web_database_service.cc', + 'webdata/common/web_database_service.h', + 'webdata/common/web_database_table.cc', + 'webdata/common/web_database_table.h', + 'webdata/common/web_data_request_manager.cc', + 'webdata/common/web_data_request_manager.h', + 'webdata/common/web_data_results.h', + 'webdata/common/web_data_service_backend.cc', + 'webdata/common/web_data_service_backend.h', + 'webdata/common/web_data_service_base.cc', + 'webdata/common/web_data_service_base.h', + 'webdata/common/web_data_service_consumer.h', + 'webdata/common/webdata_constants.cc', + 'webdata/common/webdata_constants.h', + 'webdata/common/webdata_export.h' + ], + }, + ], +} diff --git a/chromium/components/webdata/DEPS b/chromium/components/webdata/DEPS new file mode 100644 index 00000000000..1d575c83aaf --- /dev/null +++ b/chromium/components/webdata/DEPS @@ -0,0 +1,22 @@ +include_rules = [ + # WebData is used by iOS, which does not use content. + "-content", + "+sql", + "+ui", +] + +specific_include_rules = { + # TODO(caitkp): Extract unit tests from //chrome, at lower priority + # than production code. + r'(.*_unittest|.*_test_util)\.(cc|h)': [ + "+chrome/browser/webdata/keyword_table.h", + "+chrome/browser/webdata/logins_table.h", + "+chrome/browser/webdata/token_service_table.h", + "+chrome/browser/webdata/token_web_data.h", + "+chrome/browser/webdata/web_apps_table.h", + "+chrome/browser/webdata/web_data_service.h", + "+chrome/browser/webdata/web_data_service_factory.h", + "+chrome/browser/webdata/web_intents_table.h", + "+content/public/test", + ], +} diff --git a/chromium/components/webdata/OWNERS b/chromium/components/webdata/OWNERS new file mode 100644 index 00000000000..59f839504b3 --- /dev/null +++ b/chromium/components/webdata/OWNERS @@ -0,0 +1,5 @@ +joi@chromium.org +pkasting@chromium.org + +# For sqlite stuff: +shess@chromium.org diff --git a/chromium/components/webdata/README b/chromium/components/webdata/README new file mode 100644 index 00000000000..a82334ca409 --- /dev/null +++ b/chromium/components/webdata/README @@ -0,0 +1,5 @@ +WebData is not allowed to depend on content/, because it is used by iOS. +If dependences on content/ need to be added to WebData, it will have to be made +into a layered component: see +http://www.chromium.org/developers/design-documents/layered-components-design +for more information. diff --git a/chromium/components/webdata/common/web_data_request_manager.cc b/chromium/components/webdata/common/web_data_request_manager.cc new file mode 100644 index 00000000000..9aba721da25 --- /dev/null +++ b/chromium/components/webdata/common/web_data_request_manager.cc @@ -0,0 +1,145 @@ +// Copyright 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/webdata/common/web_data_request_manager.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// WebDataRequest implementation. +// +//////////////////////////////////////////////////////////////////////////////// + +WebDataRequest::WebDataRequest(WebDataServiceConsumer* consumer, + WebDataRequestManager* manager) + : manager_(manager), cancelled_(false), consumer_(consumer) { + handle_ = manager_->GetNextRequestHandle(); + message_loop_ = base::MessageLoop::current(); + manager_->RegisterRequest(this); +} + +WebDataRequest::~WebDataRequest() { + if (manager_) { + manager_->CancelRequest(handle_); + } + if (result_.get()) { + result_->Destroy(); + } +} + +WebDataServiceBase::Handle WebDataRequest::GetHandle() const { + return handle_; +} + +WebDataServiceConsumer* WebDataRequest::GetConsumer() const { + return consumer_; +} + +base::MessageLoop* WebDataRequest::GetMessageLoop() const { + return message_loop_; +} + +bool WebDataRequest::IsCancelled() const { + base::AutoLock l(cancel_lock_); + return cancelled_; +} + +void WebDataRequest::Cancel() { + base::AutoLock l(cancel_lock_); + cancelled_ = true; + consumer_ = NULL; + manager_ = NULL; +} + +void WebDataRequest::OnComplete() { + manager_= NULL; +} + +void WebDataRequest::SetResult(scoped_ptr<WDTypedResult> r) { + result_ = r.Pass(); +} + +scoped_ptr<WDTypedResult> WebDataRequest::GetResult(){ + return result_.Pass(); +} + +//////////////////////////////////////////////////////////////////////////////// +// +// WebDataRequestManager implementation. +// +//////////////////////////////////////////////////////////////////////////////// + +WebDataRequestManager::WebDataRequestManager() + : next_request_handle_(1) { +} + +WebDataRequestManager::~WebDataRequestManager() { + base::AutoLock l(pending_lock_); + for (RequestMap::iterator i = pending_requests_.begin(); + i != pending_requests_.end(); ++i) { + i->second->Cancel(); + } + pending_requests_.clear(); +} + +void WebDataRequestManager::RegisterRequest(WebDataRequest* request) { + base::AutoLock l(pending_lock_); + pending_requests_[request->GetHandle()] = request; +} + +int WebDataRequestManager::GetNextRequestHandle() { + base::AutoLock l(pending_lock_); + return ++next_request_handle_; +} + +void WebDataRequestManager::CancelRequest(WebDataServiceBase::Handle h) { + base::AutoLock l(pending_lock_); + RequestMap::iterator i = pending_requests_.find(h); + if (i == pending_requests_.end()) { + NOTREACHED() << "Canceling a nonexistent web data service request"; + return; + } + i->second->Cancel(); + pending_requests_.erase(i); +} + +void WebDataRequestManager::RequestCompleted( + scoped_ptr<WebDataRequest> request) { + base::MessageLoop* loop = request->GetMessageLoop(); + loop->PostTask(FROM_HERE, + base::Bind(&WebDataRequestManager::RequestCompletedOnThread, + this, + base::Passed(&request))); +} + +void WebDataRequestManager::RequestCompletedOnThread( + scoped_ptr<WebDataRequest> request) { + if (request->IsCancelled()) + return; + { + base::AutoLock l(pending_lock_); + RequestMap::iterator i = pending_requests_.find(request->GetHandle()); + if (i == pending_requests_.end()) { + NOTREACHED() << "Request completed called for an unknown request"; + return; + } + + // Take ownership of the request object and remove it from the map. + pending_requests_.erase(i); + } + + // Notify the consumer if needed. + if (!request->IsCancelled()) { + WebDataServiceConsumer* consumer = request->GetConsumer(); + request->OnComplete(); + if (consumer) { + scoped_ptr<WDTypedResult> r = request->GetResult(); + consumer->OnWebDataServiceRequestDone(request->GetHandle(), r.get()); + } + } + +} diff --git a/chromium/components/webdata/common/web_data_request_manager.h b/chromium/components/webdata/common/web_data_request_manager.h new file mode 100644 index 00000000000..90a5dd116d3 --- /dev/null +++ b/chromium/components/webdata/common/web_data_request_manager.h @@ -0,0 +1,138 @@ +// Copyright 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. + +// Chromium settings and storage represent user-selected preferences and +// information and MUST not be extracted, overwritten or modified except +// through Chromium defined APIs. + +#ifndef COMPONENTS_WEBDATA_COMMON_WEB_DATA_REQUEST_MANAGER_H__ +#define COMPONENTS_WEBDATA_COMMON_WEB_DATA_REQUEST_MANAGER_H__ + +#include <map> + +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" +#include "components/webdata/common/web_database_service.h" +#include "components/webdata/common/web_data_results.h" +#include "components/webdata/common/web_data_service_base.h" +#include "components/webdata/common/web_data_service_consumer.h" + +class WebDataService; +class WebDataServiceConsumer; +class WebDataRequestManager; + +namespace base { +class MessageLoop; +} + +////////////////////////////////////////////////////////////////////////////// +// +// Webdata requests +// +// Every request is processed using a request object. The object contains +// both the request parameters and the results. +////////////////////////////////////////////////////////////////////////////// +class WebDataRequest { + public: + WebDataRequest(WebDataServiceConsumer* consumer, + WebDataRequestManager* manager); + + virtual ~WebDataRequest(); + + WebDataServiceBase::Handle GetHandle() const; + + // Retrieves the |consumer_| set in the constructor. + WebDataServiceConsumer* GetConsumer() const; + + // Retrieves the original message loop the of the request. + base::MessageLoop* GetMessageLoop() const; + + // Returns |true| if the request was cancelled via the |Cancel()| method. + bool IsCancelled() const; + + // This can be invoked from any thread. From this point we assume that + // our consumer_ reference is invalid. + void Cancel(); + + // Invoked when the request has been completed. + void OnComplete(); + + // The result is owned by the request. + void SetResult(scoped_ptr<WDTypedResult> r); + + // Transfers ownership pof result to caller. Should only be called once per + // result. + scoped_ptr<WDTypedResult> GetResult(); + + private: + // Used to notify manager if request is cancelled. Uses a raw ptr instead of + // a ref_ptr so that it can be set to NULL when a request is cancelled. + WebDataRequestManager* manager_; + + // Tracks loop that the request originated on. + base::MessageLoop* message_loop_; + + // Identifier for this request. + WebDataServiceBase::Handle handle_; + + // A lock to protect against simultaneous cancellations of the request. + // Cancellation affects both the |cancelled_| flag and |consumer_|. + mutable base::Lock cancel_lock_; + bool cancelled_; + + // The originator of the service request. + WebDataServiceConsumer* consumer_; + + scoped_ptr<WDTypedResult> result_; + + DISALLOW_COPY_AND_ASSIGN(WebDataRequest); +}; + +////////////////////////////////////////////////////////////////////////////// +// +// Webdata Request Manager +// +// Tracks all WebDataRequests for a WebDataService. +// +// Note: This is an internal interface, not to be used outside of webdata/ +////////////////////////////////////////////////////////////////////////////// +class WebDataRequestManager + : public base::RefCountedThreadSafe<WebDataRequestManager> { + public: + WebDataRequestManager(); + + // Cancel any pending request. + void CancelRequest(WebDataServiceBase::Handle h); + + // Invoked by the WebDataService when |request| has been completed. + void RequestCompleted(scoped_ptr<WebDataRequest> request); + + // Register the request as a pending request. + void RegisterRequest(WebDataRequest* request); + + // Return the next request handle. + int GetNextRequestHandle(); + + private: + friend class base::RefCountedThreadSafe<WebDataRequestManager>; + + ~WebDataRequestManager(); + + // This will notify the consumer in whatever thread was used to create this + // request. + void RequestCompletedOnThread(scoped_ptr<WebDataRequest> request); + + // A lock to protect pending requests and next request handle. + base::Lock pending_lock_; + + // Next handle to be used for requests. Incremented for each use. + WebDataServiceBase::Handle next_request_handle_; + + typedef std::map<WebDataServiceBase::Handle, WebDataRequest*> RequestMap; + RequestMap pending_requests_; + + DISALLOW_COPY_AND_ASSIGN(WebDataRequestManager); +}; + +#endif // COMPONENTS_WEBDATA_COMMON_WEB_DATA_REQUEST_MANAGER_H__ diff --git a/chromium/components/webdata/common/web_data_results.h b/chromium/components/webdata/common/web_data_results.h new file mode 100644 index 00000000000..7a225e9c6ee --- /dev/null +++ b/chromium/components/webdata/common/web_data_results.h @@ -0,0 +1,135 @@ +// 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_WEBDATA_COMMON_WEB_DATA_RESULTS_H_ +#define COMPONENTS_WEBDATA_COMMON_WEB_DATA_RESULTS_H_ + +#include "base/basictypes.h" +#include "base/callback.h" +#include "components/webdata/common/webdata_export.h" + +class WDTypedResult; + +// +// Result types for WebDataService. +// +typedef enum { + BOOL_RESULT = 1, // WDResult<bool> + KEYWORDS_RESULT, // WDResult<WDKeywordsResult> + INT64_RESULT, // WDResult<int64> +#if defined(OS_WIN) + PASSWORD_IE7_RESULT, // WDResult<IE7PasswordInfo> +#endif + WEB_APP_IMAGES, // WDResult<WDAppImagesResult> + TOKEN_RESULT, // WDResult<std::vector<std::string>> + AUTOFILL_VALUE_RESULT, // WDResult<std::vector<string16>> + AUTOFILL_CHANGES, // WDResult<std::vector<AutofillChange>> + AUTOFILL_PROFILE_RESULT, // WDResult<AutofillProfile> + AUTOFILL_PROFILES_RESULT, // WDResult<std::vector<AutofillProfile*>> + AUTOFILL_CREDITCARD_RESULT, // WDResult<CreditCard> + AUTOFILL_CREDITCARDS_RESULT, // WDResult<std::vector<CreditCard*>> + WEB_INTENTS_RESULT, // WDResult<std::vector<WebIntentServiceData>> + WEB_INTENTS_DEFAULTS_RESULT, // WDResult<std::vector<DefaultWebIntentService>> +} WDResultType; + + +typedef base::Callback<void(const WDTypedResult*)> DestroyCallback; + +// +// The top level class for a result. +// +class WEBDATA_EXPORT WDTypedResult { + public: + virtual ~WDTypedResult() { + } + + // Return the result type. + WDResultType GetType() const { + return type_; + } + + virtual void Destroy() { + } + + protected: + explicit WDTypedResult(WDResultType type) + : type_(type) { + } + + private: + WDResultType type_; + DISALLOW_COPY_AND_ASSIGN(WDTypedResult); +}; + +// A result containing one specific pointer or literal value. +template <class T> class WDResult : public WDTypedResult { + public: + WDResult(WDResultType type, const T& v) + : WDTypedResult(type), value_(v) { + } + + virtual ~WDResult() { + } + + // Return a single value result. + T GetValue() const { + return value_; + } + + private: + T value_; + + DISALLOW_COPY_AND_ASSIGN(WDResult); +}; + +template <class T> class WDDestroyableResult : public WDTypedResult { + public: + WDDestroyableResult( + WDResultType type, + const T& v, + const DestroyCallback& callback) + : WDTypedResult(type), + value_(v), + callback_(callback) { + } + + virtual ~WDDestroyableResult() { + } + + + virtual void Destroy() OVERRIDE { + if (!callback_.is_null()) { + callback_.Run(this); + } + } + + // Return a single value result. + T GetValue() const { + return value_; + } + + private: + T value_; + DestroyCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(WDDestroyableResult); +}; + +template <class T> class WDObjectResult : public WDTypedResult { + public: + explicit WDObjectResult(WDResultType type) + : WDTypedResult(type) { + } + + T* GetValue() const { + return &value_; + } + + private: + // mutable to keep GetValue() const. + mutable T value_; + DISALLOW_COPY_AND_ASSIGN(WDObjectResult); +}; + +#endif // COMPONENTS_WEBDATA_COMMON_WEB_DATA_RESULTS_H_ diff --git a/chromium/components/webdata/common/web_data_service_backend.cc b/chromium/components/webdata/common/web_data_service_backend.cc new file mode 100644 index 00000000000..9f48e52be69 --- /dev/null +++ b/chromium/components/webdata/common/web_data_service_backend.cc @@ -0,0 +1,124 @@ +// 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/webdata/common/web_data_service_backend.h" + +#include "base/bind.h" +#include "base/location.h" +#include "components/webdata/common/web_data_request_manager.h" +#include "components/webdata/common/web_database.h" +#include "components/webdata/common/web_database_table.h" + +using base::Bind; +using base::FilePath; + +WebDataServiceBackend::WebDataServiceBackend( + const FilePath& path, + Delegate* delegate, + const scoped_refptr<base::MessageLoopProxy>& db_thread) + : base::RefCountedDeleteOnMessageLoop<WebDataServiceBackend>(db_thread), + db_path_(path), + request_manager_(new WebDataRequestManager()), + init_status_(sql::INIT_FAILURE), + init_complete_(false), + delegate_(delegate) { +} + +void WebDataServiceBackend::AddTable(scoped_ptr<WebDatabaseTable> table) { + DCHECK(!db_.get()); + tables_.push_back(table.release()); +} + +void WebDataServiceBackend::InitDatabase() { + LoadDatabaseIfNecessary(); + if (delegate_) { + delegate_->DBLoaded(init_status_); + } +} + +sql::InitStatus WebDataServiceBackend::LoadDatabaseIfNecessary() { + if (init_complete_ || db_path_.empty()) { + return init_status_; + } + init_complete_ = true; + db_.reset(new WebDatabase()); + + for (ScopedVector<WebDatabaseTable>::iterator it = tables_.begin(); + it != tables_.end(); + ++it) { + db_->AddTable(*it); + } + + init_status_ = db_->Init(db_path_); + if (init_status_ != sql::INIT_OK) { + LOG(ERROR) << "Cannot initialize the web database: " << init_status_; + db_.reset(NULL); + return init_status_; + } + + db_->BeginTransaction(); + return init_status_; +} + +void WebDataServiceBackend::ShutdownDatabase(bool should_reinit) { + if (db_ && init_status_ == sql::INIT_OK) + db_->CommitTransaction(); + db_.reset(NULL); + init_complete_ = !should_reinit; // Setting init_complete_ to true will ensure + // that the init sequence is not re-run. + + init_status_ = sql::INIT_FAILURE; +} + +void WebDataServiceBackend::DBWriteTaskWrapper( + const WebDatabaseService::WriteTask& task, + scoped_ptr<WebDataRequest> request) { + if (request->IsCancelled()) + return; + + ExecuteWriteTask(task); + request_manager_->RequestCompleted(request.Pass()); +} + +void WebDataServiceBackend::ExecuteWriteTask( + const WebDatabaseService::WriteTask& task) { + LoadDatabaseIfNecessary(); + if (db_ && init_status_ == sql::INIT_OK) { + WebDatabase::State state = task.Run(db_.get()); + if (state == WebDatabase::COMMIT_NEEDED) + Commit(); + } +} + +void WebDataServiceBackend::DBReadTaskWrapper( + const WebDatabaseService::ReadTask& task, + scoped_ptr<WebDataRequest> request) { + if (request->IsCancelled()) + return; + + request->SetResult(ExecuteReadTask(task).Pass()); + request_manager_->RequestCompleted(request.Pass()); +} + +scoped_ptr<WDTypedResult> WebDataServiceBackend::ExecuteReadTask( + const WebDatabaseService::ReadTask& task) { + LoadDatabaseIfNecessary(); + if (db_ && init_status_ == sql::INIT_OK) { + return task.Run(db_.get()); + } + return scoped_ptr<WDTypedResult>(); +} + +WebDataServiceBackend::~WebDataServiceBackend() { + ShutdownDatabase(false); +} + +void WebDataServiceBackend::Commit() { + if (db_ && init_status_ == sql::INIT_OK) { + db_->CommitTransaction(); + db_->BeginTransaction(); + } else { + NOTREACHED() << "Commit scheduled after Shutdown()"; + } +} diff --git a/chromium/components/webdata/common/web_data_service_backend.h b/chromium/components/webdata/common/web_data_service_backend.h new file mode 100644 index 00000000000..3a6fe6fc91f --- /dev/null +++ b/chromium/components/webdata/common/web_data_service_backend.h @@ -0,0 +1,130 @@ +// 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_WEBDATA_COMMON_WEB_DATA_SERVICE_BACKEND_H_ +#define COMPONENTS_WEBDATA_COMMON_WEB_DATA_SERVICE_BACKEND_H_ + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/ref_counted_delete_on_message_loop.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_vector.h" +#include "components/webdata/common/web_database_service.h" +#include "components/webdata/common/webdata_export.h" + +class WebDatabase; +class WebDatabaseTable; +class WebDataRequest; +class WebDataRequestManager; + +namespace tracked_objects { +class Location; +} + +// WebDataServiceBackend handles all database tasks posted by +// WebDatabaseService. It is refcounted to allow asynchronous destruction on the +// DB thread. + +// TODO(caitkp): Rename this class to WebDatabaseBackend. + +class WEBDATA_EXPORT WebDataServiceBackend + : public base::RefCountedDeleteOnMessageLoop<WebDataServiceBackend> { + public: + class Delegate { + public: + virtual ~Delegate() {} + + // Invoked when the backend has finished loading the db. + virtual void DBLoaded(sql::InitStatus status) = 0; + }; + + WebDataServiceBackend(const base::FilePath& path, + Delegate* delegate, + const scoped_refptr<base::MessageLoopProxy>& db_thread); + + // Must call only before InitDatabaseWithCallback. + void AddTable(scoped_ptr<WebDatabaseTable> table); + + // Initializes the database and notifies caller via callback when complete. + // Callback is called synchronously. + void InitDatabase(); + + // Opens the database file from the profile path if an init has not yet been + // attempted. Separated from the constructor to ease construction/destruction + // of this object on one thread but database access on the DB thread. Returns + // the status of the database. + sql::InitStatus LoadDatabaseIfNecessary(); + + // Shuts down database. |should_reinit| tells us whether or not it should be + // possible to re-initialize the DB after the shutdown. + void ShutdownDatabase(bool should_reinit); + + // Task wrappers to update requests and and notify |request_manager_|. These + // are used in cases where the request is being made from the UI thread and an + // asyncronous callback is required to notify the client of |request|'s + // completion. + void DBWriteTaskWrapper( + const WebDatabaseService::WriteTask& task, + scoped_ptr<WebDataRequest> request); + void DBReadTaskWrapper( + const WebDatabaseService::ReadTask& task, + scoped_ptr<WebDataRequest> request); + + // Task runners to run database tasks. + void ExecuteWriteTask(const WebDatabaseService::WriteTask& task); + scoped_ptr<WDTypedResult> ExecuteReadTask( + const WebDatabaseService::ReadTask& task); + + const scoped_refptr<WebDataRequestManager>& request_manager() { + return request_manager_; + } + + WebDatabase* database() { return db_.get(); } + + protected: + friend class base::RefCountedDeleteOnMessageLoop<WebDataServiceBackend>; + friend class base::DeleteHelper<WebDataServiceBackend>; + + virtual ~WebDataServiceBackend(); + + private: + // Commit the current transaction. + void Commit(); + + // Path to database file. + base::FilePath db_path_; + + // The tables that participate in managing the database. These are + // owned here but other than that this class does nothing with + // them. Their initialization is in whatever factory creates + // WebDatabaseService, and lookup by type is provided by the + // WebDatabase class. The tables need to be owned by this refcounted + // object, or they themselves would need to be refcounted. Owning + // them here rather than having WebDatabase own them makes for + // easier unit testing of WebDatabase. + ScopedVector<WebDatabaseTable> tables_; + + scoped_ptr<WebDatabase> db_; + + // Keeps track of all pending requests made to the db. + scoped_refptr<WebDataRequestManager> request_manager_; + + // State of database initialization. Used to prevent the executing of tasks + // before the db is ready. + sql::InitStatus init_status_; + + // True if an attempt has been made to load the database (even if the attempt + // fails), used to avoid continually trying to reinit if the db init fails. + bool init_complete_; + + // Delegate. See the class definition above for more information. + scoped_ptr<Delegate> delegate_; + + DISALLOW_COPY_AND_ASSIGN(WebDataServiceBackend); +}; + +#endif // COMPONENTS_WEBDATA_COMMON_WEB_DATA_SERVICE_BACKEND_H_ diff --git a/chromium/components/webdata/common/web_data_service_base.cc b/chromium/components/webdata/common/web_data_service_base.cc new file mode 100644 index 00000000000..6427e5d4869 --- /dev/null +++ b/chromium/components/webdata/common/web_data_service_base.cc @@ -0,0 +1,78 @@ +// 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/webdata/common/web_data_service_base.h" + +#include "base/bind.h" +#include "base/message_loop/message_loop.h" +#include "base/stl_util.h" +#include "base/threading/thread.h" +#include "components/webdata/common/web_database_service.h" + +//////////////////////////////////////////////////////////////////////////////// +// +// WebDataServiceBase implementation. +// +//////////////////////////////////////////////////////////////////////////////// + +using base::Bind; +using base::Time; + +WebDataServiceBase::WebDataServiceBase( + scoped_refptr<WebDatabaseService> wdbs, + const ProfileErrorCallback& callback, + const scoped_refptr<base::MessageLoopProxy>& ui_thread) + : base::RefCountedDeleteOnMessageLoop<WebDataServiceBase>(ui_thread), + wdbs_(wdbs), + profile_error_callback_(callback) { +} + +void WebDataServiceBase::ShutdownOnUIThread() { +} + +void WebDataServiceBase::Init() { + DCHECK(wdbs_.get()); + wdbs_->RegisterDBErrorCallback(profile_error_callback_); + wdbs_->LoadDatabase(); +} + +void WebDataServiceBase::UnloadDatabase() { + if (!wdbs_.get()) + return; + wdbs_->UnloadDatabase(); +} + +void WebDataServiceBase::ShutdownDatabase() { + if (!wdbs_.get()) + return; + wdbs_->ShutdownDatabase(); +} + +void WebDataServiceBase::CancelRequest(Handle h) { + if (!wdbs_.get()) + return; + wdbs_->CancelRequest(h); +} + +bool WebDataServiceBase::IsDatabaseLoaded() { + if (!wdbs_.get()) + return false; + return wdbs_->db_loaded(); +} + +void WebDataServiceBase::RegisterDBLoadedCallback( + const DBLoadedCallback& callback) { + if (!wdbs_.get()) + return; + wdbs_->RegisterDBLoadedCallback(callback); +} + +WebDatabase* WebDataServiceBase::GetDatabase() { + if (!wdbs_.get()) + return NULL; + return wdbs_->GetDatabaseOnDB(); +} + +WebDataServiceBase::~WebDataServiceBase() { +} diff --git a/chromium/components/webdata/common/web_data_service_base.h b/chromium/components/webdata/common/web_data_service_base.h new file mode 100644 index 00000000000..3a396229415 --- /dev/null +++ b/chromium/components/webdata/common/web_data_service_base.h @@ -0,0 +1,103 @@ +// 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_WEBDATA_COMMON_WEB_DATA_SERVICE_BASE_H_ +#define COMPONENTS_WEBDATA_COMMON_WEB_DATA_SERVICE_BASE_H_ + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/ref_counted_delete_on_message_loop.h" +#include "base/memory/scoped_ptr.h" +#include "components/webdata/common/webdata_export.h" +#include "sql/init_status.h" + +class WebDatabase; +class WebDatabaseService; +class WebDatabaseTable; + +namespace base { +class Thread; +} + +// Base for WebDataService class hierarchy. +// WebDataServiceBase is destroyed on the UI thread. +class WEBDATA_EXPORT WebDataServiceBase + : public base::RefCountedDeleteOnMessageLoop<WebDataServiceBase> { + public: + // All requests return an opaque handle of the following type. + typedef int Handle; + + // Users of this class may provide a callback to handle errors + // (e.g. by showing a UI). The callback is called only on error, and + // takes a single parameter, the sql::InitStatus value from trying + // to open the database. + // TODO(joi): Should we combine this with WebDatabaseService::InitCallback? + typedef base::Callback<void(sql::InitStatus)> ProfileErrorCallback; + + typedef base::Closure DBLoadedCallback; + + // |callback| will only be invoked on error, and only if + // |callback.is_null()| evaluates to false. + // + // The ownership of |wdbs| is shared, with the primary owner being the + // WebDataServiceWrapper, and secondary owners being subclasses of + // WebDataServiceBase, which receive |wdbs| upon construction. The + // WebDataServiceWrapper handles the initializing and shutting down and of + // the |wdbs| object. + // WebDataServiceBase is destroyed on |ui_thread|. + WebDataServiceBase(scoped_refptr<WebDatabaseService> wdbs, + const ProfileErrorCallback& callback, + const scoped_refptr<base::MessageLoopProxy>& ui_thread); + + // Cancel any pending request. You need to call this method if your + // WebDataServiceConsumer is about to be deleted. + virtual void CancelRequest(Handle h); + + // Shutdown the web data service. The service can no longer be used after this + // call. + virtual void ShutdownOnUIThread(); + + // Initializes the web data service. + virtual void Init(); + + // Unloads the database without actually shutting down the service. This can + // be used to temporarily reduce the browser process' memory footprint. + void UnloadDatabase(); + + // Unloads the database permanently and shuts down service. + void ShutdownDatabase(); + + // Register a callback to be notified that the database has loaded. Multiple + // callbacks may be registered, and each will be called at most once + // (following a successful database load), then cleared. + // Note: if the database load is already complete, then the callback will NOT + // be stored or called. + virtual void RegisterDBLoadedCallback(const DBLoadedCallback& callback); + + // Returns true if the database load has completetd successfully, and + // ShutdownOnUIThread has not yet been called. + virtual bool IsDatabaseLoaded(); + + // Returns a pointer to the DB (used by SyncableServices). May return NULL if + // the database is not loaded or otherwise unavailable. Must be called on + // DBThread. + virtual WebDatabase* GetDatabase(); + + protected: + friend class base::RefCountedDeleteOnMessageLoop<WebDataServiceBase>; + friend class base::DeleteHelper<WebDataServiceBase>; + + virtual ~WebDataServiceBase(); + + // Our database service. + scoped_refptr<WebDatabaseService> wdbs_; + + private: + ProfileErrorCallback profile_error_callback_; + + DISALLOW_COPY_AND_ASSIGN(WebDataServiceBase); +}; + +#endif // COMPONENTS_WEBDATA_COMMON_WEB_DATA_SERVICE_BASE_H_ diff --git a/chromium/components/webdata/common/web_data_service_consumer.h b/chromium/components/webdata/common/web_data_service_consumer.h new file mode 100644 index 00000000000..2341a8f4935 --- /dev/null +++ b/chromium/components/webdata/common/web_data_service_consumer.h @@ -0,0 +1,26 @@ +// 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_WEBDATA_COMMON_WEB_DATA_SERVICE_CONSUMER_H_ +#define COMPONENTS_WEBDATA_COMMON_WEB_DATA_SERVICE_CONSUMER_H_ + +#include "components/webdata/common/web_data_results.h" +#include "components/webdata/common/web_data_service_base.h" + +// All requests to the web data service are asynchronous. When the request has +// been performed, the data consumer is notified using the following interface. +class WebDataServiceConsumer { + public: + // Called when a request is done. h uniquely identifies the request. + // result can be NULL, if no result is expected or if the database could + // not be opened. The result object is destroyed after this call. + virtual void OnWebDataServiceRequestDone(WebDataServiceBase::Handle h, + const WDTypedResult* result) = 0; + + protected: + virtual ~WebDataServiceConsumer() {} +}; + + +#endif // COMPONENTS_WEBDATA_COMMON_WEB_DATA_SERVICE_CONSUMER_H_ diff --git a/chromium/components/webdata/common/web_data_service_test_util.cc b/chromium/components/webdata/common/web_data_service_test_util.cc new file mode 100644 index 00000000000..b1f3429f7ad --- /dev/null +++ b/chromium/components/webdata/common/web_data_service_test_util.cc @@ -0,0 +1,46 @@ +// 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/webdata/common/web_data_service_test_util.h" + +#include "components/autofill/core/browser/webdata/autofill_webdata_service.h" + +using autofill::AutofillWebDataService; + +MockWebDataServiceWrapperBase::MockWebDataServiceWrapperBase() { +} + +MockWebDataServiceWrapperBase::~MockWebDataServiceWrapperBase() { +} + +void MockWebDataServiceWrapperBase::Shutdown() { +} + +// TODO(caitkp): This won't scale well. As we get more WebData subclasses, we +// will probably need a better way to create these mocks rather than passing +// all the webdatas in. +MockWebDataServiceWrapper::MockWebDataServiceWrapper( + scoped_refptr<WebDataService> fake_service, + scoped_refptr<AutofillWebDataService> fake_autofill, + scoped_refptr<TokenWebData> fake_token) + : fake_autofill_web_data_(fake_autofill), + fake_token_web_data_(fake_token), + fake_web_data_(fake_service) { +} + +MockWebDataServiceWrapper::~MockWebDataServiceWrapper() { +} + +scoped_refptr<AutofillWebDataService> + MockWebDataServiceWrapper::GetAutofillWebData() { + return fake_autofill_web_data_; +} + +scoped_refptr<TokenWebData> MockWebDataServiceWrapper::GetTokenWebData() { + return fake_token_web_data_; +} + +scoped_refptr<WebDataService> MockWebDataServiceWrapper::GetWebData() { + return fake_web_data_; +} diff --git a/chromium/components/webdata/common/web_data_service_test_util.h b/chromium/components/webdata/common/web_data_service_test_util.h new file mode 100644 index 00000000000..ce37506b478 --- /dev/null +++ b/chromium/components/webdata/common/web_data_service_test_util.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_WEBDATA_COMMON_WEB_DATA_SERVICE_TEST_UTIL_H__ +#define COMPONENTS_WEBDATA_COMMON_WEB_DATA_SERVICE_TEST_UTIL_H__ + +#include "base/basictypes.h" +#include "base/message_loop/message_loop.h" +#include "chrome/browser/webdata/token_web_data.h" +#include "chrome/browser/webdata/web_data_service.h" +#include "chrome/browser/webdata/web_data_service_factory.h" + +// Base class for mocks of WebDataService, that does nothing in +// Shutdown(). +class MockWebDataServiceWrapperBase : public WebDataServiceWrapper { + public: + MockWebDataServiceWrapperBase(); + virtual ~MockWebDataServiceWrapperBase(); + + virtual void Shutdown() OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(MockWebDataServiceWrapperBase); +}; + +// Pass your fake WebDataService in the constructor and this will +// serve it up via GetWebData(). +class MockWebDataServiceWrapper : public MockWebDataServiceWrapperBase { + public: + MockWebDataServiceWrapper( + scoped_refptr<WebDataService> fake_service, + scoped_refptr<autofill::AutofillWebDataService> fake_autofill, + scoped_refptr<TokenWebData> fake_token); + + virtual ~MockWebDataServiceWrapper(); + + virtual scoped_refptr<autofill::AutofillWebDataService> + GetAutofillWebData() OVERRIDE; + + virtual scoped_refptr<TokenWebData> GetTokenWebData() OVERRIDE; + + virtual scoped_refptr<WebDataService> GetWebData() OVERRIDE; + + protected: + scoped_refptr<autofill::AutofillWebDataService> fake_autofill_web_data_; + scoped_refptr<TokenWebData> fake_token_web_data_; + scoped_refptr<WebDataService> fake_web_data_; + + private: + DISALLOW_COPY_AND_ASSIGN(MockWebDataServiceWrapper); +}; + +#endif // COMPONENTS_WEBDATA_COMMON_WEB_DATA_SERVICE_TEST_UTIL_H__ diff --git a/chromium/components/webdata/common/web_database.cc b/chromium/components/webdata/common/web_database.cc new file mode 100644 index 00000000000..e4e44ef1e03 --- /dev/null +++ b/chromium/components/webdata/common/web_database.cc @@ -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. + +#include "components/webdata/common/web_database.h" + +#include <algorithm> + +#include "base/stl_util.h" +#include "sql/statement.h" +#include "sql/transaction.h" + +// Current version number. Note: when changing the current version number, +// corresponding changes must happen in the unit tests, and new migration test +// added. See |WebDatabaseMigrationTest::kCurrentTestedVersionNumber|. +// static +const int WebDatabase::kCurrentVersionNumber = 52; + +namespace { + +const int kCompatibleVersionNumber = 48; + +// Change the version number and possibly the compatibility version of +// |meta_table_|. +void ChangeVersion(sql::MetaTable* meta_table, + int version_num, + bool update_compatible_version_num) { + meta_table->SetVersionNumber(version_num); + if (update_compatible_version_num) { + meta_table->SetCompatibleVersionNumber( + std::min(version_num, kCompatibleVersionNumber)); + } +} + +// Outputs the failed version number as a warning and always returns +// |sql::INIT_FAILURE|. +sql::InitStatus FailedMigrationTo(int version_num) { + LOG(WARNING) << "Unable to update web database to version " + << version_num << "."; + NOTREACHED(); + return sql::INIT_FAILURE; +} + +} // namespace + +WebDatabase::WebDatabase() {} + +WebDatabase::~WebDatabase() { +} + +void WebDatabase::AddTable(WebDatabaseTable* table) { + tables_[table->GetTypeKey()] = table; +} + +WebDatabaseTable* WebDatabase::GetTable(WebDatabaseTable::TypeKey key) { + return tables_[key]; +} + +void WebDatabase::BeginTransaction() { + db_.BeginTransaction(); +} + +void WebDatabase::CommitTransaction() { + db_.CommitTransaction(); +} + +sql::Connection* WebDatabase::GetSQLConnection() { + return &db_; +} + +sql::InitStatus WebDatabase::Init(const base::FilePath& db_name) { + db_.set_histogram_tag("Web"); + + // We don't store that much data in the tables so use a small page size. + // This provides a large benefit for empty tables (which is very likely with + // the tables we create). + db_.set_page_size(2048); + + // We shouldn't have much data and what access we currently have is quite + // infrequent. So we go with a small cache size. + db_.set_cache_size(32); + + // Run the database in exclusive mode. Nobody else should be accessing the + // database while we're running, and this will give somewhat improved perf. + db_.set_exclusive_locking(); + + if (!db_.Open(db_name)) + return sql::INIT_FAILURE; + + // Initialize various tables + sql::Transaction transaction(&db_); + if (!transaction.Begin()) + return sql::INIT_FAILURE; + + // Version check. + if (!meta_table_.Init(&db_, kCurrentVersionNumber, kCompatibleVersionNumber)) + return sql::INIT_FAILURE; + if (meta_table_.GetCompatibleVersionNumber() > kCurrentVersionNumber) { + LOG(WARNING) << "Web database is too new."; + return sql::INIT_TOO_NEW; + } + + // Initialize the tables. + for (TableMap::iterator it = tables_.begin(); + it != tables_.end(); + ++it) { + if (!it->second->Init(&db_, &meta_table_)) { + LOG(WARNING) << "Unable to initialize the web database."; + return sql::INIT_FAILURE; + } + } + + // If the file on disk is an older database version, bring it up to date. + // If the migration fails we return an error to caller and do not commit + // the migration. + sql::InitStatus migration_status = MigrateOldVersionsAsNeeded(); + if (migration_status != sql::INIT_OK) + return migration_status; + + return transaction.Commit() ? sql::INIT_OK : sql::INIT_FAILURE; +} + +sql::InitStatus WebDatabase::MigrateOldVersionsAsNeeded() { + // Some malware used to lower the version number, causing migration to + // fail. Ensure the version number is at least as high as the compatible + // version number. + int current_version = std::max(meta_table_.GetVersionNumber(), + meta_table_.GetCompatibleVersionNumber()); + if (current_version > meta_table_.GetVersionNumber()) + ChangeVersion(&meta_table_, current_version, false); + + if (current_version < 20) { + // Versions 1 - 19 are unhandled. Version numbers greater than + // kCurrentVersionNumber should have already been weeded out by the caller. + // + // When the version is too old, we return failure error code. The schema + // is too out of date to migrate. + // + // There should not be a released product that makes a database too old to + // migrate. If we do encounter such a legacy database, we will need a + // better solution to handle it (i.e., pop up a dialog to tell the user, + // erase all their prefs and start over, etc.). + LOG(WARNING) << "Web database version " << current_version << + " is too old to handle."; + NOTREACHED(); + return sql::INIT_FAILURE; + } + + for (int next_version = current_version + 1; + next_version <= kCurrentVersionNumber; + ++next_version) { + // Give each table a chance to migrate to this version. + for (TableMap::iterator it = tables_.begin(); + it != tables_.end(); + ++it) { + // Any of the tables may set this to true, but by default it is false. + bool update_compatible_version = false; + if (!it->second->MigrateToVersion(next_version, + &update_compatible_version)) { + return FailedMigrationTo(next_version); + } + + ChangeVersion(&meta_table_, next_version, update_compatible_version); + } + } + return sql::INIT_OK; +} diff --git a/chromium/components/webdata/common/web_database.h b/chromium/components/webdata/common/web_database.h new file mode 100644 index 00000000000..e7fedf072b2 --- /dev/null +++ b/chromium/components/webdata/common/web_database.h @@ -0,0 +1,73 @@ +// 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_WEBDATA_COMMON_WEB_DATABASE_H_ +#define COMPONENTS_WEBDATA_COMMON_WEB_DATABASE_H_ + +#include <map> + +#include "base/memory/scoped_ptr.h" +#include "components/webdata/common/web_database_table.h" +#include "components/webdata/common/webdata_export.h" +#include "sql/connection.h" +#include "sql/init_status.h" +#include "sql/meta_table.h" + +namespace base { +class FilePath; +} + +// This class manages a SQLite database that stores various web page meta data. +class WEBDATA_EXPORT WebDatabase { + public: + enum State { + COMMIT_NOT_NEEDED, + COMMIT_NEEDED + }; + // Exposed publicly so the keyword table can access it. + static const int kCurrentVersionNumber; + + WebDatabase(); + virtual ~WebDatabase(); + + // Adds a database table. Ownership remains with the caller, which + // must ensure that the lifetime of |table| exceeds this object's + // lifetime. Must only be called before Init. + void AddTable(WebDatabaseTable* table); + + // Retrieves a table based on its |key|. + WebDatabaseTable* GetTable(WebDatabaseTable::TypeKey key); + + // Initialize the database given a name. The name defines where the SQLite + // file is. If this returns an error code, no other method should be called. + // + // Before calling this method, you must call AddTable for any + // WebDatabaseTable objects that are supposed to participate in + // managing the database. + sql::InitStatus Init(const base::FilePath& db_name); + + // Transactions management + void BeginTransaction(); + void CommitTransaction(); + + // Exposed for testing only. + sql::Connection* GetSQLConnection(); + + private: + // Used by |Init()| to migration database schema from older versions to + // current version. + sql::InitStatus MigrateOldVersionsAsNeeded(); + + sql::Connection db_; + sql::MetaTable meta_table_; + + // Map of all the different tables that have been added to this + // object. Non-owning. + typedef std::map<WebDatabaseTable::TypeKey, WebDatabaseTable*> TableMap; + TableMap tables_; + + DISALLOW_COPY_AND_ASSIGN(WebDatabase); +}; + +#endif // COMPONENTS_WEBDATA_COMMON_WEB_DATABASE_H_ diff --git a/chromium/components/webdata/common/web_database_migration_unittest.cc b/chromium/components/webdata/common/web_database_migration_unittest.cc new file mode 100644 index 00000000000..5e6ef6e9dc3 --- /dev/null +++ b/chromium/components/webdata/common/web_database_migration_unittest.cc @@ -0,0 +1,2082 @@ +// 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/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/guid.h" +#include "base/message_loop/message_loop.h" +#include "base/path_service.h" +#include "base/stl_util.h" +#include "base/strings/string16.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" +#include "base/values.h" +#include "chrome/browser/webdata/keyword_table.h" +#include "chrome/browser/webdata/logins_table.h" +#include "chrome/browser/webdata/token_service_table.h" +#include "chrome/browser/webdata/web_apps_table.h" +#include "chrome/browser/webdata/web_intents_table.h" +#include "components/autofill/core/browser/autofill_country.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/autofill_type.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/webdata/autofill_change.h" +#include "components/autofill/core/browser/webdata/autofill_entry.h" +#include "components/autofill/core/browser/webdata/autofill_table.h" +#include "components/webdata/common/web_database.h" +#include "sql/statement.h" +#include "testing/gtest/include/gtest/gtest.h" + +using autofill::AutofillProfile; +using autofill::AutofillTable; +using autofill::CreditCard; +using base::Time; + +namespace { + +void AutofillProfile31FromStatement(const sql::Statement& s, + AutofillProfile* profile, + base::string16* label, + int* unique_id, + int64* date_modified) { + DCHECK(profile); + DCHECK(label); + DCHECK(unique_id); + DCHECK(date_modified); + *label = s.ColumnString16(0); + *unique_id = s.ColumnInt(1); + profile->SetRawInfo(autofill::NAME_FIRST, s.ColumnString16(2)); + profile->SetRawInfo(autofill::NAME_MIDDLE, s.ColumnString16(3)); + profile->SetRawInfo(autofill::NAME_LAST, s.ColumnString16(4)); + profile->SetRawInfo(autofill::EMAIL_ADDRESS, s.ColumnString16(5)); + profile->SetRawInfo(autofill::COMPANY_NAME, s.ColumnString16(6)); + profile->SetRawInfo(autofill::ADDRESS_HOME_LINE1, s.ColumnString16(7)); + profile->SetRawInfo(autofill::ADDRESS_HOME_LINE2, s.ColumnString16(8)); + profile->SetRawInfo(autofill::ADDRESS_HOME_CITY, s.ColumnString16(9)); + profile->SetRawInfo(autofill::ADDRESS_HOME_STATE, s.ColumnString16(10)); + profile->SetRawInfo(autofill::ADDRESS_HOME_ZIP, s.ColumnString16(11)); + profile->SetInfo( + autofill::AutofillType(autofill::ADDRESS_HOME_COUNTRY), + s.ColumnString16(12), "en-US"); + profile->SetRawInfo(autofill::PHONE_HOME_WHOLE_NUMBER, s.ColumnString16(13)); + *date_modified = s.ColumnInt64(15); + profile->set_guid(s.ColumnString(16)); + EXPECT_TRUE(base::IsValidGUID(profile->guid())); +} + +void AutofillProfile33FromStatement(const sql::Statement& s, + AutofillProfile* profile, + int64* date_modified) { + DCHECK(profile); + DCHECK(date_modified); + profile->set_guid(s.ColumnString(0)); + EXPECT_TRUE(base::IsValidGUID(profile->guid())); + profile->SetRawInfo(autofill::COMPANY_NAME, s.ColumnString16(1)); + profile->SetRawInfo(autofill::ADDRESS_HOME_LINE1, s.ColumnString16(2)); + profile->SetRawInfo(autofill::ADDRESS_HOME_LINE2, s.ColumnString16(3)); + profile->SetRawInfo(autofill::ADDRESS_HOME_CITY, s.ColumnString16(4)); + profile->SetRawInfo(autofill::ADDRESS_HOME_STATE, s.ColumnString16(5)); + profile->SetRawInfo(autofill::ADDRESS_HOME_ZIP, s.ColumnString16(6)); + profile->SetInfo( + autofill::AutofillType(autofill::ADDRESS_HOME_COUNTRY), + s.ColumnString16(7), "en-US"); + *date_modified = s.ColumnInt64(8); +} + +void CreditCard31FromStatement(const sql::Statement& s, + CreditCard* credit_card, + base::string16* label, + int* unique_id, + std::string* encrypted_number, + int64* date_modified) { + DCHECK(credit_card); + DCHECK(label); + DCHECK(unique_id); + DCHECK(encrypted_number); + DCHECK(date_modified); + *label = s.ColumnString16(0); + *unique_id = s.ColumnInt(1); + credit_card->SetRawInfo(autofill::CREDIT_CARD_NAME, s.ColumnString16(2)); + credit_card->SetRawInfo(autofill::CREDIT_CARD_TYPE, s.ColumnString16(3)); + credit_card->SetRawInfo(autofill::CREDIT_CARD_EXP_MONTH, s.ColumnString16(5)); + credit_card->SetRawInfo( + autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR, s.ColumnString16(6)); + int encrypted_number_len = s.ColumnByteLength(10); + if (encrypted_number_len) { + encrypted_number->resize(encrypted_number_len); + memcpy(&(*encrypted_number)[0], s.ColumnBlob(10), encrypted_number_len); + } + *date_modified = s.ColumnInt64(12); + credit_card->set_guid(s.ColumnString(13)); + EXPECT_TRUE(base::IsValidGUID(credit_card->guid())); +} + +void CreditCard32FromStatement(const sql::Statement& s, + CreditCard* credit_card, + std::string* encrypted_number, + int64* date_modified) { + DCHECK(credit_card); + DCHECK(encrypted_number); + DCHECK(date_modified); + credit_card->set_guid(s.ColumnString(0)); + EXPECT_TRUE(base::IsValidGUID(credit_card->guid())); + credit_card->SetRawInfo(autofill::CREDIT_CARD_NAME, s.ColumnString16(1)); + credit_card->SetRawInfo(autofill::CREDIT_CARD_EXP_MONTH, s.ColumnString16(2)); + credit_card->SetRawInfo( + autofill::CREDIT_CARD_EXP_4_DIGIT_YEAR, s.ColumnString16(3)); + int encrypted_number_len = s.ColumnByteLength(4); + if (encrypted_number_len) { + encrypted_number->resize(encrypted_number_len); + memcpy(&(*encrypted_number)[0], s.ColumnBlob(4), encrypted_number_len); + } + *date_modified = s.ColumnInt64(5); +} + +void CheckHasBackupData(sql::MetaTable* meta_table) { + std::string value; + EXPECT_TRUE(meta_table->GetValue( + "Default Search Provider ID Backup", &value)); + EXPECT_TRUE(meta_table->GetValue( + "Default Search Provider ID Backup Signature", &value)); +} + +void CheckNoBackupData(const sql::Connection& connection, + sql::MetaTable* meta_table) { + std::string value; + EXPECT_FALSE(meta_table->GetValue( + "Default Search Provider ID Backup", &value)); + EXPECT_FALSE(meta_table->GetValue( + "Default Search Provider ID Backup Signature", &value)); + EXPECT_FALSE(connection.DoesTableExist("keywords_backup")); +} + +} // anonymous namespace + +// The WebDatabaseMigrationTest encapsulates testing of database migrations. +// Specifically, these tests are intended to exercise any schema changes in +// the WebDatabase and data migrations that occur in +// |WebDatabase::MigrateOldVersionsAsNeeded()|. +class WebDatabaseMigrationTest : public testing::Test { + public: + WebDatabaseMigrationTest() {} + virtual ~WebDatabaseMigrationTest() {} + + virtual void SetUp() { + ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + } + + // Load the database via the WebDatabase class and migrate the database to + // the current version. + void DoMigration() { + // TODO(joi): This whole unit test file needs to stay in //chrome + // for now, as it needs to know about all the different table + // types. Once all webdata datatypes have been componentized, this + // could move to components_unittests. + AutofillTable autofill_table("en-US"); + KeywordTable keyword_table; + LoginsTable logins_table; + TokenServiceTable token_service_table; + WebAppsTable web_apps_table; + WebIntentsTable web_intents_table; + + WebDatabase db; + db.AddTable(&autofill_table); + db.AddTable(&keyword_table); + db.AddTable(&logins_table); + db.AddTable(&token_service_table); + db.AddTable(&web_apps_table); + db.AddTable(&web_intents_table); + + // This causes the migration to occur. + ASSERT_EQ(sql::INIT_OK, db.Init(GetDatabasePath())); + } + + protected: + // Current tested version number. When adding a migration in + // |WebDatabase::MigrateOldVersionsAsNeeded()| and changing the version number + // |kCurrentVersionNumber| this value should change to reflect the new version + // number and a new migration test added below. + static const int kCurrentTestedVersionNumber; + + base::FilePath GetDatabasePath() { + const base::FilePath::CharType kWebDatabaseFilename[] = + FILE_PATH_LITERAL("TestWebDatabase.sqlite3"); + return temp_dir_.path().Append(base::FilePath(kWebDatabaseFilename)); + } + + // The textual contents of |file| are read from + // "components/test/data/web_database" and returned in the string |contents|. + // Returns true if the file exists and is read successfully, false otherwise. + bool GetWebDatabaseData(const base::FilePath& file, std::string* contents) { + base::FilePath source_path; + PathService::Get(base::DIR_SOURCE_ROOT, &source_path); + source_path = source_path.AppendASCII("components"); + source_path = source_path.AppendASCII("test"); + source_path = source_path.AppendASCII("data"); + source_path = source_path.AppendASCII("web_database"); + source_path = source_path.Append(file); + return base::PathExists(source_path) && + file_util::ReadFileToString(source_path, contents); + } + + static int VersionFromConnection(sql::Connection* connection) { + // Get version. + sql::Statement s(connection->GetUniqueStatement( + "SELECT value FROM meta WHERE key='version'")); + if (!s.Step()) + return 0; + return s.ColumnInt(0); + } + + // The sql files located in "chrome/test/data/web_database" were generated by + // launching the Chromium application prior to schema change, then using the + // sqlite3 command-line application to dump the contents of the "Web Data" + // database. + // Like this: + // > .output version_nn.sql + // > .dump + void LoadDatabase(const base::FilePath::StringType& file); + + private: + base::ScopedTempDir temp_dir_; + + DISALLOW_COPY_AND_ASSIGN(WebDatabaseMigrationTest); +}; + +const int WebDatabaseMigrationTest::kCurrentTestedVersionNumber = 52; + +void WebDatabaseMigrationTest::LoadDatabase( + const base::FilePath::StringType& file) { + std::string contents; + ASSERT_TRUE(GetWebDatabaseData(base::FilePath(file), &contents)); + + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(connection.Execute(contents.data())); +} + +// Tests that the all migrations from an empty database succeed. +TEST_F(WebDatabaseMigrationTest, MigrateEmptyToCurrent) { + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // Check that expected tables are present. + EXPECT_TRUE(connection.DoesTableExist("autofill")); + EXPECT_TRUE(connection.DoesTableExist("autofill_dates")); + EXPECT_TRUE(connection.DoesTableExist("autofill_profiles")); + EXPECT_TRUE(connection.DoesTableExist("credit_cards")); + EXPECT_TRUE(connection.DoesTableExist("keywords")); + // The logins table is obsolete. (We used to store saved passwords here.) + EXPECT_FALSE(connection.DoesTableExist("logins")); + EXPECT_TRUE(connection.DoesTableExist("meta")); + EXPECT_TRUE(connection.DoesTableExist("token_service")); + EXPECT_TRUE(connection.DoesTableExist("web_app_icons")); + EXPECT_TRUE(connection.DoesTableExist("web_apps")); + EXPECT_TRUE(connection.DoesTableExist("web_intents")); + EXPECT_TRUE(connection.DoesTableExist("web_intents_defaults")); + } +} + +// Tests that the |credit_card| table gets added to the schema for a version 22 +// database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion22ToCurrent) { + // This schema is taken from a build prior to the addition of the + // |credit_card| table. Version 22 of the schema. Contrast this with the + // corrupt version below. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_22.sql"))); + + // Verify pre-conditions. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // No |credit_card| table prior to version 23. + ASSERT_FALSE(connection.DoesColumnExist("credit_cards", "guid")); + ASSERT_FALSE( + connection.DoesColumnExist("credit_cards", "card_number_encrypted")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // |credit_card| table now exists. + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "guid")); + EXPECT_TRUE( + connection.DoesColumnExist("credit_cards", "card_number_encrypted")); + } +} + +// Tests that the |credit_card| table gets added to the schema for a corrupt +// version 22 database. The corruption is that the |credit_cards| table exists +// but the schema version number was not set correctly to 23 or later. This +// test exercises code introduced to fix bug http://crbug.com/50699 that +// resulted from the corruption. +TEST_F(WebDatabaseMigrationTest, MigrateVersion22CorruptedToCurrent) { + // This schema is taken from a build after the addition of the |credit_card| + // table. Due to a bug in the migration logic the version is set incorrectly + // to 22 (it should have been updated to 23 at least). + ASSERT_NO_FATAL_FAILURE( + LoadDatabase(FILE_PATH_LITERAL("version_22_corrupt.sql"))); + + // Verify pre-conditions. These are expectations for corrupt version 22 of + // the database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Columns existing and not existing before current version. + ASSERT_TRUE(connection.DoesColumnExist("credit_cards", "unique_id")); + ASSERT_TRUE( + connection.DoesColumnExist("credit_cards", "card_number_encrypted")); + ASSERT_TRUE(connection.DoesColumnExist("keywords", "id")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + + // Columns existing and not existing before version 25. + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", "unique_id")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "guid")); + EXPECT_TRUE( + connection.DoesColumnExist("credit_cards", "card_number_encrypted")); + EXPECT_TRUE(connection.DoesColumnExist("keywords", "id")); + } +} + +// Tests that the |keywords| |created_by_policy| column gets added to the schema +// for a version 25 database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion25ToCurrent) { + // This schema is taken from a build prior to the addition of the |keywords| + // |created_by_policy| column. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_25.sql"))); + + // Verify pre-conditions. These are expectations for version 25 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // |keywords| |created_by_policy| column should have been added. + EXPECT_TRUE(connection.DoesColumnExist("keywords", "id")); + EXPECT_TRUE(connection.DoesColumnExist("keywords", "created_by_policy")); + } +} + +// Tests that the credit_cards.billing_address column is changed from a string +// to an int whilst preserving the associated billing address. This version of +// the test makes sure a stored label is converted to an ID. +TEST_F(WebDatabaseMigrationTest, MigrateVersion26ToCurrentStringLabels) { + // This schema is taken from a build prior to the change of column type for + // credit_cards.billing_address from string to int. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_26.sql"))); + + // Verify pre-conditions. These are expectations for version 26 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Columns existing and not existing before current version. + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "billing_address")); + + std::string stmt = "INSERT INTO autofill_profiles" + "(label, unique_id, first_name, middle_name, last_name, email," + " company_name, address_line_1, address_line_2, city, state, zipcode," + " country, phone, fax)" + "VALUES ('Home',1,'','','','','','','','','','','','','')"; + sql::Statement s(connection.GetUniqueStatement(stmt.c_str())); + ASSERT_TRUE(s.Run()); + + // Insert a CC linked to an existing address. + std::string stmt2 = "INSERT INTO credit_cards" + "(label, unique_id, name_on_card, type, card_number," + " expiration_month, expiration_year, verification_code, billing_address," + " shipping_address, card_number_encrypted, verification_code_encrypted)" + "VALUES ('label',2,'Jack','Visa','1234',2,2012,'','Home','','','')"; + sql::Statement s2(connection.GetUniqueStatement(stmt2.c_str())); + ASSERT_TRUE(s2.Run()); + + // |billing_address| is a string. + std::string stmt3 = "SELECT billing_address FROM credit_cards"; + sql::Statement s3(connection.GetUniqueStatement(stmt3.c_str())); + ASSERT_TRUE(s3.Step()); + EXPECT_EQ(s3.ColumnType(0), sql::COLUMN_TYPE_TEXT); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", "billing_address")); + + // Verify the credit card data is converted. + sql::Statement s(connection.GetUniqueStatement( + "SELECT guid, name_on_card, expiration_month, expiration_year, " + "card_number_encrypted, date_modified " + "FROM credit_cards")); + ASSERT_TRUE(s.Step()); + EXPECT_EQ("Jack", s.ColumnString(1)); + EXPECT_EQ(2, s.ColumnInt(2)); + EXPECT_EQ(2012, s.ColumnInt(3)); + // Column 5 is encrypted number blob. + // Column 6 is date_modified. + } +} + +// Tests that the credit_cards.billing_address column is changed from a string +// to an int whilst preserving the associated billing address. This version of +// the test makes sure a stored string ID is converted to an integer ID. +TEST_F(WebDatabaseMigrationTest, MigrateVersion26ToCurrentStringIDs) { + // This schema is taken from a build prior to the change of column type for + // credit_cards.billing_address from string to int. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_26.sql"))); + + // Verify pre-conditions. These are expectations for version 26 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "billing_address")); + + std::string stmt = "INSERT INTO autofill_profiles" + "(label, unique_id, first_name, middle_name, last_name, email," + " company_name, address_line_1, address_line_2, city, state, zipcode," + " country, phone, fax)" + "VALUES ('Home',1,'','','','','','','','','','','','','')"; + sql::Statement s(connection.GetUniqueStatement(stmt.c_str())); + ASSERT_TRUE(s.Run()); + + // Insert a CC linked to an existing address. + std::string stmt2 = "INSERT INTO credit_cards" + "(label, unique_id, name_on_card, type, card_number," + " expiration_month, expiration_year, verification_code, billing_address," + " shipping_address, card_number_encrypted, verification_code_encrypted)" + "VALUES ('label',2,'Jack','Visa','1234',2,2012,'','1','','','')"; + sql::Statement s2(connection.GetUniqueStatement(stmt2.c_str())); + ASSERT_TRUE(s2.Run()); + + // |billing_address| is a string. + std::string stmt3 = "SELECT billing_address FROM credit_cards"; + sql::Statement s3(connection.GetUniqueStatement(stmt3.c_str())); + ASSERT_TRUE(s3.Step()); + EXPECT_EQ(s3.ColumnType(0), sql::COLUMN_TYPE_TEXT); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // |keywords| |created_by_policy| column should have been added. + EXPECT_TRUE(connection.DoesColumnExist("keywords", "id")); + EXPECT_TRUE(connection.DoesColumnExist("keywords", "created_by_policy")); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", "billing_address")); + + // Verify the credit card data is converted. + sql::Statement s(connection.GetUniqueStatement( + "SELECT guid, name_on_card, expiration_month, expiration_year, " + "card_number_encrypted, date_modified " + "FROM credit_cards")); + ASSERT_TRUE(s.Step()); + EXPECT_EQ("Jack", s.ColumnString(1)); + EXPECT_EQ(2, s.ColumnInt(2)); + EXPECT_EQ(2012, s.ColumnInt(3)); + // Column 5 is encrypted credit card number blo b. + // Column 6 is date_modified. + } +} + +// Makes sure instant_url is added correctly to keywords. +TEST_F(WebDatabaseMigrationTest, MigrateVersion27ToCurrent) { + // Initialize the database. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_27.sql"))); + + // Verify pre-conditions. These are expectations for version 27 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + ASSERT_FALSE(connection.DoesColumnExist("keywords", "instant_url")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // Make sure supports_instant (added in Version 28) was ultimately dropped + // again and instant_url was added. + EXPECT_FALSE(connection.DoesColumnExist("keywords", "supports_instant")); + EXPECT_TRUE(connection.DoesColumnExist("keywords", "instant_url")); + + // Check that instant_url is empty. + std::string stmt = "SELECT instant_url FROM keywords"; + sql::Statement s(connection.GetUniqueStatement(stmt.c_str())); + ASSERT_TRUE(s.Step()); + EXPECT_EQ(std::string(), s.ColumnString(0)); + + // Verify the data made it over. + stmt = "SELECT " + KeywordTable::GetKeywordColumns() + " FROM keywords"; + sql::Statement s2(connection.GetUniqueStatement(stmt.c_str())); + ASSERT_TRUE(s2.Step()); + EXPECT_EQ(2, s2.ColumnInt(0)); + EXPECT_EQ("Google", s2.ColumnString(1)); + EXPECT_EQ("google.com", s2.ColumnString(2)); + EXPECT_EQ("http://www.google.com/favicon.ico", s2.ColumnString(3)); + EXPECT_EQ("{google:baseURL}search?{google:RLZ}{google:acceptedSuggestion}"\ + "{google:originalQueryForSuggestion}sourceid=chrome&ie={inputEncoding}"\ + "&q={searchTerms}", + s2.ColumnString(4)); + EXPECT_TRUE(s2.ColumnBool(5)); + EXPECT_EQ(std::string(), s2.ColumnString(6)); + EXPECT_EQ(0, s2.ColumnInt(7)); + EXPECT_EQ(0, s2.ColumnInt(8)); + EXPECT_EQ(std::string("UTF-8"), s2.ColumnString(9)); + EXPECT_TRUE(s2.ColumnBool(10)); + EXPECT_EQ(std::string("{google:baseSuggestURL}search?client=chrome&hl=" + "{language}&q={searchTerms}"), s2.ColumnString(11)); + EXPECT_EQ(1, s2.ColumnInt(12)); + EXPECT_FALSE(s2.ColumnBool(13)); + EXPECT_EQ(std::string(), s2.ColumnString(14)); + EXPECT_EQ(0, s2.ColumnInt(15)); + EXPECT_EQ(std::string(), s2.ColumnString(16)); + } +} + +// Makes sure date_modified is added correctly to autofill_profiles and +// credit_cards. +TEST_F(WebDatabaseMigrationTest, MigrateVersion29ToCurrent) { + // Initialize the database. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_29.sql"))); + + // Verify pre-conditions. These are expectations for version 29 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", + "date_modified")); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", + "date_modified")); + } + + Time pre_creation_time = Time::Now(); + DoMigration(); + Time post_creation_time = Time::Now(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // Check that the columns were created. + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", + "date_modified")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", + "date_modified")); + + sql::Statement s_profiles(connection.GetUniqueStatement( + "SELECT date_modified FROM autofill_profiles ")); + ASSERT_TRUE(s_profiles.is_valid()); + while (s_profiles.Step()) { + EXPECT_GE(s_profiles.ColumnInt64(0), + pre_creation_time.ToTimeT()); + EXPECT_LE(s_profiles.ColumnInt64(0), + post_creation_time.ToTimeT()); + } + EXPECT_TRUE(s_profiles.Succeeded()); + + sql::Statement s_credit_cards(connection.GetUniqueStatement( + "SELECT date_modified FROM credit_cards ")); + ASSERT_TRUE(s_credit_cards.is_valid()); + while (s_credit_cards.Step()) { + EXPECT_GE(s_credit_cards.ColumnInt64(0), + pre_creation_time.ToTimeT()); + EXPECT_LE(s_credit_cards.ColumnInt64(0), + post_creation_time.ToTimeT()); + } + EXPECT_TRUE(s_credit_cards.Succeeded()); + } +} + +// Makes sure guids are added to autofill_profiles and credit_cards tables. +TEST_F(WebDatabaseMigrationTest, MigrateVersion30ToCurrent) { + // Initialize the database. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_30.sql"))); + + // Verify pre-conditions. These are expectations for version 29 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", "guid")); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", "guid")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + ASSERT_TRUE(connection.DoesColumnExist("autofill_profiles", "guid")); + ASSERT_TRUE(connection.DoesColumnExist("credit_cards", "guid")); + + // Check that guids are non-null, non-empty, conforms to guid format, and + // are different. + sql::Statement s( + connection.GetUniqueStatement("SELECT guid FROM autofill_profiles")); + + ASSERT_TRUE(s.Step()); + std::string guid1 = s.ColumnString(0); + EXPECT_TRUE(base::IsValidGUID(guid1)); + + ASSERT_TRUE(s.Step()); + std::string guid2 = s.ColumnString(0); + EXPECT_TRUE(base::IsValidGUID(guid2)); + + EXPECT_NE(guid1, guid2); + } +} + +// Removes unique IDs and make GUIDs the primary key. Also removes unused +// columns. +TEST_F(WebDatabaseMigrationTest, MigrateVersion31ToCurrent) { + // Initialize the database. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_31.sql"))); + + // Verify pre-conditions. These are expectations for version 30 of the + // database. + AutofillProfile profile; + base::string16 profile_label; + int profile_unique_id = 0; + int64 profile_date_modified = 0; + CreditCard credit_card; + base::string16 cc_label; + int cc_unique_id = 0; + std::string cc_number_encrypted; + int64 cc_date_modified = 0; + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Verify existence of columns we'll be changing. + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "guid")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "unique_id")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "guid")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "unique_id")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "type")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "card_number")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", + "verification_code")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "billing_address")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "shipping_address")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", + "verification_code_encrypted")); + + // Fetch data in the database prior to migration. + sql::Statement s1( + connection.GetUniqueStatement( + "SELECT label, unique_id, first_name, middle_name, last_name, " + "email, company_name, address_line_1, address_line_2, city, state, " + "zipcode, country, phone, fax, date_modified, guid " + "FROM autofill_profiles")); + ASSERT_TRUE(s1.Step()); + EXPECT_NO_FATAL_FAILURE(AutofillProfile31FromStatement( + s1, &profile, &profile_label, &profile_unique_id, + &profile_date_modified)); + + sql::Statement s2( + connection.GetUniqueStatement( + "SELECT label, unique_id, name_on_card, type, card_number, " + "expiration_month, expiration_year, verification_code, " + "billing_address, shipping_address, card_number_encrypted, " + "verification_code_encrypted, date_modified, guid " + "FROM credit_cards")); + ASSERT_TRUE(s2.Step()); + EXPECT_NO_FATAL_FAILURE(CreditCard31FromStatement(s2, + &credit_card, + &cc_label, + &cc_unique_id, + &cc_number_encrypted, + &cc_date_modified)); + + EXPECT_NE(profile_unique_id, cc_unique_id); + EXPECT_NE(profile.guid(), credit_card.guid()); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // Verify existence of columns we'll be changing. + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "guid")); + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", "unique_id")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "guid")); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", "unique_id")); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", "type")); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", "card_number")); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", + "verification_code")); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", "billing_address")); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", + "shipping_address")); + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", + "verification_code_encrypted")); + + // Verify data in the database after the migration. + sql::Statement s1( + connection.GetUniqueStatement( + "SELECT guid, company_name, address_line_1, address_line_2, " + "city, state, zipcode, country, date_modified " + "FROM autofill_profiles")); + ASSERT_TRUE(s1.Step()); + + AutofillProfile profile_a; + int64 profile_date_modified_a = 0; + EXPECT_NO_FATAL_FAILURE(AutofillProfile33FromStatement( + s1, &profile_a, &profile_date_modified_a)); + EXPECT_EQ(profile.guid(), profile_a.guid()); + EXPECT_EQ(profile.GetRawInfo(autofill::COMPANY_NAME), + profile_a.GetRawInfo(autofill::COMPANY_NAME)); + EXPECT_EQ(profile.GetRawInfo(autofill::ADDRESS_HOME_LINE1), + profile_a.GetRawInfo(autofill::ADDRESS_HOME_LINE1)); + EXPECT_EQ(profile.GetRawInfo(autofill::ADDRESS_HOME_LINE2), + profile_a.GetRawInfo(autofill::ADDRESS_HOME_LINE2)); + EXPECT_EQ(profile.GetRawInfo(autofill::ADDRESS_HOME_CITY), + profile_a.GetRawInfo(autofill::ADDRESS_HOME_CITY)); + EXPECT_EQ(profile.GetRawInfo(autofill::ADDRESS_HOME_STATE), + profile_a.GetRawInfo(autofill::ADDRESS_HOME_STATE)); + EXPECT_EQ(profile.GetRawInfo(autofill::ADDRESS_HOME_ZIP), + profile_a.GetRawInfo(autofill::ADDRESS_HOME_ZIP)); + EXPECT_EQ(profile.GetRawInfo(autofill::ADDRESS_HOME_COUNTRY), + profile_a.GetRawInfo(autofill::ADDRESS_HOME_COUNTRY)); + EXPECT_EQ(profile_date_modified, profile_date_modified_a); + + sql::Statement s2( + connection.GetUniqueStatement( + "SELECT guid, name_on_card, expiration_month, " + "expiration_year, card_number_encrypted, date_modified " + "FROM credit_cards")); + ASSERT_TRUE(s2.Step()); + + CreditCard credit_card_a; + base::string16 cc_label_a; + std::string cc_number_encrypted_a; + int64 cc_date_modified_a = 0; + EXPECT_NO_FATAL_FAILURE(CreditCard32FromStatement(s2, + &credit_card_a, + &cc_number_encrypted_a, + &cc_date_modified_a)); + EXPECT_EQ(credit_card, credit_card_a); + EXPECT_EQ(cc_label, cc_label_a); + EXPECT_EQ(cc_number_encrypted, cc_number_encrypted_a); + EXPECT_EQ(cc_date_modified, cc_date_modified_a); + } +} + +// Factor |autofill_profiles| address information separately from name, email, +// and phone. +TEST_F(WebDatabaseMigrationTest, MigrateVersion32ToCurrent) { + // Initialize the database. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_32.sql"))); + + // Verify pre-conditions. These are expectations for version 32 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Verify existence of columns we'll be changing. + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "guid")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "label")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "first_name")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "middle_name")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "last_name")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "email")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", + "company_name")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", + "address_line_1")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", + "address_line_2")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "city")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "state")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "zipcode")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "country")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "phone")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "fax")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", + "date_modified")); + + EXPECT_FALSE(connection.DoesTableExist("autofill_profile_names")); + EXPECT_FALSE(connection.DoesTableExist("autofill_profile_emails")); + EXPECT_FALSE(connection.DoesTableExist("autofill_profile_phones")); + + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "label")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // Verify changes to columns. + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "guid")); + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", "label")); + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", "first_name")); + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", + "middle_name")); + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", "last_name")); + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", "email")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", + "company_name")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", + "address_line_1")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", + "address_line_2")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "city")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "state")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "zipcode")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "country")); + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", "phone")); + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", "fax")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", + "date_modified")); + + // New "names" table. + EXPECT_TRUE(connection.DoesColumnExist("autofill_profile_names", "guid")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profile_names", + "first_name")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profile_names", + "middle_name")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profile_names", + "last_name")); + + // New "emails" table. + EXPECT_TRUE(connection.DoesColumnExist("autofill_profile_emails", "guid")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profile_emails", "email")); + + // New "phones" table. + EXPECT_TRUE(connection.DoesColumnExist("autofill_profile_phones", "guid")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profile_phones", "type")); + EXPECT_TRUE(connection.DoesColumnExist("autofill_profile_phones", + "number")); + + EXPECT_FALSE(connection.DoesColumnExist("credit_cards", "label")); + + // Verify data in the database after the migration. + sql::Statement s1( + connection.GetUniqueStatement( + "SELECT guid, company_name, address_line_1, address_line_2, " + "city, state, zipcode, country, date_modified " + "FROM autofill_profiles")); + + // John Doe. + ASSERT_TRUE(s1.Step()); + EXPECT_EQ("00580526-FF81-EE2A-0546-1AC593A32E2F", s1.ColumnString(0)); + EXPECT_EQ(ASCIIToUTF16("Doe Enterprises"), s1.ColumnString16(1)); + EXPECT_EQ(ASCIIToUTF16("1 Main St"), s1.ColumnString16(2)); + EXPECT_EQ(ASCIIToUTF16("Apt 1"), s1.ColumnString16(3)); + EXPECT_EQ(ASCIIToUTF16("Los Altos"), s1.ColumnString16(4)); + EXPECT_EQ(ASCIIToUTF16("CA"), s1.ColumnString16(5)); + EXPECT_EQ(ASCIIToUTF16("94022"), s1.ColumnString16(6)); + EXPECT_EQ(ASCIIToUTF16("United States"), s1.ColumnString16(7)); + EXPECT_EQ(1297882100L, s1.ColumnInt64(8)); + + // John P. Doe. + // Gets merged during migration from 35 to 37 due to multi-valued fields. + + // Dave Smith. + ASSERT_TRUE(s1.Step()); + EXPECT_EQ("4C74A9D8-7EEE-423E-F9C2-E7FA70ED1396", s1.ColumnString(0)); + EXPECT_EQ(base::string16(), s1.ColumnString16(1)); + EXPECT_EQ(ASCIIToUTF16("2 Main Street"), s1.ColumnString16(2)); + EXPECT_EQ(base::string16(), s1.ColumnString16(3)); + EXPECT_EQ(ASCIIToUTF16("Los Altos"), s1.ColumnString16(4)); + EXPECT_EQ(ASCIIToUTF16("CA"), s1.ColumnString16(5)); + EXPECT_EQ(ASCIIToUTF16("94022"), s1.ColumnString16(6)); + EXPECT_EQ(ASCIIToUTF16("United States"), s1.ColumnString16(7)); + EXPECT_EQ(1297882100L, s1.ColumnInt64(8)); + + // Dave Smith (Part 2). + ASSERT_TRUE(s1.Step()); + EXPECT_EQ("722DF5C4-F74A-294A-46F0-31FFDED0D635", s1.ColumnString(0)); + EXPECT_EQ(base::string16(), s1.ColumnString16(1)); + EXPECT_EQ(ASCIIToUTF16("2 Main St"), s1.ColumnString16(2)); + EXPECT_EQ(base::string16(), s1.ColumnString16(3)); + EXPECT_EQ(ASCIIToUTF16("Los Altos"), s1.ColumnString16(4)); + EXPECT_EQ(ASCIIToUTF16("CA"), s1.ColumnString16(5)); + EXPECT_EQ(ASCIIToUTF16("94022"), s1.ColumnString16(6)); + EXPECT_EQ(ASCIIToUTF16("United States"), s1.ColumnString16(7)); + EXPECT_EQ(1297882100L, s1.ColumnInt64(8)); + + // Alfred E Newman. + // Gets culled during migration from 35 to 36 due to incomplete address. + + // 3 Main St. + ASSERT_TRUE(s1.Step()); + EXPECT_EQ("9E5FE298-62C7-83DF-6293-381BC589183F", s1.ColumnString(0)); + EXPECT_EQ(base::string16(), s1.ColumnString16(1)); + EXPECT_EQ(ASCIIToUTF16("3 Main St"), s1.ColumnString16(2)); + EXPECT_EQ(base::string16(), s1.ColumnString16(3)); + EXPECT_EQ(ASCIIToUTF16("Los Altos"), s1.ColumnString16(4)); + EXPECT_EQ(ASCIIToUTF16("CA"), s1.ColumnString16(5)); + EXPECT_EQ(ASCIIToUTF16("94022"), s1.ColumnString16(6)); + EXPECT_EQ(ASCIIToUTF16("United States"), s1.ColumnString16(7)); + EXPECT_EQ(1297882100L, s1.ColumnInt64(8)); + + // That should be all. + EXPECT_FALSE(s1.Step()); + + sql::Statement s2( + connection.GetUniqueStatement( + "SELECT guid, first_name, middle_name, last_name " + "FROM autofill_profile_names")); + + // John Doe. + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("00580526-FF81-EE2A-0546-1AC593A32E2F", s2.ColumnString(0)); + EXPECT_EQ(ASCIIToUTF16("John"), s2.ColumnString16(1)); + EXPECT_EQ(base::string16(), s2.ColumnString16(2)); + EXPECT_EQ(ASCIIToUTF16("Doe"), s2.ColumnString16(3)); + + // John P. Doe. Note same guid as above due to merging of multi-valued + // fields. + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("00580526-FF81-EE2A-0546-1AC593A32E2F", s2.ColumnString(0)); + EXPECT_EQ(ASCIIToUTF16("John"), s2.ColumnString16(1)); + EXPECT_EQ(ASCIIToUTF16("P."), s2.ColumnString16(2)); + EXPECT_EQ(ASCIIToUTF16("Doe"), s2.ColumnString16(3)); + + // Dave Smith. + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("4C74A9D8-7EEE-423E-F9C2-E7FA70ED1396", s2.ColumnString(0)); + EXPECT_EQ(ASCIIToUTF16("Dave"), s2.ColumnString16(1)); + EXPECT_EQ(base::string16(), s2.ColumnString16(2)); + EXPECT_EQ(ASCIIToUTF16("Smith"), s2.ColumnString16(3)); + + // Dave Smith (Part 2). + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("722DF5C4-F74A-294A-46F0-31FFDED0D635", s2.ColumnString(0)); + EXPECT_EQ(ASCIIToUTF16("Dave"), s2.ColumnString16(1)); + EXPECT_EQ(base::string16(), s2.ColumnString16(2)); + EXPECT_EQ(ASCIIToUTF16("Smith"), s2.ColumnString16(3)); + + // Alfred E Newman. + // Gets culled during migration from 35 to 36 due to incomplete address. + + // 3 Main St. + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("9E5FE298-62C7-83DF-6293-381BC589183F", s2.ColumnString(0)); + EXPECT_EQ(base::string16(), s2.ColumnString16(1)); + EXPECT_EQ(base::string16(), s2.ColumnString16(2)); + EXPECT_EQ(base::string16(), s2.ColumnString16(3)); + + // Should be all. + EXPECT_FALSE(s2.Step()); + + sql::Statement s3( + connection.GetUniqueStatement( + "SELECT guid, email " + "FROM autofill_profile_emails")); + + // John Doe. + ASSERT_TRUE(s3.Step()); + EXPECT_EQ("00580526-FF81-EE2A-0546-1AC593A32E2F", s3.ColumnString(0)); + EXPECT_EQ(ASCIIToUTF16("john@doe.com"), s3.ColumnString16(1)); + + // John P. Doe. + // Gets culled during migration from 35 to 37 due to merging of John Doe and + // John P. Doe addresses. + + // 2 Main Street. + ASSERT_TRUE(s3.Step()); + EXPECT_EQ("4C74A9D8-7EEE-423E-F9C2-E7FA70ED1396", s3.ColumnString(0)); + EXPECT_EQ(base::string16(), s3.ColumnString16(1)); + + // 2 Main St. + ASSERT_TRUE(s3.Step()); + EXPECT_EQ("722DF5C4-F74A-294A-46F0-31FFDED0D635", s3.ColumnString(0)); + EXPECT_EQ(base::string16(), s3.ColumnString16(1)); + + // Alfred E Newman. + // Gets culled during migration from 35 to 36 due to incomplete address. + + // 3 Main St. + ASSERT_TRUE(s3.Step()); + EXPECT_EQ("9E5FE298-62C7-83DF-6293-381BC589183F", s3.ColumnString(0)); + EXPECT_EQ(base::string16(), s3.ColumnString16(1)); + + // Should be all. + EXPECT_FALSE(s3.Step()); + + sql::Statement s4( + connection.GetUniqueStatement( + "SELECT guid, type, number " + "FROM autofill_profile_phones")); + + // John Doe phone. + ASSERT_TRUE(s4.Step()); + EXPECT_EQ("00580526-FF81-EE2A-0546-1AC593A32E2F", s4.ColumnString(0)); + EXPECT_EQ(0, s4.ColumnInt(1)); // 0 means phone. + EXPECT_EQ(ASCIIToUTF16("4151112222"), s4.ColumnString16(2)); + + // John Doe fax. + // Gets culled after fax type removed. + + // John P. Doe phone. + // Gets culled during migration from 35 to 37 due to merging of John Doe and + // John P. Doe addresses. + + // John P. Doe fax. + // Gets culled during migration from 35 to 37 due to merging of John Doe and + // John P. Doe addresses. + + // 2 Main Street phone. + ASSERT_TRUE(s4.Step()); + EXPECT_EQ("4C74A9D8-7EEE-423E-F9C2-E7FA70ED1396", s4.ColumnString(0)); + EXPECT_EQ(0, s4.ColumnInt(1)); // 0 means phone. + EXPECT_EQ(base::string16(), s4.ColumnString16(2)); + + // 2 Main Street fax. + // Gets culled after fax type removed. + + // 2 Main St phone. + ASSERT_TRUE(s4.Step()); + EXPECT_EQ("722DF5C4-F74A-294A-46F0-31FFDED0D635", s4.ColumnString(0)); + EXPECT_EQ(0, s4.ColumnInt(1)); // 0 means phone. + EXPECT_EQ(base::string16(), s4.ColumnString16(2)); + + // 2 Main St fax. + // Gets culled after fax type removed. + + // Note no phone or fax for Alfred E Newman. + + // 3 Main St phone. + ASSERT_TRUE(s4.Step()); + EXPECT_EQ("9E5FE298-62C7-83DF-6293-381BC589183F", s4.ColumnString(0)); + EXPECT_EQ(0, s4.ColumnInt(1)); // 0 means phone. + EXPECT_EQ(base::string16(), s4.ColumnString16(2)); + + // 2 Main St fax. + // Gets culled after fax type removed. + + // Should be all. + EXPECT_FALSE(s4.Step()); + } +} + +// Adds a column for the autofill profile's country code. +TEST_F(WebDatabaseMigrationTest, MigrateVersion33ToCurrent) { + // Initialize the database. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_33.sql"))); + + // Verify pre-conditions. These are expectations for version 33 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + EXPECT_FALSE(connection.DoesColumnExist("autofill_profiles", + "country_code")); + + // Check that the country value is the one we expect. + sql::Statement s( + connection.GetUniqueStatement("SELECT country FROM autofill_profiles")); + + ASSERT_TRUE(s.Step()); + std::string country = s.ColumnString(0); + EXPECT_EQ("United States", country); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + ASSERT_TRUE(connection.DoesColumnExist("autofill_profiles", + "country_code")); + + // Check that the country code is properly converted. + sql::Statement s(connection.GetUniqueStatement( + "SELECT country_code FROM autofill_profiles")); + + ASSERT_TRUE(s.Step()); + std::string country_code = s.ColumnString(0); + EXPECT_EQ("US", country_code); + } +} + +// Cleans up bad country code "UK" in favor of good country code "GB". +TEST_F(WebDatabaseMigrationTest, MigrateVersion34ToCurrent) { + // Initialize the database. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_34.sql"))); + + // Verify pre-conditions. These are expectations for version 34 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", + "country_code")); + + // Check that the country_code value is the one we expect. + sql::Statement s( + connection.GetUniqueStatement("SELECT country_code " + "FROM autofill_profiles")); + + ASSERT_TRUE(s.Step()); + std::string country_code = s.ColumnString(0); + EXPECT_EQ("UK", country_code); + + // Should have only one. + ASSERT_FALSE(s.Step()); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + ASSERT_TRUE(connection.DoesColumnExist("autofill_profiles", + "country_code")); + + // Check that the country_code code is properly converted. + sql::Statement s(connection.GetUniqueStatement( + "SELECT country_code FROM autofill_profiles")); + + ASSERT_TRUE(s.Step()); + std::string country_code = s.ColumnString(0); + EXPECT_EQ("GB", country_code); + + // Should have only one. + ASSERT_FALSE(s.Step()); + } +} + +// Cleans up invalid profiles based on more agressive merging. Filters out +// profiles that are subsets of other profiles, and profiles with invalid email, +// state, and incomplete address. +TEST_F(WebDatabaseMigrationTest, MigrateVersion35ToCurrent) { + // Initialize the database. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_35.sql"))); + + // Verify pre-conditions. These are expectations for version 34 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + EXPECT_FALSE(connection.DoesTableExist("autofill_profiles_trash")); + ASSERT_TRUE(connection.DoesColumnExist("autofill_profiles", "guid")); + + // Check that there are 6 profiles prior to merge. + sql::Statement s( + connection.GetUniqueStatement("SELECT guid FROM autofill_profiles")); + int i = 0; + while (s.Step()) + ++i; + EXPECT_EQ(6, i); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + ASSERT_TRUE(connection.DoesTableExist("autofill_profiles_trash")); + ASSERT_TRUE(connection.DoesColumnExist("autofill_profiles_trash", "guid")); + ASSERT_TRUE(connection.DoesColumnExist("autofill_profiles", "guid")); + + // Verify data in the database after the migration. + sql::Statement s1( + connection.GetUniqueStatement( + "SELECT guid, company_name, address_line_1, address_line_2, " + "city, state, zipcode, country, date_modified " + "FROM autofill_profiles")); + + // John Doe. + ASSERT_TRUE(s1.Step()); + EXPECT_EQ("00000000-0000-0000-0000-000000000001", s1.ColumnString(0)); + EXPECT_EQ(ASCIIToUTF16("Acme Inc."), s1.ColumnString16(1)); + EXPECT_EQ(ASCIIToUTF16("1 Main Street"), s1.ColumnString16(2)); + EXPECT_EQ(ASCIIToUTF16("Apt 2"), s1.ColumnString16(3)); + EXPECT_EQ(ASCIIToUTF16("San Francisco"), s1.ColumnString16(4)); + EXPECT_EQ(ASCIIToUTF16("CA"), s1.ColumnString16(5)); + EXPECT_EQ(ASCIIToUTF16("94102"), s1.ColumnString16(6)); + EXPECT_EQ(ASCIIToUTF16("United States"), s1.ColumnString16(7)); + EXPECT_EQ(1300131704, s1.ColumnInt64(8)); + + // That should be it. + ASSERT_FALSE(s1.Step()); + + // Check that there 5 trashed profile after the merge. + sql::Statement s2( + connection.GetUniqueStatement("SELECT guid " + "FROM autofill_profiles_trash")); + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("00000000-0000-0000-0000-000000000002", s2.ColumnString(0)); + + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("00000000-0000-0000-0000-000000000003", s2.ColumnString(0)); + + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("00000000-0000-0000-0000-000000000004", s2.ColumnString(0)); + + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("00000000-0000-0000-0000-000000000005", s2.ColumnString(0)); + + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("00000000-0000-0000-0000-000000000006", s2.ColumnString(0)); + + // That should be it. + ASSERT_FALSE(s2.Step()); + } +} + +// Tests that the |keywords| |last_modified| column gets added to the schema for +// a version 37 database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion37ToCurrent) { + // This schema is taken from a build prior to the addition of the |keywords| + // |last_modified| column. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_37.sql"))); + + // Verify pre-conditions. These are expectations for version 37 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Columns existing and not existing before current version. + ASSERT_TRUE(connection.DoesColumnExist("keywords", "id")); + ASSERT_FALSE(connection.DoesColumnExist("keywords", "last_modified")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // |keywords| |last_modified| column should have been added. + EXPECT_TRUE(connection.DoesColumnExist("keywords", "id")); + EXPECT_TRUE(connection.DoesColumnExist("keywords", "last_modified")); + } +} + +// Tests that the |keywords| |sync_guid| column gets added to the schema for +// a version 38 database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion38ToCurrent) { + // This schema is taken from a build prior to the addition of the |keywords| + // |sync_guid| column. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_38.sql"))); + + // Verify pre-conditions. These are expectations for version 38 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Columns existing and not existing before current version. + ASSERT_TRUE(connection.DoesColumnExist("keywords", "id")); + ASSERT_FALSE(connection.DoesColumnExist("keywords", "sync_guid")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // |keywords| |sync_guid| column should have been added. + EXPECT_TRUE(connection.DoesColumnExist("keywords", "id")); + EXPECT_TRUE(connection.DoesColumnExist("keywords", "sync_guid")); + } +} + +// Tests that no backup data is added to a version 39 database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion39ToCurrent) { + // This schema is taken from a build prior to the addition of the default + // search provider backup field to the meta table. + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_39.sql"))); + + // Verify pre-conditions. These are expectations for version 39 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 39, 39)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + + EXPECT_NO_FATAL_FAILURE(CheckNoBackupData(connection, &meta_table)); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, kCurrentTestedVersionNumber, + kCurrentTestedVersionNumber)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + EXPECT_NE(0, default_search_provider_id); + + EXPECT_NO_FATAL_FAILURE(CheckNoBackupData(connection, &meta_table)); + } +} + +// Tests that the backup data is removed from the database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion40ToCurrent) { + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_40.sql"))); + + // Verify pre-conditions. These are expectations for version 40 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 40, 40)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + + EXPECT_NO_FATAL_FAILURE(CheckHasBackupData(&meta_table)); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, kCurrentTestedVersionNumber, + kCurrentTestedVersionNumber)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + EXPECT_NE(0, default_search_provider_id); + + EXPECT_NO_FATAL_FAILURE(CheckNoBackupData(connection, &meta_table)); + } +} + +// Tests that the backup data is removed from the database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion41ToCurrent) { + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_41.sql"))); + + // Verify pre-conditions. These are expectations for version 41 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 41, 41)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + + EXPECT_NO_FATAL_FAILURE(CheckHasBackupData(&meta_table)); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, kCurrentTestedVersionNumber, + kCurrentTestedVersionNumber)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + EXPECT_NE(0, default_search_provider_id); + + EXPECT_NO_FATAL_FAILURE(CheckNoBackupData(connection, &meta_table)); + } +} + +// Tests that the backup data is removed from the database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion42ToCurrent) { + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_42.sql"))); + + // Verify pre-conditions. These are expectations for version 42 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 42, 42)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + + EXPECT_NO_FATAL_FAILURE(CheckHasBackupData(&meta_table)); + + EXPECT_FALSE(connection.DoesTableExist("keywords_backup")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, kCurrentTestedVersionNumber, + kCurrentTestedVersionNumber)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + EXPECT_NE(0, default_search_provider_id); + + EXPECT_NO_FATAL_FAILURE(CheckNoBackupData(connection, &meta_table)); + } +} + +// Tests that the backup data is removed from the database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion43ToCurrent) { + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_43.sql"))); + + int64 previous_default_search_provider_id; + + // Verify pre-conditions. These are expectations for version 43 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 43, 43)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + EXPECT_NE(default_search_provider_id, 0); + previous_default_search_provider_id = default_search_provider_id; + + EXPECT_NO_FATAL_FAILURE(CheckHasBackupData(&meta_table)); + EXPECT_TRUE(connection.DoesTableExist("keywords_backup")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init( + &connection, + kCurrentTestedVersionNumber, + kCurrentTestedVersionNumber)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + // Default search provider ID should not change. + EXPECT_EQ(previous_default_search_provider_id, default_search_provider_id); + + EXPECT_NO_FATAL_FAILURE(CheckNoBackupData(connection, &meta_table)); + } +} + +// Tests that the |autogenerate_keyword| and |logo_id| columns get removed from +// the keyword table schema for a version 45 database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion44ToCurrent) { + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_44.sql"))); + + // Verify pre-conditions. These are expectations for version 44 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 44, 44)); + + ASSERT_TRUE(connection.DoesColumnExist("keywords", "autogenerate_keyword")); + ASSERT_TRUE(connection.DoesColumnExist("keywords", "logo_id")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, kCurrentTestedVersionNumber, + kCurrentTestedVersionNumber)); + + // We should have removed this obsolete key. + std::string default_search_provider_backup; + EXPECT_FALSE(meta_table.GetValue("Default Search Provider Backup", + &default_search_provider_backup)); + + // Two columns should have been removed. + EXPECT_FALSE(connection.DoesColumnExist("keywords", + "autogenerate_keyword")); + EXPECT_FALSE(connection.DoesColumnExist("keywords", "logo_id")); + + // Backup data should have been removed. + EXPECT_NO_FATAL_FAILURE(CheckNoBackupData(connection, &meta_table)); + } +} + +// Tests that the web_intents and web_intents_defaults tables are +// modified to include "scheme" columns. +TEST_F(WebDatabaseMigrationTest, MigrateVersion45ToCurrent) { + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_45.sql"))); + + // Verify pre-conditions. These are expectations for version 45 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 45, 45)); + + ASSERT_FALSE(connection.DoesColumnExist("scheme", "web_intents")); + ASSERT_FALSE(connection.DoesColumnExist( + "scheme", "web_intents_defaults")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init( + &connection, + kCurrentTestedVersionNumber, + kCurrentTestedVersionNumber)); + + // A new "scheme" column should have been added to each web_intents table. + EXPECT_TRUE(connection.DoesColumnExist("web_intents", "scheme")); + EXPECT_TRUE(connection.DoesColumnExist("web_intents_defaults", "scheme")); + + // Verify existing user data was copied. + sql::Statement s1( + connection.GetUniqueStatement("SELECT * FROM web_intents")); + + ASSERT_TRUE(s1.Step()); + EXPECT_EQ("http://poodles.com/fuzzer", s1.ColumnString(0)); + EXPECT_EQ(ASCIIToUTF16("fuzz"), s1.ColumnString16(1)); + EXPECT_EQ(ASCIIToUTF16("poodle/*"), s1.ColumnString16(2)); + EXPECT_EQ(ASCIIToUTF16("Poodle Fuzzer"), s1.ColumnString16(3)); + EXPECT_EQ(ASCIIToUTF16("window"), s1.ColumnString16(4)); + EXPECT_EQ(ASCIIToUTF16(""), s1.ColumnString16(5)); + ASSERT_FALSE(s1.Step()); + + // Now we want to verify existing user data was copied + sql::Statement s2( + connection.GetUniqueStatement("SELECT * FROM web_intents_defaults")); + + ASSERT_TRUE(s2.Step()); + EXPECT_EQ("fuzz", s2.ColumnString(0)); + EXPECT_EQ(ASCIIToUTF16("poodle/*"), s2.ColumnString16(1)); + EXPECT_EQ(ASCIIToUTF16(""), s2.ColumnString16(2)); + EXPECT_EQ(0, s2.ColumnInt(3)); + EXPECT_EQ(0, s2.ColumnInt(4)); + EXPECT_EQ(ASCIIToUTF16("http://poodles.com/fuzzer"), s2.ColumnString16(5)); + EXPECT_EQ(ASCIIToUTF16(""), s2.ColumnString16(6)); + ASSERT_FALSE(s2.Step()); + + // finally ensure the migration code cleaned up after itself + EXPECT_FALSE(connection.DoesTableExist("old_web_intents")); + EXPECT_FALSE(connection.DoesTableExist("old_web_intents_defaults")); + } +} + +// Tests that the web_intents and web_intents_defaults tables are +// modified to include "scheme" columns. +TEST_F(WebDatabaseMigrationTest, MigrateVersion45InvalidToCurrent) { + ASSERT_NO_FATAL_FAILURE( + LoadDatabase(FILE_PATH_LITERAL("version_45_invalid.sql"))); + + // Verify pre-conditions. These are expectations for version 45 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 45, 45)); + + ASSERT_FALSE(connection.DoesColumnExist("scheme", "web_intents")); + ASSERT_FALSE(connection.DoesColumnExist( + "scheme", "web_intents_defaults")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init( + &connection, + kCurrentTestedVersionNumber, + kCurrentTestedVersionNumber)); + + // A new "scheme" column should have been added to each web_intents table. + EXPECT_TRUE(connection.DoesColumnExist("web_intents", "scheme")); + EXPECT_TRUE(connection.DoesColumnExist("web_intents_defaults", "scheme")); + + // Verify existing user data was copied. + sql::Statement s1( + connection.GetUniqueStatement("SELECT * FROM web_intents")); + + ASSERT_FALSE(s1.Step()); // Basically should be empty at this point. + + // Now we want to verify existing user data was copied + sql::Statement s2( + connection.GetUniqueStatement("SELECT * FROM web_intents_defaults")); + + // We were able to create the new tables, but unable to copy any data + // Given the initial bad state of the tables. + ASSERT_FALSE(s2.Step()); + + // Finally ensure the migration code cleaned up after itself. + EXPECT_FALSE(connection.DoesTableExist("old_web_intents")); + EXPECT_FALSE(connection.DoesTableExist("old_web_intents_defaults")); + } +} + +// Check that current version is forced to compatible version before migration, +// if the former is smaller. +TEST_F(WebDatabaseMigrationTest, MigrateVersion45CompatibleToCurrent) { + ASSERT_NO_FATAL_FAILURE( + LoadDatabase(FILE_PATH_LITERAL("version_45_compatible.sql"))); + + // Verify pre-conditions. These are expectations for version 45 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + // Database is actually version 45 but the version field states 40. + ASSERT_TRUE(meta_table.Init(&connection, 40, 45)); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + EXPECT_LE(45, VersionFromConnection(&connection)); + } +} + +// Tests that the |alternate_urls| column is added to the keyword table schema +// for a version 47 database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion46ToCurrent) { + ASSERT_NO_FATAL_FAILURE( + LoadDatabase(FILE_PATH_LITERAL("version_46.sql"))); + + // Verify pre-conditions. These are expectations for version 46 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 46, 46)); + + ASSERT_FALSE(connection.DoesColumnExist("keywords", "alternate_urls")); + ASSERT_FALSE(connection.DoesColumnExist("keywords_backup", + "alternate_urls")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // A new column should have been created. + EXPECT_TRUE(connection.DoesColumnExist("keywords", "alternate_urls")); + } +} + +// Tests that the backup data is removed from the database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion47ToCurrent) { + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_47.sql"))); + + // Verify pre-conditions. These are expectations for version 47 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 47, 47)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + EXPECT_NE(0, default_search_provider_id); + + EXPECT_NO_FATAL_FAILURE(CheckHasBackupData(&meta_table)); + EXPECT_TRUE(connection.DoesTableExist("keywords_backup")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, kCurrentTestedVersionNumber, + kCurrentTestedVersionNumber)); + + int64 default_search_provider_id = 0; + EXPECT_TRUE(meta_table.GetValue(KeywordTable::kDefaultSearchProviderKey, + &default_search_provider_id)); + EXPECT_NE(0, default_search_provider_id); + + EXPECT_NO_FATAL_FAILURE(CheckNoBackupData(connection, &meta_table)); + } +} + +// Tests that the |search_terms_replacement_key| column is added to the keyword +// table schema for a version 49 database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion48ToCurrent) { + ASSERT_NO_FATAL_FAILURE( + LoadDatabase(FILE_PATH_LITERAL("version_48.sql"))); + + // Verify pre-conditions. These are expectations for version 48 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 48, 48)); + + ASSERT_FALSE(connection.DoesColumnExist("keywords", + "search_terms_replacement_key")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // A new column should have been created. + EXPECT_TRUE(connection.DoesColumnExist("keywords", + "search_terms_replacement_key")); + } +} + +// Tests that the |origin| column is added to the autofill_profiles and +// credit_cards table schemas for a version 50 database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion49ToCurrent) { + ASSERT_NO_FATAL_FAILURE(LoadDatabase(FILE_PATH_LITERAL("version_49.sql"))); + + // Verify pre-conditions. These are expectations for version 49 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + + ASSERT_FALSE(connection.DoesColumnExist("autofill_profiles", "origin")); + ASSERT_FALSE(connection.DoesColumnExist("credit_cards", "origin")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // A new column should have been created in both tables. + EXPECT_TRUE(connection.DoesColumnExist("autofill_profiles", "origin")); + EXPECT_TRUE(connection.DoesColumnExist("credit_cards", "origin")); + } +} + +// Tests that the columns |image_url|, |search_url_post_params|, +// |suggest_url_post_params|, |instant_url_post_params|, |image_url_post_params| +// are added to the keyword table schema for a version 52 database. +TEST_F(WebDatabaseMigrationTest, MigrateVersion50ToCurrent) { + ASSERT_NO_FATAL_FAILURE( + LoadDatabase(FILE_PATH_LITERAL("version_50.sql"))); + + // Verify pre-conditions. These are expectations for version 50 of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + sql::MetaTable meta_table; + ASSERT_TRUE(meta_table.Init(&connection, 50, 50)); + + ASSERT_FALSE(connection.DoesColumnExist("keywords", "image_url")); + ASSERT_FALSE(connection.DoesColumnExist("keywords", + "search_url_post_params")); + ASSERT_FALSE(connection.DoesColumnExist("keywords", + "suggest_url_post_params")); + ASSERT_FALSE(connection.DoesColumnExist("keywords", + "instant_url_post_params")); + ASSERT_FALSE(connection.DoesColumnExist("keywords", + "image_url_post_params")); + } + + DoMigration(); + + // Verify post-conditions. These are expectations for current version of the + // database. + { + sql::Connection connection; + ASSERT_TRUE(connection.Open(GetDatabasePath())); + ASSERT_TRUE(sql::MetaTable::DoesTableExist(&connection)); + + // Check version. + EXPECT_EQ(kCurrentTestedVersionNumber, VersionFromConnection(&connection)); + + // New columns should have been created. + EXPECT_TRUE(connection.DoesColumnExist("keywords", "image_url")); + EXPECT_TRUE(connection.DoesColumnExist("keywords", + "search_url_post_params")); + EXPECT_TRUE(connection.DoesColumnExist("keywords", + "suggest_url_post_params")); + EXPECT_TRUE(connection.DoesColumnExist("keywords", + "instant_url_post_params")); + EXPECT_TRUE(connection.DoesColumnExist("keywords", + "image_url_post_params")); + } +} diff --git a/chromium/components/webdata/common/web_database_service.cc b/chromium/components/webdata/common/web_database_service.cc new file mode 100644 index 00000000000..11eb30b8bf1 --- /dev/null +++ b/chromium/components/webdata/common/web_database_service.cc @@ -0,0 +1,181 @@ +// 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/webdata/common/web_database_service.h" + +#include "base/bind.h" +#include "base/location.h" +#include "components/webdata/common/web_data_request_manager.h" +#include "components/webdata/common/web_data_results.h" +#include "components/webdata/common/web_data_service_backend.h" +#include "components/webdata/common/web_data_service_consumer.h" + +using base::Bind; +using base::FilePath; + +// Receives messages from the backend on the DB thread, posts them to +// WebDatabaseService on the UI thread. +class WebDatabaseService::BackendDelegate : + public WebDataServiceBackend::Delegate { + public: + BackendDelegate( + const base::WeakPtr<WebDatabaseService>& web_database_service) + : web_database_service_(web_database_service), + callback_thread_(base::MessageLoopProxy::current()) { + } + + virtual void DBLoaded(sql::InitStatus status) OVERRIDE { + callback_thread_->PostTask( + FROM_HERE, + base::Bind(&WebDatabaseService::OnDatabaseLoadDone, + web_database_service_, + status)); + } + private: + const base::WeakPtr<WebDatabaseService> web_database_service_; + scoped_refptr<base::MessageLoopProxy> callback_thread_; +}; + +WebDatabaseService::WebDatabaseService( + const base::FilePath& path, + const scoped_refptr<base::MessageLoopProxy>& ui_thread, + const scoped_refptr<base::MessageLoopProxy>& db_thread) + : base::RefCountedDeleteOnMessageLoop<WebDatabaseService>(ui_thread), + path_(path), + weak_ptr_factory_(this), + db_loaded_(false), + db_thread_(db_thread) { + // WebDatabaseService should be instantiated on UI thread. + DCHECK(ui_thread->BelongsToCurrentThread()); + // WebDatabaseService requires DB thread if instantiated. + DCHECK(db_thread.get()); +} + +WebDatabaseService::~WebDatabaseService() { +} + +void WebDatabaseService::AddTable(scoped_ptr<WebDatabaseTable> table) { + if (!wds_backend_.get()) { + wds_backend_ = new WebDataServiceBackend( + path_, new BackendDelegate(weak_ptr_factory_.GetWeakPtr()), + db_thread_); + } + wds_backend_->AddTable(table.Pass()); +} + +void WebDatabaseService::LoadDatabase() { + DCHECK(wds_backend_.get()); + + db_thread_->PostTask( + FROM_HERE, + Bind(&WebDataServiceBackend::InitDatabase, wds_backend_)); +} + +void WebDatabaseService::UnloadDatabase() { + db_loaded_ = false; + if (!wds_backend_.get()) + return; + db_thread_->PostTask(FROM_HERE, + Bind(&WebDataServiceBackend::ShutdownDatabase, + wds_backend_, true)); +} + +void WebDatabaseService::ShutdownDatabase() { + db_loaded_ = false; + weak_ptr_factory_.InvalidateWeakPtrs(); + loaded_callbacks_.clear(); + error_callbacks_.clear(); + if (!wds_backend_.get()) + return; + db_thread_->PostTask(FROM_HERE, + Bind(&WebDataServiceBackend::ShutdownDatabase, + wds_backend_, false)); +} + +WebDatabase* WebDatabaseService::GetDatabaseOnDB() const { + DCHECK(db_thread_->BelongsToCurrentThread()); + if (!wds_backend_.get()) + return NULL; + return wds_backend_->database(); +} + +scoped_refptr<WebDataServiceBackend> WebDatabaseService::GetBackend() const { + return wds_backend_; +} + +void WebDatabaseService::ScheduleDBTask( + const tracked_objects::Location& from_here, + const WriteTask& task) { + if (!wds_backend_.get()) { + NOTREACHED() << "Task scheduled after Shutdown()"; + return; + } + + scoped_ptr<WebDataRequest> request( + new WebDataRequest(NULL, wds_backend_->request_manager().get())); + + db_thread_->PostTask(from_here, + Bind(&WebDataServiceBackend::DBWriteTaskWrapper, wds_backend_, + task, base::Passed(&request))); +} + +WebDataServiceBase::Handle WebDatabaseService::ScheduleDBTaskWithResult( + const tracked_objects::Location& from_here, + const ReadTask& task, + WebDataServiceConsumer* consumer) { + DCHECK(consumer); + WebDataServiceBase::Handle handle = 0; + + if (!wds_backend_.get()) { + NOTREACHED() << "Task scheduled after Shutdown()"; + return handle; + } + + scoped_ptr<WebDataRequest> request( + new WebDataRequest(consumer, wds_backend_->request_manager().get())); + handle = request->GetHandle(); + + db_thread_->PostTask(from_here, + Bind(&WebDataServiceBackend::DBReadTaskWrapper, wds_backend_, + task, base::Passed(&request))); + + return handle; +} + +void WebDatabaseService::CancelRequest(WebDataServiceBase::Handle h) { + if (!wds_backend_.get()) + return; + wds_backend_->request_manager()->CancelRequest(h); +} + +void WebDatabaseService::RegisterDBLoadedCallback( + const DBLoadedCallback& callback) { + loaded_callbacks_.push_back(callback); +} + +void WebDatabaseService::RegisterDBErrorCallback( + const DBLoadErrorCallback& callback) { + error_callbacks_.push_back(callback); +} + +void WebDatabaseService::OnDatabaseLoadDone(sql::InitStatus status) { + if (status == sql::INIT_OK) { + db_loaded_ = true; + + for (size_t i = 0; i < loaded_callbacks_.size(); i++) { + if (!loaded_callbacks_[i].is_null()) + loaded_callbacks_[i].Run(); + } + + loaded_callbacks_.clear(); + } else { + // Notify that the database load failed. + for (size_t i = 0; i < error_callbacks_.size(); i++) { + if (!error_callbacks_[i].is_null()) + error_callbacks_[i].Run(status); + } + + error_callbacks_.clear(); + } +} diff --git a/chromium/components/webdata/common/web_database_service.h b/chromium/components/webdata/common/web_database_service.h new file mode 100644 index 00000000000..95ef5e2c7a1 --- /dev/null +++ b/chromium/components/webdata/common/web_database_service.h @@ -0,0 +1,155 @@ +// 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. + +// Chromium settings and storage represent user-selected preferences and +// information and MUST not be extracted, overwritten or modified except +// through Chromium defined APIs. + +#ifndef COMPONENTS_WEBDATA_COMMON_WEB_DATABASE_SERVICE_H_ +#define COMPONENTS_WEBDATA_COMMON_WEB_DATABASE_SERVICE_H_ + +#include "base/basictypes.h" +#include "base/callback_forward.h" +#include "base/compiler_specific.h" +#include "base/files/file_path.h" +#include "base/memory/ref_counted.h" +#include "base/memory/ref_counted_delete_on_message_loop.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/message_loop/message_loop_proxy.h" +#include "base/observer_list.h" +#include "components/webdata/common/web_data_service_base.h" +#include "components/webdata/common/web_database.h" +#include "components/webdata/common/webdata_export.h" + +class WebDataServiceBackend; +class WebDataRequestManager; + +namespace content { +class BrowserContext; +} + +namespace tracked_objects { +class Location; +} + +class WDTypedResult; +class WebDataServiceConsumer; + + +//////////////////////////////////////////////////////////////////////////////// +// +// WebDatabaseService defines the interface to a generic data repository +// responsible for controlling access to the web database (metadata associated +// with web pages). +// +//////////////////////////////////////////////////////////////////////////////// + +class WEBDATA_EXPORT WebDatabaseService + : public base::RefCountedDeleteOnMessageLoop<WebDatabaseService> { + public: + typedef base::Callback<scoped_ptr<WDTypedResult>(WebDatabase*)> ReadTask; + typedef base::Callback<WebDatabase::State(WebDatabase*)> WriteTask; + + // Types for managing DB loading callbacks. + typedef base::Closure DBLoadedCallback; + typedef base::Callback<void(sql::InitStatus)> DBLoadErrorCallback; + + // Takes the path to the WebDatabase file. + // WebDatabaseService lives on |ui_thread| and posts tasks to |db_thread|. + WebDatabaseService(const base::FilePath& path, + const scoped_refptr<base::MessageLoopProxy>& ui_thread, + const scoped_refptr<base::MessageLoopProxy>& db_thread); + + // Adds |table| as a WebDatabaseTable that will participate in + // managing the database, transferring ownership. All calls to this + // method must be made before |LoadDatabase| is called. + virtual void AddTable(scoped_ptr<WebDatabaseTable> table); + + // Initializes the web database service. + virtual void LoadDatabase(); + + // Unloads the database without actually shutting down the service. This can + // be used to temporarily reduce the browser process' memory footprint. + virtual void UnloadDatabase(); + + // Unloads database and will not reload. + virtual void ShutdownDatabase(); + + // Gets a pointer to the WebDatabase (owned by WebDatabaseService). + // TODO(caitkp): remove this method once SyncServices no longer depend on it. + virtual WebDatabase* GetDatabaseOnDB() const; + + // Returns a pointer to the WebDataServiceBackend. + scoped_refptr<WebDataServiceBackend> GetBackend() const; + + // Schedule an update/write task on the DB thread. + virtual void ScheduleDBTask( + const tracked_objects::Location& from_here, + const WriteTask& task); + + // Schedule a read task on the DB thread. + virtual WebDataServiceBase::Handle ScheduleDBTaskWithResult( + const tracked_objects::Location& from_here, + const ReadTask& task, + WebDataServiceConsumer* consumer); + + // Cancel an existing request for a task on the DB thread. + // TODO(caitkp): Think about moving the definition of the Handle type to + // somewhere else. + virtual void CancelRequest(WebDataServiceBase::Handle h); + + // Register a callback to be notified that the database has loaded. Multiple + // callbacks may be registered, and each will be called at most once + // (following a successful database load), then cleared. + // Note: if the database load is already complete, then the callback will NOT + // be stored or called. + void RegisterDBLoadedCallback(const DBLoadedCallback& callback); + + // Register a callback to be notified that the database has failed to load. + // Multiple callbacks may be registered, and each will be called at most once + // (following a database load failure), then cleared. + // Note: if the database load is already complete, then the callback will NOT + // be stored or called. + void RegisterDBErrorCallback(const DBLoadErrorCallback& callback); + + bool db_loaded() const { return db_loaded_; }; + + private: + class BackendDelegate; + friend class BackendDelegate; + friend class base::RefCountedDeleteOnMessageLoop<WebDatabaseService>; + friend class base::DeleteHelper<WebDatabaseService>; + + typedef std::vector<DBLoadedCallback> LoadedCallbacks; + typedef std::vector<DBLoadErrorCallback> ErrorCallbacks; + + virtual ~WebDatabaseService(); + + void OnDatabaseLoadDone(sql::InitStatus status); + + base::FilePath path_; + + // The primary owner is |WebDatabaseService| but is refcounted because + // PostTask on DB thread may outlive us. + scoped_refptr<WebDataServiceBackend> wds_backend_; + + // All vended weak pointers are invalidated in ShutdownDatabase(). + base::WeakPtrFactory<WebDatabaseService> weak_ptr_factory_; + + // Callbacks to be called once the DB has loaded. + LoadedCallbacks loaded_callbacks_; + + // Callbacks to be called if the DB has failed to load. + ErrorCallbacks error_callbacks_; + + // True if the WebDatabase has loaded. + bool db_loaded_; + + scoped_refptr<base::MessageLoopProxy> db_thread_; + + DISALLOW_COPY_AND_ASSIGN(WebDatabaseService); +}; + +#endif // COMPONENTS_WEBDATA_COMMON_WEB_DATABASE_SERVICE_H_ diff --git a/chromium/components/webdata/common/web_database_table.cc b/chromium/components/webdata/common/web_database_table.cc new file mode 100644 index 00000000000..57b5b96f20b --- /dev/null +++ b/chromium/components/webdata/common/web_database_table.cc @@ -0,0 +1,17 @@ +// 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/webdata/common/web_database_table.h" + +WebDatabaseTable::WebDatabaseTable() : db_(NULL), meta_table_(NULL) { +} + +WebDatabaseTable::~WebDatabaseTable() { +} + +bool WebDatabaseTable::Init(sql::Connection* db, sql::MetaTable* meta_table) { + db_ = db; + meta_table_ = meta_table; + return true; +} diff --git a/chromium/components/webdata/common/web_database_table.h b/chromium/components/webdata/common/web_database_table.h new file mode 100644 index 00000000000..b920ff61f16 --- /dev/null +++ b/chromium/components/webdata/common/web_database_table.h @@ -0,0 +1,66 @@ +// 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_WEBDATA_COMMON_WEB_DATABASE_TABLE_H_ +#define COMPONENTS_WEBDATA_COMMON_WEB_DATABASE_TABLE_H_ + +#include "base/logging.h" +#include "components/webdata/common/webdata_export.h" + +namespace sql { +class Connection; +class MetaTable; +} + +// An abstract base class representing a table within a WebDatabase. +// Each table should subclass this, adding type-specific methods as needed. +class WEBDATA_EXPORT WebDatabaseTable { + public: + // To look up a WebDatabaseTable of a certain type from WebDatabase, + // we use a void* key, so that we can simply use the address of one + // of the type's statics. + typedef void* TypeKey; + + // The object is not ready for use until Init() has been called. + WebDatabaseTable(); + virtual ~WebDatabaseTable(); + + // Retrieves the TypeKey for the concrete subtype. + virtual TypeKey GetTypeKey() const = 0; + + // Attempts to initialize the table and returns true if successful. + // + // The base class stores the members passed and always return true; + // subclasses may perform other initialization as needed. + virtual bool Init(sql::Connection* db, sql::MetaTable* meta_table); + + // In order to encourage developers to think about sync when adding or + // or altering new tables, this method must be implemented. Please get in + // contact with the sync team if you believe you're making a change that they + // should be aware of (or if you could break something). + // TODO(andybons): Implement something more robust. + virtual bool IsSyncable() = 0; + + // Migrates this table to |version|. Returns false if there was + // migration work to do and it failed, true otherwise. + // + // Implementations may set |*update_compatible_version| to true if + // the compatible version should be changed to |version|. + // Implementations should otherwise not modify this parameter. + virtual bool MigrateToVersion(int version, + bool* update_compatible_version) = 0; + + protected: + // Non-owning. These are owned by WebDatabase, valid as long as that + // class exists. Since lifetime of WebDatabaseTable objects slightly + // exceeds that of WebDatabase, they should not be used in + // ~WebDatabaseTable. + sql::Connection* db_; + sql::MetaTable* meta_table_; + + private: + DISALLOW_COPY_AND_ASSIGN(WebDatabaseTable); +}; + +#endif // COMPONENTS_WEBDATA_COMMON_WEB_DATABASE_TABLE_H_ diff --git a/chromium/components/webdata/common/webdata_constants.cc b/chromium/components/webdata/common/webdata_constants.cc new file mode 100644 index 00000000000..66dbdb40354 --- /dev/null +++ b/chromium/components/webdata/common/webdata_constants.cc @@ -0,0 +1,8 @@ +// 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/webdata/common/webdata_constants.h" + +const base::FilePath::CharType kWebDataFilename[] = + FILE_PATH_LITERAL("Web Data"); diff --git a/chromium/components/webdata/common/webdata_constants.h b/chromium/components/webdata/common/webdata_constants.h new file mode 100644 index 00000000000..0aeb1f70478 --- /dev/null +++ b/chromium/components/webdata/common/webdata_constants.h @@ -0,0 +1,13 @@ +// 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_WEBDATA_COMMON_WEBDATA_CONSTANTS_H_ +#define COMPONENTS_WEBDATA_COMMON_WEBDATA_CONSTANTS_H_ + +#include "base/files/file_path.h" +#include "components/webdata/common/webdata_export.h" + +WEBDATA_EXPORT extern const base::FilePath::CharType kWebDataFilename[]; + +#endif // COMPONENTS_WEBDATA_COMMON_WEBDATA_CONSTANTS_H_ diff --git a/chromium/components/webdata/common/webdata_export.h b/chromium/components/webdata/common/webdata_export.h new file mode 100644 index 00000000000..9441f191588 --- /dev/null +++ b/chromium/components/webdata/common/webdata_export.h @@ -0,0 +1,29 @@ +// 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_WEBDATA_COMMON_WEBDATA_EXPORT_H_ +#define COMPONENTS_WEBDATA_COMMON_WEBDATA_EXPORT_H_ + +#if defined(COMPONENT_BUILD) +#if defined(WIN32) + +#if defined(WEBDATA_IMPLEMENTATION) +#define WEBDATA_EXPORT __declspec(dllexport) +#else +#define WEBDATA_EXPORT __declspec(dllimport) +#endif // defined(WEBDATA_IMPLEMENTATION) + +#else // defined(WIN32) +#if defined(WEBDATA_IMPLEMENTATION) +#define WEBDATA_EXPORT __attribute__((visibility("default"))) +#else +#define WEBDATA_EXPORT +#endif +#endif + +#else // defined(COMPONENT_BUILD) +#define WEBDATA_EXPORT +#endif + +#endif // COMPONENTS_WEBDATA_COMMON_WEBDATA_EXPORT_H_ diff --git a/chromium/components/webdata/encryptor/DEPS b/chromium/components/webdata/encryptor/DEPS new file mode 100644 index 00000000000..d175bda6fe7 --- /dev/null +++ b/chromium/components/webdata/encryptor/DEPS @@ -0,0 +1,4 @@ +include_rules = [ + "-content", + "+crypto" +] diff --git a/chromium/components/webdata/encryptor/OWNERS b/chromium/components/webdata/encryptor/OWNERS new file mode 100644 index 00000000000..1967bf567e8 --- /dev/null +++ b/chromium/components/webdata/encryptor/OWNERS @@ -0,0 +1 @@ +thestig@chromium.org diff --git a/chromium/components/webdata/encryptor/README b/chromium/components/webdata/encryptor/README new file mode 100644 index 00000000000..ae18586d7b5 --- /dev/null +++ b/chromium/components/webdata/encryptor/README @@ -0,0 +1,4 @@ +Encryptor gives access to simple encryption and decryption of strings. + +On systems where available (currently Linux and Mac), this uses system +services to perform the encryption. diff --git a/chromium/components/webdata/encryptor/encryptor.h b/chromium/components/webdata/encryptor/encryptor.h new file mode 100644 index 00000000000..d8f1a363db2 --- /dev/null +++ b/chromium/components/webdata/encryptor/encryptor.h @@ -0,0 +1,51 @@ +// Copyright (c) 2010 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_WEBDATA_ENCRYPTOR_ENCRYPTOR_H_ +#define COMPONENTS_WEBDATA_ENCRYPTOR_ENCRYPTOR_H_ + +#include <string> + +#include "base/strings/string16.h" + +// The Encryptor class gives access to simple encryption and decryption of +// strings. Note that on Mac, access to the system Keychain is required and +// these calls can block the current thread to collect user input. +class Encryptor { + public: + // Encrypt a string16. The output (second argument) is + // really an array of bytes, but we're passing it back + // as a std::string + static bool EncryptString16(const base::string16& plaintext, + std::string* ciphertext); + + // Decrypt an array of bytes obtained with EncryptString16 + // back into a string16. Note that the input (first argument) + // is a std::string, so you need to first get your (binary) + // data into a string. + static bool DecryptString16(const std::string& ciphertext, + base::string16* plaintext); + + // Encrypt a string. + static bool EncryptString(const std::string& plaintext, + std::string* ciphertext); + + // Decrypt an array of bytes obtained with EnctryptString + // back into a string. Note that the input (first argument) + // is a std::string, so you need to first get your (binary) + // data into a string. + static bool DecryptString(const std::string& ciphertext, + std::string* plaintext); + +#if defined(OS_MACOSX) + // For unit testing purposes we instruct the Encryptor to use a mock Keychain + // on the Mac. The default is to use the real Keychain. + static void UseMockKeychain(bool use_mock); +#endif + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(Encryptor); +}; + +#endif // COMPONENTS_WEBDATA_ENCRYPTOR_ENCRYPTOR_H_ diff --git a/chromium/components/webdata/encryptor/encryptor_mac.mm b/chromium/components/webdata/encryptor/encryptor_mac.mm new file mode 100644 index 00000000000..089bf6c96f5 --- /dev/null +++ b/chromium/components/webdata/encryptor/encryptor_mac.mm @@ -0,0 +1,150 @@ +// 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/webdata/encryptor/encryptor.h" + +#include <CommonCrypto/CommonCryptor.h> // for kCCBlockSizeAES128 + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "components/webdata/encryptor/encryptor_password_mac.h" +#include "crypto/apple_keychain.h" +#include "crypto/encryptor.h" +#include "crypto/symmetric_key.h" + +using crypto::AppleKeychain; + +namespace { + +// Salt for Symmetric key derivation. +const char kSalt[] = "saltysalt"; + +// Key size required for 128 bit AES. +const size_t kDerivedKeySizeInBits = 128; + +// Constant for Symmetic key derivation. +const size_t kEncryptionIterations = 1003; + +// TODO(dhollowa): Refactor to allow dependency injection of Keychain. +static bool use_mock_keychain = false; + +// Prefix for cypher text returned by current encryption version. We prefix +// the cypher text with this string so that future data migration can detect +// this and migrate to different encryption without data loss. +const char kEncryptionVersionPrefix[] = "v10"; + +// Generates a newly allocated SymmetricKey object based on the password found +// in the Keychain. The generated key is for AES encryption. Ownership of the +// key is passed to the caller. Returns NULL key in the case password access +// is denied or key generation error occurs. +crypto::SymmetricKey* GetEncryptionKey() { + + std::string password; + if (use_mock_keychain) { + password = "mock_password"; + } else { + AppleKeychain keychain; + EncryptorPassword encryptor_password(keychain); + password = encryptor_password.GetEncryptorPassword(); + } + + if (password.empty()) + return NULL; + + std::string salt(kSalt); + + // Create an encryption key from our password and salt. + scoped_ptr<crypto::SymmetricKey> encryption_key( + crypto::SymmetricKey::DeriveKeyFromPassword(crypto::SymmetricKey::AES, + password, + salt, + kEncryptionIterations, + kDerivedKeySizeInBits)); + DCHECK(encryption_key.get()); + + return encryption_key.release(); +} + +} // namespace + +bool Encryptor::EncryptString16(const base::string16& plaintext, + std::string* ciphertext) { + return EncryptString(UTF16ToUTF8(plaintext), ciphertext); +} + +bool Encryptor::DecryptString16(const std::string& ciphertext, + base::string16* plaintext) { + std::string utf8; + if (!DecryptString(ciphertext, &utf8)) + return false; + + *plaintext = UTF8ToUTF16(utf8); + return true; +} + +bool Encryptor::EncryptString(const std::string& plaintext, + std::string* ciphertext) { + if (plaintext.empty()) { + *ciphertext = std::string(); + return true; + } + + scoped_ptr<crypto::SymmetricKey> encryption_key(GetEncryptionKey()); + if (!encryption_key.get()) + return false; + + std::string iv(kCCBlockSizeAES128, ' '); + crypto::Encryptor encryptor; + if (!encryptor.Init(encryption_key.get(), crypto::Encryptor::CBC, iv)) + return false; + + if (!encryptor.Encrypt(plaintext, ciphertext)) + return false; + + // Prefix the cypher text with version information. + ciphertext->insert(0, kEncryptionVersionPrefix); + return true; +} + +bool Encryptor::DecryptString(const std::string& ciphertext, + std::string* plaintext) { + if (ciphertext.empty()) { + *plaintext = std::string(); + return true; + } + + // Check that the incoming cyphertext was indeed encrypted with the expected + // version. If the prefix is not found then we'll assume we're dealing with + // old data saved as clear text and we'll return it directly. + // Credit card numbers are current legacy data, so false match with prefix + // won't happen. + if (ciphertext.find(kEncryptionVersionPrefix) != 0) { + *plaintext = ciphertext; + return true; + } + + // Strip off the versioning prefix before decrypting. + std::string raw_ciphertext = + ciphertext.substr(strlen(kEncryptionVersionPrefix)); + + scoped_ptr<crypto::SymmetricKey> encryption_key(GetEncryptionKey()); + if (!encryption_key.get()) + return false; + + std::string iv(kCCBlockSizeAES128, ' '); + crypto::Encryptor encryptor; + if (!encryptor.Init(encryption_key.get(), crypto::Encryptor::CBC, iv)) + return false; + + if (!encryptor.Decrypt(raw_ciphertext, plaintext)) + return false; + + return true; +} + +void Encryptor::UseMockKeychain(bool use_mock) { + use_mock_keychain = use_mock; +} + diff --git a/chromium/components/webdata/encryptor/encryptor_password_mac.h b/chromium/components/webdata/encryptor/encryptor_password_mac.h new file mode 100644 index 00000000000..f03db495c44 --- /dev/null +++ b/chromium/components/webdata/encryptor/encryptor_password_mac.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_WEBDATA_ENCRYPTOR_ENCRYPTOR_PASSWORD_MAC_H_ +#define COMPONENTS_WEBDATA_ENCRYPTOR_ENCRYPTOR_PASSWORD_MAC_H_ + +#include <string> + +#include "base/basictypes.h" + +namespace crypto { +class AppleKeychain; +} // namespace crypto + +class EncryptorPassword { + public: + explicit EncryptorPassword(const crypto::AppleKeychain& keychain) + : keychain_(keychain) { + } + + // Get the Encryptor password for this system. If no password exists + // in the Keychain then one is generated, stored in the Mac keychain, and + // returned. + // If one exists then it is fetched from the Keychain and returned. + // If the user disallows access to the Keychain (or an error occurs) then an + // empty string is returned. + std::string GetEncryptorPassword() const; + + private: + DISALLOW_COPY_AND_ASSIGN(EncryptorPassword); + const crypto::AppleKeychain& keychain_; +}; + +#endif // COMPONENTS_WEBDATA_ENCRYPTOR_ENCRYPTOR_PASSWORD_MAC_H_ diff --git a/chromium/components/webdata/encryptor/encryptor_password_mac.mm b/chromium/components/webdata/encryptor/encryptor_password_mac.mm new file mode 100644 index 00000000000..f8178014df0 --- /dev/null +++ b/chromium/components/webdata/encryptor/encryptor_password_mac.mm @@ -0,0 +1,79 @@ +// 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/webdata/encryptor/encryptor_password_mac.h" + +#import <Security/Security.h> + +#include "base/base64.h" +#include "base/mac/mac_logging.h" +#include "base/rand_util.h" +#include "crypto/apple_keychain.h" + +using crypto::AppleKeychain; + +namespace { + +// Generates a random password and adds it to the Keychain. The added password +// is returned from the function. If an error occurs, an empty password is +// returned. +std::string AddRandomPasswordToKeychain(const AppleKeychain& keychain, + const std::string& service_name, + const std::string& account_name) { + // Generate a password with 128 bits of randomness. + const int kBytes = 128 / 8; + std::string password; + base::Base64Encode(base::RandBytesAsString(kBytes), &password); + void* password_data = + const_cast<void*>(static_cast<const void*>(password.data())); + + OSStatus error = keychain.AddGenericPassword(NULL, + service_name.size(), + service_name.data(), + account_name.size(), + account_name.data(), + password.size(), + password_data, + NULL); + + if (error != noErr) { + OSSTATUS_DLOG(ERROR, error) << "Keychain add failed"; + return std::string(); + } + + return password; +} + +} // namespace + +std::string EncryptorPassword::GetEncryptorPassword() const { + // These two strings ARE indeed user facing. But they are used to access + // the encryption keyword. So as to not lose encrypted data when system + // locale changes we DO NOT LOCALIZE. + const std::string service_name = "Chrome Safe Storage"; + const std::string account_name = "Chrome"; + + UInt32 password_length = 0; + void* password_data = NULL; + OSStatus error = keychain_.FindGenericPassword(NULL, + service_name.size(), + service_name.data(), + account_name.size(), + account_name.data(), + &password_length, + &password_data, + NULL); + + if (error == noErr) { + std::string password = + std::string(static_cast<char*>(password_data), password_length); + keychain_.ItemFreeContent(NULL, password_data); + return password; + } else if (error == errSecItemNotFound) { + return AddRandomPasswordToKeychain(keychain_, service_name, account_name); + } else { + OSSTATUS_DLOG(ERROR, error) << "Keychain lookup failed"; + return std::string(); + } +} diff --git a/chromium/components/webdata/encryptor/encryptor_password_mac_unittest.cc b/chromium/components/webdata/encryptor/encryptor_password_mac_unittest.cc new file mode 100644 index 00000000000..97462229046 --- /dev/null +++ b/chromium/components/webdata/encryptor/encryptor_password_mac_unittest.cc @@ -0,0 +1,79 @@ +// 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/webdata/encryptor/encryptor_password_mac.h" +#include "crypto/mock_apple_keychain.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +using crypto::MockAppleKeychain; + +// Test that if we have an existing password in the Keychain and we are +// authorized by the user to read it then we get it back correctly. +TEST(EncryptorPasswordTest, FindPasswordSuccess) { + MockAppleKeychain keychain; + keychain.set_find_generic_result(noErr); + EncryptorPassword password(keychain); + EXPECT_FALSE(password.GetEncryptorPassword().empty()); + EXPECT_FALSE(keychain.called_add_generic()); + EXPECT_EQ(0, keychain.password_data_count()); +} + +// Test that if we do not have an existing password in the Keychain then it +// gets added successfully and returned. +TEST(EncryptorPasswordTest, FindPasswordNotFound) { + MockAppleKeychain keychain; + keychain.set_find_generic_result(errSecItemNotFound); + EncryptorPassword password(keychain); + EXPECT_EQ(24U, password.GetEncryptorPassword().length()); + EXPECT_TRUE(keychain.called_add_generic()); + EXPECT_EQ(0, keychain.password_data_count()); +} + +// Test that if get denied access by the user then we return an empty password. +// And we should not try to add one. +TEST(EncryptorPasswordTest, FindPasswordNotAuthorized) { + MockAppleKeychain keychain; + keychain.set_find_generic_result(errSecAuthFailed); + EncryptorPassword password(keychain); + EXPECT_TRUE(password.GetEncryptorPassword().empty()); + EXPECT_FALSE(keychain.called_add_generic()); + EXPECT_EQ(0, keychain.password_data_count()); +} + +// Test that if some random other error happens then we return an empty +// password, and we should not try to add one. +TEST(EncryptorPasswordTest, FindPasswordOtherError) { + MockAppleKeychain keychain; + keychain.set_find_generic_result(errSecNotAvailable); + EncryptorPassword password(keychain); + EXPECT_TRUE(password.GetEncryptorPassword().empty()); + EXPECT_FALSE(keychain.called_add_generic()); + EXPECT_EQ(0, keychain.password_data_count()); +} + +// Test that subsequent additions to the keychain give different passwords. +TEST(EncryptorPasswordTest, PasswordsDiffer) { + MockAppleKeychain keychain1; + keychain1.set_find_generic_result(errSecItemNotFound); + EncryptorPassword encryptor_password1(keychain1); + std::string password1 = encryptor_password1.GetEncryptorPassword(); + EXPECT_FALSE(password1.empty()); + EXPECT_TRUE(keychain1.called_add_generic()); + EXPECT_EQ(0, keychain1.password_data_count()); + + MockAppleKeychain keychain2; + keychain2.set_find_generic_result(errSecItemNotFound); + EncryptorPassword encryptor_password2(keychain2); + std::string password2 = encryptor_password2.GetEncryptorPassword(); + EXPECT_FALSE(password2.empty()); + EXPECT_TRUE(keychain2.called_add_generic()); + EXPECT_EQ(0, keychain2.password_data_count()); + + // And finally check that the passwords are different. + EXPECT_NE(password1, password2); +} + +} // namespace diff --git a/chromium/components/webdata/encryptor/encryptor_posix.cc b/chromium/components/webdata/encryptor/encryptor_posix.cc new file mode 100644 index 00000000000..ae16af559c7 --- /dev/null +++ b/chromium/components/webdata/encryptor/encryptor_posix.cc @@ -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. + +#include "components/webdata/encryptor/encryptor.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/strings/utf_string_conversions.h" +#include "crypto/encryptor.h" +#include "crypto/symmetric_key.h" + +namespace { + +// Salt for Symmetric key derivation. +const char kSalt[] = "saltysalt"; + +// Key size required for 128 bit AES. +const size_t kDerivedKeySizeInBits = 128; + +// Constant for Symmetic key derivation. +const size_t kEncryptionIterations = 1; + +// Size of initialization vector for AES 128-bit. +const size_t kIVBlockSizeAES128 = 16; + +// Prefix for cypher text returned by obfuscation version. We prefix the +// cyphertext with this string so that future data migration can detect +// this and migrate to full encryption without data loss. +const char kObfuscationPrefix[] = "v10"; + +// Generates a newly allocated SymmetricKey object based a hard-coded password. +// Ownership of the key is passed to the caller. Returns NULL key if a key +// generation error occurs. +crypto::SymmetricKey* GetEncryptionKey() { + // We currently "obfuscate" by encrypting and decrypting with hard-coded + // password. We need to improve this password situation by moving a secure + // password into a system-level key store. + // http://crbug.com/25404 and http://crbug.com/49115 + std::string password = "peanuts"; + std::string salt(kSalt); + + // Create an encryption key from our password and salt. + scoped_ptr<crypto::SymmetricKey> encryption_key( + crypto::SymmetricKey::DeriveKeyFromPassword(crypto::SymmetricKey::AES, + password, + salt, + kEncryptionIterations, + kDerivedKeySizeInBits)); + DCHECK(encryption_key.get()); + + return encryption_key.release(); +} + +} // namespace + +bool Encryptor::EncryptString16(const base::string16& plaintext, + std::string* ciphertext) { + return EncryptString(UTF16ToUTF8(plaintext), ciphertext); +} + +bool Encryptor::DecryptString16(const std::string& ciphertext, + base::string16* plaintext) { + std::string utf8; + if (!DecryptString(ciphertext, &utf8)) + return false; + + *plaintext = UTF8ToUTF16(utf8); + return true; +} + +bool Encryptor::EncryptString(const std::string& plaintext, + std::string* ciphertext) { + // This currently "obfuscates" by encrypting with hard-coded password. + // We need to improve this password situation by moving a secure password + // into a system-level key store. + // http://crbug.com/25404 and http://crbug.com/49115 + + if (plaintext.empty()) { + *ciphertext = std::string(); + return true; + } + + scoped_ptr<crypto::SymmetricKey> encryption_key(GetEncryptionKey()); + if (!encryption_key.get()) + return false; + + std::string iv(kIVBlockSizeAES128, ' '); + crypto::Encryptor encryptor; + if (!encryptor.Init(encryption_key.get(), crypto::Encryptor::CBC, iv)) + return false; + + if (!encryptor.Encrypt(plaintext, ciphertext)) + return false; + + // Prefix the cypher text with version information. + ciphertext->insert(0, kObfuscationPrefix); + return true; +} + +bool Encryptor::DecryptString(const std::string& ciphertext, + std::string* plaintext) { + // This currently "obfuscates" by encrypting with hard-coded password. + // We need to improve this password situation by moving a secure password + // into a system-level key store. + // http://crbug.com/25404 and http://crbug.com/49115 + + if (ciphertext.empty()) { + *plaintext = std::string(); + return true; + } + + // Check that the incoming cyphertext was indeed encrypted with the expected + // version. If the prefix is not found then we'll assume we're dealing with + // old data saved as clear text and we'll return it directly. + // Credit card numbers are current legacy data, so false match with prefix + // won't happen. + if (ciphertext.find(kObfuscationPrefix) != 0) { + *plaintext = ciphertext; + return true; + } + + // Strip off the versioning prefix before decrypting. + std::string raw_ciphertext = ciphertext.substr(strlen(kObfuscationPrefix)); + + scoped_ptr<crypto::SymmetricKey> encryption_key(GetEncryptionKey()); + if (!encryption_key.get()) + return false; + + std::string iv(kIVBlockSizeAES128, ' '); + crypto::Encryptor encryptor; + if (!encryptor.Init(encryption_key.get(), crypto::Encryptor::CBC, iv)) + return false; + + if (!encryptor.Decrypt(raw_ciphertext, plaintext)) + return false; + + return true; +} diff --git a/chromium/components/webdata/encryptor/encryptor_unittest.cc b/chromium/components/webdata/encryptor/encryptor_unittest.cc new file mode 100644 index 00000000000..95158f1f41f --- /dev/null +++ b/chromium/components/webdata/encryptor/encryptor_unittest.cc @@ -0,0 +1,143 @@ +// Copyright (c) 2010 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/webdata/encryptor/encryptor.h" + +#include <string> + +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class EncryptorTest : public testing::Test { + public: + EncryptorTest() {} + + virtual void SetUp() { +#if defined(OS_MACOSX) + Encryptor::UseMockKeychain(true); +#endif + } + + private: + DISALLOW_COPY_AND_ASSIGN(EncryptorTest); +}; + +TEST_F(EncryptorTest, String16EncryptionDecryption) { + base::string16 plaintext; + base::string16 result; + std::string utf8_plaintext; + std::string utf8_result; + std::string ciphertext; + + // Test borderline cases (empty strings). + EXPECT_TRUE(Encryptor::EncryptString16(plaintext, &ciphertext)); + EXPECT_TRUE(Encryptor::DecryptString16(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + + // Test a simple string. + plaintext = ASCIIToUTF16("hello"); + EXPECT_TRUE(Encryptor::EncryptString16(plaintext, &ciphertext)); + EXPECT_TRUE(Encryptor::DecryptString16(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + + // Test a 16-byte aligned string. This previously hit a boundary error in + // base::Encryptor::Crypt() on Mac. + plaintext = ASCIIToUTF16("1234567890123456"); + EXPECT_TRUE(Encryptor::EncryptString16(plaintext, &ciphertext)); + EXPECT_TRUE(Encryptor::DecryptString16(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + + // Test Unicode. + char16 wchars[] = { 0xdbeb, 0xdf1b, 0x4e03, 0x6708, 0x8849, + 0x661f, 0x671f, 0x56db, 0x597c, 0x4e03, + 0x6708, 0x56db, 0x6708, 0xe407, 0xdbaf, + 0xdeb5, 0x4ec5, 0x544b, 0x661f, 0x671f, + 0x65e5, 0x661f, 0x671f, 0x4e94, 0xd8b1, + 0xdce1, 0x7052, 0x5095, 0x7c0b, 0xe586, 0}; + plaintext = wchars; + utf8_plaintext = UTF16ToUTF8(plaintext); + EXPECT_EQ(plaintext, UTF8ToUTF16(utf8_plaintext)); + EXPECT_TRUE(Encryptor::EncryptString16(plaintext, &ciphertext)); + EXPECT_TRUE(Encryptor::DecryptString16(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + EXPECT_TRUE(Encryptor::DecryptString(ciphertext, &utf8_result)); + EXPECT_EQ(utf8_plaintext, UTF16ToUTF8(result)); + + EXPECT_TRUE(Encryptor::EncryptString(utf8_plaintext, &ciphertext)); + EXPECT_TRUE(Encryptor::DecryptString16(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + EXPECT_TRUE(Encryptor::DecryptString(ciphertext, &utf8_result)); + EXPECT_EQ(utf8_plaintext, UTF16ToUTF8(result)); +} + +TEST_F(EncryptorTest, EncryptionDecryption) { + std::string plaintext; + std::string result; + std::string ciphertext; + + // Test borderline cases (empty strings). + ASSERT_TRUE(Encryptor::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(Encryptor::DecryptString(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + + // Test a simple string. + plaintext = "hello"; + ASSERT_TRUE(Encryptor::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(Encryptor::DecryptString(ciphertext, &result)); + EXPECT_EQ(plaintext, result); + + // Make sure it null terminates. + plaintext.assign("hello", 3); + ASSERT_TRUE(Encryptor::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(Encryptor::DecryptString(ciphertext, &result)); + EXPECT_EQ(plaintext, "hel"); +} + +TEST_F(EncryptorTest, CypherTextDiffers) { + std::string plaintext; + std::string result; + std::string ciphertext; + + // Test borderline cases (empty strings). + ASSERT_TRUE(Encryptor::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(Encryptor::DecryptString(ciphertext, &result)); + // |cyphertext| is empty on the Mac, different on Windows. + EXPECT_TRUE(ciphertext.empty() || plaintext != ciphertext); + EXPECT_EQ(plaintext, result); + + // Test a simple string. + plaintext = "hello"; + ASSERT_TRUE(Encryptor::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(Encryptor::DecryptString(ciphertext, &result)); + EXPECT_NE(plaintext, ciphertext); + EXPECT_EQ(plaintext, result); + + // Make sure it null terminates. + plaintext.assign("hello", 3); + ASSERT_TRUE(Encryptor::EncryptString(plaintext, &ciphertext)); + ASSERT_TRUE(Encryptor::DecryptString(ciphertext, &result)); + EXPECT_NE(plaintext, ciphertext); + EXPECT_EQ(result, "hel"); +} + +TEST_F(EncryptorTest, DecryptError) { + std::string plaintext; + std::string result; + std::string ciphertext; + + // Test a simple string, messing with ciphertext prior to decrypting. + plaintext = "hello"; + ASSERT_TRUE(Encryptor::EncryptString(plaintext, &ciphertext)); + EXPECT_NE(plaintext, ciphertext); + ASSERT_LT(4UL, ciphertext.size()); + ciphertext[3] = ciphertext[3] + 1; + EXPECT_FALSE(Encryptor::DecryptString(ciphertext, &result)); + EXPECT_NE(plaintext, result); + EXPECT_TRUE(result.empty()); +} + +} // namespace diff --git a/chromium/components/webdata/encryptor/encryptor_win.cc b/chromium/components/webdata/encryptor/encryptor_win.cc new file mode 100644 index 00000000000..adf792ff326 --- /dev/null +++ b/chromium/components/webdata/encryptor/encryptor_win.cc @@ -0,0 +1,65 @@ +// Copyright (c) 2006-2008 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/webdata/encryptor/encryptor.h" + +#include <windows.h> +#include <wincrypt.h> +#include "base/strings/utf_string_conversions.h" + +#pragma comment(lib, "crypt32.lib") + +bool Encryptor::EncryptString16(const base::string16& plaintext, + std::string* ciphertext) { + return EncryptString(UTF16ToUTF8(plaintext), ciphertext); +} + +bool Encryptor::DecryptString16(const std::string& ciphertext, + base::string16* plaintext) { + std::string utf8; + if (!DecryptString(ciphertext, &utf8)) + return false; + + *plaintext = UTF8ToUTF16(utf8); + return true; +} + +bool Encryptor::EncryptString(const std::string& plaintext, + std::string* ciphertext) { + DATA_BLOB input; + input.pbData = const_cast<BYTE*>( + reinterpret_cast<const BYTE*>(plaintext.data())); + input.cbData = static_cast<DWORD>(plaintext.length()); + + DATA_BLOB output; + BOOL result = CryptProtectData(&input, L"", NULL, NULL, NULL, + 0, &output); + if (!result) + return false; + + // this does a copy + ciphertext->assign(reinterpret_cast<std::string::value_type*>(output.pbData), + output.cbData); + + LocalFree(output.pbData); + return true; +} + +bool Encryptor::DecryptString(const std::string& ciphertext, + std::string* plaintext) { + DATA_BLOB input; + input.pbData = const_cast<BYTE*>( + reinterpret_cast<const BYTE*>(ciphertext.data())); + input.cbData = static_cast<DWORD>(ciphertext.length()); + + DATA_BLOB output; + BOOL result = CryptUnprotectData(&input, NULL, NULL, NULL, NULL, + 0, &output); + if (!result) + return false; + + plaintext->assign(reinterpret_cast<char*>(output.pbData), output.cbData); + LocalFree(output.pbData); + return true; +} diff --git a/chromium/components/webdata/encryptor/ie7_password.cc b/chromium/components/webdata/encryptor/ie7_password.cc new file mode 100644 index 00000000000..e7db3ab3be0 --- /dev/null +++ b/chromium/components/webdata/encryptor/ie7_password.cc @@ -0,0 +1,144 @@ +// 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/webdata/encryptor/ie7_password.h" + +#include <wincrypt.h> +#include <string> +#include <vector> + +#include "base/memory/scoped_ptr.h" +#include "base/sha1.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" + +namespace { + +// Structures that IE7/IE8 use to store a username/password. +// Some of the fields might have been incorrectly reverse engineered. +struct PreHeader { + DWORD pre_header_size; // Size of this header structure. Always 12. + DWORD header_size; // Size of the real Header: sizeof(Header) + + // item_count * sizeof(Entry); + DWORD data_size; // Size of the data referenced by the entries. +}; + +struct Header { + char wick[4]; // The string "WICK". I don't know what it means. + DWORD fixed_header_size; // The size of this structure without the entries: + // sizeof(Header). + DWORD item_count; // Number of entries. It should always be 2. One for + // the username, and one for the password. + wchar_t two_letters[2]; // Two unknown bytes. + DWORD unknown[2]; // Two unknown DWORDs. +}; + +struct Entry { + DWORD offset; // Offset where the data referenced by this entry is + // located. + FILETIME time_stamp; // Timestamp when the password got added. + DWORD string_length; // The length of the data string. +}; + +// Main data structure. +struct PasswordEntry { + PreHeader pre_header; // Contains the size of the different sections. + Header header; // Contains the number of items. + Entry entry[1]; // List of entries containing a string. The first one + // is the username, the second one if the password. +}; + +} // namespace + +namespace ie7_password { + +bool GetUserPassFromData(const std::vector<unsigned char>& data, + std::wstring* username, + std::wstring* password) { + const PasswordEntry* information = + reinterpret_cast<const PasswordEntry*>(&data.front()); + + // Some expected values. If it's not what we expect we don't even try to + // understand the data. + if (information->pre_header.pre_header_size != sizeof(PreHeader)) + return false; + + if (information->header.item_count != 2) // Username and Password + return false; + + if (information->header.fixed_header_size != sizeof(Header)) + return false; + + const uint8* ptr = &data.front(); + const uint8* offset_to_data = ptr + information->pre_header.header_size + + information->pre_header.pre_header_size; + + const Entry* user_entry = information->entry; + const Entry* pass_entry = user_entry+1; + + *username = reinterpret_cast<const wchar_t*>(offset_to_data + + user_entry->offset); + *password = reinterpret_cast<const wchar_t*>(offset_to_data + + pass_entry->offset); + return true; +} + +std::wstring GetUrlHash(const std::wstring& url) { + std::wstring lower_case_url = StringToLowerASCII(url); + // Get a data buffer out of our std::wstring to pass to SHA1HashString. + std::string url_buffer( + reinterpret_cast<const char*>(lower_case_url.c_str()), + (lower_case_url.size() + 1) * sizeof(wchar_t)); + std::string hash_bin = base::SHA1HashString(url_buffer); + + std::wstring url_hash; + + // Transform the buffer to an hexadecimal string. + unsigned char checksum = 0; + for (size_t i = 0; i < hash_bin.size(); ++i) { + // std::string gives signed chars, which mess with StringPrintf and + // check_sum. + unsigned char hash_byte = static_cast<unsigned char>(hash_bin[i]); + checksum += hash_byte; + url_hash += base::StringPrintf(L"%2.2X", static_cast<unsigned>(hash_byte)); + } + url_hash += base::StringPrintf(L"%2.2X", checksum); + + return url_hash; +} + +bool DecryptPassword(const std::wstring& url, + const std::vector<unsigned char>& data, + std::wstring* username, std::wstring* password) { + std::wstring lower_case_url = StringToLowerASCII(url); + DATA_BLOB input = {0}; + DATA_BLOB output = {0}; + DATA_BLOB url_key = {0}; + + input.pbData = const_cast<unsigned char*>(&data.front()); + input.cbData = static_cast<DWORD>((data.size()) * + sizeof(std::string::value_type)); + + url_key.pbData = reinterpret_cast<unsigned char*>( + const_cast<wchar_t*>(lower_case_url.data())); + url_key.cbData = static_cast<DWORD>((lower_case_url.size() + 1) * + sizeof(std::wstring::value_type)); + + if (CryptUnprotectData(&input, NULL, &url_key, NULL, NULL, + CRYPTPROTECT_UI_FORBIDDEN, &output)) { + // Now that we have the decrypted information, we need to understand it. + std::vector<unsigned char> decrypted_data; + decrypted_data.resize(output.cbData); + memcpy(&decrypted_data.front(), output.pbData, output.cbData); + + GetUserPassFromData(decrypted_data, username, password); + + LocalFree(output.pbData); + return true; + } + + return false; +} + +} // namespace ie7_password diff --git a/chromium/components/webdata/encryptor/ie7_password.h b/chromium/components/webdata/encryptor/ie7_password.h new file mode 100644 index 00000000000..458426640d8 --- /dev/null +++ b/chromium/components/webdata/encryptor/ie7_password.h @@ -0,0 +1,46 @@ +// Copyright (c) 2006-2008 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_WEBDATA_ENCRYPTOR_IE7_PASSWORD_H_ +#define COMPONENTS_WEBDATA_ENCRYPTOR_IE7_PASSWORD_H_ + +#include <windows.h> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/time/time.h" + +// Contains the information read from the IE7/IE8 Storage2 key in the registry. +struct IE7PasswordInfo { + // Hash of the url. + std::wstring url_hash; + + // Encrypted data containing the username, password and some more undocumented + // fields. + std::vector<unsigned char> encrypted_data; + + // When the login was imported. + base::Time date_created; +}; + +namespace ie7_password { + +// Parses a data structure to find the password and the username. +bool GetUserPassFromData(const std::vector<unsigned char>& data, + std::wstring* username, + std::wstring* password); + +// Decrypts the username and password for a given data vector using the url as +// the key. +bool DecryptPassword(const std::wstring& url, + const std::vector<unsigned char>& data, + std::wstring* username, std::wstring* password); + +// Returns the hash of a url. +std::wstring GetUrlHash(const std::wstring& url); + +} // namespace ie7_password + +#endif // COMPONENTS_WEBDATA_ENCRYPTOR_IE7_PASSWORD_H_ diff --git a/chromium/components/webdata/encryptor/ie7_password_unittest_win.cc b/chromium/components/webdata/encryptor/ie7_password_unittest_win.cc new file mode 100644 index 00000000000..37021e8b99e --- /dev/null +++ b/chromium/components/webdata/encryptor/ie7_password_unittest_win.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 "testing/gtest/include/gtest/gtest.h" + +#include <windows.h> + +#include <vector> + +#include "base/strings/string16.h" +#include "components/webdata/encryptor/ie7_password.h" + +TEST(IE7PasswordTest, GetUserPassword) { + // This is the unencrypted values of my keys under Storage2. + // The passwords have been manually changed to abcdef... but the size remains + // the same. + unsigned char data1[] = "\x0c\x00\x00\x00\x38\x00\x00\x00\x2c\x00\x00\x00" + "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00" + "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x4e\xfa\x67\x76\x22\x94\xc8\x01" + "\x08\x00\x00\x00\x12\x00\x00\x00\x4e\xfa\x67\x76" + "\x22\x94\xc8\x01\x0c\x00\x00\x00\x61\x00\x62\x00" + "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00" + "\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00\x65\x00" + "\x66\x00\x67\x00\x68\x00\x69\x00\x6a\x00\x6b\x00" + "\x6c\x00\x00\x00"; + + unsigned char data2[] = "\x0c\x00\x00\x00\x38\x00\x00\x00\x24\x00\x00\x00" + "\x57\x49\x43\x4b\x18\x00\x00\x00\x02\x00\x00\x00" + "\x67\x00\x72\x00\x01\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\xa8\xea\xf4\xe5\x9f\x9a\xc8\x01" + "\x09\x00\x00\x00\x14\x00\x00\x00\xa8\xea\xf4\xe5" + "\x9f\x9a\xc8\x01\x07\x00\x00\x00\x61\x00\x62\x00" + "\x63\x00\x64\x00\x65\x00\x66\x00\x67\x00\x68\x00" + "\x69\x00\x00\x00\x61\x00\x62\x00\x63\x00\x64\x00" + "\x65\x00\x66\x00\x67\x00\x00\x00"; + + + + std::vector<unsigned char> decrypted_data1; + decrypted_data1.resize(arraysize(data1)); + memcpy(&decrypted_data1.front(), data1, sizeof(data1)); + + std::vector<unsigned char> decrypted_data2; + decrypted_data2.resize(arraysize(data2)); + memcpy(&decrypted_data2.front(), data2, sizeof(data2)); + + string16 password; + string16 username; + ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data1, &username, + &password)); + EXPECT_EQ(L"abcdefgh", username); + EXPECT_EQ(L"abcdefghijkl", password); + + ASSERT_TRUE(ie7_password::GetUserPassFromData(decrypted_data2, &username, + &password)); + EXPECT_EQ(L"abcdefghi", username); + EXPECT_EQ(L"abcdefg", password); +} |