| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/webdata/autocomplete_syncable_service.h" |
| |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "components/autofill/core/browser/webdata/autofill_table.h" |
| #include "components/autofill/core/browser/webdata/autofill_webdata_service.h" |
| #include "components/webdata/common/web_database.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "net/base/escape.h" |
| #include "sync/api/sync_error.h" |
| #include "sync/api/sync_error_factory.h" |
| #include "sync/protocol/autofill_specifics.pb.h" |
| #include "sync/protocol/sync.pb.h" |
| |
| using autofill::AutofillChange; |
| using autofill::AutofillChangeList; |
| using autofill::AutofillEntry; |
| using autofill::AutofillKey; |
| using autofill::AutofillTable; |
| using autofill::AutofillWebDataService; |
| using autofill::AutofillWebDataBackend; |
| using content::BrowserThread; |
| |
| namespace { |
| |
| const char kAutofillEntryNamespaceTag[] = "autofill_entry|"; |
| |
| // Merges timestamps from the |autofill| entry and |timestamps|. Returns |
| // true if they were different, false if they were the same. |
| // All of the timestamp vectors are assummed to be sorted, resulting vector is |
| // sorted as well. Only two timestamps - the earliest and the latest are stored. |
| bool MergeTimestamps(const sync_pb::AutofillSpecifics& autofill, |
| const std::vector<base::Time>& timestamps, |
| std::vector<base::Time>* new_timestamps) { |
| DCHECK(new_timestamps); |
| |
| new_timestamps->clear(); |
| size_t timestamps_count = autofill.usage_timestamp_size(); |
| if (timestamps_count == 0 && timestamps.empty()) { |
| return false; |
| } else if (timestamps_count == 0) { |
| new_timestamps->insert(new_timestamps->begin(), |
| timestamps.begin(), |
| timestamps.end()); |
| return true; |
| } else if (timestamps.empty()) { |
| new_timestamps->reserve(2); |
| new_timestamps->push_back(base::Time::FromInternalValue( |
| autofill.usage_timestamp(0))); |
| if (timestamps_count > 1) { |
| new_timestamps->push_back(base::Time::FromInternalValue( |
| autofill.usage_timestamp(timestamps_count - 1))); |
| } |
| return true; |
| } else { |
| base::Time sync_time_begin = base::Time::FromInternalValue( |
| autofill.usage_timestamp(0)); |
| base::Time sync_time_end = base::Time::FromInternalValue( |
| autofill.usage_timestamp(timestamps_count - 1)); |
| if (timestamps.front() != sync_time_begin || |
| timestamps.back() != sync_time_end) { |
| new_timestamps->push_back( |
| timestamps.front() < sync_time_begin ? timestamps.front() : |
| sync_time_begin); |
| if (new_timestamps->back() != timestamps.back() || |
| new_timestamps->back() != sync_time_end) { |
| new_timestamps->push_back( |
| timestamps.back() > sync_time_end ? timestamps.back() : |
| sync_time_end); |
| } |
| return true; |
| } else { |
| new_timestamps->insert(new_timestamps->begin(), |
| timestamps.begin(), |
| timestamps.end()); |
| return false; |
| } |
| } |
| } |
| |
| void* UserDataKey() { |
| // Use the address of a static that COMDAT folding won't ever fold |
| // with something else. |
| static int user_data_key = 0; |
| return reinterpret_cast<void*>(&user_data_key); |
| } |
| |
| } // namespace |
| |
| AutocompleteSyncableService::AutocompleteSyncableService( |
| AutofillWebDataBackend* webdata_backend) |
| : webdata_backend_(webdata_backend), |
| scoped_observer_(this), |
| cull_expired_entries_(false) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); |
| DCHECK(webdata_backend_); |
| |
| scoped_observer_.Add(webdata_backend_); |
| } |
| |
| AutocompleteSyncableService::~AutocompleteSyncableService() { |
| DCHECK(CalledOnValidThread()); |
| } |
| |
| // static |
| void AutocompleteSyncableService::CreateForWebDataServiceAndBackend( |
| AutofillWebDataService* web_data_service, |
| AutofillWebDataBackend* webdata_backend) { |
| web_data_service->GetDBUserData()->SetUserData( |
| UserDataKey(), new AutocompleteSyncableService(webdata_backend)); |
| } |
| |
| // static |
| AutocompleteSyncableService* AutocompleteSyncableService::FromWebDataService( |
| AutofillWebDataService* web_data_service) { |
| return static_cast<AutocompleteSyncableService*>( |
| web_data_service->GetDBUserData()->GetUserData(UserDataKey())); |
| } |
| |
| AutocompleteSyncableService::AutocompleteSyncableService() |
| : webdata_backend_(NULL), |
| scoped_observer_(this), |
| cull_expired_entries_(false) { |
| DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); |
| } |
| |
| void AutocompleteSyncableService::InjectStartSyncFlare( |
| const syncer::SyncableService::StartSyncFlare& flare) { |
| flare_ = flare; |
| } |
| |
| syncer::SyncMergeResult AutocompleteSyncableService::MergeDataAndStartSyncing( |
| syncer::ModelType type, |
| const syncer::SyncDataList& initial_sync_data, |
| scoped_ptr<syncer::SyncChangeProcessor> sync_processor, |
| scoped_ptr<syncer::SyncErrorFactory> error_handler) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(!sync_processor_.get()); |
| DCHECK(sync_processor.get()); |
| DCHECK(error_handler.get()); |
| VLOG(1) << "Associating Autocomplete: MergeDataAndStartSyncing"; |
| |
| syncer::SyncMergeResult merge_result(type); |
| error_handler_ = error_handler.Pass(); |
| std::vector<AutofillEntry> entries; |
| if (!LoadAutofillData(&entries)) { |
| merge_result.set_error(error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Could not get the autocomplete data from WebDatabase.")); |
| return merge_result; |
| } |
| |
| AutocompleteEntryMap new_db_entries; |
| for (std::vector<AutofillEntry>::iterator it = entries.begin(); |
| it != entries.end(); ++it) { |
| new_db_entries[it->key()] = |
| std::make_pair(syncer::SyncChange::ACTION_ADD, it); |
| } |
| |
| sync_processor_ = sync_processor.Pass(); |
| |
| std::vector<AutofillEntry> new_synced_entries; |
| // Go through and check for all the entries that sync already knows about. |
| // CreateOrUpdateEntry() will remove entries that are same with the synced |
| // ones from |new_db_entries|. |
| for (syncer::SyncDataList::const_iterator sync_iter = |
| initial_sync_data.begin(); |
| sync_iter != initial_sync_data.end(); ++sync_iter) { |
| CreateOrUpdateEntry(*sync_iter, &new_db_entries, &new_synced_entries); |
| } |
| |
| if (!SaveChangesToWebData(new_synced_entries)) { |
| merge_result.set_error(error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Failed to update webdata.")); |
| return merge_result; |
| } |
| |
| webdata_backend_->NotifyOfMultipleAutofillChanges(); |
| |
| syncer::SyncChangeList new_changes; |
| for (AutocompleteEntryMap::iterator i = new_db_entries.begin(); |
| i != new_db_entries.end(); ++i) { |
| new_changes.push_back( |
| syncer::SyncChange(FROM_HERE, |
| i->second.first, |
| CreateSyncData(*(i->second.second)))); |
| } |
| |
| if (cull_expired_entries_) { |
| // This will schedule a deletion operation on the DB thread, which will |
| // trigger a notification to propagate the deletion to Sync. |
| webdata_backend_->RemoveExpiredFormElements(); |
| } |
| |
| merge_result.set_error( |
| sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); |
| return merge_result; |
| } |
| |
| void AutocompleteSyncableService::StopSyncing(syncer::ModelType type) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK_EQ(syncer::AUTOFILL, type); |
| |
| sync_processor_.reset(NULL); |
| error_handler_.reset(); |
| } |
| |
| syncer::SyncDataList AutocompleteSyncableService::GetAllSyncData( |
| syncer::ModelType type) const { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(sync_processor_.get()); |
| DCHECK_EQ(type, syncer::AUTOFILL); |
| |
| syncer::SyncDataList current_data; |
| |
| std::vector<AutofillEntry> entries; |
| if (!LoadAutofillData(&entries)) |
| return current_data; |
| |
| for (std::vector<AutofillEntry>::iterator it = entries.begin(); |
| it != entries.end(); ++it) { |
| current_data.push_back(CreateSyncData(*it)); |
| } |
| |
| return current_data; |
| } |
| |
| syncer::SyncError AutocompleteSyncableService::ProcessSyncChanges( |
| const tracked_objects::Location& from_here, |
| const syncer::SyncChangeList& change_list) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(sync_processor_.get()); |
| |
| if (!sync_processor_.get()) { |
| syncer::SyncError error(FROM_HERE, |
| syncer::SyncError::DATATYPE_ERROR, |
| "Models not yet associated.", |
| syncer::AUTOFILL); |
| return error; |
| } |
| |
| // Data is loaded only if we get new ADD/UPDATE change. |
| std::vector<AutofillEntry> entries; |
| scoped_ptr<AutocompleteEntryMap> db_entries; |
| std::vector<AutofillEntry> new_entries; |
| |
| syncer::SyncError list_processing_error; |
| |
| for (syncer::SyncChangeList::const_iterator i = change_list.begin(); |
| i != change_list.end() && !list_processing_error.IsSet(); ++i) { |
| DCHECK(i->IsValid()); |
| switch (i->change_type()) { |
| case syncer::SyncChange::ACTION_ADD: |
| case syncer::SyncChange::ACTION_UPDATE: |
| if (!db_entries.get()) { |
| if (!LoadAutofillData(&entries)) { |
| return error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Could not get the autocomplete data from WebDatabase."); |
| } |
| db_entries.reset(new AutocompleteEntryMap); |
| for (std::vector<AutofillEntry>::iterator it = entries.begin(); |
| it != entries.end(); ++it) { |
| (*db_entries)[it->key()] = |
| std::make_pair(syncer::SyncChange::ACTION_ADD, it); |
| } |
| } |
| CreateOrUpdateEntry(i->sync_data(), db_entries.get(), &new_entries); |
| break; |
| case syncer::SyncChange::ACTION_DELETE: { |
| DCHECK(i->sync_data().GetSpecifics().has_autofill()) |
| << "Autofill specifics data not present on delete!"; |
| const sync_pb::AutofillSpecifics& autofill = |
| i->sync_data().GetSpecifics().autofill(); |
| if (autofill.has_value()) { |
| list_processing_error = AutofillEntryDelete(autofill); |
| } else { |
| DLOG(WARNING) |
| << "Delete for old-style autofill profile being dropped!"; |
| } |
| } break; |
| default: |
| NOTREACHED() << "Unexpected sync change state."; |
| return error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "ProcessSyncChanges failed on ChangeType " + |
| syncer::SyncChange::ChangeTypeToString(i->change_type())); |
| } |
| } |
| |
| if (!SaveChangesToWebData(new_entries)) { |
| return error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Failed to update webdata."); |
| } |
| |
| webdata_backend_->NotifyOfMultipleAutofillChanges(); |
| |
| if (cull_expired_entries_) { |
| // This will schedule a deletion operation on the DB thread, which will |
| // trigger a notification to propagate the deletion to Sync. |
| webdata_backend_->RemoveExpiredFormElements(); |
| } |
| |
| return list_processing_error; |
| } |
| |
| void AutocompleteSyncableService::AutofillEntriesChanged( |
| const AutofillChangeList& changes) { |
| // Check if sync is on. If we recieve this notification prior to sync being |
| // started, we'll notify sync to start as soon as it can and later process |
| // all entries when MergeData..() is called. If we receive this notification |
| // sync has exited, it will be synced next time Chrome starts. |
| if (sync_processor_.get()) { |
| ActOnChanges(changes); |
| } else if (!flare_.is_null()) { |
| flare_.Run(syncer::AUTOFILL); |
| flare_.Reset(); |
| } |
| } |
| |
| bool AutocompleteSyncableService::LoadAutofillData( |
| std::vector<AutofillEntry>* entries) const { |
| return AutofillTable::FromWebDatabase( |
| webdata_backend_->GetDatabase())->GetAllAutofillEntries(entries); |
| } |
| |
| bool AutocompleteSyncableService::SaveChangesToWebData( |
| const std::vector<AutofillEntry>& new_entries) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (!new_entries.empty() && |
| !AutofillTable::FromWebDatabase( |
| webdata_backend_->GetDatabase())->UpdateAutofillEntries( |
| new_entries)) { |
| return false; |
| } |
| return true; |
| } |
| |
| // Creates or updates an autocomplete entry based on |data|. |
| void AutocompleteSyncableService::CreateOrUpdateEntry( |
| const syncer::SyncData& data, |
| AutocompleteEntryMap* loaded_data, |
| std::vector<AutofillEntry>* new_entries) { |
| const sync_pb::EntitySpecifics& specifics = data.GetSpecifics(); |
| const sync_pb::AutofillSpecifics& autofill_specifics( |
| specifics.autofill()); |
| |
| if (!autofill_specifics.has_value()) { |
| DLOG(WARNING) |
| << "Add/Update for old-style autofill profile being dropped!"; |
| return; |
| } |
| |
| AutofillKey key(autofill_specifics.name().c_str(), |
| autofill_specifics.value().c_str()); |
| AutocompleteEntryMap::iterator it = loaded_data->find(key); |
| if (it == loaded_data->end()) { |
| // New entry. |
| std::vector<base::Time> timestamps; |
| size_t timestamps_count = autofill_specifics.usage_timestamp_size(); |
| timestamps.reserve(2); |
| if (timestamps_count) { |
| timestamps.push_back(base::Time::FromInternalValue( |
| autofill_specifics.usage_timestamp(0))); |
| } |
| if (timestamps_count > 1) { |
| timestamps.push_back(base::Time::FromInternalValue( |
| autofill_specifics.usage_timestamp(timestamps_count - 1))); |
| } |
| AutofillEntry new_entry(key, timestamps); |
| new_entries->push_back(new_entry); |
| } else { |
| // Entry already present - merge if necessary. |
| std::vector<base::Time> timestamps; |
| bool different = MergeTimestamps( |
| autofill_specifics, it->second.second->timestamps(), ×tamps); |
| if (different) { |
| AutofillEntry new_entry(it->second.second->key(), timestamps); |
| new_entries->push_back(new_entry); |
| // Update the sync db if the list of timestamps have changed. |
| *(it->second.second) = new_entry; |
| it->second.first = syncer::SyncChange::ACTION_UPDATE; |
| } else { |
| loaded_data->erase(it); |
| } |
| } |
| } |
| |
| // static |
| void AutocompleteSyncableService::WriteAutofillEntry( |
| const AutofillEntry& entry, sync_pb::EntitySpecifics* autofill_specifics) { |
| sync_pb::AutofillSpecifics* autofill = |
| autofill_specifics->mutable_autofill(); |
| autofill->set_name(UTF16ToUTF8(entry.key().name())); |
| autofill->set_value(UTF16ToUTF8(entry.key().value())); |
| const std::vector<base::Time>& ts(entry.timestamps()); |
| for (std::vector<base::Time>::const_iterator timestamp = ts.begin(); |
| timestamp != ts.end(); ++timestamp) { |
| autofill->add_usage_timestamp(timestamp->ToInternalValue()); |
| } |
| } |
| |
| syncer::SyncError AutocompleteSyncableService::AutofillEntryDelete( |
| const sync_pb::AutofillSpecifics& autofill) { |
| if (!AutofillTable::FromWebDatabase( |
| webdata_backend_->GetDatabase())->RemoveFormElement( |
| UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()))) { |
| return error_handler_->CreateAndUploadError( |
| FROM_HERE, |
| "Could not remove autocomplete entry from WebDatabase."); |
| } |
| return syncer::SyncError(); |
| } |
| |
| void AutocompleteSyncableService::ActOnChanges( |
| const AutofillChangeList& changes) { |
| DCHECK(sync_processor_.get()); |
| syncer::SyncChangeList new_changes; |
| for (AutofillChangeList::const_iterator change = changes.begin(); |
| change != changes.end(); ++change) { |
| switch (change->type()) { |
| case AutofillChange::ADD: |
| case AutofillChange::UPDATE: { |
| std::vector<base::Time> timestamps; |
| WebDatabase* db = webdata_backend_->GetDatabase(); |
| if (!AutofillTable::FromWebDatabase(db)->GetAutofillTimestamps( |
| change->key().name(), |
| change->key().value(), |
| ×tamps)) { |
| NOTREACHED(); |
| return; |
| } |
| AutofillEntry entry(change->key(), timestamps); |
| syncer::SyncChange::SyncChangeType change_type = |
| (change->type() == AutofillChange::ADD) ? |
| syncer::SyncChange::ACTION_ADD : |
| syncer::SyncChange::ACTION_UPDATE; |
| new_changes.push_back(syncer::SyncChange(FROM_HERE, |
| change_type, |
| CreateSyncData(entry))); |
| break; |
| } |
| case AutofillChange::REMOVE: { |
| std::vector<base::Time> timestamps; |
| AutofillEntry entry(change->key(), timestamps); |
| new_changes.push_back( |
| syncer::SyncChange(FROM_HERE, |
| syncer::SyncChange::ACTION_DELETE, |
| CreateSyncData(entry))); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| break; |
| } |
| } |
| syncer::SyncError error = |
| sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes); |
| if (error.IsSet()) { |
| DLOG(WARNING) << "[AUTOCOMPLETE SYNC]" |
| << " Failed processing change:" |
| << " Error:" << error.message(); |
| } |
| } |
| |
| void AutocompleteSyncableService::UpdateCullSetting( |
| bool cull_expired_entries) { |
| DCHECK(CalledOnValidThread()); |
| cull_expired_entries_ = cull_expired_entries; |
| } |
| |
| syncer::SyncData AutocompleteSyncableService::CreateSyncData( |
| const AutofillEntry& entry) const { |
| sync_pb::EntitySpecifics autofill_specifics; |
| WriteAutofillEntry(entry, &autofill_specifics); |
| std::string tag(KeyToTag(UTF16ToUTF8(entry.key().name()), |
| UTF16ToUTF8(entry.key().value()))); |
| return syncer::SyncData::CreateLocalData(tag, tag, autofill_specifics); |
| } |
| |
| // static |
| std::string AutocompleteSyncableService::KeyToTag(const std::string& name, |
| const std::string& value) { |
| std::string ns(kAutofillEntryNamespaceTag); |
| return ns + net::EscapePath(name) + "|" + net::EscapePath(value); |
| } |