diff options
author | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-07 00:26:46 +0000 |
---|---|---|
committer | tim@chromium.org <tim@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-04-07 00:26:46 +0000 |
commit | 1a5db8cc22d8a87d230f5a239bf25fcd7bec7846 (patch) | |
tree | a4ceaf13b7035833fab91247d08bb312c1c413c0 | |
parent | ee9080648d4f6ecceab3f0c1ba7048474893ce44 (diff) | |
download | chromium_src-1a5db8cc22d8a87d230f5a239bf25fcd7bec7846.zip chromium_src-1a5db8cc22d8a87d230f5a239bf25fcd7bec7846.tar.gz chromium_src-1a5db8cc22d8a87d230f5a239bf25fcd7bec7846.tar.bz2 |
Make the Autofill glue components capable of handling both types based on the NotificationType (for chrome -> sync) and the existence of certain AutofillSpecifics sub messages (for sync -> chrome).
Handle duplicate chrome profile labels by generating a unique label using current time (happens during association as well as change processing).
Handle relabelling by Remove/Add of the sync node.
Review URL: http://codereview.chromium.org/1541005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@43785 0039d316-1c4b-4281-b951-d872f2087c98
16 files changed, 1458 insertions, 231 deletions
diff --git a/chrome/browser/sync/engine/change_reorder_buffer.cc b/chrome/browser/sync/engine/change_reorder_buffer.cc index a74c62e0..7560e21 100644 --- a/chrome/browser/sync/engine/change_reorder_buffer.cc +++ b/chrome/browser/sync/engine/change_reorder_buffer.cc @@ -131,6 +131,8 @@ void ChangeReorderBuffer::GetAllChangesInTreeOrder( ChangeRecord record; record.id = i->first; record.action = ChangeRecord::ACTION_DELETE; + if (extra_data_.find(record.id) != extra_data_.end()) + record.extra = extra_data_[record.id]; changelist->push_back(record); } else { traversal.ExpandToInclude(trans, i->first); @@ -160,6 +162,8 @@ void ChangeReorderBuffer::GetAllChangesInTreeOrder( record.action = ChangeRecord::ACTION_ADD; else record.action = ChangeRecord::ACTION_UPDATE; + if (extra_data_.find(record.id) != extra_data_.end()) + record.extra = extra_data_[record.id]; changelist->push_back(record); } diff --git a/chrome/browser/sync/engine/change_reorder_buffer.h b/chrome/browser/sync/engine/change_reorder_buffer.h index 5194d6b..6c199ed 100644 --- a/chrome/browser/sync/engine/change_reorder_buffer.h +++ b/chrome/browser/sync/engine/change_reorder_buffer.h @@ -36,6 +36,8 @@ namespace sync_api { class ChangeReorderBuffer { public: typedef SyncManager::ChangeRecord ChangeRecord; + typedef SyncManager::ExtraChangeRecordData ExtraChangeRecordData; + ChangeReorderBuffer() { } // Insert an item, identified by the metahandle |id|, into the reorder @@ -62,6 +64,10 @@ class ChangeReorderBuffer { OP_UPDATE_PROPERTIES_ONLY; } + void SetExtraDataForId(int64 id, ExtraChangeRecordData* extra) { + extra_data_[id] = extra; + } + // Reset the buffer, forgetting any pushed items, so that it can be used // again to reorder a new set of changes. void Clear() { @@ -87,11 +93,15 @@ class ChangeReorderBuffer { OP_UPDATE_POSITION_AND_PROPERTIES, // UpdatedItem with position_changed=1. }; typedef std::map<int64, Operation> OperationMap; + typedef std::map<int64, ExtraChangeRecordData*> ExtraDataMap; // Stores the items that have been pushed into the buffer, and the type of // operation that was associated with them. OperationMap operations_; + // Stores extra ChangeRecord data per-ID. + ExtraDataMap extra_data_; + DISALLOW_COPY_AND_ASSIGN(ChangeReorderBuffer); }; diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc index 6e3c375..099d83d 100644 --- a/chrome/browser/sync/engine/syncapi.cc +++ b/chrome/browser/sync/engine/syncapi.cc @@ -1093,6 +1093,13 @@ class SyncManager::SyncInternal { AutoLock lock(initialized_mutex_); return initialized_; } + + void SetExtraChangeRecordData(int64 id, + syncable::ModelType type, + ChangeReorderBuffer* buffer, + const syncable::EntryKernel& original, + bool existed_before, + bool exists_now); private: // Try to authenticate using a LSID cookie. void AuthenticateWithLsid(const std::string& lsid); @@ -1686,6 +1693,21 @@ void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncApi( } } +void SyncManager::SyncInternal::SetExtraChangeRecordData(int64 id, + syncable::ModelType type, ChangeReorderBuffer* buffer, + const syncable::EntryKernel& original, bool existed_before, + bool exists_now) { + // Extra data for autofill deletions. + if (type == syncable::AUTOFILL) { + if (!exists_now && existed_before) { + sync_pb::AutofillSpecifics* s = new sync_pb::AutofillSpecifics; + s->CopyFrom(original.ref(SPECIFICS).GetExtension(sync_pb::autofill)); + ExtraChangeRecordData* extra = new ExtraAutofillChangeRecordData(s); + buffer->SetExtraDataForId(id, extra); + } + } +} + void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer( const syncable::DirectoryChangeEvent& event) { // We only expect one notification per sync step, so change_buffers_ should @@ -1715,6 +1737,9 @@ void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer( change_buffers_[type].PushDeletedItem(id); else if (exists_now && existed_before && VisiblePropertiesDiffer(*i, e)) change_buffers_[type].PushUpdatedItem(id, VisiblePositionsDiffer(*i, e)); + + SetExtraChangeRecordData(id, type, &change_buffers_[type], *i, + existed_before, exists_now); } } @@ -1975,4 +2000,8 @@ UserShare* SyncManager::GetUserShare() const { return data_->GetUserShare(); } +SyncManager::ExtraAutofillChangeRecordData::~ExtraAutofillChangeRecordData() { + delete pre_deletion_data; +} + } // namespace sync_api diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h index 1991be8..6420be6 100644 --- a/chrome/browser/sync/engine/syncapi.h +++ b/chrome/browser/sync/engine/syncapi.h @@ -467,6 +467,14 @@ class SyncManager { // internal types from clients of the interface. class SyncInternal; + // Derive from this class and add your own data members to associate extra + // information with a ChangeRecord. + class ExtraChangeRecordData { + public: + ExtraChangeRecordData() {} + virtual ~ExtraChangeRecordData() {} + }; + // ChangeRecord indicates a single item that changed as a result of a sync // operation. This gives the sync id of the node that changed, and the type // of change. To get the actual property values after an ADD or UPDATE, the @@ -477,9 +485,20 @@ class SyncManager { ACTION_DELETE, ACTION_UPDATE, }; - ChangeRecord() : id(kInvalidId), action(ACTION_ADD) {} + ChangeRecord() : id(kInvalidId), action(ACTION_ADD), extra(NULL) {} int64 id; Action action; + ExtraChangeRecordData* extra; + }; + + // Extra specifics data that certain model types require. This is only + // used for autofill DELETE changes. + class ExtraAutofillChangeRecordData : public ExtraChangeRecordData { + public: + explicit ExtraAutofillChangeRecordData(sync_pb::AutofillSpecifics* s) + : pre_deletion_data(s) {} + virtual ~ExtraAutofillChangeRecordData(); + const sync_pb::AutofillSpecifics* pre_deletion_data; }; // Status encapsulates detailed state about the internals of the SyncManager. diff --git a/chrome/browser/sync/glue/autofill_change_processor.cc b/chrome/browser/sync/glue/autofill_change_processor.cc index d8e9345..56b6e98 100644 --- a/chrome/browser/sync/glue/autofill_change_processor.cc +++ b/chrome/browser/sync/glue/autofill_change_processor.cc @@ -10,27 +10,33 @@ #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "chrome/browser/profile.h" +#include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/sync/glue/autofill_model_associator.h" #include "chrome/browser/sync/profile_sync_service.h" -#include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/webdata/autofill_change.h" #include "chrome/browser/webdata/web_data_service.h" #include "chrome/browser/webdata/web_database.h" #include "chrome/common/notification_service.h" +typedef sync_api::SyncManager::ExtraAutofillChangeRecordData + ExtraAutofillChangeRecordData; + namespace browser_sync { AutofillChangeProcessor::AutofillChangeProcessor( AutofillModelAssociator* model_associator, WebDatabase* web_database, + PersonalDataManager* personal_data, UnrecoverableErrorHandler* error_handler) : ChangeProcessor(error_handler), model_associator_(model_associator), web_database_(web_database), + personal_data_(personal_data), observing_(false) { DCHECK(model_associator); DCHECK(web_database); DCHECK(error_handler); + DCHECK(personal_data); DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); StartObserving(); } @@ -40,30 +46,145 @@ void AutofillChangeProcessor::Observe(NotificationType type, const NotificationDetails& details) { LOG(INFO) << "Observed autofill change."; DCHECK(running()); - DCHECK(NotificationType::AUTOFILL_ENTRIES_CHANGED == type); DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); if (!observing_) { return; } - AutofillChangeList* changes = Details<AutofillChangeList>(details).ptr(); - sync_api::WriteTransaction trans(share_handle()); + sync_api::ReadNode autofill_root(&trans); + if (!autofill_root.InitByTagLookup(kAutofillTag)) { + error_handler()->OnUnrecoverableError(); + LOG(ERROR) << "Server did not create the top-level autofill node. " + << "We might be running against an out-of-date server."; + return; + } + + switch (type.value) { + case NotificationType::AUTOFILL_ENTRIES_CHANGED: { + AutofillChangeList* changes = Details<AutofillChangeList>(details).ptr(); + ObserveAutofillEntriesChanged(changes, &trans, autofill_root); + break; + } + case NotificationType::AUTOFILL_PROFILE_CHANGED: { + AutofillProfileChange* change = + Details<AutofillProfileChange>(details).ptr(); + ObserveAutofillProfileChanged(change, &trans, autofill_root); + break; + } + default: + NOTREACHED() << "Invalid NotificationType."; + } +} + +void AutofillChangeProcessor::HandleMoveAsideIfNeeded( + sync_api::BaseTransaction* trans, AutoFillProfile* profile, + std::string* tag) { + DCHECK_EQ(AutofillModelAssociator::ProfileLabelToTag(profile->Label()), + *tag); + sync_api::ReadNode read_node(trans); + if (read_node.InitByClientTagLookup(syncable::AUTOFILL, *tag)) { + // Handle the edge case of duplicate labels. + string16 label(AutofillModelAssociator::MakeUniqueLabel(profile->Label(), + trans)); + if (label.empty()) { + error_handler()->OnUnrecoverableError(); + return; + } + tag->assign(AutofillModelAssociator::ProfileLabelToTag(label)); + + profile->set_label(label); + if (!web_database_->UpdateAutoFillProfile(*profile)) { + LOG(ERROR) << "Failed to overwrite label for node" << label; + error_handler()->OnUnrecoverableError(); + return; + } + + // Notify the PersonalDataManager that it's out of date. + PostOptimisticRefreshTask(); + } +} + +void AutofillChangeProcessor::PostOptimisticRefreshTask() { + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + new AutofillModelAssociator::DoOptimisticRefreshTask( + personal_data_)); +} + +void AutofillChangeProcessor::AddAutofillProfileSyncNode( + sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill, + const std::string& tag, const AutoFillProfile* profile) { + sync_api::WriteNode sync_node(trans); + if (!sync_node.InitUniqueByCreation(syncable::AUTOFILL, autofill, tag)) { + LOG(ERROR) << "Failed to create autofill sync node."; + error_handler()->OnUnrecoverableError(); + return; + } + sync_node.SetTitle(UTF8ToWide(tag)); + + WriteAutofillProfile(*profile, &sync_node); + model_associator_->Associate(&tag, sync_node.GetId()); +} + +void AutofillChangeProcessor::ObserveAutofillProfileChanged( + AutofillProfileChange* change, sync_api::WriteTransaction* trans, + const sync_api::ReadNode& autofill_root) { + std::string tag(AutofillModelAssociator::ProfileLabelToTag(change->key())); + switch (change->type()) { + case AutofillProfileChange::ADD: { + scoped_ptr<AutoFillProfile> clone( + static_cast<AutoFillProfile*>(change->profile()->Clone())); + DCHECK_EQ(AutofillModelAssociator::ProfileLabelToTag(clone->Label()), + tag); + HandleMoveAsideIfNeeded(trans, clone.get(), &tag); + AddAutofillProfileSyncNode(trans, autofill_root, tag, clone.get()); + break; + } + case AutofillProfileChange::UPDATE: { + scoped_ptr<AutoFillProfile> clone( + static_cast<AutoFillProfile*>(change->profile()->Clone())); + sync_api::WriteNode sync_node(trans); + if (change->pre_update_label() != change->profile()->Label()) { + // A re-labelling: we need to remove + add on the sync side. + RemoveSyncNode(AutofillModelAssociator::ProfileLabelToTag( + change->pre_update_label()), trans); + // Watch out! Could be relabelling to an existing label! + HandleMoveAsideIfNeeded(trans, clone.get(), &tag); + AddAutofillProfileSyncNode(trans, autofill_root, tag, + clone.get()); + return; + } + int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); + if (sync_api::kInvalidId == sync_id) { + LOG(ERROR) << "Unexpected notification for: " << tag; + error_handler()->OnUnrecoverableError(); + return; + } else { + if (!sync_node.InitByIdLookup(sync_id)) { + LOG(ERROR) << "Autofill node lookup failed."; + error_handler()->OnUnrecoverableError(); + return; + } + WriteAutofillProfile(*change->profile(), &sync_node); + } + break; + } + case AutofillProfileChange::REMOVE: { + RemoveSyncNode(tag, trans); + break; + } + } +} +void AutofillChangeProcessor::ObserveAutofillEntriesChanged( + AutofillChangeList* changes, sync_api::WriteTransaction* trans, + const sync_api::ReadNode& autofill_root) { for (AutofillChangeList::iterator change = changes->begin(); change != changes->end(); ++change) { switch (change->type()) { case AutofillChange::ADD: { - sync_api::ReadNode autofill_root(&trans); - if (!autofill_root.InitByTagLookup(kAutofillTag)) { - error_handler()->OnUnrecoverableError(); - LOG(ERROR) << "Server did not create the top-level autofill node. " - << "We might be running against an out-of-date server."; - return; - } - - sync_api::WriteNode sync_node(&trans); + sync_api::WriteNode sync_node(trans); std::string tag = AutofillModelAssociator::KeyToTag(change->key().name(), change->key().value()); @@ -87,16 +208,18 @@ void AutofillChangeProcessor::Observe(NotificationType type, sync_node.SetTitle(UTF16ToWide(change->key().name() + change->key().value())); - WriteAutofill(&sync_node, AutofillEntry(change->key(), timestamps)); - model_associator_->Associate(&(change->key()), sync_node.GetId()); + WriteAutofillEntry(AutofillEntry(change->key(), timestamps), + &sync_node); + model_associator_->Associate(&tag, sync_node.GetId()); } break; case AutofillChange::UPDATE: { - sync_api::WriteNode sync_node(&trans); - int64 sync_id = - model_associator_->GetSyncIdFromChromeId(change->key()); + sync_api::WriteNode sync_node(trans); + std::string tag = AutofillModelAssociator::KeyToTag( + change->key().name(), change->key().value()); + int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); if (sync_api::kInvalidId == sync_id) { LOG(ERROR) << "Unexpected notification for: " << change->key().name(); @@ -120,35 +243,39 @@ void AutofillChangeProcessor::Observe(NotificationType type, return; } - WriteAutofill(&sync_node, AutofillEntry(change->key(), timestamps)); + WriteAutofillEntry(AutofillEntry(change->key(), timestamps), + &sync_node); } break; - - case AutofillChange::REMOVE: - { - sync_api::WriteNode sync_node(&trans); - int64 sync_id = - model_associator_->GetSyncIdFromChromeId(change->key()); - if (sync_api::kInvalidId == sync_id) { - LOG(ERROR) << "Unexpected notification for: " << - change->key().name(); - error_handler()->OnUnrecoverableError(); - return; - } else { - if (!sync_node.InitByIdLookup(sync_id)) { - LOG(ERROR) << "Autofill node lookup failed."; - error_handler()->OnUnrecoverableError(); - return; - } - model_associator_->Disassociate(sync_node.GetId()); - sync_node.Remove(); - } + case AutofillChange::REMOVE: { + std::string tag = AutofillModelAssociator::KeyToTag( + change->key().name(), change->key().value()); + RemoveSyncNode(tag, trans); } break; } } } +void AutofillChangeProcessor::RemoveSyncNode(const std::string& tag, + sync_api::WriteTransaction* trans) { + sync_api::WriteNode sync_node(trans); + int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); + if (sync_api::kInvalidId == sync_id) { + LOG(ERROR) << "Unexpected notification"; + error_handler()->OnUnrecoverableError(); + return; + } else { + if (!sync_node.InitByIdLookup(sync_id)) { + LOG(ERROR) << "Autofill node lookup failed."; + error_handler()->OnUnrecoverableError(); + return; + } + model_associator_->Disassociate(sync_node.GetId()); + sync_node.Remove(); + } +} + void AutofillChangeProcessor::ApplyChangesFromSyncModel( const sync_api::BaseTransaction* trans, const sync_api::SyncManager::ChangeRecord* changes, @@ -167,56 +294,149 @@ void AutofillChangeProcessor::ApplyChangesFromSyncModel( std::vector<AutofillEntry> new_entries; for (int i = 0; i < change_count; ++i) { - if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == - changes[i].action) { - const AutofillKey* key = - model_associator_->GetChromeNodeFromSyncId(changes[i].id); - if (!key || !web_database_->RemoveFormElement(key->name(), - key->value())) { - LOG(ERROR) << "Could not remove autofill node."; - error_handler()->OnUnrecoverableError(); - return; - } - model_associator_->Disassociate(changes[i].id); - } else { - sync_api::ReadNode sync_node(trans); - if (!sync_node.InitByIdLookup(changes[i].id)) { - LOG(ERROR) << "Autofill node lookup failed."; - error_handler()->OnUnrecoverableError(); - return; - } + sync_api::SyncManager::ChangeRecord::Action action(changes[i].action); + if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == action) { + scoped_ptr<ExtraAutofillChangeRecordData> data( + static_cast<ExtraAutofillChangeRecordData*>(changes[i].extra)); + DCHECK(data.get()) << "Extra autofill change record data not present!"; + const sync_pb::AutofillSpecifics* autofill(data->pre_deletion_data); + if (autofill->has_value()) + ApplySyncAutofillEntryDelete(*autofill); + else if (autofill->has_profile()) + ApplySyncAutofillProfileDelete(autofill->profile(), changes[i].id); + else + NOTREACHED() << "Autofill specifics has no data!"; + continue; + } - // Check that the changed node is a child of the autofills folder. - DCHECK(autofill_root.GetId() == sync_node.GetParentId()); - DCHECK(syncable::AUTOFILL == sync_node.GetModelType()); - - const sync_pb::AutofillSpecifics& autofill( - sync_node.GetAutofillSpecifics()); - std::vector<base::Time> timestamps; - size_t timestamps_size = autofill.usage_timestamp_size(); - for (size_t c = 0; c < timestamps_size; ++c) { - timestamps.push_back( - base::Time::FromInternalValue(autofill.usage_timestamp(c))); - } - AutofillKey key(UTF8ToUTF16(autofill.name()), - UTF8ToUTF16(autofill.value())); + // Handle an update or add. + sync_api::ReadNode sync_node(trans); + if (!sync_node.InitByIdLookup(changes[i].id)) { + LOG(ERROR) << "Autofill node lookup failed."; + error_handler()->OnUnrecoverableError(); + return; + } - new_entries.push_back(AutofillEntry(key, timestamps)); + // Check that the changed node is a child of the autofills folder. + DCHECK(autofill_root.GetId() == sync_node.GetParentId()); + DCHECK(syncable::AUTOFILL == sync_node.GetModelType()); - if (sync_api::SyncManager::ChangeRecord::ACTION_ADD == - changes[i].action) { - model_associator_->Associate(&key, sync_node.GetId()); - } - } + const sync_pb::AutofillSpecifics& autofill( + sync_node.GetAutofillSpecifics()); + int64 sync_id = sync_node.GetId(); + if (autofill.has_value()) + ApplySyncAutofillEntryChange(action, autofill, &new_entries, sync_id); + else if (autofill.has_profile()) + ApplySyncAutofillProfileChange(action, autofill.profile(), sync_id); + else + NOTREACHED() << "Autofill specifics has no data!"; } + if (!web_database_->UpdateAutofillEntries(new_entries)) { LOG(ERROR) << "Could not update autofill entries."; error_handler()->OnUnrecoverableError(); return; } + + PostOptimisticRefreshTask(); + StartObserving(); } +void AutofillChangeProcessor::ApplySyncAutofillEntryDelete( + const sync_pb::AutofillSpecifics& autofill) { + if (!web_database_->RemoveFormElement( + UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()))) { + LOG(ERROR) << "Could not remove autofill node."; + error_handler()->OnUnrecoverableError(); + return; + } +} + +void AutofillChangeProcessor::ApplySyncAutofillEntryChange( + sync_api::SyncManager::ChangeRecord::Action action, + const sync_pb::AutofillSpecifics& autofill, + std::vector<AutofillEntry>* new_entries, + int64 sync_id) { + DCHECK_NE(sync_api::SyncManager::ChangeRecord::ACTION_DELETE, action); + + std::vector<base::Time> timestamps; + size_t timestamps_size = autofill.usage_timestamp_size(); + for (size_t c = 0; c < timestamps_size; ++c) { + timestamps.push_back( + base::Time::FromInternalValue(autofill.usage_timestamp(c))); + } + AutofillKey k(UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value())); + AutofillEntry new_entry(k, timestamps); + + new_entries->push_back(new_entry); + std::string tag(AutofillModelAssociator::KeyToTag(k.name(), k.value())); + model_associator_->Associate(&tag, sync_id); +} + +void AutofillChangeProcessor::ApplySyncAutofillProfileChange( + sync_api::SyncManager::ChangeRecord::Action action, + const sync_pb::AutofillProfileSpecifics& profile, + int64 sync_id) { + DCHECK_NE(sync_api::SyncManager::ChangeRecord::ACTION_DELETE, action); + + std::string tag(AutofillModelAssociator::ProfileLabelToTag( + UTF8ToUTF16(profile.label()))); + switch (action) { + case sync_api::SyncManager::ChangeRecord::ACTION_ADD: { + PersonalDataManager* pdm = model_associator_->sync_service()-> + profile()->GetPersonalDataManager(); + scoped_ptr<AutoFillProfile> p( + pdm->CreateNewEmptyAutoFillProfileForDBThread( + UTF8ToUTF16(profile.label()))); + AutofillModelAssociator::OverwriteProfileWithServerData(p.get(), + profile); + + model_associator_->Associate(&tag, sync_id); + if (!web_database_->AddAutoFillProfile(*p.get())) { + NOTREACHED() << "Couldn't add autofill profile: " << profile.label(); + return; + } + break; + } + case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE: { + AutoFillProfile* p = NULL; + string16 label = UTF8ToUTF16(profile.label()); + if (!web_database_->GetAutoFillProfileForLabel(label, &p)) { + NOTREACHED() << "Couldn't retrieve autofill profile: " << label; + return; + } + AutofillModelAssociator::OverwriteProfileWithServerData(p, profile); + if (!web_database_->UpdateAutoFillProfile(*p)) { + NOTREACHED() << "Couldn't update autofill profile: " << label; + return; + } + delete p; + break; + } + default: + NOTREACHED(); + } +} + +void AutofillChangeProcessor::ApplySyncAutofillProfileDelete( + const sync_pb::AutofillProfileSpecifics& profile, + int64 sync_id) { + string16 label(UTF8ToUTF16(profile.label())); + AutoFillProfile* ptr = NULL; + bool get_success = web_database_->GetAutoFillProfileForLabel(label, &ptr); + scoped_ptr<AutoFillProfile> p(ptr); + if (!get_success) { + NOTREACHED() << "Couldn't retrieve autofill profile: " << label; + return; + } + if (!web_database_->RemoveAutoFillProfile(p->unique_id())) { + NOTREACHED() << "Couldn't remove autofill profile: " << label; + return; + } + model_associator_->Disassociate(sync_id); +} + void AutofillChangeProcessor::StartImpl(Profile* profile) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); observing_ = true; @@ -232,19 +452,19 @@ void AutofillChangeProcessor::StartObserving() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); notification_registrar_.Add(this, NotificationType::AUTOFILL_ENTRIES_CHANGED, NotificationService::AllSources()); + notification_registrar_.Add(this, NotificationType::AUTOFILL_PROFILE_CHANGED, + NotificationService::AllSources()); } void AutofillChangeProcessor::StopObserving() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); - notification_registrar_.Remove(this, - NotificationType::AUTOFILL_ENTRIES_CHANGED, - NotificationService::AllSources()); + notification_registrar_.RemoveAll(); } // static -void AutofillChangeProcessor::WriteAutofill( - sync_api::WriteNode* node, - const AutofillEntry& entry) { +void AutofillChangeProcessor::WriteAutofillEntry( + const AutofillEntry& entry, + sync_api::WriteNode* node) { sync_pb::AutofillSpecifics autofill; autofill.set_name(UTF16ToUTF8(entry.key().name())); autofill.set_value(UTF16ToUTF8(entry.key().value())); @@ -256,4 +476,38 @@ void AutofillChangeProcessor::WriteAutofill( node->SetAutofillSpecifics(autofill); } +// static +void AutofillChangeProcessor::WriteAutofillProfile( + const AutoFillProfile& profile, sync_api::WriteNode* node) { + sync_pb::AutofillSpecifics autofill; + sync_pb::AutofillProfileSpecifics* s(autofill.mutable_profile()); + s->set_label(UTF16ToUTF8(profile.Label())); + s->set_name_first(UTF16ToUTF8( + profile.GetFieldText(AutoFillType(NAME_FIRST)))); + s->set_name_middle(UTF16ToUTF8( + profile.GetFieldText(AutoFillType(NAME_MIDDLE)))); + s->set_name_last(UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_LAST)))); + s->set_address_home_line1( + UTF16ToUTF8(profile.GetFieldText(AutoFillType(ADDRESS_HOME_LINE1)))); + s->set_address_home_line2( + UTF16ToUTF8(profile.GetFieldText(AutoFillType(ADDRESS_HOME_LINE2)))); + s->set_address_home_city(UTF16ToUTF8(profile.GetFieldText( + AutoFillType(ADDRESS_HOME_CITY)))); + s->set_address_home_state(UTF16ToUTF8(profile.GetFieldText( + AutoFillType(ADDRESS_HOME_STATE)))); + s->set_address_home_country(UTF16ToUTF8(profile.GetFieldText( + AutoFillType(ADDRESS_HOME_COUNTRY)))); + s->set_address_home_zip(UTF16ToUTF8(profile.GetFieldText( + AutoFillType(ADDRESS_HOME_ZIP)))); + s->set_email_address(UTF16ToUTF8(profile.GetFieldText( + AutoFillType(EMAIL_ADDRESS)))); + s->set_company_name(UTF16ToUTF8(profile.GetFieldText( + AutoFillType(COMPANY_NAME)))); + s->set_phone_fax_whole_number(UTF16ToUTF8(profile.GetFieldText( + AutoFillType(PHONE_FAX_WHOLE_NUMBER)))); + s->set_phone_home_whole_number(UTF16ToUTF8(profile.GetFieldText( + AutoFillType(PHONE_HOME_WHOLE_NUMBER)))); + node->SetAutofillSpecifics(autofill); +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/autofill_change_processor.h b/chrome/browser/sync/glue/autofill_change_processor.h index ccd266d..4304f7e 100644 --- a/chrome/browser/sync/glue/autofill_change_processor.h +++ b/chrome/browser/sync/glue/autofill_change_processor.h @@ -6,13 +6,21 @@ #define CHROME_BROWSER_SYNC_GLUE_AUTOFILL_CHANGE_PROCESSOR_H_ #include "base/scoped_ptr.h" +#include "chrome/browser/autofill/autofill_profile.h" +#include "chrome/browser/autofill/credit_card.h" +#include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/change_processor.h" #include "chrome/browser/sync/glue/sync_backend_host.h" +#include "chrome/browser/sync/protocol/autofill_specifics.pb.h" +#include "chrome/browser/webdata/web_data_service.h" #include "chrome/common/notification_observer.h" #include "chrome/common/notification_registrar.h" +class AutofillCreditCardChange; class AutofillEntry; +class AutofillProfileChange; +class PersonalDataManager; class WebDatabase; namespace browser_sync { @@ -22,12 +30,13 @@ class UnrecoverableErrorHandler; // This class is responsible for taking changes from the web data service and // applying them to the sync_api 'syncable' model, and vice versa. All -// operations and use of this class are from the UI thread. +// operations and use of this class are from the DB thread. class AutofillChangeProcessor : public ChangeProcessor, public NotificationObserver { public: AutofillChangeProcessor(AutofillModelAssociator* model_associator, WebDatabase* web_database, + PersonalDataManager* personal_data, UnrecoverableErrorHandler* error_handler); virtual ~AutofillChangeProcessor() {} @@ -45,8 +54,11 @@ class AutofillChangeProcessor : public ChangeProcessor, // Copy the properties of the given Autofill entry into the sync // node. - static void WriteAutofill(sync_api::WriteNode* node, - const AutofillEntry& entry); + static void WriteAutofillEntry(const AutofillEntry& entry, + sync_api::WriteNode* node); + // As above, for autofill profiles. + static void WriteAutofillProfile(const AutoFillProfile& profile, + sync_api::WriteNode* node); protected: virtual void StartImpl(Profile* profile); @@ -56,6 +68,59 @@ class AutofillChangeProcessor : public ChangeProcessor, void StartObserving(); void StopObserving(); + // A function to remove the sync node for an autofill entry or profile. + void RemoveSyncNode(const std::string& tag, + sync_api::WriteTransaction* trans); + + // These two methods are dispatched to by Observe depending on the type. + void ObserveAutofillEntriesChanged(AutofillChangeList* changes, + sync_api::WriteTransaction* trans, + const sync_api::ReadNode& autofill_root); + void ObserveAutofillProfileChanged(AutofillProfileChange* change, + sync_api::WriteTransaction* trans, + const sync_api::ReadNode& autofill_root); + + // The following methods are the implementation of ApplyChangeFromSyncModel + // for the respective autofill subtypes. + void ApplySyncAutofillEntryChange( + sync_api::SyncManager::ChangeRecord::Action action, + const sync_pb::AutofillSpecifics& autofill, + std::vector<AutofillEntry>* new_entries, + int64 sync_id); + void ApplySyncAutofillProfileChange( + sync_api::SyncManager::ChangeRecord::Action action, + const sync_pb::AutofillProfileSpecifics& profile, + int64 sync_id); + + // Delete is a special case of change application. + void ApplySyncAutofillEntryDelete( + const sync_pb::AutofillSpecifics& autofill); + void ApplySyncAutofillProfileDelete( + const sync_pb::AutofillProfileSpecifics& profile, + int64 sync_id); + + // If the chrome model tries to add an AutoFillProfile with a label that + // is already in use, we perform a move-aside by calling-back into the chrome + // model and overwriting the label with a unique value we can apply for sync. + // This method should be called on an ADD notification from the chrome model. + // |tag| contains the unique sync client tag identifier for |profile|, which + // is derived from the profile label using ProfileLabelToTag. + void HandleMoveAsideIfNeeded( + sync_api::BaseTransaction* trans, AutoFillProfile* profile, + std::string* tag); + + // Helper to create a sync node with tag |tag|, storing |profile| as + // the node's AutofillSpecifics. + void AddAutofillProfileSyncNode( + sync_api::WriteTransaction* trans, + const sync_api::BaseNode& autofill, + const std::string& tag, + const AutoFillProfile* profile); + + // Helper to post a task to the UI loop to inform the PersonalDataManager + // it needs to refresh itself. + void PostOptimisticRefreshTask(); + // The two models should be associated according to this ModelAssociator. AutofillModelAssociator* model_associator_; @@ -64,6 +129,10 @@ class AutofillChangeProcessor : public ChangeProcessor, // holding a reference. WebDatabase* web_database_; + // We periodically tell the PersonalDataManager to refresh as we make + // changes to the autofill data in the WebDatabase. + PersonalDataManager* personal_data_; + NotificationRegistrar notification_registrar_; bool observing_; diff --git a/chrome/browser/sync/glue/autofill_data_type_controller.cc b/chrome/browser/sync/glue/autofill_data_type_controller.cc index 5e7bca68..cdb31ce 100644 --- a/chrome/browser/sync/glue/autofill_data_type_controller.cc +++ b/chrome/browser/sync/glue/autofill_data_type_controller.cc @@ -25,7 +25,8 @@ AutofillDataTypeController::AutofillDataTypeController( : profile_sync_factory_(profile_sync_factory), profile_(profile), sync_service_(sync_service), - state_(NOT_RUNNING) { + state_(NOT_RUNNING), + personal_data_(NULL) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); DCHECK(profile_sync_factory); DCHECK(profile); @@ -48,6 +49,20 @@ void AutofillDataTypeController::Start(StartCallback* start_callback) { start_callback_.reset(start_callback); + // Waiting for the personal data is subtle: we do this as the PDM resets + // its cache of unique IDs once it gets loaded. If we were to proceed with + // association, the local ids in the mappings would wind up colliding. + personal_data_ = profile_->GetPersonalDataManager(); + if (!personal_data_->IsDataLoaded()) { + set_state(MODEL_STARTING); + personal_data_->SetObserver(this); + return; + } + + ContinueStartAfterPersonalDataLoaded(); +} + +void AutofillDataTypeController::ContinueStartAfterPersonalDataLoaded() { web_data_service_ = profile_->GetWebDataService(Profile::IMPLICIT_ACCESS); if (web_data_service_.get() && web_data_service_->IsDatabaseLoaded()) { set_state(ASSOCIATING); @@ -62,6 +77,12 @@ void AutofillDataTypeController::Start(StartCallback* start_callback) { } } +void AutofillDataTypeController::OnPersonalDataLoaded() { + DCHECK_EQ(state_, MODEL_STARTING); + personal_data_->RemoveObserver(this); + ContinueStartAfterPersonalDataLoaded(); +} + void AutofillDataTypeController::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { @@ -102,6 +123,7 @@ void AutofillDataTypeController::StartImpl() { profile_sync_factory_->CreateAutofillSyncComponents( sync_service_, web_data_service_->GetDatabase(), + profile_->GetPersonalDataManager(), this); model_associator_.reset(sync_components.model_associator); change_processor_.reset(sync_components.change_processor); diff --git a/chrome/browser/sync/glue/autofill_data_type_controller.h b/chrome/browser/sync/glue/autofill_data_type_controller.h index 5ae23ec..b9b5983 100644 --- a/chrome/browser/sync/glue/autofill_data_type_controller.h +++ b/chrome/browser/sync/glue/autofill_data_type_controller.h @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "base/scoped_ptr.h" +#include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/glue/data_type_controller.h" @@ -21,7 +22,8 @@ class ChangeProcessor; // A class that manages the startup and shutdown of autofill sync. class AutofillDataTypeController : public DataTypeController, - public NotificationObserver { + public NotificationObserver, + public PersonalDataManager::Observer { public: AutofillDataTypeController( ProfileSyncFactory* profile_sync_factory, @@ -64,6 +66,9 @@ class AutofillDataTypeController : public DataTypeController, const NotificationSource& source, const NotificationDetails& details); + // PersonalDataManager::Observer implementation: + virtual void OnPersonalDataLoaded(); + private: void StartImpl(); void StartDone(StartResult result, State state); @@ -72,6 +77,10 @@ class AutofillDataTypeController : public DataTypeController, void StartFailed(StartResult result); void OnUnrecoverableErrorImpl(); + // Second-half of "Start" implementation, called once personal data has + // loaded. + void ContinueStartAfterPersonalDataLoaded(); + void set_state(State state) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); state_ = state; @@ -82,6 +91,7 @@ class AutofillDataTypeController : public DataTypeController, ProfileSyncService* sync_service_; State state_; + PersonalDataManager* personal_data_; scoped_refptr<WebDataService> web_data_service_; scoped_ptr<AssociatorInterface> model_associator_; scoped_ptr<ChangeProcessor> change_processor_; diff --git a/chrome/browser/sync/glue/autofill_model_associator.cc b/chrome/browser/sync/glue/autofill_model_associator.cc index 54f254b..019c7b0 100644 --- a/chrome/browser/sync/glue/autofill_model_associator.cc +++ b/chrome/browser/sync/glue/autofill_model_associator.cc @@ -6,7 +6,10 @@ #include <vector> +#include "base/task.h" +#include "base/time.h" #include "base/utf_string_conversions.h" +#include "chrome/browser/autofill/autofill_profile.h" #include "chrome/browser/chrome_thread.h" #include "chrome/browser/profile.h" #include "chrome/browser/sync/engine/syncapi.h" @@ -16,45 +19,206 @@ #include "chrome/browser/webdata/web_database.h" #include "net/base/escape.h" +using base::TimeTicks; + namespace browser_sync { const char kAutofillTag[] = "google_chrome_autofill"; +const char kAutofillEntryNamespaceTag[] = "autofill_entry|"; +const char kAutofillProfileNamespaceTag[] = "autofill_profile|"; + +static const int kMaxNumAttemptsToFindUniqueLabel = 100; AutofillModelAssociator::AutofillModelAssociator( ProfileSyncService* sync_service, WebDatabase* web_database, + PersonalDataManager* personal_data, UnrecoverableErrorHandler* error_handler) : sync_service_(sync_service), web_database_(web_database), + personal_data_(personal_data), error_handler_(error_handler), autofill_node_id_(sync_api::kInvalidId) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); DCHECK(sync_service_); DCHECK(web_database_); DCHECK(error_handler_); + DCHECK(personal_data_); } AutofillModelAssociator::~AutofillModelAssociator() { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); } +bool AutofillModelAssociator::TraverseAndAssociateChromeAutofillEntries( + sync_api::WriteTransaction* write_trans, + const sync_api::ReadNode& autofill_root, + const std::vector<AutofillEntry>& all_entries_from_db, + std::set<AutofillKey>* current_entries, + std::vector<AutofillEntry>* new_entries) { + + const std::vector<AutofillEntry>& entries = all_entries_from_db; + for (std::vector<AutofillEntry>::const_iterator ix = entries.begin(); + ix != entries.end(); ++ix) { + std::string tag = KeyToTag(ix->key().name(), ix->key().value()); + if (id_map_.find(tag) != id_map_.end()) { + // It seems that name/value pairs are not unique in the web database. + // As a result, we have to filter out duplicates here. This is probably + // a bug in the database. + continue; + } + + sync_api::ReadNode node(write_trans); + if (node.InitByClientTagLookup(syncable::AUTOFILL, tag)) { + const sync_pb::AutofillSpecifics& autofill(node.GetAutofillSpecifics()); + DCHECK_EQ(tag, KeyToTag(UTF8ToUTF16(autofill.name()), + UTF8ToUTF16(autofill.value()))); + + std::vector<base::Time> timestamps; + if (MergeTimestamps(autofill, ix->timestamps(), ×tamps)) { + AutofillEntry new_entry(ix->key(), timestamps); + new_entries->push_back(new_entry); + + sync_api::WriteNode write_node(write_trans); + if (!write_node.InitByClientTagLookup(syncable::AUTOFILL, tag)) { + LOG(ERROR) << "Failed to write autofill sync node."; + return false; + } + AutofillChangeProcessor::WriteAutofillEntry(new_entry, &write_node); + } + + Associate(&tag, node.GetId()); + } else { + sync_api::WriteNode node(write_trans); + if (!node.InitUniqueByCreation(syncable::AUTOFILL, + autofill_root, tag)) { + LOG(ERROR) << "Failed to create autofill sync node."; + error_handler_->OnUnrecoverableError(); + return false; + } + node.SetTitle(UTF16ToWide(ix->key().name() + ix->key().value())); + AutofillChangeProcessor::WriteAutofillEntry(*ix, &node); + Associate(&tag, node.GetId()); + } + + current_entries->insert(ix->key()); + } + return true; +} + +bool AutofillModelAssociator::TraverseAndAssociateChromeAutoFillProfiles( + sync_api::WriteTransaction* write_trans, + const sync_api::ReadNode& autofill_root, + const std::vector<AutoFillProfile*>& all_profiles_from_db, + std::set<string16>* current_profiles, + std::vector<AutoFillProfile*>* updated_profiles) { + const std::vector<AutoFillProfile*>& profiles = all_profiles_from_db; + for (std::vector<AutoFillProfile*>::const_iterator ix = profiles.begin(); + ix != profiles.end(); ++ix) { + string16 label((*ix)->Label()); + std::string tag(ProfileLabelToTag(label)); + + sync_api::ReadNode node(write_trans); + if (node.InitByClientTagLookup(syncable::AUTOFILL, tag)) { + const sync_pb::AutofillSpecifics& autofill(node.GetAutofillSpecifics()); + DCHECK(autofill.has_profile()); + DCHECK_EQ(ProfileLabelToTag(UTF8ToUTF16(autofill.profile().label())), + tag); + int64 sync_id = node.GetId(); + if (id_map_.find(tag) != id_map_.end()) { + // We just looked up something we already associated. Move aside. + label = MakeUniqueLabel(label, write_trans); + if (label.empty()) { + error_handler_->OnUnrecoverableError(); + return false; + } + tag = ProfileLabelToTag(label); + (*ix)->set_label(label); + sync_id = MakeNewAutofillProfileSyncNode(write_trans, autofill_root, + tag, **ix); + updated_profiles->push_back(*ix); + } else { + // Overwrite local with cloud state. + if (OverwriteProfileWithServerData(*ix, autofill.profile())) + updated_profiles->push_back(*ix); + sync_id = node.GetId(); + } + + Associate(&tag, sync_id); + } else { + int64 id = MakeNewAutofillProfileSyncNode(write_trans, autofill_root, + tag, **ix); + Associate(&tag, id); + } + current_profiles->insert(label); + } + return true; +} + +// static +string16 AutofillModelAssociator::MakeUniqueLabel( + const string16& non_unique_label, sync_api::BaseTransaction* trans) { + int unique_id = 1; // Priming so we start by appending "2". + while (unique_id++ < kMaxNumAttemptsToFindUniqueLabel) { + string16 suffix(UTF8ToUTF16(IntToString(unique_id))); + string16 unique_label = non_unique_label + suffix; + sync_api::ReadNode node(trans); + if (node.InitByClientTagLookup(syncable::AUTOFILL, + ProfileLabelToTag(unique_label))) { + continue; + } + return unique_label; + } + + LOG(ERROR) << "Couldn't create unique tag for autofill node. Srsly?!"; + return string16(); +} + +int64 AutofillModelAssociator::MakeNewAutofillProfileSyncNode( + sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill_root, + const std::string& tag, const AutoFillProfile& profile) { + sync_api::WriteNode node(trans); + if (!node.InitUniqueByCreation(syncable::AUTOFILL, autofill_root, tag)) { + LOG(ERROR) << "Failed to create autofill sync node."; + error_handler_->OnUnrecoverableError(); + return false; + } + node.SetTitle(UTF8ToWide(tag)); + AutofillChangeProcessor::WriteAutofillProfile(profile, &node); + return node.GetId(); +} + + +bool AutofillModelAssociator::LoadAutofillData( + std::vector<AutofillEntry>* entries, + std::vector<AutoFillProfile*>* profiles) { + if (!web_database_->GetAllAutofillEntries(entries)) + return false; + + if (!web_database_->GetAutoFillProfiles(profiles)) + return false; + + return true; +} + bool AutofillModelAssociator::AssociateModels() { LOG(INFO) << "Associating Autofill Models"; DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); // TODO(zork): Attempt to load the model association from storage. std::vector<AutofillEntry> entries; - if (!web_database_->GetAllAutofillEntries(&entries)) { - LOG(ERROR) << "Could not get the autofill entries."; + ScopedVector<AutoFillProfile> profiles; + + if (!LoadAutofillData(&entries, &profiles.get())) { + LOG(ERROR) << "Could not get the autofill data from WebDatabase."; return false; } - std::set<AutofillKey> current_entries; - std::vector<AutofillEntry> new_entries; - + DataBundle bundle; { sync_api::WriteTransaction trans( sync_service_->backend()->GetUserShareHandle()); + sync_api::ReadNode autofill_root(&trans); if (!autofill_root.InitByTagLookup(kAutofillTag)) { error_handler_->OnUnrecoverableError(); @@ -63,78 +227,13 @@ bool AutofillModelAssociator::AssociateModels() { return false; } - for (std::vector<AutofillEntry>::iterator ix = entries.begin(); - ix != entries.end(); ++ix) { - if (id_map_.find(ix->key()) != id_map_.end()) { - // It seems that name/value pairs are not unique in the web database. - // As a result, we have to filter out duplicates here. This is probably - // a bug in the database. - continue; - } - - std::string tag = KeyToTag(ix->key().name(), ix->key().value()); - - sync_api::ReadNode node(&trans); - if (node.InitByClientTagLookup(syncable::AUTOFILL, tag)) { - const sync_pb::AutofillSpecifics& autofill(node.GetAutofillSpecifics()); - DCHECK_EQ(tag, KeyToTag(UTF8ToUTF16(autofill.name()), - UTF8ToUTF16(autofill.value()))); - - std::vector<base::Time> timestamps; - if (MergeTimestamps(autofill, ix->timestamps(), ×tamps)) { - AutofillEntry new_entry(ix->key(), timestamps); - new_entries.push_back(new_entry); - - sync_api::WriteNode write_node(&trans); - if (!write_node.InitByClientTagLookup(syncable::AUTOFILL, tag)) { - LOG(ERROR) << "Failed to write autofill sync node."; - return false; - } - AutofillChangeProcessor::WriteAutofill(&write_node, new_entry); - } - - Associate(&(ix->key()), node.GetId()); - } else { - sync_api::WriteNode node(&trans); - if (!node.InitUniqueByCreation(syncable::AUTOFILL, - autofill_root, tag)) { - LOG(ERROR) << "Failed to create autofill sync node."; - error_handler_->OnUnrecoverableError(); - return false; - } - node.SetTitle(UTF16ToWide(ix->key().name() + ix->key().value())); - AutofillChangeProcessor::WriteAutofill(&node, *ix); - Associate(&(ix->key()), node.GetId()); - } - - current_entries.insert(ix->key()); - } - - int64 sync_child_id = autofill_root.GetFirstChildId(); - while (sync_child_id != sync_api::kInvalidId) { - sync_api::ReadNode sync_child_node(&trans); - if (!sync_child_node.InitByIdLookup(sync_child_id)) { - LOG(ERROR) << "Failed to fetch child node."; - error_handler_->OnUnrecoverableError(); - return false; - } - const sync_pb::AutofillSpecifics& autofill( - sync_child_node.GetAutofillSpecifics()); - AutofillKey key(UTF8ToUTF16(autofill.name()), - UTF8ToUTF16(autofill.value())); - - if (current_entries.find(key) == current_entries.end()) { - std::vector<base::Time> timestamps; - int timestamps_count = autofill.usage_timestamp_size(); - for (int c = 0; c < timestamps_count; ++c) { - timestamps.push_back(base::Time::FromInternalValue( - autofill.usage_timestamp(c))); - } - Associate(&key, sync_child_node.GetId()); - new_entries.push_back(AutofillEntry(key, timestamps)); - } - - sync_child_id = sync_child_node.GetSuccessorId(); + if (!TraverseAndAssociateChromeAutofillEntries(&trans, autofill_root, + entries, &bundle.current_entries, &bundle.new_entries) || + !TraverseAndAssociateChromeAutoFillProfiles(&trans, autofill_root, + profiles.get(), &bundle.current_profiles, + &bundle.updated_profiles) || + !TraverseAndAssociateAllSyncNodes(&trans, autofill_root, &bundle)) { + return false; } } @@ -143,16 +242,99 @@ bool AutofillModelAssociator::AssociateModels() { // this is the only thread that writes to the database. We also don't have // to worry about the sync model getting out of sync, because changes are // propogated to the ChangeProcessor on this thread. - if (new_entries.size() && - !web_database_->UpdateAutofillEntries(new_entries)) { + if (!SaveChangesToWebData(bundle)) { LOG(ERROR) << "Failed to update autofill entries."; error_handler_->OnUnrecoverableError(); return false; } + ChromeThread::PostTask(ChromeThread::UI, FROM_HERE, + new DoOptimisticRefreshTask(personal_data_)); + return true; +} + +bool AutofillModelAssociator::SaveChangesToWebData(const DataBundle& bundle) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + if (bundle.new_entries.size() && + !web_database_->UpdateAutofillEntries(bundle.new_entries)) { + return false; + } + + for (size_t i = 0; i < bundle.new_profiles.size(); i++) { + if (!web_database_->AddAutoFillProfile(*bundle.new_profiles[i])) + return false; + } + + for (size_t i = 0; i < bundle.updated_profiles.size(); i++) { + if (!web_database_->UpdateAutoFillProfile(*bundle.updated_profiles[i])) + return false; + } return true; } +bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes( + sync_api::WriteTransaction* write_trans, + const sync_api::ReadNode& autofill_root, + DataBundle* bundle) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + + int64 sync_child_id = autofill_root.GetFirstChildId(); + while (sync_child_id != sync_api::kInvalidId) { + sync_api::ReadNode sync_child(write_trans); + if (!sync_child.InitByIdLookup(sync_child_id)) { + LOG(ERROR) << "Failed to fetch child node."; + error_handler_->OnUnrecoverableError(); + return false; + } + const sync_pb::AutofillSpecifics& autofill( + sync_child.GetAutofillSpecifics()); + + if (autofill.has_value()) + AddNativeEntryIfNeeded(autofill, bundle, sync_child); + else if (autofill.has_profile()) + AddNativeProfileIfNeeded(autofill.profile(), bundle, sync_child); + else + NOTREACHED() << "AutofillSpecifics has no autofill data!"; + + sync_child_id = sync_child.GetSuccessorId(); + } + return true; +} + +void AutofillModelAssociator::AddNativeEntryIfNeeded( + const sync_pb::AutofillSpecifics& autofill, DataBundle* bundle, + const sync_api::ReadNode& node) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + AutofillKey key(UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value())); + + if (bundle->current_entries.find(key) == bundle->current_entries.end()) { + std::vector<base::Time> timestamps; + int timestamps_count = autofill.usage_timestamp_size(); + for (int c = 0; c < timestamps_count; ++c) { + timestamps.push_back(base::Time::FromInternalValue( + autofill.usage_timestamp(c))); + } + std::string tag(KeyToTag(key.name(), key.value())); + Associate(&tag, node.GetId()); + bundle->new_entries.push_back(AutofillEntry(key, timestamps)); + } +} + +void AutofillModelAssociator::AddNativeProfileIfNeeded( + const sync_pb::AutofillProfileSpecifics& profile, DataBundle* bundle, + const sync_api::ReadNode& node) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); + if (bundle->current_profiles.find(UTF8ToUTF16(profile.label())) == + bundle->current_profiles.end()) { + std::string tag(ProfileLabelToTag(UTF8ToUTF16(profile.label()))); + Associate(&tag, node.GetId()); + AutoFillProfile* p = personal_data_-> + CreateNewEmptyAutoFillProfileForDBThread(UTF8ToUTF16(profile.label())); + OverwriteProfileWithServerData(p, profile); + bundle->new_profiles.push_back(p); + } +} + bool AutofillModelAssociator::DisassociateModels() { id_map_.clear(); id_map_inverse_.clear(); @@ -192,13 +374,13 @@ bool AutofillModelAssociator::ChromeModelHasUserCreatedNodes(bool* has_nodes) { } int64 AutofillModelAssociator::GetSyncIdFromChromeId( - const AutofillKey autofill) { + const std::string autofill) { AutofillToSyncIdMap::const_iterator iter = id_map_.find(autofill); return iter == id_map_.end() ? sync_api::kInvalidId : iter->second; } void AutofillModelAssociator::Associate( - const AutofillKey* autofill, int64 sync_id) { + const std::string* autofill, int64 sync_id) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); DCHECK_NE(sync_api::kInvalidId, sync_id); DCHECK(id_map_.find(*autofill) == id_map_.end()); @@ -207,16 +389,6 @@ void AutofillModelAssociator::Associate( id_map_inverse_[sync_id] = *autofill; } -const AutofillKey* AutofillModelAssociator::GetChromeNodeFromSyncId( - int64 sync_id) { - DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); - SyncIdToAutofillMap::iterator iter = id_map_inverse_.find(sync_id); - if (iter == id_map_inverse_.end()) - return NULL; - - return &(iter->second); -} - void AutofillModelAssociator::Disassociate(int64 sync_id) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); SyncIdToAutofillMap::iterator iter = id_map_inverse_.find(sync_id); @@ -240,7 +412,15 @@ bool AutofillModelAssociator::GetSyncIdForTaggedNode(const std::string& tag, // static std::string AutofillModelAssociator::KeyToTag(const string16& name, const string16& value) { - return EscapePath(UTF16ToUTF8(name)) + "|" + EscapePath(UTF16ToUTF8(value)); + std::string ns(kAutofillEntryNamespaceTag); + return ns + EscapePath(UTF16ToUTF8(name)) + "|" + + EscapePath(UTF16ToUTF8(value)); +} + +// static +std::string AutofillModelAssociator::ProfileLabelToTag(const string16& label) { + std::string ns(kAutofillProfileNamespaceTag); + return ns + EscapePath(UTF16ToUTF8(label)); } // static @@ -270,4 +450,39 @@ bool AutofillModelAssociator::MergeTimestamps( return different; } +// Helper to compare the local value and cloud value of a field, merge into +// the local value if they differ, and return whether the merge happened. +bool MergeField(FormGroup* f, AutoFillFieldType t, + const std::string& specifics_field) { + if (UTF16ToUTF8(f->GetFieldText(AutoFillType(t))) == specifics_field) + return false; + f->SetInfo(AutoFillType(t), UTF8ToUTF16(specifics_field)); + return true; +} + +// static +bool AutofillModelAssociator::OverwriteProfileWithServerData( + AutoFillProfile* merge_into, + const sync_pb::AutofillProfileSpecifics& specifics) { + bool diff = false; + AutoFillProfile* p = merge_into; + const sync_pb::AutofillProfileSpecifics& s(specifics); + diff = MergeField(p, NAME_FIRST, s.name_first()) || diff; + diff = MergeField(p, NAME_LAST, s.name_last()) || diff; + diff = MergeField(p, NAME_MIDDLE, s.name_middle()) || diff; + diff = MergeField(p, ADDRESS_HOME_LINE1, s.address_home_line1()) || diff; + diff = MergeField(p, ADDRESS_HOME_LINE2, s.address_home_line2()) || diff; + diff = MergeField(p, ADDRESS_HOME_CITY, s.address_home_city()) || diff; + diff = MergeField(p, ADDRESS_HOME_STATE, s.address_home_state()) || diff; + diff = MergeField(p, ADDRESS_HOME_COUNTRY, s.address_home_country()) || diff; + diff = MergeField(p, ADDRESS_HOME_ZIP, s.address_home_zip()) || diff; + diff = MergeField(p, EMAIL_ADDRESS, s.email_address()) || diff; + diff = MergeField(p, COMPANY_NAME, s.company_name()) || diff; + diff = MergeField(p, PHONE_FAX_WHOLE_NUMBER, s.phone_fax_whole_number()) + || diff; + diff = MergeField(p, PHONE_HOME_WHOLE_NUMBER, s.phone_home_whole_number()) + || diff; + return diff; +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/autofill_model_associator.h b/chrome/browser/sync/glue/autofill_model_associator.h index eb279ed..3ae9703 100644 --- a/chrome/browser/sync/glue/autofill_model_associator.h +++ b/chrome/browser/sync/glue/autofill_model_associator.h @@ -12,34 +12,58 @@ #include "base/basictypes.h" #include "base/scoped_ptr.h" +#include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/chrome_thread.h" +#include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/model_associator.h" #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/webdata/autofill_entry.h" +class AutoFillProfile; + class ProfileSyncService; class WebDatabase; +namespace sync_api { +class WriteTransaction; +} + namespace browser_sync { class AutofillChangeProcessor; class UnrecoverableErrorHandler; extern const char kAutofillTag[]; +extern const char kAutofillProfileNamespaceTag[]; +extern const char kAutofillEntryNamespaceTag[]; // Contains all model association related logic: // * Algorithm to associate autofill model and sync model. // We do not check if we have local data before this run; we always // merge and sync. class AutofillModelAssociator - : public PerDataTypeAssociatorInterface<AutofillKey, AutofillKey> { + : public PerDataTypeAssociatorInterface<std::string, std::string> { public: static syncable::ModelType model_type() { return syncable::AUTOFILL; } AutofillModelAssociator(ProfileSyncService* sync_service, WebDatabase* web_database, + PersonalDataManager* data_manager, UnrecoverableErrorHandler* error_handler); virtual ~AutofillModelAssociator(); + // A task used by this class and the change processor to inform the + // PersonalDataManager living on the UI thread that it needs to refresh. + class DoOptimisticRefreshTask : public Task { + public: + explicit DoOptimisticRefreshTask(PersonalDataManager* pdm) : pdm_(pdm) {} + virtual void Run() { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + pdm_->Refresh(); + } + private: + PersonalDataManager* pdm_; + }; + // PerDataTypeAssociatorInterface implementation. // // Iterates through the sync model looking for matched pairs of items. @@ -56,20 +80,23 @@ class AutofillModelAssociator // user-defined autofill entries. virtual bool ChromeModelHasUserCreatedNodes(bool* has_nodes); - virtual const AutofillKey* GetChromeNodeFromSyncId(int64 sync_id); + // Not implemented. + virtual const std::string* GetChromeNodeFromSyncId(int64 sync_id) { + return NULL; + } // Not implemented. - virtual bool InitSyncNodeFromChromeId(AutofillKey node_id, + virtual bool InitSyncNodeFromChromeId(std::string node_id, sync_api::BaseNode* sync_node) { return false; } // Returns the sync id for the given autofill name, or sync_api::kInvalidId // if the autofill name is not associated to any sync id. - virtual int64 GetSyncIdFromChromeId(AutofillKey node_id); + virtual int64 GetSyncIdFromChromeId(std::string node_id); // Associates the given autofill name with the given sync id. - virtual void Associate(const AutofillKey* node, int64 sync_id); + virtual void Associate(const std::string* node, int64 sync_id); // Remove the association that corresponds to the given sync id. virtual void Disassociate(int64 sync_id); @@ -79,20 +106,91 @@ class AutofillModelAssociator virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id); static std::string KeyToTag(const string16& name, const string16& value); + static std::string ProfileLabelToTag(const string16& label); + static bool MergeTimestamps(const sync_pb::AutofillSpecifics& autofill, const std::vector<base::Time>& timestamps, std::vector<base::Time>* new_timestamps); + static bool OverwriteProfileWithServerData( + AutoFillProfile* merge_into, + const sync_pb::AutofillProfileSpecifics& specifics); - protected: // Returns sync service instance. ProfileSyncService* sync_service() { return sync_service_; } + static string16 MakeUniqueLabel(const string16& non_unique_label, + sync_api::BaseTransaction* trans); + private: - typedef std::map<AutofillKey, int64> AutofillToSyncIdMap; - typedef std::map<int64, AutofillKey> SyncIdToAutofillMap; + typedef std::map<std::string, int64> AutofillToSyncIdMap; + typedef std::map<int64, std::string> SyncIdToAutofillMap; + + // A convenience wrapper of a bunch of state we pass around while associating + // models, and send to the WebDatabase for persistence. + struct DataBundle { + std::set<AutofillKey> current_entries; + std::vector<AutofillEntry> new_entries; + std::set<string16> current_profiles; + std::vector<AutoFillProfile*> updated_profiles; + std::vector<AutoFillProfile*> new_profiles; // We own these pointers. + ~DataBundle() { STLDeleteElements(&new_profiles); } + }; + + // Helper to query WebDatabase for the current autofill state. + bool LoadAutofillData(std::vector<AutofillEntry>* entries, + std::vector<AutoFillProfile*>* profiles); + + // We split up model association first by autofill sub-type (entries, and + // profiles. There is a Traverse* method for each of these. + bool TraverseAndAssociateChromeAutofillEntries( + sync_api::WriteTransaction* write_trans, + const sync_api::ReadNode& autofill_root, + const std::vector<AutofillEntry>& all_entries_from_db, + std::set<AutofillKey>* current_entries, + std::vector<AutofillEntry>* new_entries); + bool TraverseAndAssociateChromeAutoFillProfiles( + sync_api::WriteTransaction* write_trans, + const sync_api::ReadNode& autofill_root, + const std::vector<AutoFillProfile*>& all_profiles_from_db, + std::set<string16>* current_profiles, + std::vector<AutoFillProfile*>* updated_profiles); + + // Once the above traversals are complete, we traverse the sync model to + // associate all remaining nodes. + bool TraverseAndAssociateAllSyncNodes( + sync_api::WriteTransaction* write_trans, + const sync_api::ReadNode& autofill_root, + DataBundle* bundle); + + // Helper to persist any changes that occured during model association to + // the WebDatabase. + bool SaveChangesToWebData(const DataBundle& bundle); + + // Helper to insert an AutofillEntry into the WebDatabase (e.g. in response + // to encountering a sync node that doesn't exist yet locally). + void AddNativeEntryIfNeeded(const sync_pb::AutofillSpecifics& autofill, + DataBundle* bundle, + const sync_api::ReadNode& node); + + // Helper to insert an AutoFillProfile into the WebDatabase (e.g. in response + // to encountering a sync node that doesn't exist yet locally). + void AddNativeProfileIfNeeded( + const sync_pb::AutofillProfileSpecifics& profile, + DataBundle* bundle, + const sync_api::ReadNode& node); + + // Helper to insert a sync node for the given AutoFillProfile (e.g. in + // response to encountering a native profile that doesn't exist yet in the + // cloud). + int64 MakeNewAutofillProfileSyncNode( + sync_api::WriteTransaction* trans, + const sync_api::BaseNode& autofill_root, + const std::string& tag, + const AutoFillProfile& profile); ProfileSyncService* sync_service_; WebDatabase* web_database_; + PersonalDataManager* personal_data_; UnrecoverableErrorHandler* error_handler_; int64 autofill_node_id_; diff --git a/chrome/browser/sync/glue/autofill_model_associator_unittest.cc b/chrome/browser/sync/glue/autofill_model_associator_unittest.cc index 240962a..cfd7e8d 100644 --- a/chrome/browser/sync/glue/autofill_model_associator_unittest.cc +++ b/chrome/browser/sync/glue/autofill_model_associator_unittest.cc @@ -13,16 +13,28 @@ class AutofillModelAssociatorTest : public testing::Test { }; TEST_F(AutofillModelAssociatorTest, KeyToTag) { - EXPECT_EQ("foo|bar", + EXPECT_EQ("autofill_entry|foo|bar", AutofillModelAssociator::KeyToTag(UTF8ToUTF16("foo"), UTF8ToUTF16("bar"))); - EXPECT_EQ("%7C|%7C", + EXPECT_EQ("autofill_entry|%7C|%7C", AutofillModelAssociator::KeyToTag(UTF8ToUTF16("|"), UTF8ToUTF16("|"))); - EXPECT_EQ("%7C|", + EXPECT_EQ("autofill_entry|%7C|", AutofillModelAssociator::KeyToTag(UTF8ToUTF16("|"), UTF8ToUTF16(""))); - EXPECT_EQ("|%7C", + EXPECT_EQ("autofill_entry||%7C", AutofillModelAssociator::KeyToTag(UTF8ToUTF16(""), UTF8ToUTF16("|"))); } + +TEST_F(AutofillModelAssociatorTest, ProfileLabelToTag) { + string16 label(ASCIIToUTF16("awesome_address")); + EXPECT_EQ("autofill_profile|awesome_address", + AutofillModelAssociator::ProfileLabelToTag(label)); + + EXPECT_EQ("autofill_profile|%7C%7C", + AutofillModelAssociator::ProfileLabelToTag(ASCIIToUTF16("||"))); + EXPECT_NE(AutofillModelAssociator::KeyToTag(ASCIIToUTF16("autofill_profile"), + ASCIIToUTF16("home")), + AutofillModelAssociator::ProfileLabelToTag(ASCIIToUTF16("home"))); +} diff --git a/chrome/browser/sync/profile_sync_factory.h b/chrome/browser/sync/profile_sync_factory.h index 67b6296..5a19f1f 100644 --- a/chrome/browser/sync/profile_sync_factory.h +++ b/chrome/browser/sync/profile_sync_factory.h @@ -12,6 +12,7 @@ #include "chrome/browser/sync/glue/model_associator.h" #include "chrome/browser/sync/unrecoverable_error_handler.h" +class PersonalDataManager; class ProfileSyncService; class WebDatabase; @@ -60,6 +61,7 @@ class ProfileSyncFactory { virtual SyncComponents CreateAutofillSyncComponents( ProfileSyncService* profile_sync_service, WebDatabase* web_database, + PersonalDataManager* personal_data, browser_sync::UnrecoverableErrorHandler* error_handler) = 0; // Instantiates both a model associator and change processor for the diff --git a/chrome/browser/sync/profile_sync_factory_impl.cc b/chrome/browser/sync/profile_sync_factory_impl.cc index 3e2367a..4be1d5f 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.cc +++ b/chrome/browser/sync/profile_sync_factory_impl.cc @@ -107,14 +107,17 @@ ProfileSyncFactory::SyncComponents ProfileSyncFactoryImpl::CreateAutofillSyncComponents( ProfileSyncService* profile_sync_service, WebDatabase* web_database, + PersonalDataManager* personal_data, browser_sync::UnrecoverableErrorHandler* error_handler) { AutofillModelAssociator* model_associator = new AutofillModelAssociator(profile_sync_service, web_database, + personal_data, error_handler); AutofillChangeProcessor* change_processor = new AutofillChangeProcessor(model_associator, web_database, + personal_data, error_handler); return SyncComponents(model_associator, change_processor); } diff --git a/chrome/browser/sync/profile_sync_factory_impl.h b/chrome/browser/sync/profile_sync_factory_impl.h index 8b1cb32..68539b9 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.h +++ b/chrome/browser/sync/profile_sync_factory_impl.h @@ -26,6 +26,7 @@ class ProfileSyncFactoryImpl : public ProfileSyncFactory { virtual SyncComponents CreateAutofillSyncComponents( ProfileSyncService* profile_sync_service, WebDatabase* web_database, + PersonalDataManager* personal_data, browser_sync::UnrecoverableErrorHandler* error_handler); virtual SyncComponents CreateBookmarkSyncComponents( diff --git a/chrome/browser/sync/profile_sync_factory_mock.h b/chrome/browser/sync/profile_sync_factory_mock.h index 806899e..4a4cf92 100644 --- a/chrome/browser/sync/profile_sync_factory_mock.h +++ b/chrome/browser/sync/profile_sync_factory_mock.h @@ -28,10 +28,11 @@ class ProfileSyncFactoryMock : public ProfileSyncFactory { browser_sync::DataTypeManager*( browser_sync::SyncBackendHost*, const browser_sync::DataTypeController::TypeMap&)); - MOCK_METHOD3(CreateAutofillSyncComponents, + MOCK_METHOD4(CreateAutofillSyncComponents, SyncComponents( ProfileSyncService* profile_sync_service, WebDatabase* web_database, + PersonalDataManager* personal_data, browser_sync::UnrecoverableErrorHandler* error_handler)); MOCK_METHOD2(CreateBookmarkSyncComponents, SyncComponents(ProfileSyncService* profile_sync_service, diff --git a/chrome/browser/sync/profile_sync_service_autofill_unittest.cc b/chrome/browser/sync/profile_sync_service_autofill_unittest.cc index 85c12b0..b64136e 100644 --- a/chrome/browser/sync/profile_sync_service_autofill_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_autofill_unittest.cc @@ -14,6 +14,7 @@ #include "base/time.h" #include "base/utf_string_conversions.h" #include "base/waitable_event.h" +#include "chrome/browser/autofill/autofill_common_unittest.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/autofill_change_processor.h" #include "chrome/browser/sync/glue/autofill_data_type_controller.h" @@ -63,8 +64,10 @@ using testing::_; using testing::DoAll; using testing::DoDefault; using testing::ElementsAre; +using testing::Eq; using testing::Invoke; using testing::Return; +using testing::SaveArg; using testing::SetArgumentPointee; class WebDatabaseMock : public WebDatabase { @@ -78,7 +81,15 @@ class WebDatabaseMock : public WebDatabase { const string16& value, std::vector<base::Time>* timestamps)); MOCK_METHOD1(UpdateAutofillEntries, - bool(const std::vector<AutofillEntry>& entries)); // NOLINT + bool(const std::vector<AutofillEntry>&)); // NOLINT + MOCK_METHOD1(GetAutoFillProfiles, + bool(std::vector<AutoFillProfile*>*)); // NOLINT + MOCK_METHOD1(UpdateAutoFillProfile, + bool(const AutoFillProfile&)); // NOLINT + MOCK_METHOD1(AddAutoFillProfile, + bool(const AutoFillProfile&)); // NOLINT + MOCK_METHOD1(RemoveAutoFillProfile, + bool(int)); // NOLINT }; class WebDataServiceFake : public WebDataService { @@ -94,12 +105,20 @@ class WebDataServiceFake : public WebDataService { } }; -ACTION_P3(MakeAutofillSyncComponents, service, wd, dtc) { +class PersonalDataManagerMock: public PersonalDataManager { + public: + MOCK_CONST_METHOD0(IsDataLoaded, bool()); + MOCK_METHOD0(LoadProfiles, void()); + MOCK_METHOD0(LoadCreditCards, void()); + MOCK_METHOD0(Refresh, void()); +}; + +ACTION_P4(MakeAutofillSyncComponents, service, wd, pdm, dtc) { DCHECK(ChromeThread::CurrentlyOn(ChromeThread::DB)); AutofillModelAssociator* model_associator = - new AutofillModelAssociator(service, wd, dtc); + new AutofillModelAssociator(service, wd, pdm, dtc); AutofillChangeProcessor* change_processor = - new AutofillChangeProcessor(model_associator, wd, dtc); + new AutofillChangeProcessor(model_associator, wd, pdm, dtc); return ProfileSyncFactory::SyncComponents(model_associator, change_processor); } @@ -113,6 +132,7 @@ class ProfileSyncServiceAutofillTest : public testing::Test { virtual void SetUp() { web_data_service_ = new WebDataServiceFake(); + personal_data_manager_.Init(&profile_); db_thread_.Start(); notification_service_ = new ThreadNotificationService(&db_thread_); @@ -136,9 +156,10 @@ class ProfileSyncServiceAutofillTest : public testing::Test { &profile_, service_.get()); - EXPECT_CALL(factory_, CreateAutofillSyncComponents(_, _, _)). + EXPECT_CALL(factory_, CreateAutofillSyncComponents(_, _, _, _)). WillOnce(MakeAutofillSyncComponents(service_.get(), &web_database_, + &personal_data_manager_, data_type_controller)); EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). WillOnce(MakeDataTypeManager(&backend_)); @@ -146,6 +167,12 @@ class ProfileSyncServiceAutofillTest : public testing::Test { EXPECT_CALL(profile_, GetWebDataService(_)). WillOnce(Return(web_data_service_.get())); + EXPECT_CALL(profile_, GetPersonalDataManager()). + WillRepeatedly(Return(&personal_data_manager_)); + + EXPECT_CALL(personal_data_manager_, IsDataLoaded()). + WillRepeatedly(Return(true)); + // State changes once for the backend init and once for startup done. EXPECT_CALL(observer_, OnStateChanged()). WillOnce(InvokeTask(task)). @@ -194,10 +221,28 @@ class ProfileSyncServiceAutofillTest : public testing::Test { ASSERT_TRUE(node.InitUniqueByCreation(syncable::AUTOFILL, autofill_root, tag)); - AutofillChangeProcessor::WriteAutofill(&node, entry); + AutofillChangeProcessor::WriteAutofillEntry(entry, &node); + } + + void AddAutofillProfileSyncNode(const AutoFillProfile& profile) { + sync_api::WriteTransaction trans( + service_->backend()->GetUserShareHandle()); + sync_api::ReadNode autofill_root(&trans); + ASSERT_TRUE(autofill_root.InitByTagLookup(browser_sync::kAutofillTag)); + sync_api::WriteNode node(&trans); + std::string tag = AutofillModelAssociator::ProfileLabelToTag( + profile.Label()); + ASSERT_TRUE(node.InitUniqueByCreation(syncable::AUTOFILL, + autofill_root, + tag)); + AutofillChangeProcessor::WriteAutofillProfile(profile, &node); + sync_pb::AutofillSpecifics s(node.GetAutofillSpecifics()); + s.mutable_profile()->set_label(UTF16ToUTF8(profile.Label())); + node.SetAutofillSpecifics(s); } - void GetAutofillEntriesFromSyncDB(std::vector<AutofillEntry>* entries) { + void GetAutofillEntriesFromSyncDB(std::vector<AutofillEntry>* entries, + std::vector<AutoFillProfile>* profiles) { sync_api::ReadTransaction trans(service_->backend()->GetUserShareHandle()); sync_api::ReadNode autofill_root(&trans); ASSERT_TRUE(autofill_root.InitByTagLookup(browser_sync::kAutofillTag)); @@ -209,15 +254,22 @@ class ProfileSyncServiceAutofillTest : public testing::Test { const sync_pb::AutofillSpecifics& autofill( child_node.GetAutofillSpecifics()); - AutofillKey key(UTF8ToUTF16(autofill.name()), - UTF8ToUTF16(autofill.value())); - std::vector<base::Time> timestamps; - int timestamps_count = autofill.usage_timestamp_size(); - for (int i = 0; i < timestamps_count; ++i) { - timestamps.push_back(Time::FromInternalValue( - autofill.usage_timestamp(i))); + if (autofill.has_value()) { + AutofillKey key(UTF8ToUTF16(autofill.name()), + UTF8ToUTF16(autofill.value())); + std::vector<base::Time> timestamps; + int timestamps_count = autofill.usage_timestamp_size(); + for (int i = 0; i < timestamps_count; ++i) { + timestamps.push_back(Time::FromInternalValue( + autofill.usage_timestamp(i))); + } + entries->push_back(AutofillEntry(key, timestamps)); + } else if (autofill.has_profile()) { + AutoFillProfile p(UTF8ToUTF16(autofill.profile().label()), 0); + AutofillModelAssociator::OverwriteProfileWithServerData(&p, + autofill.profile()); + profiles->push_back(p); } - entries->push_back(AutofillEntry(key, timestamps)); child_id = child_node.GetSuccessorId(); } } @@ -262,6 +314,7 @@ class ProfileSyncServiceAutofillTest : public testing::Test { SyncBackendHostMock backend_; WebDatabaseMock web_database_; scoped_refptr<WebDataService> web_data_service_; + PersonalDataManagerMock personal_data_manager_; TestIdFactory ids_; }; @@ -283,8 +336,9 @@ class CreateAutofillRootTask : public Task { class AddAutofillEntriesTask : public Task { public: AddAutofillEntriesTask(ProfileSyncServiceAutofillTest* test, - const std::vector<AutofillEntry>& entries) - : test_(test), entries_(entries) { + const std::vector<AutofillEntry>& entries, + const std::vector<AutoFillProfile>& profiles) + : test_(test), entries_(entries), profiles_(profiles) { } virtual void Run() { @@ -292,15 +346,22 @@ class AddAutofillEntriesTask : public Task { for (size_t i = 0; i < entries_.size(); ++i) { test_->AddAutofillSyncNode(entries_[i]); } + for (size_t i = 0; i < profiles_.size(); ++i) { + test_->AddAutofillProfileSyncNode(profiles_[i]); + } + } private: ProfileSyncServiceAutofillTest* test_; const std::vector<AutofillEntry>& entries_; + const std::vector<AutoFillProfile>& profiles_; }; // TODO(skrul): Test abort startup. // TODO(skrul): Test processing of cloud changes. +// TODO(tim): Add autofill data type controller test, and a case to cover +// waiting for the PersonalDataManager. TEST_F(ProfileSyncServiceAutofillTest, FailModelAssociation) { // Backend will be paused but not resumed. EXPECT_CALL(backend_, RequestPause()). @@ -313,26 +374,132 @@ TEST_F(ProfileSyncServiceAutofillTest, FailModelAssociation) { TEST_F(ProfileSyncServiceAutofillTest, EmptyNativeEmptySync) { EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); SetIdleChangeProcessorExpectations(); CreateAutofillRootTask task(this); StartSyncService(&task); std::vector<AutofillEntry> sync_entries; - GetAutofillEntriesFromSyncDB(&sync_entries); + std::vector<AutoFillProfile> sync_profiles; + GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles); EXPECT_EQ(0U, sync_entries.size()); + EXPECT_EQ(0U, sync_profiles.size()); } -TEST_F(ProfileSyncServiceAutofillTest, HasNativeEmptySync) { +TEST_F(ProfileSyncServiceAutofillTest, HasNativeEntriesEmptySync) { std::vector<AutofillEntry> entries; entries.push_back(MakeAutofillEntry("foo", "bar", 1)); EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true))); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); SetIdleChangeProcessorExpectations(); CreateAutofillRootTask task(this); StartSyncService(&task); std::vector<AutofillEntry> sync_entries; - GetAutofillEntriesFromSyncDB(&sync_entries); + std::vector<AutoFillProfile> sync_profiles; + GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles); ASSERT_EQ(1U, entries.size()); EXPECT_TRUE(entries[0] == sync_entries[0]); + EXPECT_EQ(0U, sync_profiles.size()); +} + +TEST_F(ProfileSyncServiceAutofillTest, HasMixedNativeEmptySync) { + std::vector<AutofillEntry> entries; + entries.push_back(MakeAutofillEntry("foo", "bar", 1)); + EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). + WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true))); + + std::vector<AutoFillProfile*> profiles; + std::vector<AutoFillProfile> expected_profiles; + // Owned by GetAutoFillProfiles caller. + AutoFillProfile* profile0 = new AutoFillProfile(string16(), 0); + autofill_unittest::SetProfileInfo(profile0, + "Billing", "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910", "01987654321"); + profiles.push_back(profile0); + expected_profiles.push_back(*profile0); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + WillOnce(DoAll(SetArgumentPointee<0>(profiles), Return(true))); + EXPECT_CALL(personal_data_manager_, Refresh()); + SetIdleChangeProcessorExpectations(); + CreateAutofillRootTask task(this); + StartSyncService(&task); + std::vector<AutofillEntry> sync_entries; + std::vector<AutoFillProfile> sync_profiles; + GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles); + ASSERT_EQ(1U, entries.size()); + EXPECT_TRUE(entries[0] == sync_entries[0]); + EXPECT_EQ(1U, sync_profiles.size()); + EXPECT_EQ(expected_profiles[0], sync_profiles[0]); +} + +bool ProfilesMatchExceptLabelImpl(AutoFillProfile p1, AutoFillProfile p2) { + const AutoFillFieldType types[] = { NAME_FIRST, + NAME_MIDDLE, + NAME_LAST, + EMAIL_ADDRESS, + COMPANY_NAME, + ADDRESS_HOME_LINE1, + ADDRESS_HOME_LINE2, + ADDRESS_HOME_CITY, + ADDRESS_HOME_STATE, + ADDRESS_HOME_ZIP, + ADDRESS_HOME_COUNTRY, + PHONE_HOME_NUMBER, + PHONE_FAX_NUMBER }; + if (p1.Label() == p2.Label()) + return false; + + for (size_t index = 0; index < arraysize(types); ++index) { + if (p1.GetFieldText(AutoFillType(types[index])) != + p2.GetFieldText(AutoFillType(types[index]))) + return false; + } + return true; +} + +MATCHER_P(ProfileMatchesExceptLabel, profile, "") { + return ProfilesMatchExceptLabelImpl(arg, profile); +} + +TEST_F(ProfileSyncServiceAutofillTest, HasDuplicateProfileLabelsEmptySync) { + std::vector<AutoFillProfile> expected_profiles; + std::vector<AutoFillProfile*> profiles; + AutoFillProfile* profile0 = new AutoFillProfile(string16(), 0); + autofill_unittest::SetProfileInfo(profile0, + "Billing", "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910", "01987654321"); + AutoFillProfile* profile1 = new AutoFillProfile(string16(), 0); + autofill_unittest::SetProfileInfo(profile1, + "Billing", "Same", "Label", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910", "01987654321"); + profiles.push_back(profile0); + profiles.push_back(profile1); + expected_profiles.push_back(*profile0); + expected_profiles.push_back(*profile1); + AutoFillProfile relabelled_profile; + EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); + EXPECT_CALL(personal_data_manager_, Refresh()); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + WillOnce(DoAll(SetArgumentPointee<0>(profiles), Return(true))); + EXPECT_CALL(web_database_, UpdateAutoFillProfile( + ProfileMatchesExceptLabel(expected_profiles[1]))). + WillOnce(DoAll(SaveArg<0>(&relabelled_profile), Return(true))); + + SetIdleChangeProcessorExpectations(); + CreateAutofillRootTask task(this); + StartSyncService(&task); + std::vector<AutofillEntry> sync_entries; + std::vector<AutoFillProfile> sync_profiles; + GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles); + EXPECT_EQ(0U, sync_entries.size()); + EXPECT_EQ(2U, sync_profiles.size()); + EXPECT_EQ(expected_profiles[0], sync_profiles[1]); + EXPECT_TRUE(ProfilesMatchExceptLabelImpl(expected_profiles[1], + sync_profiles[0])); + EXPECT_EQ(sync_profiles[0].Label(), relabelled_profile.Label()); } TEST_F(ProfileSyncServiceAutofillTest, HasNativeWithDuplicatesEmptySync) { @@ -344,43 +511,76 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeWithDuplicatesEmptySync) { entries.push_back(MakeAutofillEntry("dup", "", 3)); EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true))); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); SetIdleChangeProcessorExpectations(); CreateAutofillRootTask task(this); StartSyncService(&task); std::vector<AutofillEntry> sync_entries; - GetAutofillEntriesFromSyncDB(&sync_entries); + std::vector<AutoFillProfile> sync_profiles; + GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles); EXPECT_EQ(2U, sync_entries.size()); } TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncNoMerge) { AutofillEntry native_entry(MakeAutofillEntry("native", "entry", 1)); AutofillEntry sync_entry(MakeAutofillEntry("sync", "entry", 2)); + AutoFillProfile sync_profile(string16(), 0); + autofill_unittest::SetProfileInfo(&sync_profile, + "Billing", "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910", "01987654321"); + + AutoFillProfile* native_profile = new AutoFillProfile(string16(), 0); + autofill_unittest::SetProfileInfo(native_profile, + "Work", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); std::vector<AutofillEntry> native_entries; native_entries.push_back(native_entry); + std::vector<AutoFillProfile*> native_profiles; + native_profiles.push_back(native_profile); + + std::vector<AutoFillProfile> expected_profiles; + expected_profiles.push_back(*native_profile); + expected_profiles.push_back(sync_profile); + EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true))); - + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); std::vector<AutofillEntry> sync_entries; sync_entries.push_back(sync_entry); - AddAutofillEntriesTask task(this, sync_entries); + std::vector<AutoFillProfile> sync_profiles; + sync_profiles.push_back(sync_profile); + AddAutofillEntriesTask task(this, sync_entries, sync_profiles); + AutoFillProfile to_be_added(sync_profile); + to_be_added.set_unique_id(1); EXPECT_CALL(web_database_, UpdateAutofillEntries(ElementsAre(sync_entry))). WillOnce(Return(true)); + EXPECT_CALL(web_database_, AddAutoFillProfile(Eq(to_be_added))). + WillOnce(Return(true)); + EXPECT_CALL(personal_data_manager_, Refresh()); StartSyncService(&task); - std::set<AutofillEntry> expected; - expected.insert(native_entry); - expected.insert(sync_entry); + std::set<AutofillEntry> expected_entries; + expected_entries.insert(native_entry); + expected_entries.insert(sync_entry); std::vector<AutofillEntry> new_sync_entries; - GetAutofillEntriesFromSyncDB(&new_sync_entries); + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); std::set<AutofillEntry> new_sync_entries_set(new_sync_entries.begin(), new_sync_entries.end()); - EXPECT_TRUE(expected == new_sync_entries_set); + + EXPECT_TRUE(expected_entries == new_sync_entries_set); + EXPECT_EQ(2U, new_sync_profiles.size()); + EXPECT_EQ(expected_profiles[0], new_sync_profiles[0]); + EXPECT_EQ(expected_profiles[1], new_sync_profiles[1]); } -TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMerge) { +TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMergeEntry) { AutofillEntry native_entry(MakeAutofillEntry("merge", "entry", 1)); AutofillEntry sync_entry(MakeAutofillEntry("merge", "entry", 2)); AutofillEntry merged_entry(MakeAutofillEntry("merge", "entry", 1, 2)); @@ -389,23 +589,63 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMerge) { native_entries.push_back(native_entry); EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true))); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); std::vector<AutofillEntry> sync_entries; + std::vector<AutoFillProfile> sync_profiles; sync_entries.push_back(sync_entry); - AddAutofillEntriesTask task(this, sync_entries); + AddAutofillEntriesTask task(this, sync_entries, sync_profiles); EXPECT_CALL(web_database_, UpdateAutofillEntries(ElementsAre(merged_entry))). WillOnce(Return(true)); StartSyncService(&task); std::vector<AutofillEntry> new_sync_entries; - GetAutofillEntriesFromSyncDB(&new_sync_entries); + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); ASSERT_EQ(1U, new_sync_entries.size()); EXPECT_TRUE(merged_entry == new_sync_entries[0]); } -TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAdd) { +TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMergeProfile) { + AutoFillProfile sync_profile(string16(), 0); + autofill_unittest::SetProfileInfo(&sync_profile, + "Billing", "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910", "01987654321"); + + AutoFillProfile* native_profile = new AutoFillProfile(string16(), 0); + autofill_unittest::SetProfileInfo(native_profile, + "Billing", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + + std::vector<AutoFillProfile*> native_profiles; + native_profiles.push_back(native_profile); + EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(Return(true)); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); + + std::vector<AutofillEntry> sync_entries; + std::vector<AutoFillProfile> sync_profiles; + sync_profiles.push_back(sync_profile); + AddAutofillEntriesTask task(this, sync_entries, sync_profiles); + + EXPECT_CALL(web_database_, UpdateAutoFillProfile(Eq(sync_profile))). + WillOnce(Return(true)); + EXPECT_CALL(personal_data_manager_, Refresh()); + StartSyncService(&task); + + std::vector<AutofillEntry> new_sync_entries; + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); + ASSERT_EQ(1U, new_sync_profiles.size()); + EXPECT_TRUE(sync_profile == new_sync_profiles[0]); +} + +TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddEntry) { EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); SetIdleChangeProcessorExpectations(); CreateAutofillRootTask task(this); StartSyncService(&task); @@ -423,18 +663,96 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAdd) { Details<AutofillChangeList>(&changes)); std::vector<AutofillEntry> new_sync_entries; - GetAutofillEntriesFromSyncDB(&new_sync_entries); + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); ASSERT_EQ(1U, new_sync_entries.size()); EXPECT_TRUE(added_entry == new_sync_entries[0]); } -TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdate) { +TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddProfile) { + EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + SetIdleChangeProcessorExpectations(); + CreateAutofillRootTask task(this); + StartSyncService(&task); + + AutoFillProfile added_profile(string16(), 0); + autofill_unittest::SetProfileInfo(&added_profile, + "Billing", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + + AutofillProfileChange change(AutofillProfileChange::ADD, + added_profile.Label(), &added_profile, string16()); + scoped_refptr<ThreadNotifier> notifier = new ThreadNotifier(&db_thread_); + notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, + Details<AutofillProfileChange>(&change)); + + std::vector<AutofillEntry> new_sync_entries; + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); + ASSERT_EQ(1U, new_sync_profiles.size()); + EXPECT_TRUE(added_profile == new_sync_profiles[0]); +} + +TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddProfileConflict) { + AutoFillProfile sync_profile(string16(), 0); + autofill_unittest::SetProfileInfo(&sync_profile, + "Billing", "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910", "01987654321"); + + std::vector<AutofillEntry> sync_entries; + std::vector<AutoFillProfile> sync_profiles; + sync_profiles.push_back(sync_profile); + AddAutofillEntriesTask task(this, sync_entries, sync_profiles); + + sync_profile.set_unique_id(1); + EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(web_database_, AddAutoFillProfile(Eq(sync_profile))). + WillOnce(Return(true)); + SetIdleChangeProcessorExpectations(); + StartSyncService(&task); + + AutoFillProfile added_profile(string16(), 0); + autofill_unittest::SetProfileInfo(&added_profile, + "Billing", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + + AutofillProfileChange change(AutofillProfileChange::ADD, + added_profile.Label(), &added_profile, string16()); + + AutoFillProfile relabelled_profile; + EXPECT_CALL(web_database_, UpdateAutoFillProfile( + ProfileMatchesExceptLabel(added_profile))). + WillOnce(DoAll(SaveArg<0>(&relabelled_profile), Return(true))); + EXPECT_CALL(personal_data_manager_, Refresh()); + + scoped_refptr<ThreadNotifier> notifier = new ThreadNotifier(&db_thread_); + notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, + Details<AutofillProfileChange>(&change)); + + std::vector<AutofillEntry> new_sync_entries; + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); + ASSERT_EQ(2U, new_sync_profiles.size()); + sync_profile.set_unique_id(0); // The sync DB doesn't store IDs. + EXPECT_EQ(sync_profile, new_sync_profiles[1]); + EXPECT_TRUE(ProfilesMatchExceptLabelImpl(added_profile, + new_sync_profiles[0])); + EXPECT_EQ(new_sync_profiles[0].Label(), relabelled_profile.Label()); +} + +TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateEntry) { AutofillEntry original_entry(MakeAutofillEntry("my", "entry", 1)); std::vector<AutofillEntry> original_entries; original_entries.push_back(original_entry); EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true))); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); CreateAutofillRootTask task(this); StartSyncService(&task); @@ -452,18 +770,138 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdate) { Details<AutofillChangeList>(&changes)); std::vector<AutofillEntry> new_sync_entries; - GetAutofillEntriesFromSyncDB(&new_sync_entries); + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); ASSERT_EQ(1U, new_sync_entries.size()); EXPECT_TRUE(updated_entry == new_sync_entries[0]); } -TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemove) { + +TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateProfile) { + AutoFillProfile* native_profile = new AutoFillProfile(string16(), 0); + autofill_unittest::SetProfileInfo(native_profile, + "Billing", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + std::vector<AutoFillProfile*> native_profiles; + native_profiles.push_back(native_profile); + EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(Return(true)); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); + CreateAutofillRootTask task(this); + StartSyncService(&task); + + AutoFillProfile update_profile(string16(), 0); + autofill_unittest::SetProfileInfo(&update_profile, + "Billing", "Changin'", "Mah", "Namez", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + + AutofillProfileChange change(AutofillProfileChange::UPDATE, + update_profile.Label(), &update_profile, + ASCIIToUTF16("Billing")); + scoped_refptr<ThreadNotifier> notifier = new ThreadNotifier(&db_thread_); + notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, + Details<AutofillProfileChange>(&change)); + + std::vector<AutofillEntry> new_sync_entries; + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); + ASSERT_EQ(1U, new_sync_profiles.size()); + EXPECT_TRUE(update_profile == new_sync_profiles[0]); +} + +TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateProfileRelabel) { + AutoFillProfile* native_profile = new AutoFillProfile(string16(), 0); + autofill_unittest::SetProfileInfo(native_profile, + "Billing", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + std::vector<AutoFillProfile*> native_profiles; + native_profiles.push_back(native_profile); + EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(Return(true)); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); + CreateAutofillRootTask task(this); + StartSyncService(&task); + + AutoFillProfile update_profile(string16(), 0); + autofill_unittest::SetProfileInfo(&update_profile, + "TRYIN 2 FOOL U", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + + AutofillProfileChange change(AutofillProfileChange::UPDATE, + update_profile.Label(), &update_profile, + ASCIIToUTF16("Billing")); + scoped_refptr<ThreadNotifier> notifier = new ThreadNotifier(&db_thread_); + notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, + Details<AutofillProfileChange>(&change)); + + std::vector<AutofillEntry> new_sync_entries; + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); + ASSERT_EQ(1U, new_sync_profiles.size()); + EXPECT_TRUE(update_profile == new_sync_profiles[0]); +} + +TEST_F(ProfileSyncServiceAutofillTest, + ProcessUserChangeUpdateProfileRelabelConflict) { + AutoFillProfile* native_profile = new AutoFillProfile(string16(), 0); + autofill_unittest::SetProfileInfo(native_profile, + "Billing", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + AutoFillProfile* native_profile2 = new AutoFillProfile(string16(), 0); + autofill_unittest::SetProfileInfo(native_profile2, + "ExistingLabel", "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910", "01987654321"); + std::vector<AutoFillProfile*> native_profiles; + native_profiles.push_back(native_profile); + native_profiles.push_back(native_profile2); + AutoFillProfile marion(*native_profile2); + AutoFillProfile josephine_update(*native_profile); + + EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(Return(true)); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); + CreateAutofillRootTask task(this); + StartSyncService(&task); + + josephine_update.set_label(ASCIIToUTF16("ExistingLabel")); + AutoFillProfile relabelled_profile; + EXPECT_CALL(web_database_, UpdateAutoFillProfile( + ProfileMatchesExceptLabel(josephine_update))). + WillOnce(DoAll(SaveArg<0>(&relabelled_profile), Return(true))); + EXPECT_CALL(personal_data_manager_, Refresh()); + + AutofillProfileChange change(AutofillProfileChange::UPDATE, + josephine_update.Label(), &josephine_update, + ASCIIToUTF16("Billing")); + scoped_refptr<ThreadNotifier> notifier = new ThreadNotifier(&db_thread_); + notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, + Details<AutofillProfileChange>(&change)); + + std::vector<AutofillEntry> new_sync_entries; + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); + ASSERT_EQ(2U, new_sync_profiles.size()); + marion.set_unique_id(0); // The sync DB doesn't store IDs. + EXPECT_EQ(marion, new_sync_profiles[1]); + EXPECT_TRUE(ProfilesMatchExceptLabelImpl(josephine_update, + new_sync_profiles[0])); + EXPECT_EQ(new_sync_profiles[0].Label(), relabelled_profile.Label()); +} + +TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemoveEntry) { AutofillEntry original_entry(MakeAutofillEntry("my", "entry", 1)); std::vector<AutofillEntry> original_entries; original_entries.push_back(original_entry); EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true))); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); CreateAutofillRootTask task(this); StartSyncService(&task); @@ -475,12 +913,52 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemove) { Details<AutofillChangeList>(&changes)); std::vector<AutofillEntry> new_sync_entries; - GetAutofillEntriesFromSyncDB(&new_sync_entries); + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); + ASSERT_EQ(0U, new_sync_entries.size()); +} + +TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemoveProfile) { + AutoFillProfile sync_profile(string16(), 0); + autofill_unittest::SetProfileInfo(&sync_profile, + "Billing", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + AutoFillProfile* native_profile = new AutoFillProfile(string16(), 0); + autofill_unittest::SetProfileInfo(native_profile, + "Billing", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + + std::vector<AutoFillProfile*> native_profiles; + native_profiles.push_back(native_profile); + EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(Return(true)); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); + + std::vector<AutofillEntry> sync_entries; + std::vector<AutoFillProfile> sync_profiles; + sync_profiles.push_back(sync_profile); + AddAutofillEntriesTask task(this, sync_entries, sync_profiles); + + EXPECT_CALL(personal_data_manager_, Refresh()); + StartSyncService(&task); + + AutofillProfileChange change(AutofillProfileChange::REMOVE, + sync_profile.Label(), NULL, string16()); + scoped_refptr<ThreadNotifier> notifier = new ThreadNotifier(&db_thread_); + notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, + Details<AutofillProfileChange>(&change)); + + std::vector<AutofillEntry> new_sync_entries; + std::vector<AutoFillProfile> new_sync_profiles; + GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles); ASSERT_EQ(0U, new_sync_entries.size()); } TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeError) { EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); CreateAutofillRootTask task(this); StartSyncService(&task); |