summaryrefslogtreecommitdiffstats
path: root/components/syncable_prefs
diff options
context:
space:
mode:
authorsdefresne <sdefresne@chromium.org>2015-09-16 05:01:28 -0700
committerCommit bot <commit-bot@chromium.org>2015-09-16 12:01:59 +0000
commit875d078307b9c4da92f36b9d4d0d4de4a7d8c9be (patch)
tree514baf91f065ebf2df675326c93d23caf9f2fe86 /components/syncable_prefs
parent40b5b417c5ebcfd5e21132e361bf78658c0ef617 (diff)
downloadchromium_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')
-rw-r--r--components/syncable_prefs/BUILD.gn65
-rw-r--r--components/syncable_prefs/DEPS11
-rw-r--r--components/syncable_prefs/OWNERS4
-rw-r--r--components/syncable_prefs/pref_model_associator.cc624
-rw-r--r--components/syncable_prefs/pref_model_associator.h200
-rw-r--r--components/syncable_prefs/pref_model_associator_client.h45
-rw-r--r--components/syncable_prefs/pref_model_associator_unittest.cc472
-rw-r--r--components/syncable_prefs/pref_service_mock_factory.cc13
-rw-r--r--components/syncable_prefs/pref_service_mock_factory.h20
-rw-r--r--components/syncable_prefs/pref_service_syncable.cc182
-rw-r--r--components/syncable_prefs/pref_service_syncable.h112
-rw-r--r--components/syncable_prefs/pref_service_syncable_factory.cc69
-rw-r--r--components/syncable_prefs/pref_service_syncable_factory.h54
-rw-r--r--components/syncable_prefs/pref_service_syncable_observer.h17
-rw-r--r--components/syncable_prefs/pref_service_syncable_unittest.cc741
-rw-r--r--components/syncable_prefs/synced_pref_change_registrar.cc66
-rw-r--r--components/syncable_prefs/synced_pref_change_registrar.h56
-rw-r--r--components/syncable_prefs/synced_pref_observer.h15
-rw-r--r--components/syncable_prefs/testing_pref_service_syncable.cc72
-rw-r--r--components/syncable_prefs/testing_pref_service_syncable.h51
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_
+