summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorrouslan <rouslan@chromium.org>2015-06-23 15:10:15 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-23 22:11:51 +0000
commitfd7f4646661ae21fce8658b0a39b9ffe9616b897 (patch)
tree75abdf68bc2a24c94f63bfe30c907faf54644131
parent611a53e15beeda82fe4f00b04d8dfba33043e356 (diff)
downloadchromium_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}
-rw-r--r--chrome/browser/sync/profile_sync_components_factory_impl.cc4
-rw-r--r--components/autofill.gypi2
-rw-r--r--components/autofill/core/browser/BUILD.gn3
-rw-r--r--components/autofill/core/browser/webdata/autofill_change.cc21
-rw-r--r--components/autofill/core/browser/webdata/autofill_change.h46
-rw-r--r--components/autofill/core/browser/webdata/autofill_profile_syncable_service.cc27
-rw-r--r--components/autofill/core/browser/webdata/autofill_profile_syncable_service.h5
-rw-r--r--components/autofill/core/browser/webdata/autofill_profile_syncable_service_unittest.cc19
-rw-r--r--components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.cc519
-rw-r--r--components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.h151
-rw-r--r--components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service_unittest.cc841
-rw-r--r--components/autofill/core/browser/webdata/autofill_webdata_backend.h8
-rw-r--r--components/autofill/core/browser/webdata/autofill_webdata_backend_impl.cc55
-rw-r--r--components/autofill/core/browser/webdata/autofill_webdata_service_observer.h8
-rw-r--r--components/components_tests.gyp1
-rw-r--r--components/webdata_services/web_data_service_wrapper.cc4
-rw-r--r--sync/api/sync_data.h2
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;