diff options
author | sdefresne <sdefresne@chromium.org> | 2015-09-16 05:01:28 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-09-16 12:01:59 +0000 |
commit | 875d078307b9c4da92f36b9d4d0d4de4a7d8c9be (patch) | |
tree | 514baf91f065ebf2df675326c93d23caf9f2fe86 /components/syncable_prefs | |
parent | 40b5b417c5ebcfd5e21132e361bf78658c0ef617 (diff) | |
download | chromium_src-875d078307b9c4da92f36b9d4d0d4de4a7d8c9be.zip chromium_src-875d078307b9c4da92f36b9d4d0d4de4a7d8c9be.tar.gz chromium_src-875d078307b9c4da92f36b9d4d0d4de4a7d8c9be.tar.bz2 |
Componentize PrefSyncableService and support classes.
Create components/syncable_prefs and move PrefSyncableService files,
the support code and the unit tests.
BUG=522544,522537,522536,522532,522530,522527,522526
Review URL: https://codereview.chromium.org/1305213009
Cr-Commit-Position: refs/heads/master@{#349098}
Diffstat (limited to 'components/syncable_prefs')
20 files changed, 2889 insertions, 0 deletions
diff --git a/components/syncable_prefs/BUILD.gn b/components/syncable_prefs/BUILD.gn new file mode 100644 index 0000000..7d0ceda --- /dev/null +++ b/components/syncable_prefs/BUILD.gn @@ -0,0 +1,65 @@ +# Copyright 2015 The Chromium Authors. All rights reserved. +# Use of this source code is governed by a BSD-style license that can be +# found in the LICENSE file. + +import("//build/config/features.gni") + +source_set("syncable_prefs") { + sources = [ + "pref_model_associator.cc", + "pref_model_associator.h", + "pref_model_associator_client.h", + "pref_service_syncable.cc", + "pref_service_syncable.h", + "pref_service_syncable_factory.cc", + "pref_service_syncable_factory.h", + "pref_service_syncable_observer.h", + "synced_pref_change_registrar.cc", + "synced_pref_change_registrar.h", + "synced_pref_observer.h", + ] + + deps = [ + "//base", + "//base:prefs", + "//components/pref_registry", + "//sync", + ] + + if (enable_configuration_policy) { + deps += [ + "//components/policy:policy_component_browser", + "//components/policy:policy_component_common", + ] + } +} + +source_set("test_support") { + testonly = true + sources = [ + "pref_service_mock_factory.cc", + "pref_service_mock_factory.h", + "testing_pref_service_syncable.cc", + "testing_pref_service_syncable.h", + ] + + deps = [ + "//testing/gtest", + ":syncable_prefs", + ] +} + +source_set("unit_tests") { + testonly = true + sources = [ + "pref_model_associator_unittest.cc", + "pref_service_syncable_unittest.cc", + ] + + deps = [ + ":syncable_prefs", + ":test_support", + "//testing/gtest", + "//sync:test_support_sync_api", + ] +} diff --git a/components/syncable_prefs/DEPS b/components/syncable_prefs/DEPS new file mode 100644 index 0000000..2d2851d --- /dev/null +++ b/components/syncable_prefs/DEPS @@ -0,0 +1,11 @@ +include_rules = [ + "+components/policy/core/browser", + "+components/policy/core/common", + "+components/pref_registry", + "+sync", + + # syncable_prefs can be used on all platforms, including iOS. Do not allow + # platform-specific dependencies. + "-content", + "-ios", +] diff --git a/components/syncable_prefs/OWNERS b/components/syncable_prefs/OWNERS new file mode 100644 index 0000000..2d87038 --- /dev/null +++ b/components/syncable_prefs/OWNERS @@ -0,0 +1,4 @@ +battre@chromium.org +bauerb@chromium.org +gab@chromium.org +pam@chromium.org diff --git a/components/syncable_prefs/pref_model_associator.cc b/components/syncable_prefs/pref_model_associator.cc new file mode 100644 index 0000000..b4448f3 --- /dev/null +++ b/components/syncable_prefs/pref_model_associator.cc @@ -0,0 +1,624 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/syncable_prefs/pref_model_associator.h" + +#include "base/auto_reset.h" +#include "base/json/json_reader.h" +#include "base/json/json_string_value_serializer.h" +#include "base/location.h" +#include "base/logging.h" +#include "base/prefs/pref_service.h" +#include "base/stl_util.h" +#include "base/strings/utf_string_conversions.h" +#include "base/values.h" +#include "components/syncable_prefs/pref_model_associator_client.h" +#include "components/syncable_prefs/pref_service_syncable.h" +#include "sync/api/sync_change.h" +#include "sync/api/sync_error_factory.h" +#include "sync/protocol/preference_specifics.pb.h" +#include "sync/protocol/sync.pb.h" + +using syncer::PREFERENCES; +using syncer::PRIORITY_PREFERENCES; + +namespace { + +const sync_pb::PreferenceSpecifics& GetSpecifics(const syncer::SyncData& pref) { + DCHECK(pref.GetDataType() == syncer::PREFERENCES || + pref.GetDataType() == syncer::PRIORITY_PREFERENCES); + if (pref.GetDataType() == syncer::PRIORITY_PREFERENCES) { + return pref.GetSpecifics().priority_preference().preference(); + } else { + return pref.GetSpecifics().preference(); + } +} + +sync_pb::PreferenceSpecifics* GetMutableSpecifics( + const syncer::ModelType type, + sync_pb::EntitySpecifics* specifics) { + if (type == syncer::PRIORITY_PREFERENCES) { + DCHECK(!specifics->has_preference()); + return specifics->mutable_priority_preference()->mutable_preference(); + } else { + DCHECK(!specifics->has_priority_preference()); + return specifics->mutable_preference(); + } +} + +} // namespace + +PrefModelAssociator::PrefModelAssociator( + const PrefModelAssociatorClient* client, + syncer::ModelType type) + : models_associated_(false), + processing_syncer_changes_(false), + pref_service_(NULL), + type_(type), + client_(client) { + DCHECK(CalledOnValidThread()); + DCHECK(type_ == PREFERENCES || type_ == PRIORITY_PREFERENCES); +} + +PrefModelAssociator::~PrefModelAssociator() { + DCHECK(CalledOnValidThread()); + pref_service_ = NULL; + + STLDeleteContainerPairSecondPointers(synced_pref_observers_.begin(), + synced_pref_observers_.end()); + synced_pref_observers_.clear(); +} + +void PrefModelAssociator::InitPrefAndAssociate( + const syncer::SyncData& sync_pref, + const std::string& pref_name, + syncer::SyncChangeList* sync_changes, + SyncDataMap* migrated_preference_list) { + const base::Value* user_pref_value = pref_service_->GetUserPrefValue( + pref_name.c_str()); + VLOG(1) << "Associating preference " << pref_name; + + if (sync_pref.IsValid()) { + const sync_pb::PreferenceSpecifics& preference = GetSpecifics(sync_pref); + std::string old_pref_name; + DCHECK(pref_name == preference.name() || + (client_ && + client_->IsMigratedPreference(pref_name, &old_pref_name) && + preference.name() == old_pref_name)); + base::JSONReader reader; + scoped_ptr<base::Value> sync_value(reader.ReadToValue(preference.value())); + if (!sync_value.get()) { + LOG(ERROR) << "Failed to deserialize preference value: " + << reader.GetErrorMessage(); + return; + } + + if (user_pref_value) { + DVLOG(1) << "Found user pref value for " << pref_name; + // We have both server and local values. Merge them. + scoped_ptr<base::Value> new_value( + MergePreference(pref_name, *user_pref_value, *sync_value)); + + // Update the local preference based on what we got from the + // sync server. Note: this only updates the user value store, which is + // ignored if the preference is policy controlled. + if (new_value->IsType(base::Value::TYPE_NULL)) { + LOG(WARNING) << "Sync has null value for pref " << pref_name.c_str(); + pref_service_->ClearPref(pref_name.c_str()); + } else if (!new_value->IsType(user_pref_value->GetType())) { + LOG(WARNING) << "Synced value for " << preference.name() + << " is of type " << new_value->GetType() + << " which doesn't match pref type " + << user_pref_value->GetType(); + } else if (!user_pref_value->Equals(new_value.get())) { + pref_service_->Set(pref_name.c_str(), *new_value); + } + + // If the merge resulted in an updated value, inform the syncer. + if (!sync_value->Equals(new_value.get())) { + syncer::SyncData sync_data; + if (!CreatePrefSyncData(pref_name, *new_value, &sync_data)) { + LOG(ERROR) << "Failed to update preference."; + return; + } + + std::string old_pref_name; + if (client_ && + client_->IsMigratedPreference(pref_name, &old_pref_name)) { + // This preference has been migrated from an old version that must be + // kept in sync on older versions of Chrome. + if (preference.name() == old_pref_name) { + DCHECK(migrated_preference_list); + // If the name the syncer has is the old pre-migration value, then + // it's possible the new migrated preference name hasn't been synced + // yet. In that case the SyncChange should be an ACTION_ADD rather + // than an ACTION_UPDATE. Defer the decision of whether to sync with + // ACTION_ADD or ACTION_UPDATE until the migrated_preferences phase. + if (migrated_preference_list) + (*migrated_preference_list)[pref_name] = sync_data; + } else { + DCHECK_EQ(preference.name(), pref_name); + sync_changes->push_back( + syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_UPDATE, + sync_data)); + } + + syncer::SyncData old_sync_data; + if (!CreatePrefSyncData(old_pref_name, *new_value, &old_sync_data)) { + LOG(ERROR) << "Failed to update preference."; + return; + } + if (migrated_preference_list) + (*migrated_preference_list)[old_pref_name] = old_sync_data; + } else { + sync_changes->push_back( + syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_UPDATE, + sync_data)); + } + } + } else if (!sync_value->IsType(base::Value::TYPE_NULL)) { + // Only a server value exists. Just set the local user value. + pref_service_->Set(pref_name.c_str(), *sync_value); + } else { + LOG(WARNING) << "Sync has null value for pref " << pref_name.c_str(); + } + synced_preferences_.insert(preference.name()); + } else if (user_pref_value) { + // The server does not know about this preference and should be added + // to the syncer's database. + syncer::SyncData sync_data; + if (!CreatePrefSyncData(pref_name, *user_pref_value, &sync_data)) { + LOG(ERROR) << "Failed to update preference."; + return; + } + sync_changes->push_back( + syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_ADD, + sync_data)); + synced_preferences_.insert(pref_name); + } + + // Else this pref does not have a sync value but also does not have a user + // controlled value (either it's a default value or it's policy controlled, + // either way it's not interesting). We can ignore it. Once it gets changed, + // we'll send the new user controlled value to the syncer. +} + +syncer::SyncMergeResult PrefModelAssociator::MergeDataAndStartSyncing( + syncer::ModelType type, + const syncer::SyncDataList& initial_sync_data, + scoped_ptr<syncer::SyncChangeProcessor> sync_processor, + scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) { + DCHECK_EQ(type_, type); + DCHECK(CalledOnValidThread()); + DCHECK(pref_service_); + DCHECK(!sync_processor_.get()); + DCHECK(sync_processor.get()); + DCHECK(sync_error_factory.get()); + syncer::SyncMergeResult merge_result(type); + sync_processor_ = sync_processor.Pass(); + sync_error_factory_ = sync_error_factory.Pass(); + + syncer::SyncChangeList new_changes; + std::set<std::string> remaining_preferences = registered_preferences_; + + // Maintains a list of old migrated preference names that we wish to sync. + // Keep track of these in a list such that when the preference iteration + // loops below are complete we can go back and determine whether + SyncDataMap migrated_preference_list; + + // Go through and check for all preferences we care about that sync already + // knows about. + for (syncer::SyncDataList::const_iterator sync_iter = + initial_sync_data.begin(); + sync_iter != initial_sync_data.end(); + ++sync_iter) { + DCHECK_EQ(type_, sync_iter->GetDataType()); + + const sync_pb::PreferenceSpecifics& preference = GetSpecifics(*sync_iter); + std::string sync_pref_name = preference.name(); + + if (remaining_preferences.count(sync_pref_name) == 0) { + std::string new_pref_name; + if (client_ && + client_->IsOldMigratedPreference(sync_pref_name, &new_pref_name)) { + // This old pref name is not syncable locally anymore but we accept + // changes from other Chrome installs of previous versions and migrate + // them to the new name. Note that we will be merging any differences + // between the new and old values and sync'ing them back. + sync_pref_name = new_pref_name; + } else { + // We're not syncing this preference locally, ignore the sync data. + // TODO(zea): Eventually we want to be able to have the syncable service + // reconstruct all sync data for its datatype (therefore having + // GetAllSyncData be a complete representation). We should store this + // data somewhere, even if we don't use it. + continue; + } + } else { + remaining_preferences.erase(sync_pref_name); + } + InitPrefAndAssociate(*sync_iter, sync_pref_name, &new_changes, + &migrated_preference_list); + } + + // Go through and build sync data for any remaining preferences. + for (std::set<std::string>::iterator pref_name_iter = + remaining_preferences.begin(); + pref_name_iter != remaining_preferences.end(); + ++pref_name_iter) { + InitPrefAndAssociate(syncer::SyncData(), *pref_name_iter, &new_changes, + &migrated_preference_list); + } + + // Now go over any migrated preference names and build sync data for them too. + for (SyncDataMap::const_iterator migrated_pref_iter = + migrated_preference_list.begin(); + migrated_pref_iter != migrated_preference_list.end(); + ++migrated_pref_iter) { + syncer::SyncChange::SyncChangeType change_type = + (synced_preferences_.count(migrated_pref_iter->first) == 0) ? + syncer::SyncChange::ACTION_ADD : + syncer::SyncChange::ACTION_UPDATE; + new_changes.push_back( + syncer::SyncChange(FROM_HERE, change_type, migrated_pref_iter->second)); + synced_preferences_.insert(migrated_pref_iter->first); + } + + // Push updates to sync. + merge_result.set_error( + sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes)); + if (merge_result.error().IsSet()) + return merge_result; + + models_associated_ = true; + pref_service_->OnIsSyncingChanged(); + return merge_result; +} + +void PrefModelAssociator::StopSyncing(syncer::ModelType type) { + DCHECK_EQ(type_, type); + models_associated_ = false; + sync_processor_.reset(); + sync_error_factory_.reset(); + pref_service_->OnIsSyncingChanged(); +} + +scoped_ptr<base::Value> PrefModelAssociator::MergePreference( + const std::string& name, + const base::Value& local_value, + const base::Value& server_value) { + // This function special cases preferences individually, so don't attempt + // to merge for all migrated values. + if (client_) { + std::string new_pref_name; + DCHECK(!client_->IsOldMigratedPreference(name, &new_pref_name)); + if (client_->IsMergeableListPreference(name)) + return make_scoped_ptr(MergeListValues(local_value, server_value)); + if (client_->IsMergeableDictionaryPreference(name)) + return make_scoped_ptr(MergeDictionaryValues(local_value, server_value)); + } + + // If this is not a specially handled preference, server wins. + return make_scoped_ptr(server_value.DeepCopy()); +} + +bool PrefModelAssociator::CreatePrefSyncData( + const std::string& name, + const base::Value& value, + syncer::SyncData* sync_data) const { + if (value.IsType(base::Value::TYPE_NULL)) { + LOG(ERROR) << "Attempting to sync a null pref value for " << name; + return false; + } + + std::string serialized; + // TODO(zea): consider JSONWriter::Write since you don't have to check + // failures to deserialize. + JSONStringValueSerializer json(&serialized); + if (!json.Serialize(value)) { + LOG(ERROR) << "Failed to serialize preference value."; + return false; + } + + sync_pb::EntitySpecifics specifics; + sync_pb::PreferenceSpecifics* pref_specifics = + GetMutableSpecifics(type_, &specifics); + + pref_specifics->set_name(name); + pref_specifics->set_value(serialized); + *sync_data = syncer::SyncData::CreateLocalData(name, name, specifics); + return true; +} + +base::Value* PrefModelAssociator::MergeListValues(const base::Value& from_value, + const base::Value& to_value) { + if (from_value.GetType() == base::Value::TYPE_NULL) + return to_value.DeepCopy(); + if (to_value.GetType() == base::Value::TYPE_NULL) + return from_value.DeepCopy(); + + DCHECK(from_value.GetType() == base::Value::TYPE_LIST); + DCHECK(to_value.GetType() == base::Value::TYPE_LIST); + const base::ListValue& from_list_value = + static_cast<const base::ListValue&>(from_value); + const base::ListValue& to_list_value = + static_cast<const base::ListValue&>(to_value); + base::ListValue* result = to_list_value.DeepCopy(); + + for (base::ListValue::const_iterator i = from_list_value.begin(); + i != from_list_value.end(); ++i) { + base::Value* value = (*i)->DeepCopy(); + result->AppendIfNotPresent(value); + } + return result; +} + +base::Value* PrefModelAssociator::MergeDictionaryValues( + const base::Value& from_value, + const base::Value& to_value) { + if (from_value.GetType() == base::Value::TYPE_NULL) + return to_value.DeepCopy(); + if (to_value.GetType() == base::Value::TYPE_NULL) + return from_value.DeepCopy(); + + DCHECK_EQ(from_value.GetType(), base::Value::TYPE_DICTIONARY); + DCHECK_EQ(to_value.GetType(), base::Value::TYPE_DICTIONARY); + const base::DictionaryValue& from_dict_value = + static_cast<const base::DictionaryValue&>(from_value); + const base::DictionaryValue& to_dict_value = + static_cast<const base::DictionaryValue&>(to_value); + base::DictionaryValue* result = to_dict_value.DeepCopy(); + + for (base::DictionaryValue::Iterator it(from_dict_value); !it.IsAtEnd(); + it.Advance()) { + const base::Value* from_key_value = &it.value(); + base::Value* to_key_value; + if (result->GetWithoutPathExpansion(it.key(), &to_key_value)) { + if (from_key_value->GetType() == base::Value::TYPE_DICTIONARY && + to_key_value->GetType() == base::Value::TYPE_DICTIONARY) { + base::Value* merged_value = + MergeDictionaryValues(*from_key_value, *to_key_value); + result->SetWithoutPathExpansion(it.key(), merged_value); + } + // Note that for all other types we want to preserve the "to" + // values so we do nothing here. + } else { + result->SetWithoutPathExpansion(it.key(), from_key_value->DeepCopy()); + } + } + return result; +} + +// Note: This will build a model of all preferences registered as syncable +// with user controlled data. We do not track any information for preferences +// not registered locally as syncable and do not inform the syncer of +// non-user controlled preferences. +syncer::SyncDataList PrefModelAssociator::GetAllSyncData( + syncer::ModelType type) + const { + DCHECK_EQ(type_, type); + syncer::SyncDataList current_data; + for (PreferenceSet::const_iterator iter = synced_preferences_.begin(); + iter != synced_preferences_.end(); + ++iter) { + std::string name = *iter; + const PrefService::Preference* pref = + pref_service_->FindPreference(name.c_str()); + DCHECK(pref); + if (!pref->IsUserControlled() || pref->IsDefaultValue()) + continue; // This is not data we care about. + // TODO(zea): plumb a way to read the user controlled value. + syncer::SyncData sync_data; + if (!CreatePrefSyncData(name, *pref->GetValue(), &sync_data)) + continue; + current_data.push_back(sync_data); + } + return current_data; +} + +syncer::SyncError PrefModelAssociator::ProcessSyncChanges( + const tracked_objects::Location& from_here, + const syncer::SyncChangeList& change_list) { + if (!models_associated_) { + syncer::SyncError error(FROM_HERE, + syncer::SyncError::DATATYPE_ERROR, + "Models not yet associated.", + PREFERENCES); + return error; + } + base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true); + syncer::SyncChangeList::const_iterator iter; + for (iter = change_list.begin(); iter != change_list.end(); ++iter) { + DCHECK_EQ(type_, iter->sync_data().GetDataType()); + + const sync_pb::PreferenceSpecifics& pref_specifics = + GetSpecifics(iter->sync_data()); + + std::string name = pref_specifics.name(); + // It is possible that we may receive a change to a preference we do not + // want to sync. For example if the user is syncing a Mac client and a + // Windows client, the Windows client does not support + // kConfirmToQuitEnabled. Ignore updates from these preferences. + std::string pref_name = pref_specifics.name(); + std::string new_pref_name; + // We migrated this preference name, so do as if the name had not changed. + if (client_ && client_->IsOldMigratedPreference(name, &new_pref_name)) { + pref_name = new_pref_name; + } + + if (!IsPrefRegistered(pref_name.c_str())) + continue; + + if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) { + pref_service_->ClearPref(pref_name); + continue; + } + + scoped_ptr<base::Value> value(ReadPreferenceSpecifics(pref_specifics)); + if (!value.get()) { + // Skip values we can't deserialize. + // TODO(zea): consider taking some further action such as erasing the bad + // data. + continue; + } + + // This will only modify the user controlled value store, which takes + // priority over the default value but is ignored if the preference is + // policy controlled. + pref_service_->Set(pref_name, *value); + + NotifySyncedPrefObservers(pref_specifics.name(), true /*from_sync*/); + + // Keep track of any newly synced preferences. + if (iter->change_type() == syncer::SyncChange::ACTION_ADD) { + synced_preferences_.insert(pref_specifics.name()); + } + } + return syncer::SyncError(); +} + +base::Value* PrefModelAssociator::ReadPreferenceSpecifics( + const sync_pb::PreferenceSpecifics& preference) { + base::JSONReader reader; + scoped_ptr<base::Value> value(reader.ReadToValue(preference.value())); + if (!value.get()) { + std::string err = "Failed to deserialize preference value: " + + reader.GetErrorMessage(); + LOG(ERROR) << err; + return NULL; + } + return value.release(); +} + +bool PrefModelAssociator::IsPrefSynced(const std::string& name) const { + return synced_preferences_.find(name) != synced_preferences_.end(); +} + +void PrefModelAssociator::AddSyncedPrefObserver(const std::string& name, + SyncedPrefObserver* observer) { + SyncedPrefObserverList* observers = synced_pref_observers_[name]; + if (observers == NULL) { + observers = new SyncedPrefObserverList; + synced_pref_observers_[name] = observers; + } + observers->AddObserver(observer); +} + +void PrefModelAssociator::RemoveSyncedPrefObserver(const std::string& name, + SyncedPrefObserver* observer) { + SyncedPrefObserverMap::iterator observer_iter = + synced_pref_observers_.find(name); + if (observer_iter == synced_pref_observers_.end()) + return; + SyncedPrefObserverList* observers = observer_iter->second; + observers->RemoveObserver(observer); +} + +void PrefModelAssociator::SetPrefModelAssociatorClientForTesting( + const PrefModelAssociatorClient* client) { + DCHECK(!client_); + client_ = client; +} + +std::set<std::string> PrefModelAssociator::registered_preferences() const { + return registered_preferences_; +} + +void PrefModelAssociator::RegisterPref(const char* name) { + DCHECK(!models_associated_ && registered_preferences_.count(name) == 0); + registered_preferences_.insert(name); +} + +bool PrefModelAssociator::IsPrefRegistered(const char* name) { + return registered_preferences_.count(name) > 0; +} + +void PrefModelAssociator::ProcessPrefChange(const std::string& name) { + if (processing_syncer_changes_) + return; // These are changes originating from us, ignore. + + // We only process changes if we've already associated models. + if (!models_associated_) + return; + + const PrefService::Preference* preference = + pref_service_->FindPreference(name.c_str()); + if (!preference) + return; + + if (!IsPrefRegistered(name.c_str())) + return; // We are not syncing this preference. + + syncer::SyncChangeList changes; + + if (!preference->IsUserModifiable()) { + // If the preference is no longer user modifiable, it must now be controlled + // by policy, whose values we do not sync. Just return. If the preference + // stops being controlled by policy, it will revert back to the user value + // (which we continue to update with sync changes). + return; + } + + base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true); + + NotifySyncedPrefObservers(name, false /*from_sync*/); + + if (synced_preferences_.count(name) == 0) { + // Not in synced_preferences_ means no synced data. InitPrefAndAssociate(..) + // will determine if we care about its data (e.g. if it has a default value + // and hasn't been changed yet we don't) and take care syncing any new data. + InitPrefAndAssociate(syncer::SyncData(), name, &changes, NULL); + } else { + // We are already syncing this preference, just update it's sync node. + syncer::SyncData sync_data; + if (!CreatePrefSyncData(name, *preference->GetValue(), &sync_data)) { + LOG(ERROR) << "Failed to update preference."; + return; + } + changes.push_back( + syncer::SyncChange(FROM_HERE, + syncer::SyncChange::ACTION_UPDATE, + sync_data)); + // This preference has been migrated from an old version that must be kept + // in sync on older versions of Chrome. + std::string old_pref_name; + if (client_ && client_->IsMigratedPreference(name, &old_pref_name)) { + if (!CreatePrefSyncData(old_pref_name, + *preference->GetValue(), + &sync_data)) { + LOG(ERROR) << "Failed to update preference."; + return; + } + + syncer::SyncChange::SyncChangeType change_type = + (synced_preferences_.count(old_pref_name) == 0) ? + syncer::SyncChange::ACTION_ADD : + syncer::SyncChange::ACTION_UPDATE; + changes.push_back( + syncer::SyncChange(FROM_HERE, change_type, sync_data)); + } + } + + syncer::SyncError error = + sync_processor_->ProcessSyncChanges(FROM_HERE, changes); +} + +void PrefModelAssociator::SetPrefService(PrefServiceSyncable* pref_service) { + DCHECK(pref_service_ == NULL); + pref_service_ = pref_service; +} + +void PrefModelAssociator::NotifySyncedPrefObservers(const std::string& path, + bool from_sync) const { + SyncedPrefObserverMap::const_iterator observer_iter = + synced_pref_observers_.find(path); + if (observer_iter == synced_pref_observers_.end()) + return; + SyncedPrefObserverList* observers = observer_iter->second; + FOR_EACH_OBSERVER(SyncedPrefObserver, *observers, + OnSyncedPrefChanged(path, from_sync)); +} diff --git a/components/syncable_prefs/pref_model_associator.h b/components/syncable_prefs/pref_model_associator.h new file mode 100644 index 0000000..09f63e6 --- /dev/null +++ b/components/syncable_prefs/pref_model_associator.h @@ -0,0 +1,200 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SYNCABLE_PREFS_PREF_MODEL_ASSOCIATOR_H_ +#define COMPONENTS_SYNCABLE_PREFS_PREF_MODEL_ASSOCIATOR_H_ + +#include <map> +#include <set> +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/containers/hash_tables.h" +#include "base/gtest_prod_util.h" +#include "base/observer_list.h" +#include "base/threading/non_thread_safe.h" +#include "components/syncable_prefs/synced_pref_observer.h" +#include "sync/api/sync_data.h" +#include "sync/api/syncable_service.h" + +class PrefModelAssociatorClient; +class PrefRegistrySyncable; +class PrefServiceSyncable; + +namespace sync_pb { +class PreferenceSpecifics; +} + +namespace base { +class Value; +} + +// Contains all preference sync related logic. +// TODO(sync): Merge this into PrefService once we separate the profile +// PrefService from the local state PrefService. +class PrefModelAssociator + : public syncer::SyncableService, + public base::NonThreadSafe { + public: + // Constructs a PrefModelAssociator initializing the |client_| and |type_| + // instance variable. The |client| is not owned by this object and the caller + // must ensure that it oulives the PrefModelAssociator. + PrefModelAssociator(const PrefModelAssociatorClient* client, + syncer::ModelType type); + ~PrefModelAssociator() override; + + // See description above field for details. + bool models_associated() const { return models_associated_; } + + // syncer::SyncableService implementation. + syncer::SyncDataList GetAllSyncData(syncer::ModelType type) const override; + syncer::SyncError ProcessSyncChanges( + const tracked_objects::Location& from_here, + const syncer::SyncChangeList& change_list) override; + syncer::SyncMergeResult MergeDataAndStartSyncing( + syncer::ModelType type, + const syncer::SyncDataList& initial_sync_data, + scoped_ptr<syncer::SyncChangeProcessor> sync_processor, + scoped_ptr<syncer::SyncErrorFactory> sync_error_factory) override; + void StopSyncing(syncer::ModelType type) override; + + // Returns the list of preference names that are registered as syncable, and + // hence should be monitored for changes. + std::set<std::string> registered_preferences() const; + + // Register a preference with the specified name for syncing. We do not care + // about the type at registration time, but when changes arrive from the + // syncer, we check if they can be applied and if not drop them. + // Note: This should only be called at profile startup time (before sync + // begins). + virtual void RegisterPref(const char* name); + + // Returns true if the specified preference is registered for syncing. + virtual bool IsPrefRegistered(const char* name); + + // Process a local preference change. This can trigger new SyncChanges being + // sent to the syncer. + virtual void ProcessPrefChange(const std::string& name); + + void SetPrefService(PrefServiceSyncable* pref_service); + + // Merges the local_value into the supplied server_value and returns + // the result (caller takes ownership). If there is a conflict, the server + // value always takes precedence. Note that only certain preferences will + // actually be merged, all others will return a copy of the server value. See + // the method's implementation for details. + scoped_ptr<base::Value> MergePreference(const std::string& name, + const base::Value& local_value, + const base::Value& server_value); + + // Fills |sync_data| with a sync representation of the preference data + // provided. + bool CreatePrefSyncData(const std::string& name, + const base::Value& value, + syncer::SyncData* sync_data) const; + + // Extract preference value from sync specifics. + base::Value* ReadPreferenceSpecifics( + const sync_pb::PreferenceSpecifics& specifics); + + // Returns true if the pref under the given name is pulled down from sync. + // Note this does not refer to SYNCABLE_PREF. + bool IsPrefSynced(const std::string& name) const; + + // Adds a SyncedPrefObserver to watch for changes to a specific pref. + void AddSyncedPrefObserver(const std::string& name, + SyncedPrefObserver* observer); + + // Removes a SyncedPrefObserver from a pref's list of observers. + void RemoveSyncedPrefObserver(const std::string& name, + SyncedPrefObserver* observer); + + // Returns the PrefModelAssociatorClient for this object. + const PrefModelAssociatorClient* client() const { return client_; } + + // Set the PrefModelAssociatorClient to use for that object during tests. + void SetPrefModelAssociatorClientForTesting( + const PrefModelAssociatorClient* client); + + protected: + friend class PrefServiceSyncableTest; + + typedef std::map<std::string, syncer::SyncData> SyncDataMap; + + // Create an association for a given preference. If |sync_pref| is valid, + // signifying that sync has data for this preference, we reconcile their data + // with ours and append a new UPDATE SyncChange to |sync_changes|. If + // sync_pref is not set, we append an ADD SyncChange to |sync_changes| with + // the current preference data. + // |migrated_preference_list| points to a vector that may be updated with a + // string containing the old name of the preference described by |pref_name|. + // Note: We do not modify the sync data for preferences that are either + // controlled by policy (are not user modifiable) or have their default value + // (are not user controlled). + void InitPrefAndAssociate(const syncer::SyncData& sync_pref, + const std::string& pref_name, + syncer::SyncChangeList* sync_changes, + SyncDataMap* migrated_preference_list); + + static base::Value* MergeListValues( + const base::Value& from_value, const base::Value& to_value); + static base::Value* MergeDictionaryValues(const base::Value& from_value, + const base::Value& to_value); + + // Do we have an active association between the preferences and sync models? + // Set when start syncing, reset in StopSyncing. While this is not set, we + // ignore any local preference changes (when we start syncing we will look + // up the most recent values anyways). + bool models_associated_; + + // Whether we're currently processing changes from the syncer. While this is + // true, we ignore any local preference changes, since we triggered them. + bool processing_syncer_changes_; + + // A set of preference names. + typedef std::set<std::string> PreferenceSet; + + // All preferences that have registered as being syncable with this profile. + PreferenceSet registered_preferences_; + + // The preferences that are currently synced (excludes those preferences + // that have never had sync data and currently have default values or are + // policy controlled). + // Note: this set never decreases, only grows to eventually match + // registered_preferences_ as more preferences are synced. It determines + // whether a preference change should update an existing sync node or create + // a new sync node. + PreferenceSet synced_preferences_; + + // The PrefService we are syncing to. + PrefServiceSyncable* pref_service_; + + // Sync's syncer::SyncChange handler. We push all our changes through this. + scoped_ptr<syncer::SyncChangeProcessor> sync_processor_; + + // Sync's error handler. We use this to create sync errors. + scoped_ptr<syncer::SyncErrorFactory> sync_error_factory_; + + // The datatype that this associator is responible for, either PREFERENCES or + // PRIORITY_PREFERENCES. + syncer::ModelType type_; + + private: + // Map prefs to lists of observers. Observers will receive notification when + // a pref changes, including the detail of whether or not the change came + // from sync. + typedef base::ObserverList<SyncedPrefObserver> SyncedPrefObserverList; + typedef base::hash_map<std::string, SyncedPrefObserverList*> + SyncedPrefObserverMap; + + void NotifySyncedPrefObservers(const std::string& path, bool from_sync) const; + + SyncedPrefObserverMap synced_pref_observers_; + const PrefModelAssociatorClient* client_; // Weak. + + DISALLOW_COPY_AND_ASSIGN(PrefModelAssociator); +}; + +#endif // COMPONENTS_SYNCABLE_PREFS_PREF_MODEL_ASSOCIATOR_H_ diff --git a/components/syncable_prefs/pref_model_associator_client.h b/components/syncable_prefs/pref_model_associator_client.h new file mode 100644 index 0000000..923d5d1 --- /dev/null +++ b/components/syncable_prefs/pref_model_associator_client.h @@ -0,0 +1,45 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SYNCABLE_PREFS_PREF_MODEL_ASSOCIATOR_CLIENT_H_ +#define COMPONENTS_SYNCABLE_PREFS_PREF_MODEL_ASSOCIATOR_CLIENT_H_ + +#include <string> + +#include "base/macros.h" + +// This class allows the embedder to configure the PrefModelAssociator to +// have a different behaviour when receiving preference synchronisations +// events from the server. +class PrefModelAssociatorClient { + public: + // Returns true if the preference named |pref_name| is a list preference + // whose server value is merged with local value during synchronisation. + virtual bool IsMergeableListPreference( + const std::string& pref_name) const = 0; + + // Returns true if the preference named |pref_name| is a dictionary preference + // whose server value is merged with local value during synchronisation. + virtual bool IsMergeableDictionaryPreference( + const std::string& pref_name) const = 0; + + // Returns true if the preference named |new_pref_name| is the new name for + // an old preference. The old name will be returned via |old_pref_name|. + virtual bool IsMigratedPreference(const std::string& new_pref_name, + std::string* old_pref_name) const = 0; + + // Returns true if the preference named |old_pref_name| is the new name for + // a new preference. The old name will be returned via |new_pref_name|. + virtual bool IsOldMigratedPreference(const std::string& old_pref_name, + std::string* new_pref_name) const = 0; + + protected: + PrefModelAssociatorClient() {} + virtual ~PrefModelAssociatorClient() {} + + private: + DISALLOW_COPY_AND_ASSIGN(PrefModelAssociatorClient); +}; + +#endif // COMPONENTS_SYNCABLE_PREFS_PREF_MODEL_ASSOCIATOR_CLIENT_H_ diff --git a/components/syncable_prefs/pref_model_associator_unittest.cc b/components/syncable_prefs/pref_model_associator_unittest.cc new file mode 100644 index 0000000..75e90ff --- /dev/null +++ b/components/syncable_prefs/pref_model_associator_unittest.cc @@ -0,0 +1,472 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/prefs/scoped_user_pref_update.h" +#include "base/values.h" +#include "components/syncable_prefs/pref_model_associator.h" +#include "components/syncable_prefs/pref_model_associator_client.h" +#include "components/syncable_prefs/pref_service_mock_factory.h" +#include "components/syncable_prefs/pref_service_syncable.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +const char kStringPrefName[] = "pref.string"; +const char kListPrefName[] = "pref.list"; +const char kDictionaryPrefName[] = "pref.dictionary"; + +class TestPrefModelAssociatorClient : public PrefModelAssociatorClient { + public: + TestPrefModelAssociatorClient() {} + ~TestPrefModelAssociatorClient() override {} + + // PrefModelAssociatorClient implementation. + bool IsMergeableListPreference(const std::string& pref_name) const override { + return pref_name == kListPrefName; + } + + bool IsMergeableDictionaryPreference( + const std::string& pref_name) const override { + return pref_name == kDictionaryPrefName; + } + + bool IsMigratedPreference(const std::string& new_pref_name, + std::string* old_pref_name) const override { + return false; + } + + bool IsOldMigratedPreference(const std::string& old_pref_name, + std::string* new_pref_name) const override { + return false; + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestPrefModelAssociatorClient); +}; + +class AbstractPreferenceMergeTest : public testing::Test { + protected: + AbstractPreferenceMergeTest() { + PrefServiceMockFactory factory; + factory.SetPrefModelAssociatorClient(&client_); + scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry( + new user_prefs::PrefRegistrySyncable); + pref_registry->RegisterStringPref( + kStringPrefName, + std::string(), + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); + pref_registry->RegisterListPref( + kListPrefName, + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); + pref_registry->RegisterDictionaryPref( + kDictionaryPrefName, + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); + pref_service_ = factory.CreateSyncable(pref_registry.get()); + pref_sync_service_ = static_cast<PrefModelAssociator*>( + pref_service_->GetSyncableService(syncer::PREFERENCES)); + } + + void SetContentPattern(base::DictionaryValue* patterns_dict, + const std::string& expression, + int setting) { + base::DictionaryValue* expression_dict; + bool found = + patterns_dict->GetDictionaryWithoutPathExpansion(expression, + &expression_dict); + if (!found) { + expression_dict = new base::DictionaryValue; + patterns_dict->SetWithoutPathExpansion(expression, expression_dict); + } + expression_dict->SetWithoutPathExpansion( + "setting", new base::FundamentalValue(setting)); + } + + void SetPrefToEmpty(const std::string& pref_name) { + scoped_ptr<base::Value> empty_value; + const PrefService::Preference* pref = + pref_service_->FindPreference(pref_name.c_str()); + ASSERT_TRUE(pref); + base::Value::Type type = pref->GetType(); + if (type == base::Value::TYPE_DICTIONARY) + empty_value.reset(new base::DictionaryValue); + else if (type == base::Value::TYPE_LIST) + empty_value.reset(new base::ListValue); + else + FAIL(); + pref_service_->Set(pref_name.c_str(), *empty_value); + } + + TestPrefModelAssociatorClient client_; + scoped_ptr<PrefServiceSyncable> pref_service_; + PrefModelAssociator* pref_sync_service_; +}; + +class ListPreferenceMergeTest : public AbstractPreferenceMergeTest { + protected: + ListPreferenceMergeTest() + : server_url0_("http://example.com/server0"), + server_url1_("http://example.com/server1"), + local_url0_("http://example.com/local0"), + local_url1_("http://example.com/local1") { + server_url_list_.Append(new base::StringValue(server_url0_)); + server_url_list_.Append(new base::StringValue(server_url1_)); + } + + std::string server_url0_; + std::string server_url1_; + std::string local_url0_; + std::string local_url1_; + base::ListValue server_url_list_; +}; + +TEST_F(ListPreferenceMergeTest, NotListOrDictionary) { + pref_service_->SetString(kStringPrefName, local_url0_); + const PrefService::Preference* pref = + pref_service_->FindPreference(kStringPrefName); + scoped_ptr<base::Value> server_value(new base::StringValue(server_url0_)); + scoped_ptr<base::Value> merged_value( + pref_sync_service_->MergePreference(pref->name(), + *pref->GetValue(), + *server_value)); + EXPECT_TRUE(merged_value->Equals(server_value.get())); +} + +TEST_F(ListPreferenceMergeTest, LocalEmpty) { + SetPrefToEmpty(kListPrefName); + const PrefService::Preference* pref = + pref_service_->FindPreference(kListPrefName); + scoped_ptr<base::Value> merged_value( + pref_sync_service_->MergePreference(pref->name(), + *pref->GetValue(), + server_url_list_)); + EXPECT_TRUE(merged_value->Equals(&server_url_list_)); +} + +TEST_F(ListPreferenceMergeTest, ServerNull) { + scoped_ptr<base::Value> null_value = base::Value::CreateNullValue(); + { + ListPrefUpdate update(pref_service_.get(), kListPrefName); + base::ListValue* local_list_value = update.Get(); + local_list_value->Append(new base::StringValue(local_url0_)); + } + + const PrefService::Preference* pref = + pref_service_->FindPreference(kListPrefName); + scoped_ptr<base::Value> merged_value( + pref_sync_service_->MergePreference(pref->name(), + *pref->GetValue(), + *null_value)); + const base::ListValue* local_list_value = + pref_service_->GetList(kListPrefName); + EXPECT_TRUE(merged_value->Equals(local_list_value)); +} + +TEST_F(ListPreferenceMergeTest, ServerEmpty) { + scoped_ptr<base::Value> empty_value(new base::ListValue); + { + ListPrefUpdate update(pref_service_.get(), kListPrefName); + base::ListValue* local_list_value = update.Get(); + local_list_value->Append(new base::StringValue(local_url0_)); + } + + const PrefService::Preference* pref = + pref_service_->FindPreference(kListPrefName); + scoped_ptr<base::Value> merged_value( + pref_sync_service_->MergePreference(pref->name(), + *pref->GetValue(), + *empty_value)); + const base::ListValue* local_list_value = + pref_service_->GetList(kListPrefName); + EXPECT_TRUE(merged_value->Equals(local_list_value)); +} + +TEST_F(ListPreferenceMergeTest, Merge) { + { + ListPrefUpdate update(pref_service_.get(), kListPrefName); + base::ListValue* local_list_value = update.Get(); + local_list_value->Append(new base::StringValue(local_url0_)); + local_list_value->Append(new base::StringValue(local_url1_)); + } + + const PrefService::Preference* pref = + pref_service_->FindPreference(kListPrefName); + scoped_ptr<base::Value> merged_value( + pref_sync_service_->MergePreference(pref->name(), + *pref->GetValue(), + server_url_list_)); + + base::ListValue expected; + expected.Append(new base::StringValue(server_url0_)); + expected.Append(new base::StringValue(server_url1_)); + expected.Append(new base::StringValue(local_url0_)); + expected.Append(new base::StringValue(local_url1_)); + EXPECT_TRUE(merged_value->Equals(&expected)); +} + +TEST_F(ListPreferenceMergeTest, Duplicates) { + { + ListPrefUpdate update(pref_service_.get(), kListPrefName); + base::ListValue* local_list_value = update.Get(); + local_list_value->Append(new base::StringValue(local_url0_)); + local_list_value->Append(new base::StringValue(server_url0_)); + local_list_value->Append(new base::StringValue(server_url1_)); + } + + const PrefService::Preference* pref = + pref_service_->FindPreference(kListPrefName); + scoped_ptr<base::Value> merged_value( + pref_sync_service_->MergePreference(pref->name(), + *pref->GetValue(), + server_url_list_)); + + base::ListValue expected; + expected.Append(new base::StringValue(server_url0_)); + expected.Append(new base::StringValue(server_url1_)); + expected.Append(new base::StringValue(local_url0_)); + EXPECT_TRUE(merged_value->Equals(&expected)); +} + +TEST_F(ListPreferenceMergeTest, Equals) { + { + ListPrefUpdate update(pref_service_.get(), kListPrefName); + base::ListValue* local_list_value = update.Get(); + local_list_value->Append(new base::StringValue(server_url0_)); + local_list_value->Append(new base::StringValue(server_url1_)); + } + + scoped_ptr<base::Value> original(server_url_list_.DeepCopy()); + const PrefService::Preference* pref = + pref_service_->FindPreference(kListPrefName); + scoped_ptr<base::Value> merged_value( + pref_sync_service_->MergePreference(pref->name(), + *pref->GetValue(), + server_url_list_)); + EXPECT_TRUE(merged_value->Equals(original.get())); +} + +class DictionaryPreferenceMergeTest : public AbstractPreferenceMergeTest { + protected: + DictionaryPreferenceMergeTest() + : expression0_("expression0"), + expression1_("expression1"), + expression2_("expression2"), + expression3_("expression3"), + expression4_("expression4") { + SetContentPattern(&server_patterns_, expression0_, 1); + SetContentPattern(&server_patterns_, expression1_, 2); + SetContentPattern(&server_patterns_, expression2_, 1); + } + + std::string expression0_; + std::string expression1_; + std::string expression2_; + std::string expression3_; + std::string expression4_; + base::DictionaryValue server_patterns_; +}; + +TEST_F(DictionaryPreferenceMergeTest, LocalEmpty) { + SetPrefToEmpty(kDictionaryPrefName); + const PrefService::Preference* pref = + pref_service_->FindPreference(kDictionaryPrefName); + scoped_ptr<base::Value> merged_value( + pref_sync_service_->MergePreference(pref->name(), + *pref->GetValue(), + server_patterns_)); + EXPECT_TRUE(merged_value->Equals(&server_patterns_)); +} + +TEST_F(DictionaryPreferenceMergeTest, ServerNull) { + scoped_ptr<base::Value> null_value = base::Value::CreateNullValue(); + { + DictionaryPrefUpdate update(pref_service_.get(), kDictionaryPrefName); + base::DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression3_, 1); + } + + const PrefService::Preference* pref = + pref_service_->FindPreference(kDictionaryPrefName); + scoped_ptr<base::Value> merged_value( + pref_sync_service_->MergePreference(pref->name(), + *pref->GetValue(), + *null_value)); + const base::DictionaryValue* local_dict_value = + pref_service_->GetDictionary(kDictionaryPrefName); + EXPECT_TRUE(merged_value->Equals(local_dict_value)); +} + +TEST_F(DictionaryPreferenceMergeTest, ServerEmpty) { + scoped_ptr<base::Value> empty_value(new base::DictionaryValue); + { + DictionaryPrefUpdate update(pref_service_.get(), kDictionaryPrefName); + base::DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression3_, 1); + } + + const PrefService::Preference* pref = + pref_service_->FindPreference(kDictionaryPrefName); + scoped_ptr<base::Value> merged_value( + pref_sync_service_->MergePreference(pref->name(), + *pref->GetValue(), + *empty_value)); + const base::DictionaryValue* local_dict_value = + pref_service_->GetDictionary(kDictionaryPrefName); + EXPECT_TRUE(merged_value->Equals(local_dict_value)); +} + +TEST_F(DictionaryPreferenceMergeTest, MergeNoConflicts) { + { + DictionaryPrefUpdate update(pref_service_.get(), kDictionaryPrefName); + base::DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression3_, 1); + } + + scoped_ptr<base::Value> merged_value(pref_sync_service_->MergePreference( + kDictionaryPrefName, + *pref_service_->FindPreference(kDictionaryPrefName)->GetValue(), + server_patterns_)); + + base::DictionaryValue expected; + SetContentPattern(&expected, expression0_, 1); + SetContentPattern(&expected, expression1_, 2); + SetContentPattern(&expected, expression2_, 1); + SetContentPattern(&expected, expression3_, 1); + EXPECT_TRUE(merged_value->Equals(&expected)); +} + +TEST_F(DictionaryPreferenceMergeTest, MergeConflicts) { + { + DictionaryPrefUpdate update(pref_service_.get(), kDictionaryPrefName); + base::DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression0_, 2); + SetContentPattern(local_dict_value, expression2_, 1); + SetContentPattern(local_dict_value, expression3_, 1); + SetContentPattern(local_dict_value, expression4_, 2); + } + + scoped_ptr<base::Value> merged_value(pref_sync_service_->MergePreference( + kDictionaryPrefName, + *pref_service_->FindPreference(kDictionaryPrefName)->GetValue(), + server_patterns_)); + + base::DictionaryValue expected; + SetContentPattern(&expected, expression0_, 1); + SetContentPattern(&expected, expression1_, 2); + SetContentPattern(&expected, expression2_, 1); + SetContentPattern(&expected, expression3_, 1); + SetContentPattern(&expected, expression4_, 2); + EXPECT_TRUE(merged_value->Equals(&expected)); +} + +TEST_F(DictionaryPreferenceMergeTest, MergeValueToDictionary) { + base::DictionaryValue local_dict_value; + local_dict_value.SetInteger("key", 0); + + base::DictionaryValue server_dict_value; + server_dict_value.SetInteger("key.subkey", 0); + + scoped_ptr<base::Value> merged_value(pref_sync_service_->MergePreference( + kDictionaryPrefName, + local_dict_value, + server_dict_value)); + + EXPECT_TRUE(merged_value->Equals(&server_dict_value)); +} + +TEST_F(DictionaryPreferenceMergeTest, Equal) { + { + DictionaryPrefUpdate update(pref_service_.get(), kDictionaryPrefName); + base::DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression0_, 1); + SetContentPattern(local_dict_value, expression1_, 2); + SetContentPattern(local_dict_value, expression2_, 1); + } + + scoped_ptr<base::Value> merged_value(pref_sync_service_->MergePreference( + kDictionaryPrefName, + *pref_service_->FindPreference(kDictionaryPrefName)->GetValue(), + server_patterns_)); + EXPECT_TRUE(merged_value->Equals(&server_patterns_)); +} + +TEST_F(DictionaryPreferenceMergeTest, ConflictButServerWins) { + { + DictionaryPrefUpdate update(pref_service_.get(), kDictionaryPrefName); + base::DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression0_, 2); + SetContentPattern(local_dict_value, expression1_, 2); + SetContentPattern(local_dict_value, expression2_, 1); + } + + scoped_ptr<base::Value> merged_value(pref_sync_service_->MergePreference( + kDictionaryPrefName, + *pref_service_->FindPreference(kDictionaryPrefName)->GetValue(), + server_patterns_)); + EXPECT_TRUE(merged_value->Equals(&server_patterns_)); +} + +class IndividualPreferenceMergeTest : public AbstractPreferenceMergeTest { + protected: + IndividualPreferenceMergeTest() + : url0_("http://example.com/server0"), + url1_("http://example.com/server1"), + expression0_("expression0"), + expression1_("expression1") { + server_url_list_.Append(new base::StringValue(url0_)); + SetContentPattern(&server_patterns_, expression0_, 1); + } + + bool MergeListPreference(const char* pref) { + { + ListPrefUpdate update(pref_service_.get(), pref); + base::ListValue* local_list_value = update.Get(); + local_list_value->Append(new base::StringValue(url1_)); + } + + scoped_ptr<base::Value> merged_value(pref_sync_service_->MergePreference( + pref, + *pref_service_->GetUserPrefValue(pref), + server_url_list_)); + + base::ListValue expected; + expected.Append(new base::StringValue(url0_)); + expected.Append(new base::StringValue(url1_)); + return merged_value->Equals(&expected); + } + + bool MergeDictionaryPreference(const char* pref) { + { + DictionaryPrefUpdate update(pref_service_.get(), pref); + base::DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression1_, 1); + } + + scoped_ptr<base::Value> merged_value(pref_sync_service_->MergePreference( + pref, + *pref_service_->GetUserPrefValue(pref), + server_patterns_)); + + base::DictionaryValue expected; + SetContentPattern(&expected, expression0_, 1); + SetContentPattern(&expected, expression1_, 1); + return merged_value->Equals(&expected); + } + + std::string url0_; + std::string url1_; + std::string expression0_; + std::string expression1_; + std::string content_type0_; + base::ListValue server_url_list_; + base::DictionaryValue server_patterns_; +}; + +TEST_F(IndividualPreferenceMergeTest, ListPreference) { + EXPECT_TRUE(MergeListPreference(kListPrefName)); +} + +} // namespace diff --git a/components/syncable_prefs/pref_service_mock_factory.cc b/components/syncable_prefs/pref_service_mock_factory.cc new file mode 100644 index 0000000..c176b50 --- /dev/null +++ b/components/syncable_prefs/pref_service_mock_factory.cc @@ -0,0 +1,13 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/syncable_prefs/pref_service_mock_factory.h" + +#include "base/prefs/testing_pref_store.h" + +PrefServiceMockFactory::PrefServiceMockFactory() { + user_prefs_ = new TestingPrefStore; +} + +PrefServiceMockFactory::~PrefServiceMockFactory() {} diff --git a/components/syncable_prefs/pref_service_mock_factory.h b/components/syncable_prefs/pref_service_mock_factory.h new file mode 100644 index 0000000..0ea6e10 --- /dev/null +++ b/components/syncable_prefs/pref_service_mock_factory.h @@ -0,0 +1,20 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_MOCK_FACTORY_H_ +#define COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_MOCK_FACTORY_H_ + +#include "components/syncable_prefs/pref_service_syncable_factory.h" + +// A helper that allows convenient building of custom PrefServices in tests. +class PrefServiceMockFactory : public PrefServiceSyncableFactory { + public: + PrefServiceMockFactory(); + ~PrefServiceMockFactory() override; + + private: + DISALLOW_COPY_AND_ASSIGN(PrefServiceMockFactory); +}; + +#endif // COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_MOCK_FACTORY_H_ diff --git a/components/syncable_prefs/pref_service_syncable.cc b/components/syncable_prefs/pref_service_syncable.cc new file mode 100644 index 0000000..46f7ec7 --- /dev/null +++ b/components/syncable_prefs/pref_service_syncable.cc @@ -0,0 +1,182 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/syncable_prefs/pref_service_syncable.h" + +#include "base/bind.h" +#include "base/callback.h" +#include "base/files/file_path.h" +#include "base/prefs/default_pref_store.h" +#include "base/prefs/overlay_user_pref_store.h" +#include "base/prefs/pref_notifier_impl.h" +#include "base/prefs/pref_registry.h" +#include "base/prefs/pref_value_store.h" +#include "base/strings/string_number_conversions.h" +#include "base/value_conversions.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/syncable_prefs/pref_model_associator.h" +#include "components/syncable_prefs/pref_service_syncable_observer.h" + +PrefServiceSyncable::PrefServiceSyncable( + PrefNotifierImpl* pref_notifier, + PrefValueStore* pref_value_store, + PersistentPrefStore* user_prefs, + user_prefs::PrefRegistrySyncable* pref_registry, + const PrefModelAssociatorClient* pref_model_associator_client, + base::Callback<void(PersistentPrefStore::PrefReadError)> + read_error_callback, + bool async) + : PrefService(pref_notifier, + pref_value_store, + user_prefs, + pref_registry, + read_error_callback, + async), + pref_sync_associator_(pref_model_associator_client, syncer::PREFERENCES), + priority_pref_sync_associator_(pref_model_associator_client, + syncer::PRIORITY_PREFERENCES) { + pref_sync_associator_.SetPrefService(this); + priority_pref_sync_associator_.SetPrefService(this); + + // Let PrefModelAssociators know about changes to preference values. + pref_value_store->set_callback(base::Bind( + &PrefServiceSyncable::ProcessPrefChange, base::Unretained(this))); + + // Add already-registered syncable preferences to PrefModelAssociator. + for (PrefRegistry::const_iterator it = pref_registry->begin(); + it != pref_registry->end(); ++it) { + const std::string& path = it->first; + AddRegisteredSyncablePreference(path, + pref_registry_->GetRegistrationFlags(path)); + } + + // Watch for syncable preferences registered after this point. + pref_registry->SetSyncableRegistrationCallback( + base::Bind(&PrefServiceSyncable::AddRegisteredSyncablePreference, + base::Unretained(this))); +} + +PrefServiceSyncable::~PrefServiceSyncable() { + // Remove our callback from the registry, since it may outlive us. + user_prefs::PrefRegistrySyncable* registry = + static_cast<user_prefs::PrefRegistrySyncable*>(pref_registry_.get()); + registry->SetSyncableRegistrationCallback( + user_prefs::PrefRegistrySyncable::SyncableRegistrationCallback()); +} + +PrefServiceSyncable* PrefServiceSyncable::CreateIncognitoPrefService( + PrefStore* incognito_extension_pref_store, + const std::vector<const char*>& overlay_pref_names) { + pref_service_forked_ = true; + PrefNotifierImpl* pref_notifier = new PrefNotifierImpl(); + OverlayUserPrefStore* incognito_pref_store = + new OverlayUserPrefStore(user_pref_store_.get()); + for (const char* overlay_pref_name : overlay_pref_names) + incognito_pref_store->RegisterOverlayPref(overlay_pref_name); + + scoped_refptr<user_prefs::PrefRegistrySyncable> forked_registry = + static_cast<user_prefs::PrefRegistrySyncable*>( + pref_registry_.get())->ForkForIncognito(); + PrefServiceSyncable* incognito_service = new PrefServiceSyncable( + pref_notifier, + pref_value_store_->CloneAndSpecialize(NULL, // managed + NULL, // supervised_user + incognito_extension_pref_store, + NULL, // command_line_prefs + incognito_pref_store, + NULL, // recommended + forked_registry->defaults().get(), + pref_notifier), + incognito_pref_store, + forked_registry.get(), + pref_sync_associator_.client(), + read_error_callback_, + false); + return incognito_service; +} + +bool PrefServiceSyncable::IsSyncing() { + return pref_sync_associator_.models_associated(); +} + +bool PrefServiceSyncable::IsPrioritySyncing() { + return priority_pref_sync_associator_.models_associated(); +} + +bool PrefServiceSyncable::IsPrefSynced(const std::string& name) const { + return pref_sync_associator_.IsPrefSynced(name) || + priority_pref_sync_associator_.IsPrefSynced(name); +} + +void PrefServiceSyncable::AddObserver(PrefServiceSyncableObserver* observer) { + observer_list_.AddObserver(observer); +} + +void PrefServiceSyncable::RemoveObserver( + PrefServiceSyncableObserver* observer) { + observer_list_.RemoveObserver(observer); +} + +syncer::SyncableService* PrefServiceSyncable::GetSyncableService( + const syncer::ModelType& type) { + if (type == syncer::PREFERENCES) { + return &pref_sync_associator_; + } else if (type == syncer::PRIORITY_PREFERENCES) { + return &priority_pref_sync_associator_; + } else { + NOTREACHED() << "invalid model type: " << type; + return NULL; + } +} + +void PrefServiceSyncable::UpdateCommandLinePrefStore( + PrefStore* cmd_line_store) { + // If |pref_service_forked_| is true, then this PrefService and the forked + // copies will be out of sync. + DCHECK(!pref_service_forked_); + PrefService::UpdateCommandLinePrefStore(cmd_line_store); +} + +void PrefServiceSyncable::AddSyncedPrefObserver(const std::string& name, + SyncedPrefObserver* observer) { + pref_sync_associator_.AddSyncedPrefObserver(name, observer); + priority_pref_sync_associator_.AddSyncedPrefObserver(name, observer); +} + +void PrefServiceSyncable::RemoveSyncedPrefObserver( + const std::string& name, + SyncedPrefObserver* observer) { + pref_sync_associator_.RemoveSyncedPrefObserver(name, observer); + priority_pref_sync_associator_.RemoveSyncedPrefObserver(name, observer); +} + +// Set the PrefModelAssociatorClient to use for that object during tests. +void PrefServiceSyncable::SetPrefModelAssociatorClientForTesting( + const PrefModelAssociatorClient* pref_model_associator_client) { + pref_sync_associator_.SetPrefModelAssociatorClientForTesting( + pref_model_associator_client); + priority_pref_sync_associator_.SetPrefModelAssociatorClientForTesting( + pref_model_associator_client); +} + +void PrefServiceSyncable::AddRegisteredSyncablePreference( + const std::string& path, + uint32 flags) { + DCHECK(FindPreference(path)); + if (flags & user_prefs::PrefRegistrySyncable::SYNCABLE_PREF) { + pref_sync_associator_.RegisterPref(path.c_str()); + } else if (flags & user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF) { + priority_pref_sync_associator_.RegisterPref(path.c_str()); + } +} + +void PrefServiceSyncable::OnIsSyncingChanged() { + FOR_EACH_OBSERVER(PrefServiceSyncableObserver, observer_list_, + OnIsSyncingChanged()); +} + +void PrefServiceSyncable::ProcessPrefChange(const std::string& name) { + pref_sync_associator_.ProcessPrefChange(name); + priority_pref_sync_associator_.ProcessPrefChange(name); +} diff --git a/components/syncable_prefs/pref_service_syncable.h b/components/syncable_prefs/pref_service_syncable.h new file mode 100644 index 0000000..b3cfe84 --- /dev/null +++ b/components/syncable_prefs/pref_service_syncable.h @@ -0,0 +1,112 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_SYNCABLE_H_ +#define COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_SYNCABLE_H_ + +#include <vector> + +#include "base/callback_forward.h" +#include "base/prefs/pref_service.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/syncable_prefs/pref_model_associator.h" +#include "components/syncable_prefs/synced_pref_observer.h" + +class PrefModelAssociatorClient; +class PrefServiceSyncableObserver; + +namespace syncer { +class SyncableService; +} + +// A PrefService that can be synced. Users are forced to declare +// whether preferences are syncable or not when registering them to +// this PrefService. +class PrefServiceSyncable : public PrefService { + public: + // You may wish to use PrefServiceFactory or one of its subclasses + // for simplified construction. + PrefServiceSyncable( + PrefNotifierImpl* pref_notifier, + PrefValueStore* pref_value_store, + PersistentPrefStore* user_prefs, + user_prefs::PrefRegistrySyncable* pref_registry, + const PrefModelAssociatorClient* pref_model_associato_client, + base::Callback<void(PersistentPrefStore::PrefReadError)> + read_error_callback, + bool async); + ~PrefServiceSyncable() override; + + // Creates an incognito copy of the pref service that shares most pref stores + // but uses a fresh non-persistent overlay for the user pref store and an + // individual extension pref store (to cache the effective extension prefs for + // incognito windows). |overlay_pref_names| is a list of preference names + // whose changes will not be persisted by the returned incognito pref service. + PrefServiceSyncable* CreateIncognitoPrefService( + PrefStore* incognito_extension_pref_store, + const std::vector<const char*>& overlay_pref_names); + + // Returns true if preferences state has synchronized with the remote + // preferences. If true is returned it can be assumed the local preferences + // has applied changes from the remote preferences. The two may not be + // identical if a change is in flight (from either side). + // + // TODO(albertb): Given that we now support priority preferences, callers of + // this method are likely better off making the preferences they care about + // into priority preferences and calling IsPrioritySyncing(). + bool IsSyncing(); + + // Returns true if priority preferences state has synchronized with the remote + // priority preferences. + bool IsPrioritySyncing(); + + // Returns true if the pref under the given name is pulled down from sync. + // Note this does not refer to SYNCABLE_PREF. + bool IsPrefSynced(const std::string& name) const; + + void AddObserver(PrefServiceSyncableObserver* observer); + void RemoveObserver(PrefServiceSyncableObserver* observer); + + // TODO(zea): Have PrefServiceSyncable implement + // syncer::SyncableService directly. + syncer::SyncableService* GetSyncableService(const syncer::ModelType& type); + + // Do not call this after having derived an incognito or per tab pref service. + void UpdateCommandLinePrefStore(PrefStore* cmd_line_store) override; + + void AddSyncedPrefObserver(const std::string& name, + SyncedPrefObserver* observer); + void RemoveSyncedPrefObserver(const std::string& name, + SyncedPrefObserver* observer); + + protected: + // Set the PrefModelAssociatorClient to use for that object during tests. + void SetPrefModelAssociatorClientForTesting( + const PrefModelAssociatorClient* pref_model_associator_client); + + private: + friend class PrefModelAssociator; + + void AddRegisteredSyncablePreference(const std::string& path, uint32 flags); + + // Invoked internally when the IsSyncing() state changes. + void OnIsSyncingChanged(); + + // Process a local preference change. This can trigger new SyncChanges being + // sent to the syncer. + void ProcessPrefChange(const std::string& name); + + // Whether CreateIncognitoPrefService() has been called to create a + // "forked" PrefService. + bool pref_service_forked_; + + PrefModelAssociator pref_sync_associator_; + PrefModelAssociator priority_pref_sync_associator_; + + base::ObserverList<PrefServiceSyncableObserver> observer_list_; + + DISALLOW_COPY_AND_ASSIGN(PrefServiceSyncable); +}; + +#endif // COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_SYNCABLE_H_ diff --git a/components/syncable_prefs/pref_service_syncable_factory.cc b/components/syncable_prefs/pref_service_syncable_factory.cc new file mode 100644 index 0000000..4e3ae2d --- /dev/null +++ b/components/syncable_prefs/pref_service_syncable_factory.cc @@ -0,0 +1,69 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/syncable_prefs/pref_service_syncable_factory.h" + +#include "base/prefs/default_pref_store.h" +#include "base/prefs/pref_notifier_impl.h" +#include "base/prefs/pref_value_store.h" +#include "base/trace_event/trace_event.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/syncable_prefs/pref_service_syncable.h" + +#if defined(ENABLE_CONFIGURATION_POLICY) +#include "components/policy/core/browser/browser_policy_connector.h" +#include "components/policy/core/browser/configuration_policy_pref_store.h" +#include "components/policy/core/common/policy_service.h" +#include "components/policy/core/common/policy_types.h" +#endif + +PrefServiceSyncableFactory::PrefServiceSyncableFactory() { +} + +PrefServiceSyncableFactory::~PrefServiceSyncableFactory() { +} + +#if defined(ENABLE_CONFIGURATION_POLICY) +void PrefServiceSyncableFactory::SetManagedPolicies( + policy::PolicyService* service, + policy::BrowserPolicyConnector* connector) { + set_managed_prefs(new policy::ConfigurationPolicyPrefStore( + service, connector->GetHandlerList(), policy::POLICY_LEVEL_MANDATORY)); +} + +void PrefServiceSyncableFactory::SetRecommendedPolicies( + policy::PolicyService* service, + policy::BrowserPolicyConnector* connector) { + set_recommended_prefs(new policy::ConfigurationPolicyPrefStore( + service, connector->GetHandlerList(), policy::POLICY_LEVEL_RECOMMENDED)); +} +#endif + +void PrefServiceSyncableFactory::SetPrefModelAssociatorClient( + PrefModelAssociatorClient* pref_model_associator_client) { + pref_model_associator_client_ = pref_model_associator_client; +} + +scoped_ptr<PrefServiceSyncable> PrefServiceSyncableFactory::CreateSyncable( + user_prefs::PrefRegistrySyncable* pref_registry) { + TRACE_EVENT0("browser", "PrefServiceSyncableFactory::CreateSyncable"); + PrefNotifierImpl* pref_notifier = new PrefNotifierImpl(); + scoped_ptr<PrefServiceSyncable> pref_service( + new PrefServiceSyncable( + pref_notifier, + new PrefValueStore(managed_prefs_.get(), + supervised_user_prefs_.get(), + extension_prefs_.get(), + command_line_prefs_.get(), + user_prefs_.get(), + recommended_prefs_.get(), + pref_registry->defaults().get(), + pref_notifier), + user_prefs_.get(), + pref_registry, + pref_model_associator_client_, + read_error_callback_, + async_)); + return pref_service.Pass(); +} diff --git a/components/syncable_prefs/pref_service_syncable_factory.h b/components/syncable_prefs/pref_service_syncable_factory.h new file mode 100644 index 0000000..9896831 --- /dev/null +++ b/components/syncable_prefs/pref_service_syncable_factory.h @@ -0,0 +1,54 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_SYNCABLE_FACTORY_H_ +#define COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_SYNCABLE_FACTORY_H_ + +#include "base/prefs/pref_service_factory.h" + +class PrefModelAssociatorClient; +class PrefServiceSyncable; + +namespace base { +class CommandLine; +} + +namespace policy { +class BrowserPolicyConnector; +class PolicyService; +} + +namespace user_prefs { +class PrefRegistrySyncable; +} + +// A PrefServiceFactory that also knows how to build a +// PrefServiceSyncable, and may know about Chrome concepts such as +// PolicyService. +class PrefServiceSyncableFactory : public base::PrefServiceFactory { + public: + PrefServiceSyncableFactory(); + ~PrefServiceSyncableFactory() override; + +#if defined(ENABLE_CONFIGURATION_POLICY) + // Set up policy pref stores using the given policy service and connector. + void SetManagedPolicies(policy::PolicyService* service, + policy::BrowserPolicyConnector* connector); + void SetRecommendedPolicies(policy::PolicyService* service, + policy::BrowserPolicyConnector* connector); +#endif + + void SetPrefModelAssociatorClient( + PrefModelAssociatorClient* pref_model_associator_client); + + scoped_ptr<PrefServiceSyncable> CreateSyncable( + user_prefs::PrefRegistrySyncable* registry); + + private: + PrefModelAssociatorClient* pref_model_associator_client_; + + DISALLOW_COPY_AND_ASSIGN(PrefServiceSyncableFactory); +}; + +#endif // COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_SYNCABLE_FACTORY_H_ diff --git a/components/syncable_prefs/pref_service_syncable_observer.h b/components/syncable_prefs/pref_service_syncable_observer.h new file mode 100644 index 0000000..ba32463 --- /dev/null +++ b/components/syncable_prefs/pref_service_syncable_observer.h @@ -0,0 +1,17 @@ +// Copyright 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_SYNCABLE_OBSERVER_H_ +#define COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_SYNCABLE_OBSERVER_H_ + +class PrefServiceSyncableObserver { + public: + // Invoked when PrefService::IsSyncing() changes. + virtual void OnIsSyncingChanged() = 0; + + protected: + virtual ~PrefServiceSyncableObserver() {} +}; + +#endif // COMPONENTS_SYNCABLE_PREFS_PREF_SERVICE_SYNCABLE_OBSERVER_H_ diff --git a/components/syncable_prefs/pref_service_syncable_unittest.cc b/components/syncable_prefs/pref_service_syncable_unittest.cc new file mode 100644 index 0000000..013e611 --- /dev/null +++ b/components/syncable_prefs/pref_service_syncable_unittest.cc @@ -0,0 +1,741 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/syncable_prefs/pref_service_syncable.h" + +#include "base/json/json_reader.h" +#include "base/json/json_string_value_serializer.h" +#include "base/json/json_writer.h" +#include "base/message_loop/message_loop.h" +#include "base/prefs/scoped_user_pref_update.h" +#include "base/strings/utf_string_conversions.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "components/syncable_prefs/pref_model_associator.h" +#include "components/syncable_prefs/pref_model_associator_client.h" +#include "components/syncable_prefs/testing_pref_service_syncable.h" +#include "sync/api/attachments/attachment_id.h" +#include "sync/api/sync_change.h" +#include "sync/api/sync_data.h" +#include "sync/api/sync_error_factory_mock.h" +#include "sync/api/syncable_service.h" +#include "sync/internal_api/public/attachments/attachment_service_proxy_for_test.h" +#include "sync/protocol/preference_specifics.pb.h" +#include "sync/protocol/sync.pb.h" +#include "testing/gtest/include/gtest/gtest.h" + +using syncer::SyncChange; +using syncer::SyncData; + +namespace { + +const char kExampleUrl0[] = "http://example.com/0"; +const char kExampleUrl1[] = "http://example.com/1"; +const char kExampleUrl2[] = "http://example.com/2"; +const char kStringPrefName[] = "string_pref_name"; +const char kListPrefName[] = "new_list_pref_name"; +const char kListOldPrefName[] = "list_pref_name"; +const char kUnsyncedPreferenceName[] = "nonsense_pref_name"; +const char kUnsyncedPreferenceDefaultValue[] = "default"; +const char kDefaultCharsetPrefName[] = "default_charset"; +const char kNonDefaultCharsetValue[] = "foo"; +const char kDefaultCharsetValue[] = "utf-8"; + +class TestPrefModelAssociatorClient : public PrefModelAssociatorClient { + public: + TestPrefModelAssociatorClient() {} + ~TestPrefModelAssociatorClient() override {} + + // PrefModelAssociatorClient implementation. + bool IsMergeableListPreference(const std::string& pref_name) const override { + return pref_name == kListPrefName; + } + + bool IsMergeableDictionaryPreference( + const std::string& pref_name) const override { + return false; + } + + bool IsMigratedPreference(const std::string& new_pref_name, + std::string* old_pref_name) const override { + if (new_pref_name != kListPrefName) + return false; + old_pref_name->assign(kListOldPrefName); + return true; + } + + bool IsOldMigratedPreference(const std::string& old_pref_name, + std::string* new_pref_name) const override { + if (old_pref_name != kListOldPrefName) + return false; + new_pref_name->assign(kListPrefName); + return true; + } + + private: + DISALLOW_COPY_AND_ASSIGN(TestPrefModelAssociatorClient); +}; + +class TestSyncProcessorStub : public syncer::SyncChangeProcessor { + public: + explicit TestSyncProcessorStub(syncer::SyncChangeList* output) + : output_(output), fail_next_(false) {} + syncer::SyncError ProcessSyncChanges( + const tracked_objects::Location& from_here, + const syncer::SyncChangeList& change_list) override { + if (output_) + output_->insert(output_->end(), change_list.begin(), change_list.end()); + if (fail_next_) { + fail_next_ = false; + return syncer::SyncError( + FROM_HERE, syncer::SyncError::DATATYPE_ERROR, "Error", + syncer::PREFERENCES); + } + return syncer::SyncError(); + } + + void FailNextProcessSyncChanges() { + fail_next_ = true; + } + + syncer::SyncDataList GetAllSyncData(syncer::ModelType type) const override { + return syncer::SyncDataList(); + } + private: + syncer::SyncChangeList* output_; + bool fail_next_; +}; + +class PrefServiceSyncableTest : public testing::Test { + public: + PrefServiceSyncableTest() + : pref_sync_service_(NULL), + test_processor_(NULL), + next_pref_remote_sync_node_id_(0) {} + + void SetUp() override { + prefs_.SetPrefModelAssociatorClientForTesting(&client_); + prefs_.registry()->RegisterStringPref(kUnsyncedPreferenceName, + kUnsyncedPreferenceDefaultValue); + prefs_.registry()->RegisterStringPref( + kStringPrefName, + std::string(), + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); + prefs_.registry()->RegisterListPref( + kListPrefName, + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); + prefs_.registry()->RegisterListPref(kListOldPrefName); + prefs_.registry()->RegisterStringPref( + kDefaultCharsetPrefName, + kDefaultCharsetValue, + user_prefs::PrefRegistrySyncable::SYNCABLE_PREF); + + pref_sync_service_ = reinterpret_cast<PrefModelAssociator*>( + prefs_.GetSyncableService(syncer::PREFERENCES)); + ASSERT_TRUE(pref_sync_service_); + next_pref_remote_sync_node_id_ = 0; + } + + syncer::SyncChange MakeRemoteChange( + int64 id, + const std::string& name, + const base::Value& value, + SyncChange::SyncChangeType type) { + std::string serialized; + JSONStringValueSerializer json(&serialized); + if (!json.Serialize(value)) + return syncer::SyncChange(); + sync_pb::EntitySpecifics entity; + sync_pb::PreferenceSpecifics* pref_one = entity.mutable_preference(); + pref_one->set_name(name); + pref_one->set_value(serialized); + return syncer::SyncChange( + FROM_HERE, + type, + syncer::SyncData::CreateRemoteData( + id, + entity, + base::Time(), + syncer::AttachmentIdList(), + syncer::AttachmentServiceProxyForTest::Create())); + } + + void AddToRemoteDataList(const std::string& name, + const base::Value& value, + syncer::SyncDataList* out) { + std::string serialized; + JSONStringValueSerializer json(&serialized); + ASSERT_TRUE(json.Serialize(value)); + sync_pb::EntitySpecifics one; + sync_pb::PreferenceSpecifics* pref_one = one.mutable_preference(); + pref_one->set_name(name); + pref_one->set_value(serialized); + out->push_back(SyncData::CreateRemoteData( + ++next_pref_remote_sync_node_id_, + one, + base::Time(), + syncer::AttachmentIdList(), + syncer::AttachmentServiceProxyForTest::Create())); + } + + void InitWithSyncDataTakeOutput(const syncer::SyncDataList& initial_data, + syncer::SyncChangeList* output) { + test_processor_ = new TestSyncProcessorStub(output); + syncer::SyncMergeResult r = pref_sync_service_->MergeDataAndStartSyncing( + syncer::PREFERENCES, initial_data, + scoped_ptr<syncer::SyncChangeProcessor>(test_processor_), + scoped_ptr<syncer::SyncErrorFactory>( + new syncer::SyncErrorFactoryMock())); + EXPECT_FALSE(r.error().IsSet()); + } + + void InitWithNoSyncData() { + InitWithSyncDataTakeOutput(syncer::SyncDataList(), NULL); + } + + const base::Value& GetPreferenceValue(const std::string& name) { + const PrefService::Preference* preference = + prefs_.FindPreference(name.c_str()); + return *preference->GetValue(); + } + + scoped_ptr<base::Value> FindValue(const std::string& name, + const syncer::SyncChangeList& list) { + syncer::SyncChangeList::const_iterator it = list.begin(); + for (; it != list.end(); ++it) { + if (syncer::SyncDataLocal(it->sync_data()).GetTag() == name) { + return base::JSONReader::Read( + it->sync_data().GetSpecifics().preference().value()); + } + } + return scoped_ptr<base::Value>(); + } + + bool IsSynced(const std::string& pref_name) { + return pref_sync_service_->registered_preferences().count(pref_name) > 0; + } + + bool HasSyncData(const std::string& pref_name) { + return pref_sync_service_->IsPrefSynced(pref_name); + } + + // Returns whether a given preference name is a new name of a migrated + // preference. Exposed here for testing. + bool IsMigratedPreference(const char* preference_name) { + std::string old_pref_name; + return client_.IsMigratedPreference(preference_name, &old_pref_name); + } + + bool IsOldMigratedPreference(const char* old_preference_name) { + std::string new_pref_name; + return client_.IsOldMigratedPreference(old_preference_name, &new_pref_name); + } + + PrefService* GetPrefs() { return &prefs_; } + TestingPrefServiceSyncable* GetTestingPrefService() { return &prefs_; } + + protected: + TestPrefModelAssociatorClient client_; + TestingPrefServiceSyncable prefs_; + + PrefModelAssociator* pref_sync_service_; + TestSyncProcessorStub* test_processor_; + + // TODO(tim): Remove this by fixing AttachmentServiceProxyForTest. + base::MessageLoop loop_; + + int next_pref_remote_sync_node_id_; +}; + +TEST_F(PrefServiceSyncableTest, CreatePrefSyncData) { + prefs_.SetString(kStringPrefName, kExampleUrl0); + + const PrefService::Preference* pref = prefs_.FindPreference(kStringPrefName); + syncer::SyncData sync_data; + EXPECT_TRUE(pref_sync_service_->CreatePrefSyncData(pref->name(), + *pref->GetValue(), &sync_data)); + EXPECT_EQ(std::string(kStringPrefName), + syncer::SyncDataLocal(sync_data).GetTag()); + const sync_pb::PreferenceSpecifics& specifics(sync_data.GetSpecifics(). + preference()); + EXPECT_EQ(std::string(kStringPrefName), specifics.name()); + + scoped_ptr<base::Value> value = base::JSONReader::Read(specifics.value()); + EXPECT_TRUE(pref->GetValue()->Equals(value.get())); +} + +TEST_F(PrefServiceSyncableTest, ModelAssociationDoNotSyncDefaults) { + const PrefService::Preference* pref = prefs_.FindPreference(kStringPrefName); + EXPECT_TRUE(pref->IsDefaultValue()); + syncer::SyncChangeList out; + InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); + + EXPECT_TRUE(IsSynced(kStringPrefName)); + EXPECT_TRUE(pref->IsDefaultValue()); + EXPECT_FALSE(FindValue(kStringPrefName, out).get()); +} + +TEST_F(PrefServiceSyncableTest, ModelAssociationEmptyCloud) { + prefs_.SetString(kStringPrefName, kExampleUrl0); + { + ListPrefUpdate update(GetPrefs(), kListPrefName); + base::ListValue* url_list = update.Get(); + url_list->Append(new base::StringValue(kExampleUrl0)); + url_list->Append(new base::StringValue(kExampleUrl1)); + } + syncer::SyncChangeList out; + InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); + + scoped_ptr<base::Value> value(FindValue(kStringPrefName, out)); + ASSERT_TRUE(value.get()); + EXPECT_TRUE(GetPreferenceValue(kStringPrefName).Equals(value.get())); + value = FindValue(kListPrefName, out).Pass(); + ASSERT_TRUE(value.get()); + EXPECT_TRUE(GetPreferenceValue(kListPrefName).Equals(value.get())); +} + +TEST_F(PrefServiceSyncableTest, ModelAssociationCloudHasData) { + prefs_.SetString(kStringPrefName, kExampleUrl0); + { + ListPrefUpdate update(GetPrefs(), kListPrefName); + base::ListValue* url_list = update.Get(); + url_list->Append(new base::StringValue(kExampleUrl0)); + url_list->Append(new base::StringValue(kExampleUrl1)); + } + + syncer::SyncDataList in; + syncer::SyncChangeList out; + AddToRemoteDataList(kStringPrefName, base::StringValue(kExampleUrl1), &in); + base::ListValue urls_to_restore; + urls_to_restore.Append(new base::StringValue(kExampleUrl1)); + urls_to_restore.Append(new base::StringValue(kExampleUrl2)); + AddToRemoteDataList(kListPrefName, urls_to_restore, &in); + AddToRemoteDataList(kDefaultCharsetPrefName, + base::StringValue(kNonDefaultCharsetValue), + &in); + InitWithSyncDataTakeOutput(in, &out); + + ASSERT_FALSE(FindValue(kStringPrefName, out).get()); + ASSERT_FALSE(FindValue(kDefaultCharsetPrefName, out).get()); + + EXPECT_EQ(kExampleUrl1, prefs_.GetString(kStringPrefName)); + + scoped_ptr<base::ListValue> expected_urls(new base::ListValue); + expected_urls->Append(new base::StringValue(kExampleUrl1)); + expected_urls->Append(new base::StringValue(kExampleUrl2)); + expected_urls->Append(new base::StringValue(kExampleUrl0)); + scoped_ptr<base::Value> value(FindValue(kListPrefName, out)); + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->Equals(expected_urls.get())); + EXPECT_TRUE(GetPreferenceValue(kListPrefName).Equals(expected_urls.get())); + EXPECT_EQ(kNonDefaultCharsetValue, prefs_.GetString(kDefaultCharsetPrefName)); +} + +TEST_F(PrefServiceSyncableTest, ModelAssociationMigrateOldData) { + ASSERT_TRUE(IsMigratedPreference(kListPrefName)); + ASSERT_TRUE(IsOldMigratedPreference(kListOldPrefName)); + + syncer::SyncDataList in; + syncer::SyncChangeList out; + base::ListValue urls_to_restore; + urls_to_restore.Append(new base::StringValue(kExampleUrl1)); + urls_to_restore.Append(new base::StringValue(kExampleUrl2)); + AddToRemoteDataList(kListOldPrefName, urls_to_restore, &in); + InitWithSyncDataTakeOutput(in, &out); + + // Expect that the new preference data contains the old pref's values. + scoped_ptr<base::ListValue> expected_urls(new base::ListValue); + expected_urls->Append(new base::StringValue(kExampleUrl1)); + expected_urls->Append(new base::StringValue(kExampleUrl2)); + + ASSERT_TRUE(HasSyncData(kListPrefName)); + scoped_ptr<base::Value> value(FindValue(kListPrefName, out)); + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->Equals(expected_urls.get())); + EXPECT_TRUE(GetPreferenceValue(kListPrefName).Equals(expected_urls.get())); + + // The old preference value should be the same. + expected_urls.reset(new base::ListValue); + ASSERT_FALSE(FindValue(kListOldPrefName, out).get()); + EXPECT_TRUE(GetPreferenceValue(kListOldPrefName).Equals(expected_urls.get())); +} + +TEST_F(PrefServiceSyncableTest, ModelAssociationCloudHasOldMigratedData) { + ASSERT_TRUE(IsMigratedPreference(kListPrefName)); + ASSERT_TRUE(IsOldMigratedPreference(kListOldPrefName)); + prefs_.SetString(kStringPrefName, kExampleUrl0); + { + ListPrefUpdate update(GetPrefs(), kListPrefName); + base::ListValue* url_list = update.Get(); + url_list->Append(new base::StringValue(kExampleUrl0)); + url_list->Append(new base::StringValue(kExampleUrl1)); + } + + syncer::SyncDataList in; + syncer::SyncChangeList out; + base::ListValue urls_to_restore; + urls_to_restore.Append(new base::StringValue(kExampleUrl1)); + urls_to_restore.Append(new base::StringValue(kExampleUrl2)); + AddToRemoteDataList(kListOldPrefName, urls_to_restore, &in); + AddToRemoteDataList(kStringPrefName, base::StringValue(kExampleUrl1), &in); + InitWithSyncDataTakeOutput(in, &out); + + ASSERT_FALSE(FindValue(kStringPrefName, out).get()); + + // Expect that the new preference data contains the merged old prefs values. + scoped_ptr<base::ListValue> expected_urls(new base::ListValue); + expected_urls->Append(new base::StringValue(kExampleUrl1)); + expected_urls->Append(new base::StringValue(kExampleUrl2)); + expected_urls->Append(new base::StringValue(kExampleUrl0)); + + ASSERT_TRUE(HasSyncData(kListPrefName)); + scoped_ptr<base::Value> value(FindValue(kListPrefName, out)); + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->Equals(expected_urls.get())); + EXPECT_TRUE(GetPreferenceValue(kListPrefName).Equals(expected_urls.get())); + + expected_urls.reset(new base::ListValue); + value = FindValue(kListOldPrefName, out).Pass(); + ASSERT_TRUE(value.get()); + EXPECT_TRUE(GetPreferenceValue(kListOldPrefName).Equals(expected_urls.get())); +} + +TEST_F(PrefServiceSyncableTest, ModelAssociationCloudHasNewMigratedData) { + ASSERT_TRUE(IsMigratedPreference(kListPrefName)); + ASSERT_TRUE(IsOldMigratedPreference(kListOldPrefName)); + prefs_.SetString(kStringPrefName, kExampleUrl0); + { + ListPrefUpdate update(GetPrefs(), kListOldPrefName); + base::ListValue* url_list = update.Get(); + url_list->Append(new base::StringValue(kExampleUrl0)); + url_list->Append(new base::StringValue(kExampleUrl1)); + } + + syncer::SyncDataList in; + syncer::SyncChangeList out; + base::ListValue urls_to_restore; + urls_to_restore.Append(new base::StringValue(kExampleUrl1)); + urls_to_restore.Append(new base::StringValue(kExampleUrl2)); + AddToRemoteDataList(kListOldPrefName, urls_to_restore, &in); + AddToRemoteDataList(kStringPrefName, base::StringValue(kExampleUrl1), &in); + InitWithSyncDataTakeOutput(in, &out); + + scoped_ptr<base::Value> value(FindValue(kStringPrefName, out)); + ASSERT_FALSE(value.get()); + + // Expect that the cloud data under the new migrated preference name sticks. + scoped_ptr<base::ListValue> expected_urls(new base::ListValue); + expected_urls->Append(new base::StringValue(kExampleUrl1)); + expected_urls->Append(new base::StringValue(kExampleUrl2)); + + ASSERT_TRUE(HasSyncData(kListPrefName)); + value = FindValue(kListPrefName, out).Pass(); + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->Equals(expected_urls.get())); + EXPECT_TRUE(GetPreferenceValue(kListPrefName).Equals(expected_urls.get())); + + // The old preference data should still be here, though not synced. + expected_urls.reset(new base::ListValue); + expected_urls->Append(new base::StringValue(kExampleUrl0)); + expected_urls->Append(new base::StringValue(kExampleUrl1)); + + value = FindValue(kListOldPrefName, out).Pass(); + ASSERT_FALSE(value.get()); + EXPECT_TRUE(GetPreferenceValue(kListOldPrefName).Equals(expected_urls.get())); +} + +TEST_F(PrefServiceSyncableTest, + ModelAssociationCloudAddsOldAndNewMigratedData) { + ASSERT_TRUE(IsMigratedPreference(kListPrefName)); + ASSERT_TRUE(IsOldMigratedPreference(kListOldPrefName)); + prefs_.SetString(kStringPrefName, kExampleUrl0); + { + ListPrefUpdate update_old(GetPrefs(), kListOldPrefName); + base::ListValue* url_list_old = update_old.Get(); + url_list_old->Append(new base::StringValue(kExampleUrl0)); + url_list_old->Append(new base::StringValue(kExampleUrl1)); + ListPrefUpdate update(GetPrefs(), kListPrefName); + base::ListValue* url_list = update.Get(); + url_list->Append(new base::StringValue(kExampleUrl1)); + url_list->Append(new base::StringValue(kExampleUrl2)); + } + + syncer::SyncDataList in; + syncer::SyncChangeList out; + AddToRemoteDataList(kStringPrefName, base::StringValue(kExampleUrl1), &in); + InitWithSyncDataTakeOutput(in, &out); + + scoped_ptr<base::Value> value(FindValue(kStringPrefName, out)); + ASSERT_FALSE(value.get()); + + // Expect that the cloud data under the new migrated preference name sticks. + scoped_ptr<base::ListValue> expected_urls(new base::ListValue); + expected_urls->Append(new base::StringValue(kExampleUrl1)); + expected_urls->Append(new base::StringValue(kExampleUrl2)); + + ASSERT_TRUE(HasSyncData(kListPrefName)); + value = FindValue(kListPrefName, out).Pass(); + ASSERT_TRUE(value.get()); + EXPECT_TRUE(value->Equals(expected_urls.get())); + EXPECT_TRUE(GetPreferenceValue(kListPrefName).Equals(expected_urls.get())); + + // Should not have synced in the old startup url values. + value = FindValue(kListOldPrefName, out).Pass(); + ASSERT_FALSE(value.get()); + EXPECT_FALSE( + GetPreferenceValue(kListOldPrefName).Equals(expected_urls.get())); +} + +TEST_F(PrefServiceSyncableTest, FailModelAssociation) { + syncer::SyncChangeList output; + TestSyncProcessorStub* stub = new TestSyncProcessorStub(&output); + stub->FailNextProcessSyncChanges(); + syncer::SyncMergeResult r = pref_sync_service_->MergeDataAndStartSyncing( + syncer::PREFERENCES, syncer::SyncDataList(), + scoped_ptr<syncer::SyncChangeProcessor>(stub), + scoped_ptr<syncer::SyncErrorFactory>( + new syncer::SyncErrorFactoryMock())); + EXPECT_TRUE(r.error().IsSet()); +} + +TEST_F(PrefServiceSyncableTest, UpdatedPreferenceWithDefaultValue) { + const PrefService::Preference* pref = prefs_.FindPreference(kStringPrefName); + EXPECT_TRUE(pref->IsDefaultValue()); + + syncer::SyncChangeList out; + InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); + out.clear(); + + base::StringValue expected(kExampleUrl0); + GetPrefs()->Set(kStringPrefName, expected); + + scoped_ptr<base::Value> actual(FindValue(kStringPrefName, out)); + ASSERT_TRUE(actual.get()); + EXPECT_TRUE(expected.Equals(actual.get())); +} + +TEST_F(PrefServiceSyncableTest, UpdatedPreferenceWithValue) { + GetPrefs()->SetString(kStringPrefName, kExampleUrl0); + syncer::SyncChangeList out; + InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); + out.clear(); + + base::StringValue expected(kExampleUrl1); + GetPrefs()->Set(kStringPrefName, expected); + + scoped_ptr<base::Value> actual(FindValue(kStringPrefName, out)); + ASSERT_TRUE(actual.get()); + EXPECT_TRUE(expected.Equals(actual.get())); +} + +TEST_F(PrefServiceSyncableTest, UpdatedSyncNodeActionUpdate) { + GetPrefs()->SetString(kStringPrefName, kExampleUrl0); + InitWithNoSyncData(); + + base::StringValue expected(kExampleUrl1); + syncer::SyncChangeList list; + list.push_back(MakeRemoteChange( + 1, kStringPrefName, expected, SyncChange::ACTION_UPDATE)); + pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); + + const base::Value& actual = GetPreferenceValue(kStringPrefName); + EXPECT_TRUE(expected.Equals(&actual)); +} + +TEST_F(PrefServiceSyncableTest, UpdatedSyncNodeActionAdd) { + InitWithNoSyncData(); + + base::StringValue expected(kExampleUrl0); + syncer::SyncChangeList list; + list.push_back(MakeRemoteChange( + 1, kStringPrefName, expected, SyncChange::ACTION_ADD)); + pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); + + const base::Value& actual = GetPreferenceValue(kStringPrefName); + EXPECT_TRUE(expected.Equals(&actual)); + EXPECT_EQ(1U, + pref_sync_service_->registered_preferences().count(kStringPrefName)); +} + +TEST_F(PrefServiceSyncableTest, UpdatedSyncNodeUnknownPreference) { + InitWithNoSyncData(); + syncer::SyncChangeList list; + base::StringValue expected(kExampleUrl0); + list.push_back(MakeRemoteChange( + 1, "unknown preference", expected, SyncChange::ACTION_UPDATE)); + pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); + // Nothing interesting happens on the client when it gets an update + // of an unknown preference. We just should not crash. +} + +TEST_F(PrefServiceSyncableTest, ManagedPreferences) { + // Make the homepage preference managed. + base::StringValue managed_value("http://example.com"); + prefs_.SetManagedPref(kStringPrefName, managed_value.DeepCopy()); + + syncer::SyncChangeList out; + InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); + out.clear(); + + // Changing the homepage preference should not sync anything. + base::StringValue user_value("http://chromium..com"); + prefs_.SetUserPref(kStringPrefName, user_value.DeepCopy()); + EXPECT_TRUE(out.empty()); + + // An incoming sync transaction should change the user value, not the managed + // value. + base::StringValue sync_value("http://crbug.com"); + syncer::SyncChangeList list; + list.push_back(MakeRemoteChange( + 1, kStringPrefName, sync_value, SyncChange::ACTION_UPDATE)); + pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); + + EXPECT_TRUE(managed_value.Equals(prefs_.GetManagedPref(kStringPrefName))); + EXPECT_TRUE(sync_value.Equals(prefs_.GetUserPref(kStringPrefName))); +} + +// List preferences have special handling at association time due to our ability +// to merge the local and sync value. Make sure the merge logic doesn't merge +// managed preferences. +TEST_F(PrefServiceSyncableTest, ManagedListPreferences) { + // Make the list of urls to restore on startup managed. + base::ListValue managed_value; + managed_value.Append(new base::StringValue(kExampleUrl0)); + managed_value.Append(new base::StringValue(kExampleUrl1)); + prefs_.SetManagedPref(kListPrefName, managed_value.DeepCopy()); + + // Set a cloud version. + syncer::SyncDataList in; + syncer::SyncChangeList out; + base::ListValue urls_to_restore; + urls_to_restore.Append(new base::StringValue(kExampleUrl1)); + urls_to_restore.Append(new base::StringValue(kExampleUrl2)); + AddToRemoteDataList(kListPrefName, urls_to_restore, &in); + + // Start sync and verify the synced value didn't get merged. + InitWithSyncDataTakeOutput(in, &out); + EXPECT_FALSE(FindValue(kListPrefName, out).get()); + out.clear(); + + // Changing the user's urls to restore on startup pref should not sync + // anything. + base::ListValue user_value; + user_value.Append(new base::StringValue("http://chromium.org")); + prefs_.SetUserPref(kListPrefName, user_value.DeepCopy()); + EXPECT_FALSE(FindValue(kListPrefName, out).get()); + + // An incoming sync transaction should change the user value, not the managed + // value. + base::ListValue sync_value; + sync_value.Append(new base::StringValue("http://crbug.com")); + syncer::SyncChangeList list; + list.push_back(MakeRemoteChange( + 1, kListPrefName, sync_value, + SyncChange::ACTION_UPDATE)); + pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); + + EXPECT_TRUE(managed_value.Equals(prefs_.GetManagedPref(kListPrefName))); + EXPECT_TRUE(sync_value.Equals(prefs_.GetUserPref(kListPrefName))); +} + +TEST_F(PrefServiceSyncableTest, DynamicManagedPreferences) { + syncer::SyncChangeList out; + InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); + out.clear(); + base::StringValue initial_value("http://example.com/initial"); + GetPrefs()->Set(kStringPrefName, initial_value); + scoped_ptr<base::Value> actual(FindValue(kStringPrefName, out)); + ASSERT_TRUE(actual.get()); + EXPECT_TRUE(initial_value.Equals(actual.get())); + + // Switch kHomePage to managed and set a different value. + base::StringValue managed_value("http://example.com/managed"); + GetTestingPrefService()->SetManagedPref(kStringPrefName, + managed_value.DeepCopy()); + + // The pref value should be the one dictated by policy. + EXPECT_TRUE(managed_value.Equals(&GetPreferenceValue(kStringPrefName))); + + // Switch kHomePage back to unmanaged. + GetTestingPrefService()->RemoveManagedPref(kStringPrefName); + + // The original value should be picked up. + EXPECT_TRUE(initial_value.Equals(&GetPreferenceValue(kStringPrefName))); +} + +TEST_F(PrefServiceSyncableTest, DynamicManagedPreferencesWithSyncChange) { + syncer::SyncChangeList out; + InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); + out.clear(); + + base::StringValue initial_value("http://example.com/initial"); + GetPrefs()->Set(kStringPrefName, initial_value); + scoped_ptr<base::Value> actual(FindValue(kStringPrefName, out)); + EXPECT_TRUE(initial_value.Equals(actual.get())); + + // Switch kHomePage to managed and set a different value. + base::StringValue managed_value("http://example.com/managed"); + GetTestingPrefService()->SetManagedPref(kStringPrefName, + managed_value.DeepCopy()); + + // Change the sync value. + base::StringValue sync_value("http://example.com/sync"); + syncer::SyncChangeList list; + list.push_back(MakeRemoteChange( + 1, kStringPrefName, sync_value, SyncChange::ACTION_UPDATE)); + pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); + + // The pref value should still be the one dictated by policy. + EXPECT_TRUE(managed_value.Equals(&GetPreferenceValue(kStringPrefName))); + + // Switch kHomePage back to unmanaged. + GetTestingPrefService()->RemoveManagedPref(kStringPrefName); + + // Sync value should be picked up. + EXPECT_TRUE(sync_value.Equals(&GetPreferenceValue(kStringPrefName))); +} + +TEST_F(PrefServiceSyncableTest, DynamicManagedDefaultPreferences) { + const PrefService::Preference* pref = prefs_.FindPreference(kStringPrefName); + EXPECT_TRUE(pref->IsDefaultValue()); + syncer::SyncChangeList out; + InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out); + + EXPECT_TRUE(IsSynced(kStringPrefName)); + EXPECT_TRUE(pref->IsDefaultValue()); + EXPECT_FALSE(FindValue(kStringPrefName, out).get()); + out.clear(); + + // Switch kHomePage to managed and set a different value. + base::StringValue managed_value("http://example.com/managed"); + GetTestingPrefService()->SetManagedPref(kStringPrefName, + managed_value.DeepCopy()); + // The pref value should be the one dictated by policy. + EXPECT_TRUE(managed_value.Equals(&GetPreferenceValue(kStringPrefName))); + EXPECT_FALSE(pref->IsDefaultValue()); + // There should be no synced value. + EXPECT_FALSE(FindValue(kStringPrefName, out).get()); + // Switch kHomePage back to unmanaged. + GetTestingPrefService()->RemoveManagedPref(kStringPrefName); + // The original value should be picked up. + EXPECT_TRUE(pref->IsDefaultValue()); + // There should still be no synced value. + EXPECT_FALSE(FindValue(kStringPrefName, out).get()); +} + +TEST_F(PrefServiceSyncableTest, DeletePreference) { + prefs_.SetString(kStringPrefName, kExampleUrl0); + const PrefService::Preference* pref = prefs_.FindPreference(kStringPrefName); + EXPECT_FALSE(pref->IsDefaultValue()); + + InitWithNoSyncData(); + + scoped_ptr<base::Value> null_value = base::Value::CreateNullValue(); + syncer::SyncChangeList list; + list.push_back(MakeRemoteChange( + 1, kStringPrefName, *null_value, SyncChange::ACTION_DELETE)); + pref_sync_service_->ProcessSyncChanges(FROM_HERE, list); + EXPECT_TRUE(pref->IsDefaultValue()); +} + +} // namespace diff --git a/components/syncable_prefs/synced_pref_change_registrar.cc b/components/syncable_prefs/synced_pref_change_registrar.cc new file mode 100644 index 0000000..96602ba --- /dev/null +++ b/components/syncable_prefs/synced_pref_change_registrar.cc @@ -0,0 +1,66 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/syncable_prefs/synced_pref_change_registrar.h" + +#include "base/bind.h" + +namespace { + +void InvokeUnnamedCallback( + const SyncedPrefChangeRegistrar::ChangeCallback& callback, + const std::string& path, + bool from_sync) { + callback.Run(from_sync); +} + +} // namespace + +SyncedPrefChangeRegistrar::SyncedPrefChangeRegistrar( + PrefServiceSyncable* pref_service) { + pref_service_ = pref_service; +} + +SyncedPrefChangeRegistrar::~SyncedPrefChangeRegistrar() { + RemoveAll(); +} + +void SyncedPrefChangeRegistrar::Add(const char *path, + const ChangeCallback& callback) { + Add(path, base::Bind(InvokeUnnamedCallback, callback)); +} + +void SyncedPrefChangeRegistrar::Add(const char *path, + const NamedChangeCallback& callback) { + DCHECK(!IsObserved(path)); + observers_[path] = callback; + pref_service_->AddSyncedPrefObserver(path, this); +} + +void SyncedPrefChangeRegistrar::Remove(const char *path) { + observers_.erase(path); + pref_service_->RemoveSyncedPrefObserver(path, this); +} + +void SyncedPrefChangeRegistrar::RemoveAll() { + for (ObserverMap::iterator iter = observers_.begin(); + iter != observers_.end(); ++iter) { + pref_service_->RemoveSyncedPrefObserver(iter->first, this); + } + observers_.clear(); +} + +bool SyncedPrefChangeRegistrar::IsObserved(const char* path) const { + return observers_.find(path) != observers_.end(); +} + +void SyncedPrefChangeRegistrar::OnSyncedPrefChanged(const std::string& path, + bool from_sync) { + if (pref_service_->IsManagedPreference(path.c_str())) + return; + ObserverMap::const_iterator iter = observers_.find(path); + if (iter == observers_.end()) + return; + iter->second.Run(path, from_sync); +} diff --git a/components/syncable_prefs/synced_pref_change_registrar.h b/components/syncable_prefs/synced_pref_change_registrar.h new file mode 100644 index 0000000..8e06cd1 --- /dev/null +++ b/components/syncable_prefs/synced_pref_change_registrar.h @@ -0,0 +1,56 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SYNCABLE_PREFS_SYNCED_PREF_CHANGE_REGISTRAR_H_ +#define COMPONENTS_SYNCABLE_PREFS_SYNCED_PREF_CHANGE_REGISTRAR_H_ + +#include <map> +#include <string> + +#include "base/callback.h" +#include "components/syncable_prefs/pref_service_syncable.h" +#include "components/syncable_prefs/synced_pref_observer.h" + +// Manages the registration of one or more SyncedPrefObservers on a +// PrefServiceSyncable. This is modeled after base::PrefChangeRegistrar, and +// it should be used whenever it's necessary to determine whether a pref change +// has come from sync or from some other mechanism (managed, UI, external, etc.) +class SyncedPrefChangeRegistrar : public SyncedPrefObserver { + public: + // Registered callbacks may optionally take a path argument. + // The boolean argument indicates whether (true) or not (false) + // the change was a result of syncing. + typedef base::Callback<void(bool)> ChangeCallback; + typedef base::Callback<void(const std::string&, bool)> NamedChangeCallback; + + explicit SyncedPrefChangeRegistrar(PrefServiceSyncable* pref_service); + virtual ~SyncedPrefChangeRegistrar(); + + // Register an observer callback for sync change events on the pref at + // |path|. Only one callback may be registered per pref. + void Add(const char* path, const ChangeCallback& callback); + void Add(const char* path, const NamedChangeCallback& callback); + + // Remove the registered observer for |path|. + void Remove(const char* path); + + // Remove all registered observers. + void RemoveAll(); + + // Indicates whether or not an observer is already registered for |path|. + bool IsObserved(const char* path) const; + + private: + // SyncedPrefObserver implementation + void OnSyncedPrefChanged(const std::string& path, bool from_sync) override; + + typedef std::map<std::string, NamedChangeCallback> ObserverMap; + + PrefServiceSyncable* pref_service_; + ObserverMap observers_; + + DISALLOW_COPY_AND_ASSIGN(SyncedPrefChangeRegistrar); +}; + +#endif // COMPONENTS_SYNCABLE_PREFS_SYNCED_PREF_CHANGE_REGISTRAR_H_ diff --git a/components/syncable_prefs/synced_pref_observer.h b/components/syncable_prefs/synced_pref_observer.h new file mode 100644 index 0000000..4377eb4 --- /dev/null +++ b/components/syncable_prefs/synced_pref_observer.h @@ -0,0 +1,15 @@ +// Copyright 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SYNCABLE_PREFS_SYNCED_PREF_OBSERVER_H_ +#define COMPONENTS_SYNCABLE_PREFS_SYNCED_PREF_OBSERVER_H_ + +#include <string> + +class SyncedPrefObserver { + public: + virtual void OnSyncedPrefChanged(const std::string& path, bool from_sync) = 0; +}; + +#endif // COMPONENTS_SYNCABLE_PREFS_SYNCED_PREF_OBSERVER_H_ diff --git a/components/syncable_prefs/testing_pref_service_syncable.cc b/components/syncable_prefs/testing_pref_service_syncable.cc new file mode 100644 index 0000000..81e71fb --- /dev/null +++ b/components/syncable_prefs/testing_pref_service_syncable.cc @@ -0,0 +1,72 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/syncable_prefs/testing_pref_service_syncable.h" + +#include "base/bind.h" +#include "base/prefs/pref_notifier_impl.h" +#include "base/prefs/pref_value_store.h" +#include "components/pref_registry/pref_registry_syncable.h" +#include "testing/gtest/include/gtest/gtest.h" + +template <> +TestingPrefServiceBase<PrefServiceSyncable, user_prefs::PrefRegistrySyncable>:: + TestingPrefServiceBase(TestingPrefStore* managed_prefs, + TestingPrefStore* user_prefs, + TestingPrefStore* recommended_prefs, + user_prefs::PrefRegistrySyncable* pref_registry, + PrefNotifierImpl* pref_notifier) + : PrefServiceSyncable( + pref_notifier, + new PrefValueStore(managed_prefs, + nullptr, // supervised_user_prefs + nullptr, // extension_prefs + nullptr, // command_line_prefs + user_prefs, + recommended_prefs, + pref_registry->defaults().get(), + pref_notifier), + user_prefs, + pref_registry, + nullptr, // pref_model_associator_client + base::Bind(&TestingPrefServiceBase< + PrefServiceSyncable, + user_prefs::PrefRegistrySyncable>::HandleReadError), + false), + managed_prefs_(managed_prefs), + user_prefs_(user_prefs), + recommended_prefs_(recommended_prefs) {} + +TestingPrefServiceSyncable::TestingPrefServiceSyncable() + : TestingPrefServiceBase<PrefServiceSyncable, + user_prefs::PrefRegistrySyncable>( + new TestingPrefStore(), + new TestingPrefStore(), + new TestingPrefStore(), + new user_prefs::PrefRegistrySyncable(), + new PrefNotifierImpl()) { +} + +TestingPrefServiceSyncable::TestingPrefServiceSyncable( + TestingPrefStore* managed_prefs, + TestingPrefStore* user_prefs, + TestingPrefStore* recommended_prefs, + user_prefs::PrefRegistrySyncable* pref_registry, + PrefNotifierImpl* pref_notifier) + : TestingPrefServiceBase<PrefServiceSyncable, + user_prefs::PrefRegistrySyncable>( + managed_prefs, + user_prefs, + recommended_prefs, + pref_registry, + pref_notifier) { +} + +TestingPrefServiceSyncable::~TestingPrefServiceSyncable() { +} + +user_prefs::PrefRegistrySyncable* TestingPrefServiceSyncable::registry() { + return static_cast<user_prefs::PrefRegistrySyncable*>( + DeprecatedGetPrefRegistry()); +} diff --git a/components/syncable_prefs/testing_pref_service_syncable.h b/components/syncable_prefs/testing_pref_service_syncable.h new file mode 100644 index 0000000..440b417 --- /dev/null +++ b/components/syncable_prefs/testing_pref_service_syncable.h @@ -0,0 +1,51 @@ +// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_SYNCABLE_PREFS_TESTING_PREF_SERVICE_SYNCABLE_H_ +#define COMPONENTS_SYNCABLE_PREFS_TESTING_PREF_SERVICE_SYNCABLE_H_ + +#include "base/basictypes.h" +#include "base/prefs/testing_pref_service.h" +#include "components/syncable_prefs/pref_service_syncable.h" + +class PrefModelAssociatorClient; + +namespace user_prefs { +class PrefRegistrySyncable; +} + +// Test version of PrefServiceSyncable. +class TestingPrefServiceSyncable + : public TestingPrefServiceBase<PrefServiceSyncable, + user_prefs::PrefRegistrySyncable> { + public: + TestingPrefServiceSyncable(); + TestingPrefServiceSyncable(TestingPrefStore* managed_prefs, + TestingPrefStore* user_prefs, + TestingPrefStore* recommended_prefs, + user_prefs::PrefRegistrySyncable* pref_registry, + PrefNotifierImpl* pref_notifier); + ~TestingPrefServiceSyncable() override; + + // This is provided as a convenience; on a production PrefService + // you would do all registrations before constructing it, passing it + // a PrefRegistry via its constructor (or via e.g. PrefServiceFactory). + user_prefs::PrefRegistrySyncable* registry(); + + using PrefServiceSyncable::SetPrefModelAssociatorClientForTesting; + + private: + DISALLOW_COPY_AND_ASSIGN(TestingPrefServiceSyncable); +}; + +template <> +TestingPrefServiceBase<PrefServiceSyncable, user_prefs::PrefRegistrySyncable>:: + TestingPrefServiceBase(TestingPrefStore* managed_prefs, + TestingPrefStore* user_prefs, + TestingPrefStore* recommended_prefs, + user_prefs::PrefRegistrySyncable* pref_registry, + PrefNotifierImpl* pref_notifier); + +#endif // COMPONENTS_SYNCABLE_PREFS_TESTING_PREF_SERVICE_SYNCABLE_H_ + |