diff options
author | rouslan <rouslan@chromium.org> | 2015-06-23 15:10:15 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-06-23 22:11:51 +0000 |
commit | fd7f4646661ae21fce8658b0a39b9ffe9616b897 (patch) | |
tree | 75abdf68bc2a24c94f63bfe30c907faf54644131 | |
parent | 611a53e15beeda82fe4f00b04d8dfba33043e356 (diff) | |
download | chromium_src-fd7f4646661ae21fce8658b0a39b9ffe9616b897.zip chromium_src-fd7f4646661ae21fce8658b0a39b9ffe9616b897.tar.gz chromium_src-fd7f4646661ae21fce8658b0a39b9ffe9616b897.tar.bz2 |
Reland: [autofill] Sync server card and address metadata.
First landed in: http://crrev.com/7c5be3b
Reverted due to crashing in: http://crrev.com/df8814b
This patch has the original patch and the fix for the crash. The fix is
tested in AutofillProfileSyncableServiceTest.IgnoreServerProfileUpdate.
BUG=481595
Review URL: https://codereview.chromium.org/1172133002
Cr-Commit-Position: refs/heads/master@{#335766}
17 files changed, 1653 insertions, 63 deletions
diff --git a/chrome/browser/sync/profile_sync_components_factory_impl.cc b/chrome/browser/sync/profile_sync_components_factory_impl.cc index afd8ae2..3e5d1f4 100644 --- a/chrome/browser/sync/profile_sync_components_factory_impl.cc +++ b/chrome/browser/sync/profile_sync_components_factory_impl.cc @@ -45,6 +45,7 @@ #include "chrome/common/pref_names.h" #include "components/autofill/core/browser/webdata/autocomplete_syncable_service.h" #include "components/autofill/core/browser/webdata/autofill_profile_syncable_service.h" +#include "components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.h" #include "components/autofill/core/browser/webdata/autofill_wallet_syncable_service.h" #include "components/autofill/core/browser/webdata/autofill_webdata_service.h" #include "components/autofill/core/common/autofill_pref_names.h" @@ -519,7 +520,8 @@ base::WeakPtr<syncer::SyncableService> ProfileSyncComponentsFactoryImpl:: return autofill::AutofillProfileSyncableService::FromWebDataService( web_data_service_.get())->AsWeakPtr(); } else if (type == syncer::AUTOFILL_WALLET_METADATA) { - return base::WeakPtr<syncer::SyncableService>(); + return autofill::AutofillWalletMetadataSyncableService:: + FromWebDataService(web_data_service_.get())->AsWeakPtr(); } return autofill::AutofillWalletSyncableService::FromWebDataService( web_data_service_.get())->AsWeakPtr(); diff --git a/components/autofill.gypi b/components/autofill.gypi index 355aefe..baa477d 100644 --- a/components/autofill.gypi +++ b/components/autofill.gypi @@ -202,6 +202,8 @@ 'autofill/core/browser/webdata/autofill_profile_syncable_service.h', 'autofill/core/browser/webdata/autofill_table.cc', 'autofill/core/browser/webdata/autofill_table.h', + 'autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.cc', + 'autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.h', 'autofill/core/browser/webdata/autofill_wallet_syncable_service.cc', 'autofill/core/browser/webdata/autofill_wallet_syncable_service.h', 'autofill/core/browser/webdata/autofill_webdata.h', diff --git a/components/autofill/core/browser/BUILD.gn b/components/autofill/core/browser/BUILD.gn index d2341c2..28e57a8 100644 --- a/components/autofill/core/browser/BUILD.gn +++ b/components/autofill/core/browser/BUILD.gn @@ -123,6 +123,8 @@ static_library("browser") { "webdata/autofill_profile_syncable_service.h", "webdata/autofill_table.cc", "webdata/autofill_table.h", + "webdata/autofill_wallet_metadata_syncable_service.cc", + "webdata/autofill_wallet_metadata_syncable_service.h", "webdata/autofill_wallet_syncable_service.cc", "webdata/autofill_wallet_syncable_service.h", "webdata/autofill_webdata.h", @@ -248,6 +250,7 @@ source_set("unit_tests") { "validation_unittest.cc", "webdata/autofill_profile_syncable_service_unittest.cc", "webdata/autofill_table_unittest.cc", + "webdata/autofill_wallet_metadata_syncable_service_unittest.cc", "webdata/web_data_service_unittest.cc", ] diff --git a/components/autofill/core/browser/webdata/autofill_change.cc b/components/autofill/core/browser/webdata/autofill_change.cc index c02bdd8..f9a498e 100644 --- a/components/autofill/core/browser/webdata/autofill_change.cc +++ b/components/autofill/core/browser/webdata/autofill_change.cc @@ -5,8 +5,6 @@ #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 { @@ -17,23 +15,4 @@ AutofillChange::AutofillChange(Type type, const AutofillKey& 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/components/autofill/core/browser/webdata/autofill_change.h b/components/autofill/core/browser/webdata/autofill_change.h index 0e85657..6212c60 100644 --- a/components/autofill/core/browser/webdata/autofill_change.h +++ b/components/autofill/core/browser/webdata/autofill_change.h @@ -5,16 +5,19 @@ #ifndef COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_CHANGE_H__ #define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_CHANGE_H__ +#include <string> #include <vector> +#include "base/logging.h" #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. +// Autofill++ types such as AutofillProfile and CreditCard simply use a string. template <typename KeyType> class GenericAutofillChange { public: @@ -49,27 +52,40 @@ class AutofillChange : public GenericAutofillChange<AutofillKey> { typedef std::vector<AutofillChange> AutofillChangeList; -// Change notification details for Autofill profile changes. -class AutofillProfileChange : public GenericAutofillChange<std::string> { +// Change notification details for Autofill profile or credit card changes. +template <typename DataType> +class AutofillDataModelChange : 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); - ~AutofillProfileChange() override; - - const AutofillProfile* profile() const { return profile_; } - bool operator==(const AutofillProfileChange& change) const; + // which is expected to be the GUID identifying the |data_model|. + // When |type| == ADD, |data_model| should be non-NULL. + // When |type| == UPDATE, |data_model| should be non-NULL. + // When |type| == REMOVE, |data_model| should be NULL. + AutofillDataModelChange(Type type, + const std::string& key, + const DataType* data_model) + : GenericAutofillChange<std::string>(type, key), data_model_(data_model) { + DCHECK(type == REMOVE ? !data_model + : data_model && data_model->guid() == key); + } + + ~AutofillDataModelChange() override {} + + const DataType* data_model() const { return data_model_; } + + bool operator==(const AutofillDataModelChange<DataType>& change) const { + return type() == change.type() && key() == change.key() && + (type() == REMOVE || *data_model() == *change.data_model()); + } private: // Weak reference, can be NULL. - const AutofillProfile* profile_; + const DataType* data_model_; }; +typedef AutofillDataModelChange<AutofillProfile> AutofillProfileChange; +typedef AutofillDataModelChange<CreditCard> CreditCardChange; + } // namespace autofill #endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_CHANGE_H__ diff --git a/components/autofill/core/browser/webdata/autofill_profile_syncable_service.cc b/components/autofill/core/browser/webdata/autofill_profile_syncable_service.cc index ded701c..0049914 100644 --- a/components/autofill/core/browser/webdata/autofill_profile_syncable_service.cc +++ b/components/autofill/core/browser/webdata/autofill_profile_syncable_service.cc @@ -547,10 +547,17 @@ AutofillProfileSyncableService::CreateOrUpdateProfile( void AutofillProfileSyncableService::ActOnChange( const AutofillProfileChange& change) { - DCHECK((change.type() == AutofillProfileChange::REMOVE && - !change.profile()) || - (change.type() != AutofillProfileChange::REMOVE && change.profile())); + DCHECK( + (change.type() == AutofillProfileChange::REMOVE && + !change.data_model()) || + (change.type() != AutofillProfileChange::REMOVE && change.data_model())); DCHECK(sync_processor_.get()); + + if (change.data_model() && + change.data_model()->record_type() != AutofillProfile::LOCAL_PROFILE) { + return; + } + syncer::SyncChangeList new_changes; DataBundle bundle; switch (change.type()) { @@ -558,21 +565,21 @@ void AutofillProfileSyncableService::ActOnChange( new_changes.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD, - CreateData(*(change.profile())))); - DCHECK(profiles_map_.find(change.profile()->guid()) == + CreateData(*(change.data_model())))); + DCHECK(profiles_map_.find(change.data_model()->guid()) == profiles_map_.end()); - profiles_.push_back(new AutofillProfile(*(change.profile()))); - profiles_map_[change.profile()->guid()] = profiles_.get().back(); + profiles_.push_back(new AutofillProfile(*(change.data_model()))); + profiles_map_[change.data_model()->guid()] = profiles_.get().back(); break; case AutofillProfileChange::UPDATE: { GUIDToProfileMap::iterator it = profiles_map_.find( - change.profile()->guid()); + change.data_model()->guid()); DCHECK(it != profiles_map_.end()); - *(it->second) = *(change.profile()); + *(it->second) = *(change.data_model()); new_changes.push_back( syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, - CreateData(*(change.profile())))); + CreateData(*(change.data_model())))); break; } case AutofillProfileChange::REMOVE: { diff --git a/components/autofill/core/browser/webdata/autofill_profile_syncable_service.h b/components/autofill/core/browser/webdata/autofill_profile_syncable_service.h index 75757de..23bf2a7 100644 --- a/components/autofill/core/browser/webdata/autofill_profile_syncable_service.h +++ b/components/autofill/core/browser/webdata/autofill_profile_syncable_service.h @@ -36,7 +36,10 @@ class AutofillWebDataService; extern const char kAutofillProfileTag[]; -// The sync implementation for AutofillProfiles. +// The sync implementation for local AutofillProfiles, which can be managed in +// settings and can be written to the sync server. (Server profiles cannot be +// managed in settings and can only be read from the sync server.) +// // MergeDataAndStartSyncing() called first, it does cloud->local and // local->cloud syncs. Then for each cloud change we receive // ProcessSyncChanges() and for each local change Observe() is called. diff --git a/components/autofill/core/browser/webdata/autofill_profile_syncable_service_unittest.cc b/components/autofill/core/browser/webdata/autofill_profile_syncable_service_unittest.cc index e4bddcf..43286aa 100644 --- a/components/autofill/core/browser/webdata/autofill_profile_syncable_service_unittest.cc +++ b/components/autofill/core/browser/webdata/autofill_profile_syncable_service_unittest.cc @@ -1240,4 +1240,23 @@ TEST_F(AutofillProfileSyncableServiceTest, ClientOverwritesUsageStats) { autofill_syncable_service_.StopSyncing(syncer::AUTOFILL_PROFILE); } +// Server profile updates should be ignored. +TEST_F(AutofillProfileSyncableServiceTest, IgnoreServerProfileUpdate) { + EXPECT_CALL(autofill_syncable_service_, LoadAutofillData(_)) + .Times(1) + .WillOnce(Return(true)); + EXPECT_CALL(autofill_syncable_service_, SaveChangesToWebData(_)) + .Times(1) + .WillOnce(Return(true)); + autofill_syncable_service_.MergeDataAndStartSyncing( + syncer::AUTOFILL_PROFILE, syncer::SyncDataList(), + make_scoped_ptr(new TestSyncChangeProcessor), + scoped_ptr<syncer::SyncErrorFactory>(new syncer::SyncErrorFactoryMock())); + AutofillProfile server_profile(AutofillProfile::SERVER_PROFILE, "server-id"); + + // Should not crash: + autofill_syncable_service_.AutofillProfileChanged(AutofillProfileChange( + AutofillProfileChange::UPDATE, server_profile.guid(), &server_profile)); +} + } // namespace autofill diff --git a/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.cc b/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.cc new file mode 100644 index 0000000..1f3b962 --- /dev/null +++ b/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.cc @@ -0,0 +1,519 @@ +// Copyright 2015 The Chromium Authors. 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_wallet_metadata_syncable_service.h" + +#include "base/bind.h" +#include "base/containers/scoped_ptr_hash_map.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/memory/scoped_vector.h" +#include "base/numerics/safe_conversions.h" +#include "base/time/time.h" +#include "components/autofill/core/browser/autofill_data_model.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_table.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_backend.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service.h" +#include "sync/api/sync_change.h" +#include "sync/api/sync_change_processor.h" +#include "sync/api/sync_data.h" +#include "sync/api/sync_error_factory.h" +#include "sync/protocol/sync.pb.h" + +namespace autofill { + +namespace { + +void* UserDataKey() { + // Use the address of a static so that COMDAT folding won't ever fold + // with something else. + static int user_data_key = 0; + return reinterpret_cast<void*>(&user_data_key); +} + +// Returns syncable metadata for the |local| profile or credit card. +syncer::SyncData BuildSyncData(sync_pb::WalletMetadataSpecifics::Type type, + const std::string& server_id, + const AutofillDataModel& local) { + sync_pb::EntitySpecifics entity; + sync_pb::WalletMetadataSpecifics* metadata = entity.mutable_wallet_metadata(); + metadata->set_type(type); + metadata->set_id(server_id); + metadata->set_use_count(local.use_count()); + metadata->set_use_date(local.use_date().ToInternalValue()); + + std::string sync_tag; + switch (type) { + case sync_pb::WalletMetadataSpecifics::ADDRESS: + sync_tag = "address-" + server_id; + break; + case sync_pb::WalletMetadataSpecifics::CARD: + sync_tag = "card-" + server_id; + break; + case sync_pb::WalletMetadataSpecifics::UNKNOWN: + NOTREACHED(); + break; + } + + return syncer::SyncData::CreateLocalData(sync_tag, sync_tag, entity); +} + +// If the metadata exists locally, undelete it on the sync server. +template <class DataType> +void UndeleteMetadataIfExisting( + const std::string& server_id, + const sync_pb::WalletMetadataSpecifics::Type& metadata_type, + base::ScopedPtrHashMap<std::string, scoped_ptr<DataType>>* locals, + syncer::SyncChangeList* changes_to_sync) { + const auto& it = locals->find(server_id); + if (it != locals->end()) { + scoped_ptr<DataType> local_metadata = locals->take_and_erase(it); + changes_to_sync->push_back(syncer::SyncChange( + FROM_HERE, syncer::SyncChange::ACTION_ADD, + BuildSyncData(metadata_type, local_metadata->server_id(), + *local_metadata))); + } +} + +syncer::SyncDataList::iterator FindServerIdAndTypeInCache( + const std::string& server_id, + const sync_pb::WalletMetadataSpecifics::Type& type, + syncer::SyncDataList* cache) { + for (auto it = cache->begin(); it != cache->end(); ++it) { + if (server_id == it->GetSpecifics().wallet_metadata().id() && + type == it->GetSpecifics().wallet_metadata().type()) { + return it; + } + } + + return cache->end(); +} + +syncer::SyncDataList::iterator FindItemInCache(const syncer::SyncData& item, + syncer::SyncDataList* cache) { + return FindServerIdAndTypeInCache( + item.GetSpecifics().wallet_metadata().id(), + item.GetSpecifics().wallet_metadata().type(), cache); +} + +void RemoveItemFromCache(const syncer::SyncData& item, + syncer::SyncDataList* cache) { + auto it = FindItemInCache(item, cache); + if (it != cache->end()) + cache->erase(it); +} + +void AddOrUpdateItemInCache(const syncer::SyncData& item, + syncer::SyncDataList* cache) { + auto it = FindItemInCache(item, cache); + if (it != cache->end()) + *it = item; + else + cache->push_back(item); +} + +void ApplyChangesToCache(const syncer::SyncChangeList& changes, + syncer::SyncDataList* cache) { + for (const syncer::SyncChange& change : changes) { + switch (change.change_type()) { + case syncer::SyncChange::ACTION_ADD: + // Intentional fall through. + case syncer::SyncChange::ACTION_UPDATE: + AddOrUpdateItemInCache(change.sync_data(), cache); + break; + + case syncer::SyncChange::ACTION_DELETE: + RemoveItemFromCache(change.sync_data(), cache); + break; + + case syncer::SyncChange::ACTION_INVALID: + NOTREACHED(); + break; + } + } +} + +// Merges |remote| metadata into a collection of metadata |locals|. Returns true +// if the corresponding local metadata was found. +// +// Stores an "update" in |changes_to_sync| if |remote| corresponds to an item in +// |locals| that has higher use count and later use date. +template <class DataType> +bool MergeRemote( + const syncer::SyncData& remote, + const base::Callback<bool(const DataType&)>& updater, + base::ScopedPtrHashMap<std::string, scoped_ptr<DataType>>* locals, + syncer::SyncChangeList* changes_to_sync) { + DCHECK(locals); + DCHECK(changes_to_sync); + + const sync_pb::WalletMetadataSpecifics& remote_metadata = + remote.GetSpecifics().wallet_metadata(); + auto it = locals->find(remote_metadata.id()); + if (it == locals->end()) + return false; + + scoped_ptr<DataType> local_metadata = locals->take_and_erase(it); + + size_t remote_use_count = + base::checked_cast<size_t>(remote_metadata.use_count()); + bool is_local_modified = false; + bool is_remote_outdated = false; + if (local_metadata->use_count() < remote_use_count) { + local_metadata->set_use_count(remote_use_count); + is_local_modified = true; + } else if (local_metadata->use_count() > remote_use_count) { + is_remote_outdated = true; + } + + base::Time remote_use_date = + base::Time::FromInternalValue(remote_metadata.use_date()); + if (local_metadata->use_date() < remote_use_date) { + local_metadata->set_use_date(remote_use_date); + is_local_modified = true; + } else if (local_metadata->use_date() > remote_use_date) { + is_remote_outdated = true; + } + + if (is_remote_outdated) { + changes_to_sync->push_back(syncer::SyncChange( + FROM_HERE, syncer::SyncChange::ACTION_UPDATE, + BuildSyncData(remote_metadata.type(), remote_metadata.id(), + *local_metadata))); + } + + if (is_local_modified) + updater.Run(*local_metadata); + + return true; +} + +} // namespace + +AutofillWalletMetadataSyncableService:: + ~AutofillWalletMetadataSyncableService() {} + +syncer::SyncMergeResult +AutofillWalletMetadataSyncableService::MergeDataAndStartSyncing( + syncer::ModelType type, + const syncer::SyncDataList& initial_sync_data, + scoped_ptr<syncer::SyncChangeProcessor> sync_processor, + scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK(!sync_processor_); + DCHECK(!sync_error_factory_); + DCHECK_EQ(syncer::AUTOFILL_WALLET_METADATA, type); + + sync_processor_ = sync_processor.Pass(); + sync_error_factory_ = sync_error_factory.Pass(); + + cache_ = initial_sync_data; + + return MergeData(initial_sync_data); +} + +void AutofillWalletMetadataSyncableService::StopSyncing( + syncer::ModelType type) { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(syncer::AUTOFILL_WALLET_METADATA, type); + + sync_processor_.reset(); + sync_error_factory_.reset(); + cache_.clear(); +} + +syncer::SyncDataList AutofillWalletMetadataSyncableService::GetAllSyncData( + syncer::ModelType type) const { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_EQ(syncer::AUTOFILL_WALLET_METADATA, type); + + syncer::SyncDataList data_list; + base::ScopedPtrHashMap<std::string, scoped_ptr<AutofillProfile>> profiles; + base::ScopedPtrHashMap<std::string, scoped_ptr<CreditCard>> cards; + if (GetLocalData(&profiles, &cards)) { + for (const auto& it : profiles) { + data_list.push_back(BuildSyncData( + sync_pb::WalletMetadataSpecifics::ADDRESS, it.first, *it.second)); + } + + for (const auto& it : cards) { + data_list.push_back(BuildSyncData(sync_pb::WalletMetadataSpecifics::CARD, + it.first, *it.second)); + } + } + + return data_list; +} + +syncer::SyncError AutofillWalletMetadataSyncableService::ProcessSyncChanges( + const tracked_objects::Location& from_here, + const syncer::SyncChangeList& changes_from_sync) { + DCHECK(thread_checker_.CalledOnValidThread()); + + ApplyChangesToCache(changes_from_sync, &cache_); + + base::ScopedPtrHashMap<std::string, scoped_ptr<AutofillProfile>> profiles; + base::ScopedPtrHashMap<std::string, scoped_ptr<CreditCard>> cards; + GetLocalData(&profiles, &cards); + + // base::Unretained is used because the callbacks are invoked synchronously. + base::Callback<bool(const AutofillProfile&)> address_updater = + base::Bind(&AutofillWalletMetadataSyncableService::UpdateAddressStats, + base::Unretained(this)); + base::Callback<bool(const CreditCard&)> card_updater = + base::Bind(&AutofillWalletMetadataSyncableService::UpdateCardStats, + base::Unretained(this)); + + syncer::SyncChangeList changes_to_sync; + for (const syncer::SyncChange& change : changes_from_sync) { + const sync_pb::WalletMetadataSpecifics& remote_metadata = + change.sync_data().GetSpecifics().wallet_metadata(); + switch (change.change_type()) { + case syncer::SyncChange::ACTION_ADD: + // Intentional fall through. + case syncer::SyncChange::ACTION_UPDATE: + switch (remote_metadata.type()) { + case sync_pb::WalletMetadataSpecifics::ADDRESS: + MergeRemote(change.sync_data(), address_updater, &profiles, + &changes_to_sync); + break; + + case sync_pb::WalletMetadataSpecifics::CARD: + MergeRemote(change.sync_data(), card_updater, &cards, + &changes_to_sync); + break; + + case sync_pb::WalletMetadataSpecifics::UNKNOWN: + NOTREACHED(); + break; + } + break; + + // Metadata should only be deleted when the underlying data is deleted. + case syncer::SyncChange::ACTION_DELETE: + switch (remote_metadata.type()) { + case sync_pb::WalletMetadataSpecifics::ADDRESS: + UndeleteMetadataIfExisting( + remote_metadata.id(), sync_pb::WalletMetadataSpecifics::ADDRESS, + &profiles, &changes_to_sync); + break; + + case sync_pb::WalletMetadataSpecifics::CARD: + UndeleteMetadataIfExisting(remote_metadata.id(), + sync_pb::WalletMetadataSpecifics::CARD, + &cards, &changes_to_sync); + break; + + case sync_pb::WalletMetadataSpecifics::UNKNOWN: + NOTREACHED(); + break; + } + break; + + case syncer::SyncChange::ACTION_INVALID: + NOTREACHED(); + break; + } + } + + syncer::SyncError status; + if (!changes_to_sync.empty()) + status = SendChangesToSyncServer(changes_to_sync); + + return status; +} + +void AutofillWalletMetadataSyncableService::AutofillProfileChanged( + const AutofillProfileChange& change) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (sync_processor_ && change.data_model() && + change.data_model()->record_type() != AutofillProfile::LOCAL_PROFILE) { + AutofillDataModelChanged(change.data_model()->server_id(), + sync_pb::WalletMetadataSpecifics::ADDRESS, + *change.data_model()); + } +} + +void AutofillWalletMetadataSyncableService::CreditCardChanged( + const CreditCardChange& change) { + DCHECK(thread_checker_.CalledOnValidThread()); + + if (sync_processor_ && change.data_model() && + change.data_model()->record_type() != CreditCard::LOCAL_CARD) { + AutofillDataModelChanged(change.data_model()->server_id(), + sync_pb::WalletMetadataSpecifics::CARD, + *change.data_model()); + } +} + +void AutofillWalletMetadataSyncableService::AutofillMultipleChanged() { + if (sync_processor_) + MergeData(cache_); +} + +// static +void AutofillWalletMetadataSyncableService::CreateForWebDataServiceAndBackend( + AutofillWebDataService* web_data_service, + AutofillWebDataBackend* web_data_backend, + const std::string& app_locale) { + web_data_service->GetDBUserData()->SetUserData( + UserDataKey(), + new AutofillWalletMetadataSyncableService(web_data_backend, app_locale)); +} + +// static +AutofillWalletMetadataSyncableService* +AutofillWalletMetadataSyncableService::FromWebDataService( + AutofillWebDataService* web_data_service) { + return static_cast<AutofillWalletMetadataSyncableService*>( + web_data_service->GetDBUserData()->GetUserData(UserDataKey())); +} + +AutofillWalletMetadataSyncableService::AutofillWalletMetadataSyncableService( + AutofillWebDataBackend* web_data_backend, + const std::string& app_locale) + : web_data_backend_(web_data_backend), scoped_observer_(this) { + scoped_observer_.Add(web_data_backend_); +} + +bool AutofillWalletMetadataSyncableService::GetLocalData( + base::ScopedPtrHashMap<std::string, scoped_ptr<AutofillProfile>>* profiles, + base::ScopedPtrHashMap<std::string, scoped_ptr<CreditCard>>* cards) const { + ScopedVector<AutofillProfile> profile_list; + bool success = + AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase()) + ->GetServerProfiles(&profile_list.get()); + while (!profile_list.empty()) { + profiles->add(profile_list.front()->server_id(), + make_scoped_ptr(profile_list.front())); + profile_list.weak_erase(profile_list.begin()); + } + + ScopedVector<CreditCard> card_list; + success &= AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase()) + ->GetServerCreditCards(&card_list.get()); + while (!card_list.empty()) { + cards->add(card_list.front()->server_id(), + make_scoped_ptr(card_list.front())); + card_list.weak_erase(card_list.begin()); + } + + return success; +} + +bool AutofillWalletMetadataSyncableService::UpdateAddressStats( + const AutofillProfile& profile) { + return AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase()) + ->UpdateServerAddressUsageStats(profile); +} + +bool AutofillWalletMetadataSyncableService::UpdateCardStats( + const CreditCard& credit_card) { + return AutofillTable::FromWebDatabase(web_data_backend_->GetDatabase()) + ->UpdateServerCardUsageStats(credit_card); +} + +syncer::SyncError +AutofillWalletMetadataSyncableService::SendChangesToSyncServer( + const syncer::SyncChangeList& changes_to_sync) { + DCHECK(sync_processor_); + ApplyChangesToCache(changes_to_sync, &cache_); + return sync_processor_->ProcessSyncChanges(FROM_HERE, changes_to_sync); +} + +syncer::SyncMergeResult AutofillWalletMetadataSyncableService::MergeData( + const syncer::SyncDataList& sync_data) { + base::ScopedPtrHashMap<std::string, scoped_ptr<AutofillProfile>> profiles; + base::ScopedPtrHashMap<std::string, scoped_ptr<CreditCard>> cards; + GetLocalData(&profiles, &cards); + + syncer::SyncMergeResult result(syncer::AUTOFILL_WALLET_METADATA); + result.set_num_items_before_association(profiles.size() + cards.size()); + + // base::Unretained is used because the callbacks are invoked synchronously. + base::Callback<bool(const AutofillProfile&)> address_updater = + base::Bind(&AutofillWalletMetadataSyncableService::UpdateAddressStats, + base::Unretained(this)); + base::Callback<bool(const CreditCard&)> card_updater = + base::Bind(&AutofillWalletMetadataSyncableService::UpdateCardStats, + base::Unretained(this)); + + syncer::SyncChangeList changes_to_sync; + for (const syncer::SyncData& remote : sync_data) { + DCHECK(remote.IsValid()); + DCHECK_EQ(syncer::AUTOFILL_WALLET_METADATA, remote.GetDataType()); + switch (remote.GetSpecifics().wallet_metadata().type()) { + case sync_pb::WalletMetadataSpecifics::ADDRESS: + if (!MergeRemote(remote, address_updater, &profiles, + &changes_to_sync)) { + changes_to_sync.push_back(syncer::SyncChange( + FROM_HERE, syncer::SyncChange::ACTION_DELETE, remote)); + } + break; + + case sync_pb::WalletMetadataSpecifics::CARD: + if (!MergeRemote(remote, card_updater, &cards, &changes_to_sync)) { + changes_to_sync.push_back(syncer::SyncChange( + FROM_HERE, syncer::SyncChange::ACTION_DELETE, remote)); + } + break; + + case sync_pb::WalletMetadataSpecifics::UNKNOWN: + NOTREACHED(); + break; + } + } + + // The remainder of |profiles| were not listed in |sync_data|. + for (const auto& it : profiles) { + changes_to_sync.push_back(syncer::SyncChange( + FROM_HERE, syncer::SyncChange::ACTION_ADD, + BuildSyncData(sync_pb::WalletMetadataSpecifics::ADDRESS, it.first, + *it.second))); + } + + // The remainder of |cards| were not listed in |sync_data|. + for (const auto& it : cards) { + changes_to_sync.push_back( + syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_ADD, + BuildSyncData(sync_pb::WalletMetadataSpecifics::CARD, + it.first, *it.second))); + } + + // The only operation that is performed locally in response to a sync is an + // update. Adds and deletes are performed in response to changes to the Wallet + // data. + result.set_num_items_after_association(result.num_items_before_association()); + result.set_num_items_added(0); + result.set_num_items_deleted(0); + + if (!changes_to_sync.empty()) + result.set_error(SendChangesToSyncServer(changes_to_sync)); + + return result; +} + +void AutofillWalletMetadataSyncableService::AutofillDataModelChanged( + const std::string& server_id, + const sync_pb::WalletMetadataSpecifics::Type& type, + const AutofillDataModel& local) { + auto it = FindServerIdAndTypeInCache(server_id, type, &cache_); + if (it == cache_.end()) + return; + + const sync_pb::WalletMetadataSpecifics& remote = + it->GetSpecifics().wallet_metadata(); + if (base::checked_cast<size_t>(remote.use_count()) < local.use_count() && + base::Time::FromInternalValue(remote.use_date()) < local.use_date()) { + SendChangesToSyncServer(syncer::SyncChangeList( + 1, syncer::SyncChange(FROM_HERE, syncer::SyncChange::ACTION_UPDATE, + BuildSyncData(remote.type(), server_id, local)))); + } +} + +} // namespace autofill diff --git a/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.h b/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.h new file mode 100644 index 0000000..8411fc1 --- /dev/null +++ b/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.h @@ -0,0 +1,151 @@ +// Copyright 2015 The Chromium 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_WALLET_METADATA_SYNCABLE_SERVICE_H_ +#define COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WALLET_METADATA_SYNCABLE_SERVICE_H_ + +#include <string> + +#include "base/callback_forward.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/scoped_observer.h" +#include "base/supports_user_data.h" +#include "base/threading/thread_checker.h" +#include "components/autofill/core/browser/webdata/autofill_change.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_service_observer.h" +#include "sync/api/sync_error.h" +#include "sync/api/sync_merge_result.h" +#include "sync/api/syncable_service.h" +#include "sync/protocol/autofill_specifics.pb.h" + +namespace base { +template <typename, typename> +class ScopedPtrHashMap; +} + +namespace syncer { +class SyncChangeProcessor; +class SyncData; +class SyncErrorFactory; +} + +namespace tracked_objects { +class Location; +} + +namespace autofill { + +class AutofillDataModel; +class AutofillProfile; +class AutofillWebDataBackend; +class AutofillWebDataService; +class CreditCard; + +// Syncs usage counts and last use dates (metadata) for Wallet cards and +// addresses (data). +// +// The sync server generates the data, and the client can only download it. No +// data upload is possible. Chrome generates the corresponding metadata locally +// and uses the sync server to propagate the metadata to the other instances of +// Chrome. See the design doc at https://goo.gl/LS2y6M for more details. +class AutofillWalletMetadataSyncableService + : public base::SupportsUserData::Data, + public syncer::SyncableService, + public AutofillWebDataServiceObserverOnDBThread { + public: + ~AutofillWalletMetadataSyncableService() override; + + // syncer::SyncableService implementation. + syncer::SyncMergeResult MergeDataAndStartSyncing( + syncer::ModelType type, + const syncer::SyncDataList& initial_sync_data, + scoped_ptr<syncer::SyncChangeProcessor> sync_processor, + scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) override; + void StopSyncing(syncer::ModelType type) override; + syncer::SyncDataList GetAllSyncData(syncer::ModelType type) const override; + syncer::SyncError ProcessSyncChanges( + const tracked_objects::Location& from_here, + const syncer::SyncChangeList& changes_from_sync) override; + + // AutofillWebDataServiceObserverOnDBThread implementation. + void AutofillProfileChanged(const AutofillProfileChange& change) override; + void CreditCardChanged(const CreditCardChange& change) override; + void AutofillMultipleChanged() override; + + // Creates a new AutofillWalletMetadataSyncableService and hangs it off of + // |web_data_service|, which takes ownership. This method should only be + // called on |web_data_service|'s DB thread. |web_data_backend| is expected to + // outlive this object. + static void CreateForWebDataServiceAndBackend( + AutofillWebDataService* web_data_service, + AutofillWebDataBackend* web_data_backend, + const std::string& app_locale); + + // Retrieves the AutofillWalletMetadataSyncableService stored on + // |web_data_service|. + static AutofillWalletMetadataSyncableService* FromWebDataService( + AutofillWebDataService* web_data_service); + + protected: + AutofillWalletMetadataSyncableService( + AutofillWebDataBackend* web_data_backend, + const std::string& app_locale); + + // Populates the provided |profiles| and |cards| with mappings from server ID + // to server profiles and server cards read from disk. This data contains the + // usage stats. Returns true on success. + virtual bool GetLocalData( + base::ScopedPtrHashMap<std::string, scoped_ptr<AutofillProfile>>* + profiles, + base::ScopedPtrHashMap<std::string, scoped_ptr<CreditCard>>* cards) const; + + // Updates the stats for |profile| stored on disk. Does not trigger + // notifications that this profile was updated. + virtual bool UpdateAddressStats(const AutofillProfile& profile); + + // Updates the stats for |credit_card| stored on disk. Does not trigger + // notifications that this credit card was updated. + virtual bool UpdateCardStats(const CreditCard& credit_card); + + // Sends the |changes_to_sync| to the sync server. + virtual syncer::SyncError SendChangesToSyncServer( + const syncer::SyncChangeList& changes_to_sync); + + private: + // Merges local metadata with |sync_data|. + // + // Sends an "update" to the sync server if |sync_data| contains metadata that + // is present locally, but local metadata has higher use count and later use + // date. + // + // Sends a "create" to the sync server if |sync_data| does not have some local + // metadata. + // + // Sends a "delete" to the sync server if |sync_data| contains metadata that + // is not present locally. + syncer::SyncMergeResult MergeData(const syncer::SyncDataList& sync_data); + + // Sends updates to the sync server. + void AutofillDataModelChanged( + const std::string& server_id, + const sync_pb::WalletMetadataSpecifics::Type& type, + const AutofillDataModel& local); + + base::ThreadChecker thread_checker_; + AutofillWebDataBackend* web_data_backend_; // Weak ref. + ScopedObserver<AutofillWebDataBackend, AutofillWalletMetadataSyncableService> + scoped_observer_; + scoped_ptr<syncer::SyncChangeProcessor> sync_processor_; + scoped_ptr<syncer::SyncErrorFactory> sync_error_factory_; + + // Local metadata plus metadata for the data that hasn't synced down yet. + syncer::SyncDataList cache_; + + DISALLOW_COPY_AND_ASSIGN(AutofillWalletMetadataSyncableService); +}; + +} // namespace autofill + +#endif // COMPONENTS_AUTOFILL_CORE_BROWSER_WEBDATA_AUTOFILL_WALLET_METADATA_SYNCABLE_SERVICE_H_ diff --git a/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service_unittest.cc b/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service_unittest.cc new file mode 100644 index 0000000..3d0b017 --- /dev/null +++ b/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service_unittest.cc @@ -0,0 +1,841 @@ +// Copyright 2015 The Chromium Authors. 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_wallet_metadata_syncable_service.h" + +#include <vector> + +#include "base/basictypes.h" +#include "base/containers/scoped_ptr_hash_map.h" +#include "base/location.h" +#include "base/numerics/safe_conversions.h" +#include "base/time/time.h" +#include "components/autofill/core/browser/autofill_profile.h" +#include "components/autofill/core/browser/credit_card.h" +#include "components/autofill/core/browser/webdata/autofill_webdata_backend.h" +#include "sync/api/sync_change.h" +#include "sync/api/sync_change_processor_wrapper_for_test.h" +#include "sync/api/sync_error_factory_mock.h" +#include "sync/protocol/autofill_specifics.pb.h" +#include "sync/protocol/sync.pb.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace autofill { +namespace { + +using testing::DoAll; +using testing::ElementsAre; +using testing::Invoke; +using testing::NiceMock; +using testing::Return; +using testing::Test; +using testing::UnorderedElementsAre; +using testing::Value; +using testing::_; + +// Map values are owned by the caller to GetLocalData. +ACTION_P2(GetCopiesOf, profiles, cards) { + for (const auto& profile : *profiles) { + arg0->add(profile.server_id(), + make_scoped_ptr(new AutofillProfile(profile))); + } + + for (const auto& card : *cards) { + arg1->add(card.server_id(), make_scoped_ptr(new CreditCard(card))); + } +} + +ACTION_P(SaveDataIn, list) { + for (auto& item : *list) { + if (item.server_id() == arg0.server_id()) { + item = arg0; + return; + } + } + + list->push_back(arg0); +} + +// A syncable service for Wallet metadata that mocks out disk IO. +class MockService : public AutofillWalletMetadataSyncableService { + public: + MockService(AutofillWebDataBackend* web_data_backend) + : AutofillWalletMetadataSyncableService(web_data_backend, std::string()) { + ON_CALL(*this, GetLocalData(_, _)) + .WillByDefault(DoAll(GetCopiesOf(&server_profiles_, &server_cards_), + Return(true))); + + ON_CALL(*this, UpdateAddressStats(_)) + .WillByDefault(DoAll(SaveDataIn(&server_profiles_), Return(true))); + + ON_CALL(*this, UpdateCardStats(_)) + .WillByDefault(DoAll(SaveDataIn(&server_cards_), Return(true))); + + ON_CALL(*this, SendChangesToSyncServer(_)) + .WillByDefault( + Invoke(this, &MockService::SendChangesToSyncServerConcrete)); + } + + ~MockService() override {} + + MOCK_METHOD1(UpdateAddressStats, bool(const AutofillProfile&)); + MOCK_METHOD1(UpdateCardStats, bool(const CreditCard&)); + MOCK_METHOD1(SendChangesToSyncServer, + syncer::SyncError(const syncer::SyncChangeList&)); + + void ClearServerData() { + server_profiles_.clear(); + server_cards_.clear(); + } + + private: + MOCK_CONST_METHOD2( + GetLocalData, + bool(base::ScopedPtrHashMap<std::string, scoped_ptr<AutofillProfile>>*, + base::ScopedPtrHashMap<std::string, scoped_ptr<CreditCard>>*)); + + syncer::SyncError SendChangesToSyncServerConcrete( + const syncer::SyncChangeList& changes) { + return AutofillWalletMetadataSyncableService::SendChangesToSyncServer( + changes); + } + + syncer::SyncDataList GetAllSyncDataConcrete(syncer::ModelType type) const { + return AutofillWalletMetadataSyncableService::GetAllSyncData(type); + } + + std::vector<AutofillProfile> server_profiles_; + std::vector<CreditCard> server_cards_; + + DISALLOW_COPY_AND_ASSIGN(MockService); +}; + +class NoOpWebData : public AutofillWebDataBackend { + public: + NoOpWebData() {} + ~NoOpWebData() override {} + + private: + // AutofillWebDataBackend implementation. + WebDatabase* GetDatabase() override { return nullptr; } + void AddObserver( + AutofillWebDataServiceObserverOnDBThread* observer) override {} + void RemoveObserver( + AutofillWebDataServiceObserverOnDBThread* observer) override {} + void RemoveExpiredFormElements() override {} + void NotifyOfMultipleAutofillChanges() override {} + + DISALLOW_COPY_AND_ASSIGN(NoOpWebData); +}; + +class AutofillWalletMetadataSyncableServiceTest : public Test { + public: + AutofillWalletMetadataSyncableServiceTest() + : local_(&no_op_web_data_), remote_(&no_op_web_data_) {} + ~AutofillWalletMetadataSyncableServiceTest() override {} + + // Outlives local_ and remote_. + NoOpWebData no_op_web_data_; + + // Outlived by no_op_web_data_. + NiceMock<MockService> local_; + NiceMock<MockService> remote_; + + private: + DISALLOW_COPY_AND_ASSIGN(AutofillWalletMetadataSyncableServiceTest); +}; + +// Verify that nothing is sent to the sync server when there's no metadata on +// disk. +TEST_F(AutofillWalletMetadataSyncableServiceTest, NoMetadataToReturn) { + EXPECT_TRUE(local_.GetAllSyncData(syncer::AUTOFILL_WALLET_METADATA).empty()); +} + +AutofillProfile BuildAddress(const std::string& server_id, + int64 use_count, + int64 use_date) { + AutofillProfile profile(AutofillProfile::SERVER_PROFILE, server_id); + profile.set_use_count(use_count); + profile.set_use_date(base::Time::FromInternalValue(use_date)); + return profile; +} + +CreditCard BuildCard(const std::string& server_id, + int64 use_count, + int64 use_date) { + CreditCard card(CreditCard::MASKED_SERVER_CARD, server_id); + card.set_use_count(use_count); + card.set_use_date(base::Time::FromInternalValue(use_date)); + return card; +} + +MATCHER_P5(SyncDataMatches, + sync_tag, + metadata_type, + server_id, + use_count, + use_date, + "") { + return arg.IsValid() && + syncer::AUTOFILL_WALLET_METADATA == arg.GetDataType() && + sync_tag == syncer::SyncDataLocal(arg).GetTag() && + metadata_type == arg.GetSpecifics().wallet_metadata().type() && + server_id == arg.GetSpecifics().wallet_metadata().id() && + use_count == arg.GetSpecifics().wallet_metadata().use_count() && + use_date == arg.GetSpecifics().wallet_metadata().use_date(); +} + +// Verify that all metadata from disk is sent to the sync server. +TEST_F(AutofillWalletMetadataSyncableServiceTest, ReturnAllMetadata) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + + EXPECT_THAT( + local_.GetAllSyncData(syncer::AUTOFILL_WALLET_METADATA), + UnorderedElementsAre( + SyncDataMatches("address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 1, + 2), + SyncDataMatches("card-card", sync_pb::WalletMetadataSpecifics::CARD, + "card", 3, 4))); +} + +void MergeMetadata(MockService* local, MockService* remote) { + // The wrapper for |remote| gives it a null change processor, so sending + // changes is not possible. + ON_CALL(*remote, SendChangesToSyncServer(_)) + .WillByDefault(Return(syncer::SyncError())); + + scoped_ptr<syncer::SyncErrorFactoryMock> errors( + new syncer::SyncErrorFactoryMock); + EXPECT_CALL(*errors, CreateAndUploadError(_, _)).Times(0); + EXPECT_FALSE( + local->MergeDataAndStartSyncing( + syncer::AUTOFILL_WALLET_METADATA, + remote->GetAllSyncData(syncer::AUTOFILL_WALLET_METADATA), + scoped_ptr<syncer::SyncChangeProcessor>( + new syncer::SyncChangeProcessorWrapperForTest(remote)), + errors.Pass()) + .error() + .IsSet()); +} + +// Verify that nothing is written to disk or sent to the sync server when two +// empty clients are syncing. +TEST_F(AutofillWalletMetadataSyncableServiceTest, TwoEmptyClients) { + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + MergeMetadata(&local_, &remote_); +} + +MATCHER_P2(SyncChangeMatches, change_type, sync_tag, "") { + return arg.IsValid() && change_type == arg.change_type() && + sync_tag == syncer::SyncDataLocal(arg.sync_data()).GetTag() && + syncer::AUTOFILL_WALLET_METADATA == arg.sync_data().GetDataType(); +} + +// Verify that remote data without local counterpart is deleted during the +// initial merge. +TEST_F(AutofillWalletMetadataSyncableServiceTest, DeleteFromServerOnMerge) { + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL( + local_, + SendChangesToSyncServer(UnorderedElementsAre( + SyncChangeMatches(syncer::SyncChange::ACTION_DELETE, "address-addr"), + SyncChangeMatches(syncer::SyncChange::ACTION_DELETE, "card-card")))); + + MergeMetadata(&local_, &remote_); +} + +MATCHER_P6(SyncChangeAndDataMatch, + change_type, + sync_tag, + metadata_type, + server_id, + use_count, + use_date, + "") { + return Value(arg, SyncChangeMatches(change_type, sync_tag)) && + Value(arg.sync_data(), + SyncDataMatches(sync_tag, metadata_type, server_id, use_count, + use_date)); +} + +// Verify that local data is sent to the sync server during the initial merge, +// if the server does not have the data already. +TEST_F(AutofillWalletMetadataSyncableServiceTest, AddToServerOnMerge) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL(local_, + SendChangesToSyncServer(UnorderedElementsAre( + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_ADD, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 1, 2), + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_ADD, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", 3, 4)))); + + MergeMetadata(&local_, &remote_); +} + +// Verify that no data is written to disk or sent to the sync server if the +// local and remote data are identical during the initial merge. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + IgnoreIdenticalValuesOnMerge) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + MergeMetadata(&local_, &remote_); +} + +MATCHER_P3(AutofillMetadataMatches, server_id, use_count, use_date, "") { + return arg.server_id() == server_id && + arg.use_count() == base::checked_cast<size_t>(use_count) && + arg.use_date() == base::Time::FromInternalValue(use_date); +} + +// Verify that remote data with higher values of use count and last use date is +// saved to disk during the initial merge. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + SaveHigherValuesLocallyOnMerge) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 10, 20)); + remote_.UpdateCardStats(BuildCard("card", 30, 40)); + + EXPECT_CALL(local_, + UpdateAddressStats(AutofillMetadataMatches("addr", 10, 20))); + EXPECT_CALL(local_, UpdateCardStats(AutofillMetadataMatches("card", 30, 40))); + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + MergeMetadata(&local_, &remote_); +} + +// Verify that local data with higher values of use count and last use date is +// sent to the sync server during the initial merge. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + SendHigherValuesToServerOnMerge) { + local_.UpdateAddressStats(BuildAddress("addr", 10, 20)); + local_.UpdateCardStats(BuildCard("card", 30, 40)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL( + local_, + SendChangesToSyncServer(UnorderedElementsAre( + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_UPDATE, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 10, 20), + SyncChangeAndDataMatch(syncer::SyncChange::ACTION_UPDATE, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", + 30, 40)))); + + MergeMetadata(&local_, &remote_); +} + +// Verify that lower values of metadata are not sent to the sync server when +// local metadata is updated. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + DontSendLowerValueToServerOnSingleChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + AutofillProfile address = BuildAddress("addr", 0, 0); + CreditCard card = BuildCard("card", 0, 0); + + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + local_.AutofillProfileChanged(AutofillProfileChange( + AutofillProfileChange::UPDATE, address.guid(), &address)); + local_.CreditCardChanged( + CreditCardChange(CreditCardChange::UPDATE, card.guid(), &card)); +} + +// Verify that higher values of metadata are sent to the sync server when local +// metadata is updated. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + SendHigherValuesToServerOnLocalSingleChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + AutofillProfile address = BuildAddress("addr", 10, 20); + CreditCard card = BuildCard("card", 30, 40); + + EXPECT_CALL(local_, + SendChangesToSyncServer(ElementsAre(SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_UPDATE, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 10, 20)))); + EXPECT_CALL(local_, + SendChangesToSyncServer(ElementsAre(SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_UPDATE, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", 30, 40)))); + + local_.AutofillProfileChanged(AutofillProfileChange( + AutofillProfileChange::UPDATE, address.guid(), &address)); + local_.CreditCardChanged( + CreditCardChange(CreditCardChange::UPDATE, card.guid(), &card)); +} + +// Verify that one-off addition of metadata is not sent to the sync +// server. Metadata add and delete trigger multiple changes notification +// instead. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + DontAddToServerOnSingleChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + AutofillProfile address = BuildAddress("new-addr", 5, 6); + CreditCard card = BuildCard("new-card", 7, 8); + + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + local_.AutofillProfileChanged(AutofillProfileChange( + AutofillProfileChange::UPDATE, address.guid(), &address)); + local_.CreditCardChanged( + CreditCardChange(CreditCardChange::UPDATE, card.guid(), &card)); +} + +// Verify that new metadata is sent to the sync server when multiple metadata +// values change at once. +TEST_F(AutofillWalletMetadataSyncableServiceTest, AddToServerOnMultiChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + // These methods do not trigger notifications or sync: + local_.UpdateAddressStats(BuildAddress("new-addr", 5, 6)); + local_.UpdateCardStats(BuildCard("new-card", 7, 8)); + + EXPECT_CALL( + local_, + SendChangesToSyncServer(UnorderedElementsAre( + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_ADD, "address-new-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "new-addr", 5, 6), + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_ADD, "card-new-card", + sync_pb::WalletMetadataSpecifics::CARD, "new-card", 7, 8)))); + + local_.AutofillMultipleChanged(); +} + +// Verify that higher values of existing metadata are sent to the sync server +// when multiple metadata values change at once. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + UpdateToHigherValueOnServerOnMultiChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + // These methods do not trigger notifications or sync: + local_.UpdateAddressStats(BuildAddress("addr", 5, 6)); + local_.UpdateCardStats(BuildCard("card", 7, 8)); + + EXPECT_CALL(local_, + SendChangesToSyncServer(UnorderedElementsAre( + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_UPDATE, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 5, 6), + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_UPDATE, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", 7, 8)))); + + local_.AutofillMultipleChanged(); +} + +// Verify that lower values of existing metadata are not sent to the sync server +// when multiple metadata values change at once. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + DontUpdateToLowerValueOnServerOnMultiChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + // These methods do not trigger notifications or sync: + local_.UpdateAddressStats(BuildAddress("addr", 0, 0)); + local_.UpdateCardStats(BuildCard("card", 0, 0)); + + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + local_.AutofillMultipleChanged(); +} + +// Verify that erased local metadata is also erased from the sync server when +// multiple metadata values change at once. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + DeleteFromServerOnMultiChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + // This method dooes not trigger notifications or sync: + local_.ClearServerData(); + + EXPECT_CALL( + local_, + SendChangesToSyncServer(UnorderedElementsAre( + SyncChangeMatches(syncer::SyncChange::ACTION_DELETE, "address-addr"), + SyncChangeMatches(syncer::SyncChange::ACTION_DELETE, "card-card")))); + + local_.AutofillMultipleChanged(); +} + +// Verify that empty sync change from the sync server does not trigger writing +// to disk or sending any data to the sync server. +TEST_F(AutofillWalletMetadataSyncableServiceTest, EmptySyncChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + local_.ProcessSyncChanges(FROM_HERE, syncer::SyncChangeList()); +} + +syncer::SyncChange BuildChange( + syncer::SyncChange::SyncChangeType change_type, + const std::string& sync_tag, + sync_pb::WalletMetadataSpecifics::Type metadata_type, + const std::string& server_id, + int64 use_count, + int64 use_date) { + sync_pb::EntitySpecifics entity; + entity.mutable_wallet_metadata()->set_type(metadata_type); + entity.mutable_wallet_metadata()->set_id(server_id); + entity.mutable_wallet_metadata()->set_use_count(use_count); + entity.mutable_wallet_metadata()->set_use_date(use_date); + return syncer::SyncChange( + FROM_HERE, change_type, + syncer::SyncData::CreateLocalData(sync_tag, sync_tag, entity)); +} + +// Verify that new metadata from the sync server is ignored when processing +// on-going sync changes. There should be no disk writes or messages to the sync +// server. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + IgnoreNewMetadataFromServerOnSyncChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + syncer::SyncChangeList changes; + changes.push_back( + BuildChange(syncer::SyncChange::ACTION_ADD, "address-new-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "new-addr", 5, 6)); + changes.push_back(BuildChange(syncer::SyncChange::ACTION_ADD, "card-new-card", + sync_pb::WalletMetadataSpecifics::CARD, + "new-card", 7, 8)); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + local_.ProcessSyncChanges(FROM_HERE, changes); +} + +// Verify that higher values of metadata from the sync server are saved to +// disk when processing on-going sync changes. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + SaveHigherValuesFromServerOnSyncChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + syncer::SyncChangeList changes; + changes.push_back( + BuildChange(syncer::SyncChange::ACTION_UPDATE, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 10, 20)); + changes.push_back(BuildChange(syncer::SyncChange::ACTION_UPDATE, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", + 30, 40)); + + EXPECT_CALL(local_, + UpdateAddressStats(AutofillMetadataMatches("addr", 10, 20))); + EXPECT_CALL(local_, UpdateCardStats(AutofillMetadataMatches("card", 30, 40))); + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + local_.ProcessSyncChanges(FROM_HERE, changes); +} + +// Verify that higher local values of metadata are sent to the sync server when +// processing on-going sync changes. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + SendHigherValuesToServerOnSyncChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + syncer::SyncChangeList changes; + changes.push_back( + BuildChange(syncer::SyncChange::ACTION_UPDATE, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 0, 0)); + changes.push_back(BuildChange(syncer::SyncChange::ACTION_UPDATE, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", + 0, 0)); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL(local_, + SendChangesToSyncServer(UnorderedElementsAre( + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_UPDATE, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 1, 2), + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_UPDATE, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", 3, 4)))); + + local_.ProcessSyncChanges(FROM_HERE, changes); +} + +// Verify that addition of known metadata is treated the same as an update. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + TreatAdditionOfKnownMetadataAsUpdateOnSyncChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + syncer::SyncChangeList changes; + changes.push_back(BuildChange(syncer::SyncChange::ACTION_ADD, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, + "addr", 0, 0)); + changes.push_back(BuildChange(syncer::SyncChange::ACTION_ADD, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", + 0, 0)); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL(local_, + SendChangesToSyncServer(UnorderedElementsAre( + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_UPDATE, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 1, 2), + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_UPDATE, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", 3, 4)))); + + local_.ProcessSyncChanges(FROM_HERE, changes); +} + +// Verify that an update of locally unknown metadata is ignored. There should be +// no disk writes and no messages sent to the server. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + IgnoreUpdateOfUnknownMetadataOnSyncChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + syncer::SyncChangeList changes; + changes.push_back(BuildChange( + syncer::SyncChange::ACTION_UPDATE, "address-unknown-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "unknown-addr", 0, 0)); + changes.push_back(BuildChange( + syncer::SyncChange::ACTION_UPDATE, "card-unknown-card", + sync_pb::WalletMetadataSpecifics::CARD, "unknown-card", 0, 0)); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + local_.ProcessSyncChanges(FROM_HERE, changes); +} + +// Verify that deletion from the sync server of locally unknown metadata is +// ignored. There should be no disk writes and no messages sent to the server. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + IgnoreDeleteOfUnknownMetadataOnSyncChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + syncer::SyncChangeList changes; + changes.push_back(BuildChange( + syncer::SyncChange::ACTION_DELETE, "address-unknown-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "unknown-addr", 0, 0)); + changes.push_back(BuildChange( + syncer::SyncChange::ACTION_DELETE, "card-unknown-card", + sync_pb::WalletMetadataSpecifics::CARD, "unknown-card", 0, 0)); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + local_.ProcessSyncChanges(FROM_HERE, changes); +} + +// Verify that deletion from the sync server of locally existing metadata will +// trigger an undelete message sent to the server. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + UndeleteExistingMetadataOnSyncChange) { + local_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + local_.UpdateCardStats(BuildCard("card", 3, 4)); + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + syncer::SyncChangeList changes; + changes.push_back( + BuildChange(syncer::SyncChange::ACTION_DELETE, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 0, 0)); + changes.push_back(BuildChange(syncer::SyncChange::ACTION_DELETE, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", + 0, 0)); + + EXPECT_CALL(local_, UpdateAddressStats(_)).Times(0); + EXPECT_CALL(local_, UpdateCardStats(_)).Times(0); + EXPECT_CALL(local_, + SendChangesToSyncServer(UnorderedElementsAre( + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_ADD, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 1, 2), + SyncChangeAndDataMatch( + syncer::SyncChange::ACTION_ADD, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", 3, 4)))); + + local_.ProcessSyncChanges(FROM_HERE, changes); +} + +// Verify that processing sync changes maintains the local cache of sync server +// data, which is used to avoid calling the expensive GetAllSyncData() function. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + CacheIsUpToDateAfterSyncChange) { + local_.UpdateAddressStats(BuildAddress("addr1", 1, 2)); + local_.UpdateAddressStats(BuildAddress("addr2", 3, 4)); + local_.UpdateCardStats(BuildCard("card1", 5, 6)); + local_.UpdateCardStats(BuildCard("card2", 7, 8)); + remote_.UpdateAddressStats(BuildAddress("addr1", 1, 2)); + remote_.UpdateAddressStats(BuildAddress("addr2", 3, 4)); + remote_.UpdateCardStats(BuildCard("card1", 5, 6)); + remote_.UpdateCardStats(BuildCard("card2", 7, 8)); + MergeMetadata(&local_, &remote_); + syncer::SyncChangeList changes; + changes.push_back( + BuildChange(syncer::SyncChange::ACTION_UPDATE, "address-addr1", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr1", 10, 20)); + changes.push_back(BuildChange(syncer::SyncChange::ACTION_UPDATE, "card-card1", + sync_pb::WalletMetadataSpecifics::CARD, "card1", + 50, 60)); + local_.ProcessSyncChanges(FROM_HERE, changes); + // This method dooes not trigger notifications or sync: + local_.ClearServerData(); + + EXPECT_CALL( + local_, + SendChangesToSyncServer(UnorderedElementsAre( + SyncChangeMatches(syncer::SyncChange::ACTION_DELETE, "address-addr1"), + SyncChangeMatches(syncer::SyncChange::ACTION_DELETE, "address-addr2"), + SyncChangeMatches(syncer::SyncChange::ACTION_DELETE, "card-card1"), + SyncChangeMatches(syncer::SyncChange::ACTION_DELETE, "card-card2")))); + + local_.AutofillMultipleChanged(); +} + +// Verify that Wallet data arriving after metadata will not send lower metadata +// values to the sync server. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + SaveHigherValuesLocallyOnLateDataArrival) { + remote_.UpdateAddressStats(BuildAddress("addr", 1, 2)); + remote_.UpdateCardStats(BuildCard("card", 3, 4)); + MergeMetadata(&local_, &remote_); + syncer::SyncChangeList changes; + changes.push_back( + BuildChange(syncer::SyncChange::ACTION_UPDATE, "address-addr", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr", 5, 6)); + changes.push_back(BuildChange(syncer::SyncChange::ACTION_UPDATE, "card-card", + sync_pb::WalletMetadataSpecifics::CARD, "card", + 7, 8)); + local_.ProcessSyncChanges(FROM_HERE, changes); + local_.UpdateAddressStats(BuildAddress("addr", 0, 0)); + local_.UpdateCardStats(BuildCard("card", 0, 0)); + + EXPECT_CALL(local_, + UpdateAddressStats(AutofillMetadataMatches("addr", 5, 6))); + EXPECT_CALL(local_, UpdateCardStats(AutofillMetadataMatches("card", 7, 8))); + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + local_.AutofillMultipleChanged(); +} + +// Verify that processing a small subset of metadata changes before any Wallet +// data arrived will not cause sending lower metadata values to the sync server +// once the data finally arrives. +TEST_F(AutofillWalletMetadataSyncableServiceTest, + SaveHigherValuesLocallyOnLateDataArrivalAfterPartialUpdates) { + remote_.UpdateAddressStats(BuildAddress("addr1", 1, 2)); + remote_.UpdateAddressStats(BuildAddress("addr2", 3, 4)); + remote_.UpdateCardStats(BuildCard("card3", 5, 6)); + remote_.UpdateCardStats(BuildCard("card4", 7, 8)); + MergeMetadata(&local_, &remote_); + syncer::SyncChangeList changes; + changes.push_back( + BuildChange(syncer::SyncChange::ACTION_UPDATE, "address-addr1", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr1", 9, 10)); + changes.push_back(BuildChange(syncer::SyncChange::ACTION_UPDATE, "card-card1", + sync_pb::WalletMetadataSpecifics::CARD, "card1", + 11, 12)); + changes.push_back( + BuildChange(syncer::SyncChange::ACTION_UPDATE, "address-addr2", + sync_pb::WalletMetadataSpecifics::ADDRESS, "addr2", 13, 14)); + changes.push_back(BuildChange(syncer::SyncChange::ACTION_UPDATE, "card-card2", + sync_pb::WalletMetadataSpecifics::CARD, "card2", + 15, 16)); + local_.ProcessSyncChanges(FROM_HERE, changes); + changes.resize(2); + local_.ProcessSyncChanges(FROM_HERE, changes); + local_.UpdateAddressStats(BuildAddress("addr1", 0, 0)); + local_.UpdateAddressStats(BuildAddress("addr2", 0, 0)); + local_.UpdateCardStats(BuildCard("card1", 0, 0)); + local_.UpdateCardStats(BuildCard("card2", 0, 0)); + + EXPECT_CALL(local_, + UpdateAddressStats(AutofillMetadataMatches("addr1", 9, 10))); + EXPECT_CALL(local_, + UpdateCardStats(AutofillMetadataMatches("card1", 11, 12))); + EXPECT_CALL(local_, + UpdateAddressStats(AutofillMetadataMatches("addr2", 13, 14))); + EXPECT_CALL(local_, + UpdateCardStats(AutofillMetadataMatches("card2", 15, 16))); + EXPECT_CALL(local_, SendChangesToSyncServer(_)).Times(0); + + local_.AutofillMultipleChanged(); +} + +} // namespace +} // namespace autofill diff --git a/components/autofill/core/browser/webdata/autofill_webdata_backend.h b/components/autofill/core/browser/webdata/autofill_webdata_backend.h index 2f46301..8b06ffb 100644 --- a/components/autofill/core/browser/webdata/autofill_webdata_backend.h +++ b/components/autofill/core/browser/webdata/autofill_webdata_backend.h @@ -31,10 +31,10 @@ class AutofillWebDataBackend { // 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. + // Notifies listeners on both DB and UI threads 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. The UI + // thread notifications are asynchronous. virtual void NotifyOfMultipleAutofillChanges() = 0; }; diff --git a/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc b/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc index ec48b80..187f3e6 100644 --- a/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc +++ b/components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc @@ -67,6 +67,12 @@ void AutofillWebDataBackendImpl::RemoveExpiredFormElements() { void AutofillWebDataBackendImpl::NotifyOfMultipleAutofillChanges() { DCHECK(db_thread_->BelongsToCurrentThread()); + + // DB thread notification. + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, db_observer_list_, + AutofillMultipleChanged()); + + // UI thread notification. ui_thread_->PostTask(FROM_HERE, on_changed_callback_); } @@ -278,6 +284,10 @@ WebDatabase::State AutofillWebDataBackendImpl::AddCreditCard( return WebDatabase::COMMIT_NOT_NEEDED; } + FOR_EACH_OBSERVER( + AutofillWebDataServiceObserverOnDBThread, db_observer_list_, + CreditCardChanged(CreditCardChange(CreditCardChange::ADD, + credit_card.guid(), &credit_card))); return WebDatabase::COMMIT_NEEDED; } @@ -297,6 +307,11 @@ WebDatabase::State AutofillWebDataBackendImpl::UpdateCreditCard( NOTREACHED(); return WebDatabase::COMMIT_NOT_NEEDED; } + + FOR_EACH_OBSERVER( + AutofillWebDataServiceObserverOnDBThread, db_observer_list_, + CreditCardChanged(CreditCardChange(CreditCardChange::UPDATE, + credit_card.guid(), &credit_card))); return WebDatabase::COMMIT_NEEDED; } @@ -307,6 +322,10 @@ WebDatabase::State AutofillWebDataBackendImpl::RemoveCreditCard( NOTREACHED(); return WebDatabase::COMMIT_NOT_NEEDED; } + + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, db_observer_list_, + CreditCardChanged(CreditCardChange(CreditCardChange::REMOVE, + guid, nullptr))); return WebDatabase::COMMIT_NEEDED; } @@ -361,20 +380,31 @@ WebDatabase::State AutofillWebDataBackendImpl::UpdateServerCardUsageStats( const CreditCard& card, WebDatabase* db) { DCHECK(db_thread_->BelongsToCurrentThread()); - if (AutofillTable::FromWebDatabase(db)->UpdateServerCardUsageStats(card)) - return WebDatabase::COMMIT_NEEDED; - return WebDatabase::COMMIT_NOT_NEEDED; + if (!AutofillTable::FromWebDatabase(db)->UpdateServerCardUsageStats(card)) + return WebDatabase::COMMIT_NOT_NEEDED; + + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, db_observer_list_, + CreditCardChanged(CreditCardChange(CreditCardChange::UPDATE, + card.guid(), &card))); + + return WebDatabase::COMMIT_NEEDED; } WebDatabase::State AutofillWebDataBackendImpl::UpdateServerAddressUsageStats( const AutofillProfile& profile, WebDatabase* db) { DCHECK(db_thread_->BelongsToCurrentThread()); - if (AutofillTable::FromWebDatabase(db)->UpdateServerAddressUsageStats( + if (!AutofillTable::FromWebDatabase(db)->UpdateServerAddressUsageStats( profile)) { - return WebDatabase::COMMIT_NEEDED; + return WebDatabase::COMMIT_NOT_NEEDED; } - return WebDatabase::COMMIT_NOT_NEEDED; + + FOR_EACH_OBSERVER( + AutofillWebDataServiceObserverOnDBThread, db_observer_list_, + AutofillProfileChanged(AutofillProfileChange( + AutofillProfileChange::UPDATE, profile.guid(), &profile))); + + return WebDatabase::COMMIT_NEEDED; } WebDatabase::State AutofillWebDataBackendImpl::ClearAllServerData( @@ -400,12 +430,17 @@ WebDatabase::State 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 (const std::string& guid : profile_guids) { FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, db_observer_list_, - AutofillProfileChanged(change)); + AutofillProfileChanged(AutofillProfileChange( + AutofillProfileChange::REMOVE, guid, nullptr))); + } + for (const std::string& guid : credit_card_guids) { + FOR_EACH_OBSERVER(AutofillWebDataServiceObserverOnDBThread, + db_observer_list_, + CreditCardChanged(CreditCardChange( + CreditCardChange::REMOVE, guid, nullptr))); } // Note: It is the caller's responsibility to post notifications for any // changes, e.g. by calling the Refresh() method of PersonalDataManager. diff --git a/components/autofill/core/browser/webdata/autofill_webdata_service_observer.h b/components/autofill/core/browser/webdata/autofill_webdata_service_observer.h index f4333e5..e8bf79c 100644 --- a/components/autofill/core/browser/webdata/autofill_webdata_service_observer.h +++ b/components/autofill/core/browser/webdata/autofill_webdata_service_observer.h @@ -18,6 +18,14 @@ class AutofillWebDataServiceObserverOnDBThread { // in the WebDatabase. virtual void AutofillProfileChanged(const AutofillProfileChange& change) {} + // Called on DB thread when a CreditCard has been added/removed/updated in the + // WebDatabase. + virtual void CreditCardChanged(const CreditCardChange& change) {} + + // Called on DB thread when multiple Autofill entries have been modified by + // Sync. + virtual void AutofillMultipleChanged() {} + protected: virtual ~AutofillWebDataServiceObserverOnDBThread() {} }; diff --git a/components/components_tests.gyp b/components/components_tests.gyp index 1d368c3..be4aad8 100644 --- a/components/components_tests.gyp +++ b/components/components_tests.gyp @@ -58,6 +58,7 @@ 'autofill/core/browser/ui/card_unmask_prompt_controller_impl_unittest.cc', 'autofill/core/browser/validation_unittest.cc', 'autofill/core/browser/webdata/autofill_profile_syncable_service_unittest.cc', + 'autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service_unittest.cc', 'autofill/core/browser/webdata/autofill_table_unittest.cc', 'autofill/core/browser/webdata/web_data_service_unittest.cc', 'autofill/core/common/autofill_regexes_unittest.cc', diff --git a/components/webdata_services/web_data_service_wrapper.cc b/components/webdata_services/web_data_service_wrapper.cc index 73d48e7..d1aeea8 100644 --- a/components/webdata_services/web_data_service_wrapper.cc +++ b/components/webdata_services/web_data_service_wrapper.cc @@ -11,6 +11,7 @@ #include "components/autofill/core/browser/webdata/autocomplete_syncable_service.h" #include "components/autofill/core/browser/webdata/autofill_profile_syncable_service.h" #include "components/autofill/core/browser/webdata/autofill_table.h" +#include "components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.h" #include "components/autofill/core/browser/webdata/autofill_wallet_syncable_service.h" #include "components/autofill/core/browser/webdata/autofill_webdata_service.h" #include "components/password_manager/core/browser/webdata/logins_table.h" @@ -47,6 +48,9 @@ void InitSyncableServicesOnDBThread( autofill_web_data.get(), autofill_backend, app_locale); autofill::AutofillWalletSyncableService::CreateForWebDataServiceAndBackend( autofill_web_data.get(), autofill_backend, app_locale); + autofill::AutofillWalletMetadataSyncableService:: + CreateForWebDataServiceAndBackend(autofill_web_data.get(), + autofill_backend, app_locale); autofill::AutofillProfileSyncableService::FromWebDataService( autofill_web_data.get())->InjectStartSyncFlare(sync_flare); diff --git a/sync/api/sync_data.h b/sync/api/sync_data.h index 6815896..e3885291 100644 --- a/sync/api/sync_data.h +++ b/sync/api/sync_data.h @@ -193,7 +193,7 @@ class SYNC_EXPORT SyncDataRemote : public SyncData { }; // gmock printer helper. -void PrintTo(const SyncData& sync_data, std::ostream* os); +void SYNC_EXPORT PrintTo(const SyncData& sync_data, std::ostream* os); typedef std::vector<SyncData> SyncDataList; |