Reland "Support late registration of syncable preferences."

Sync integration tests where flaky on some platforms. Fixed
those integration tests by enabling self-notifications and
only modifying one client (and not the verifier as well).
Inspected try-bot runs from two executions verifying no flakes.

Original CL description:
Support late registration of syncable preferences.

Today, there's already a race when registering a preference
through the mojo preference service: the asynchronous
registration call might be slower than starting sync. As
more services are being implemented, those scenarios are
getting more likely -- especially as services could be
started at arbitrary times.

Before this path, the synced preferences implementation did
not support this scenario. This patch changes the synced
preference implementation to receive and persist remote
data for preferences before they get registered. Currently,
this only applies for a whitelisted set of preferences.
More details about the approach can be found in the design
doc linked from the crbug/840332.

Bug: 840332

Change-Id: I5fd2883d403b961a6332d7ee7bcdbad72f5cab8f
Reviewed-on: https://chromium-review.googlesource.com/1074688
Reviewed-by: Ilya Sherman <[email protected]>
Reviewed-by: Dominic Battré <[email protected]>
Reviewed-by: Marc Treib <[email protected]>
Commit-Queue: Tim Schumann <[email protected]>
Cr-Commit-Position: refs/heads/master@{#562689}
diff --git a/components/pref_registry/pref_registry_syncable.cc b/components/pref_registry/pref_registry_syncable.cc
index f00f0c6..f458cf7f 100644
--- a/components/pref_registry/pref_registry_syncable.cc
+++ b/components/pref_registry/pref_registry_syncable.cc
@@ -48,4 +48,14 @@
   return registry;
 }
 
+void PrefRegistrySyncable::WhitelistLateRegistrationPrefForSync(
+    const std::string& pref_name) {
+  sync_unknown_prefs_whitelist_.insert(pref_name);
+}
+
+bool PrefRegistrySyncable::IsWhitelistedLateRegistrationPref(
+    const std::string& path) const {
+  return sync_unknown_prefs_whitelist_.count(path) != 0;
+}
+
 }  // namespace user_prefs
diff --git a/components/pref_registry/pref_registry_syncable.h b/components/pref_registry/pref_registry_syncable.h
index 7eef955..d8e7c457 100644
--- a/components/pref_registry/pref_registry_syncable.h
+++ b/components/pref_registry/pref_registry_syncable.h
@@ -7,6 +7,7 @@
 
 #include <stdint.h>
 
+#include <set>
 #include <string>
 
 #include "base/callback.h"
@@ -73,6 +74,16 @@
   // store.
   scoped_refptr<PrefRegistrySyncable> ForkForIncognito();
 
+  // Adds a the preference with name |pref_name| to the whitelist of prefs which
+  // will be synced even before they got registered. Note that it's still
+  // illegal to read or write a whitelisted preference via the PrefService
+  // before its registration.
+  void WhitelistLateRegistrationPrefForSync(const std::string& pref_name);
+
+  // Checks weather the preference with name |path| is on the whitelist of
+  // sync-supported prefs before registration.
+  bool IsWhitelistedLateRegistrationPref(const std::string& path) const;
+
  private:
   ~PrefRegistrySyncable() override;
 
@@ -82,6 +93,7 @@
                         uint32_t flags) override;
 
   SyncableRegistrationCallback callback_;
+  std::set<std::string> sync_unknown_prefs_whitelist_;
 
   DISALLOW_COPY_AND_ASSIGN(PrefRegistrySyncable);
 };
diff --git a/components/prefs/pref_service.cc b/components/prefs/pref_service.cc
index 7254d0b..f9d2e67 100644
--- a/components/prefs/pref_service.cc
+++ b/components/prefs/pref_service.cc
@@ -68,9 +68,9 @@
     bool async)
     : pref_notifier_(std::move(pref_notifier)),
       pref_value_store_(std::move(pref_value_store)),
-      pref_registry_(std::move(pref_registry)),
       user_pref_store_(std::move(user_prefs)),
-      read_error_callback_(std::move(read_error_callback)) {
+      read_error_callback_(std::move(read_error_callback)),
+      pref_registry_(std::move(pref_registry)) {
   pref_notifier_->SetPrefService(this);
 
   DCHECK(pref_registry_);
diff --git a/components/prefs/pref_service.h b/components/prefs/pref_service.h
index 3952552..1da3854 100644
--- a/components/prefs/pref_service.h
+++ b/components/prefs/pref_service.h
@@ -354,8 +354,6 @@
   // this PrefService. Subclasses may access it for unit testing.
   const std::unique_ptr<PrefValueStore> pref_value_store_;
 
-  const scoped_refptr<PrefRegistry> pref_registry_;
-
   // Pref Stores and profile that we passed to the PrefValueStore.
   const scoped_refptr<PersistentPrefStore> user_pref_store_;
 
@@ -431,6 +429,8 @@
   // actually get the value.).
   const base::Value* GetPreferenceValue(const std::string& path) const;
 
+  const scoped_refptr<PrefRegistry> pref_registry_;
+
   // Local cache of registered Preference objects. The pref_registry_
   // is authoritative with respect to what the types and default values
   // of registered preferences are.
diff --git a/components/sync_preferences/BUILD.gn b/components/sync_preferences/BUILD.gn
index 49ca951..e55d6b6 100644
--- a/components/sync_preferences/BUILD.gn
+++ b/components/sync_preferences/BUILD.gn
@@ -17,6 +17,8 @@
     "synced_pref_change_registrar.cc",
     "synced_pref_change_registrar.h",
     "synced_pref_observer.h",
+    "unknown_user_pref_accessor.cc",
+    "unknown_user_pref_accessor.h",
   ]
 
   deps = [
@@ -61,6 +63,7 @@
   deps = [
     ":sync_preferences",
     ":test_support",
+    "//base/test:test_support",
     "//components/pref_registry",
     "//components/prefs",
     "//components/prefs:test_support",
diff --git a/components/sync_preferences/pref_model_associator.cc b/components/sync_preferences/pref_model_associator.cc
index 2aeff8d..c08ac5b5 100644
--- a/components/sync_preferences/pref_model_associator.cc
+++ b/components/sync_preferences/pref_model_associator.cc
@@ -15,8 +15,10 @@
 #include "base/location.h"
 #include "base/logging.h"
 #include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
 #include "base/strings/utf_string_conversions.h"
 #include "base/values.h"
+#include "components/prefs/persistent_pref_store.h"
 #include "components/prefs/pref_service.h"
 #include "components/sync/model/sync_change.h"
 #include "components/sync/model/sync_error_factory.h"
@@ -24,6 +26,7 @@
 #include "components/sync/protocol/sync.pb.h"
 #include "components/sync_preferences/pref_model_associator_client.h"
 #include "components/sync_preferences/pref_service_syncable.h"
+#include "components/sync_preferences/synced_pref_observer.h"
 
 using syncer::PREFERENCES;
 using syncer::PRIORITY_PREFERENCES;
@@ -58,12 +61,9 @@
 
 PrefModelAssociator::PrefModelAssociator(
     const PrefModelAssociatorClient* client,
-    syncer::ModelType type)
-    : models_associated_(false),
-      processing_syncer_changes_(false),
-      pref_service_(nullptr),
-      type_(type),
-      client_(client) {
+    syncer::ModelType type,
+    UnknownUserPrefAccessor* accessor)
+    : pref_accessor_(accessor), type_(type), client_(client) {
   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
   DCHECK(type_ == PREFERENCES || type_ == PRIORITY_PREFERENCES);
 }
@@ -79,8 +79,15 @@
     const syncer::SyncData& sync_pref,
     const std::string& pref_name,
     syncer::SyncChangeList* sync_changes) {
-  const base::Value* user_pref_value =
-      pref_service_->GetUserPrefValue(pref_name);
+  UnknownUserPrefAccessor::PreferenceState local_pref_state =
+      pref_accessor_->GetPreferenceState(pref_name);
+  if (local_pref_state.registration_state ==
+          UnknownUserPrefAccessor::RegistrationState::kUnknown ||
+      local_pref_state.registration_state ==
+          UnknownUserPrefAccessor::RegistrationState::kNotSyncable) {
+    // Only process syncable prefs and unknown prefs if whitelisted.
+    return;
+  }
   VLOG(1) << "Associating preference " << pref_name;
 
   if (sync_pref.IsValid()) {
@@ -95,25 +102,20 @@
       return;
     }
 
-    if (user_pref_value) {
+    if (local_pref_state.persisted_value) {
       DVLOG(1) << "Found user pref value for " << pref_name;
       // We have both server and local values. Merge them.
-      std::unique_ptr<base::Value> new_value(
-          MergePreference(pref_name, *user_pref_value, *sync_value));
+      std::unique_ptr<base::Value> new_value(MergePreference(
+          pref_name, *local_pref_state.persisted_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->is_none()) {
         LOG(WARNING) << "Sync has null value for pref " << pref_name.c_str();
-        pref_service_->ClearPref(pref_name);
-      } else if (new_value->type() != user_pref_value->type()) {
-        LOG(WARNING) << "Synced value for " << preference.name()
-                     << " is of type " << new_value->type()
-                     << " which doesn't match pref type "
-                     << user_pref_value->type();
-      } else if (!user_pref_value->Equals(new_value.get())) {
-        pref_service_->Set(pref_name, *new_value);
+        pref_accessor_->ClearPref(pref_name, local_pref_state);
+      } else if (!local_pref_state.persisted_value->Equals(new_value.get())) {
+        pref_accessor_->SetPref(pref_name, local_pref_state, *new_value);
       }
 
       // If the merge resulted in an updated value, inform the syncer.
@@ -129,16 +131,19 @@
       }
     } else if (!sync_value->is_none()) {
       // Only a server value exists. Just set the local user value.
-      pref_service_->Set(pref_name, *sync_value);
+      pref_accessor_->SetPref(pref_name, local_pref_state, *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) {
+  } else if (local_pref_state.persisted_value) {
+    DCHECK_EQ(local_pref_state.registration_state,
+              UnknownUserPrefAccessor::RegistrationState::kSyncable);
     // 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)) {
+    if (!CreatePrefSyncData(pref_name, *local_pref_state.persisted_value,
+                            &sync_data)) {
       LOG(ERROR) << "Failed to update preference.";
       return;
     }
@@ -188,16 +193,6 @@
 
     const sync_pb::PreferenceSpecifics& preference = GetSpecifics(*sync_iter);
     std::string sync_pref_name = preference.name();
-
-    if (remaining_preferences.count(sync_pref_name) == 0) {
-      // 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;
-    }
-
     remaining_preferences.erase(sync_pref_name);
     InitPrefAndAssociate(*sync_iter, sync_pref_name, &new_changes);
   }
@@ -209,6 +204,9 @@
     InitPrefAndAssociate(syncer::SyncData(), *pref_name_iter, &new_changes);
   }
 
+  UMA_HISTOGRAM_COUNTS_1000("Sync.Preferences.SyncingUnknownPrefs",
+                            pref_accessor_->GetNumberOfSyncingUnknownPrefs());
+
   // Push updates to sync.
   merge_result.set_error(
       sync_processor_->ProcessSyncChanges(FROM_HERE, new_changes));
@@ -329,10 +327,6 @@
   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);
@@ -340,6 +334,10 @@
   for (PreferenceSet::const_iterator iter = synced_preferences_.begin();
        iter != synced_preferences_.end(); ++iter) {
     std::string name = *iter;
+    if (pref_accessor_->GetPreferenceState(name).registration_state !=
+        UnknownUserPrefAccessor::RegistrationState::kSyncable) {
+      continue;
+    }
     const PrefService::Preference* pref = pref_service_->FindPreference(name);
     DCHECK(pref);
     if (!pref->IsUserControlled() || pref->IsDefaultValue())
@@ -369,39 +367,47 @@
     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();
-    if (!IsPrefRegistered(pref_name))
+    UnknownUserPrefAccessor::PreferenceState local_pref_state =
+        pref_accessor_->GetPreferenceState(pref_specifics.name());
+    if (local_pref_state.registration_state ==
+        UnknownUserPrefAccessor::RegistrationState::kUnknown) {
+      // 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.
+      // We only sync such prefs if they are whitelisted.
       continue;
-
+    }
+    if (local_pref_state.registration_state ==
+        UnknownUserPrefAccessor::RegistrationState::kNotSyncable) {
+      // Don't process remote changes for prefs this client doesn't want synced.
+      continue;
+    }
     if (iter->change_type() == syncer::SyncChange::ACTION_DELETE) {
-      pref_service_->ClearPref(pref_name);
+      pref_accessor_->ClearPref(pref_specifics.name(), local_pref_state);
       continue;
     }
 
-    std::unique_ptr<base::Value> value(ReadPreferenceSpecifics(pref_specifics));
-    if (!value.get()) {
+    std::unique_ptr<base::Value> new_value(
+        ReadPreferenceSpecifics(pref_specifics));
+    if (!new_value.get()) {
       // Skip values we can't deserialize.
-      // TODO(zea): consider taking some further action such as erasing the bad
-      // data.
+      // 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);
+    pref_accessor_->SetPref(pref_specifics.name(), local_pref_state,
+                            *new_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());
-    }
+    // Keep track of any newly synced preferences. This can happen if a
+    // preference was late registered or remotely added (ACTION_ADD).
+    synced_preferences_.insert(pref_specifics.name());
   }
   return syncer::SyncError();
 }
@@ -443,9 +449,16 @@
   observer_iter->second->RemoveObserver(observer);
 }
 
-void PrefModelAssociator::RegisterPref(const char* name) {
-  DCHECK(!models_associated_ && registered_preferences_.count(name) == 0);
+void PrefModelAssociator::RegisterPref(const std::string& name) {
+  DCHECK(!registered_preferences_.count(name));
   registered_preferences_.insert(name);
+
+  // This pref might be registered after sync started. Make sure data in the
+  // local store matches the registered type.
+  // If this results in a modification of the local pref store, we don't want
+  // to tell ChromeSync about these -- it's a local anomaly,
+  base::AutoReset<bool> processing_changes(&processing_syncer_changes_, true);
+  pref_accessor_->EnforceRegisteredTypeInStore(name);
 }
 
 bool PrefModelAssociator::IsPrefRegistered(const std::string& name) const {
@@ -457,24 +470,33 @@
     return;  // These are changes originating from us, ignore.
 
   // We only process changes if we've already associated models.
+  // This also filters out local changes during the initial merge.
   if (!models_associated_)
     return;
 
+  // From now on, this method does not have to deal with lazily registered
+  // prefs, as local changes can only happen after they were registered.
+
   const PrefService::Preference* preference =
       pref_service_->FindPreference(name);
+  // TODO(tschumann): When can this ever happen? Should this be a DCHECK?
   if (!preference)
     return;
 
-  if (!IsPrefRegistered(name))
-    return;  // We are not syncing this preference.
+  if (!IsPrefRegistered(name)) {
+    // We are not syncing this preference -- this also filters out synced
+    // preferences of the wrong type (priority preference are handled by a
+    // separate associator).
+    return;
+  }
 
   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).
+    // 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;
   }
 
@@ -483,9 +505,10 @@
   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.
+    // 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);
   } else {
     // We are already syncing this preference, just update it's sync node.
diff --git a/components/sync_preferences/pref_model_associator.h b/components/sync_preferences/pref_model_associator.h
index d47a36b..5fb8e927 100644
--- a/components/sync_preferences/pref_model_associator.h
+++ b/components/sync_preferences/pref_model_associator.h
@@ -19,7 +19,7 @@
 #include "base/sequence_checker.h"
 #include "components/sync/model/sync_data.h"
 #include "components/sync/model/syncable_service.h"
-#include "components/sync_preferences/synced_pref_observer.h"
+#include "components/sync_preferences/unknown_user_pref_accessor.h"
 
 namespace base {
 class Value;
@@ -33,6 +33,7 @@
 
 class PrefModelAssociatorClient;
 class PrefServiceSyncable;
+class SyncedPrefObserver;
 
 // Contains all preference sync related logic.
 // TODO(sync): Merge this into PrefService once we separate the profile
@@ -40,16 +41,22 @@
 class PrefModelAssociator : public syncer::SyncableService {
  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.
+  // instance variable. |client| and |accessor| are not owned by this object
+  // and the caller must ensure they outlive the PrefModelAssociator.
   PrefModelAssociator(const PrefModelAssociatorClient* client,
-                      syncer::ModelType type);
+                      syncer::ModelType type,
+                      UnknownUserPrefAccessor* accessor);
   ~PrefModelAssociator() override;
 
   // See description above field for details.
   bool models_associated() const { return models_associated_; }
 
   // syncer::SyncableService implementation.
+
+  // Note for GetAllSyncData: 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 GetAllSyncData(syncer::ModelType type) const override;
   syncer::SyncError ProcessSyncChanges(
       const base::Location& from_here,
@@ -66,12 +73,15 @@
   // 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);
+  void RegisterPref(const std::string& name);
 
   // Process a local preference change. This can trigger new SyncChanges being
   // sent to the syncer.
-  virtual void ProcessPrefChange(const std::string& name);
+  void ProcessPrefChange(const std::string& name);
 
+  // TODO(tschumann): Remove the associator's dependency on PrefServiceSyncable.
+  // It's only needed for calling OnIsSyncingChanged. This logic can be moved
+  // onto the associator: PrefServiceSyncable forwards the registration calls.
   void SetPrefService(PrefServiceSyncable* pref_service);
 
   // Merges the local_value into the supplied server_value and returns
@@ -136,15 +146,17 @@
   static base::Value* ReadPreferenceSpecifics(
       const sync_pb::PreferenceSpecifics& specifics);
 
+  void NotifySyncedPrefObservers(const std::string& path, bool from_sync) const;
+
   // 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_;
+  bool models_associated_ = false;
 
   // 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_;
+  bool processing_syncer_changes_ = false;
 
   // A set of preference names.
   typedef std::set<std::string> PreferenceSet;
@@ -162,7 +174,10 @@
   PreferenceSet synced_preferences_;
 
   // The PrefService we are syncing to.
-  PrefServiceSyncable* pref_service_;
+  PrefServiceSyncable* pref_service_ = nullptr;
+
+  // A pref accessor to access prefs which might not be registered.
+  UnknownUserPrefAccessor* pref_accessor_;
 
   // Sync's syncer::SyncChange handler. We push all our changes through this.
   std::unique_ptr<syncer::SyncChangeProcessor> sync_processor_;
@@ -174,14 +189,13 @@
   // PRIORITY_PREFERENCES.
   syncer::ModelType type_;
 
-  void NotifySyncedPrefObservers(const std::string& path, bool from_sync) const;
-
   // 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.
   base::hash_map<std::string,
                  std::unique_ptr<base::ObserverList<SyncedPrefObserver>>>
       synced_pref_observers_;
+
   const PrefModelAssociatorClient* client_;  // Weak.
 
   std::vector<base::Closure> callback_list_;
diff --git a/components/sync_preferences/pref_model_associator_unittest.cc b/components/sync_preferences/pref_model_associator_unittest.cc
index 55682ad..87a9760 100644
--- a/components/sync_preferences/pref_model_associator_unittest.cc
+++ b/components/sync_preferences/pref_model_associator_unittest.cc
@@ -10,6 +10,7 @@
 #include "base/memory/ref_counted.h"
 #include "base/values.h"
 #include "components/prefs/scoped_user_pref_update.h"
+#include "components/prefs/testing_pref_store.h"
 #include "components/sync_preferences/pref_model_associator_client.h"
 #include "components/sync_preferences/pref_service_mock_factory.h"
 #include "components/sync_preferences/pref_service_syncable.h"
@@ -44,9 +45,11 @@
 
 class AbstractPreferenceMergeTest : public testing::Test {
  protected:
-  AbstractPreferenceMergeTest() {
+  AbstractPreferenceMergeTest()
+      : user_prefs_(base::MakeRefCounted<TestingPrefStore>()) {
     PrefServiceMockFactory factory;
     factory.SetPrefModelAssociatorClient(&client_);
+    factory.set_user_prefs(user_prefs_);
     scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry(
         new user_prefs::PrefRegistrySyncable);
     pref_registry->RegisterStringPref(
@@ -89,6 +92,7 @@
   }
 
   TestPrefModelAssociatorClient client_;
+  scoped_refptr<TestingPrefStore> user_prefs_;
   std::unique_ptr<PrefServiceSyncable> pref_service_;
   PrefModelAssociator* pref_sync_service_;
 };
diff --git a/components/sync_preferences/pref_service_syncable.cc b/components/sync_preferences/pref_service_syncable.cc
index 478d278a..c8ed9a9 100644
--- a/components/sync_preferences/pref_service_syncable.cc
+++ b/components/sync_preferences/pref_service_syncable.cc
@@ -27,6 +27,10 @@
 
 namespace sync_preferences {
 
+// TODO(tschumann): Handing out pointers to this in the constructor is an
+// anti-pattern. Instead, introduce a factory method which first constructs
+// the PrefServiceSyncable instance and then the members which need a reference
+// to the PrefServiceSycnable instance.
 PrefServiceSyncable::PrefServiceSyncable(
     std::unique_ptr<PrefNotifierImpl> pref_notifier,
     std::unique_ptr<PrefValueStore> pref_value_store,
@@ -39,13 +43,18 @@
     : PrefService(std::move(pref_notifier),
                   std::move(pref_value_store),
                   std::move(user_prefs),
-                  std::move(pref_registry),
+                  pref_registry,
                   std::move(read_error_callback),
                   async),
       pref_service_forked_(false),
-      pref_sync_associator_(pref_model_associator_client, syncer::PREFERENCES),
+      unknown_pref_accessor_(this, pref_registry.get(), user_pref_store_.get()),
+      pref_sync_associator_(pref_model_associator_client,
+                            syncer::PREFERENCES,
+                            &unknown_pref_accessor_),
       priority_pref_sync_associator_(pref_model_associator_client,
-                                     syncer::PRIORITY_PREFERENCES) {
+                                     syncer::PRIORITY_PREFERENCES,
+                                     &unknown_pref_accessor_),
+      pref_registry_(std::move(pref_registry)) {
   pref_sync_associator_.SetPrefService(this);
   priority_pref_sync_associator_.SetPrefService(this);
 
@@ -69,9 +78,7 @@
 
 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(
+  pref_registry_->SetSyncableRegistrationCallback(
       user_prefs::PrefRegistrySyncable::SyncableRegistrationCallback());
 }
 
@@ -84,8 +91,7 @@
   auto pref_notifier = std::make_unique<PrefNotifierImpl>();
 
   scoped_refptr<user_prefs::PrefRegistrySyncable> forked_registry =
-      static_cast<user_prefs::PrefRegistrySyncable*>(pref_registry_.get())
-          ->ForkForIncognito();
+      pref_registry_->ForkForIncognito();
 
   auto overlay = base::MakeRefCounted<InMemoryPrefStore>();
   if (delegate) {
@@ -179,9 +185,13 @@
     uint32_t flags) {
   DCHECK(FindPreference(path));
   if (flags & user_prefs::PrefRegistrySyncable::SYNCABLE_PREF) {
-    pref_sync_associator_.RegisterPref(path.c_str());
+    DCHECK(!pref_sync_associator_.models_associated() ||
+           pref_registry_->IsWhitelistedLateRegistrationPref(path));
+    pref_sync_associator_.RegisterPref(path);
   } else if (flags & user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF) {
-    priority_pref_sync_associator_.RegisterPref(path.c_str());
+    DCHECK(!priority_pref_sync_associator_.models_associated() ||
+           pref_registry_->IsWhitelistedLateRegistrationPref(path));
+    priority_pref_sync_associator_.RegisterPref(path);
   }
 }
 
diff --git a/components/sync_preferences/pref_service_syncable.h b/components/sync_preferences/pref_service_syncable.h
index 656b10e8..c94f5f2 100644
--- a/components/sync_preferences/pref_service_syncable.h
+++ b/components/sync_preferences/pref_service_syncable.h
@@ -18,6 +18,7 @@
 #include "components/prefs/pref_value_store.h"
 #include "components/sync_preferences/pref_model_associator.h"
 #include "components/sync_preferences/synced_pref_observer.h"
+#include "components/sync_preferences/unknown_user_pref_accessor.h"
 
 namespace syncer {
 class SyncableService;
@@ -107,8 +108,10 @@
   // "forked" PrefService.
   bool pref_service_forked_;
 
+  UnknownUserPrefAccessor unknown_pref_accessor_;
   PrefModelAssociator pref_sync_associator_;
   PrefModelAssociator priority_pref_sync_associator_;
+  const scoped_refptr<user_prefs::PrefRegistrySyncable> pref_registry_;
 
   base::ObserverList<PrefServiceSyncableObserver> observer_list_;
 
diff --git a/components/sync_preferences/pref_service_syncable_unittest.cc b/components/sync_preferences/pref_service_syncable_unittest.cc
index 1129b29..1113531 100644
--- a/components/sync_preferences/pref_service_syncable_unittest.cc
+++ b/components/sync_preferences/pref_service_syncable_unittest.cc
@@ -14,6 +14,7 @@
 #include "base/macros.h"
 #include "base/memory/ptr_util.h"
 #include "base/strings/utf_string_conversions.h"
+#include "base/test/histogram_tester.h"
 #include "components/pref_registry/pref_registry_syncable.h"
 #include "components/prefs/pref_notifier_impl.h"
 #include "components/prefs/scoped_user_pref_update.h"
@@ -26,12 +27,17 @@
 #include "components/sync/protocol/sync.pb.h"
 #include "components/sync_preferences/pref_model_associator.h"
 #include "components/sync_preferences/pref_model_associator_client.h"
+#include "components/sync_preferences/synced_pref_observer.h"
 #include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 using syncer::SyncChange;
 using syncer::SyncData;
+using testing::Eq;
+using testing::IsEmpty;
+using testing::Not;
 using testing::NotNull;
+using testing::SizeIs;
 
 namespace sync_preferences {
 
@@ -57,6 +63,7 @@
  public:
   explicit TestSyncProcessorStub(syncer::SyncChangeList* output)
       : output_(output), fail_next_(false) {}
+
   syncer::SyncError ProcessSyncChanges(
       const base::Location& from_here,
       const syncer::SyncChangeList& change_list) override {
@@ -265,6 +272,23 @@
   EXPECT_EQ(kNonDefaultCharsetValue, prefs_.GetString(kDefaultCharsetPrefName));
 }
 
+// Verifies that the implementation gracefully handles an initial remote sync
+// data of wrong type. The local version should not get modified in these cases.
+TEST_F(PrefServiceSyncableTest, ModelAssociationWithDataTypeMismatch) {
+  base::HistogramTester histogram_tester;
+  prefs_.SetString(kStringPrefName, kExampleUrl0);
+
+  syncer::SyncDataList in;
+  base::Value remote_int_value(123);
+  AddToRemoteDataList(kStringPrefName, remote_int_value, &in);
+  syncer::SyncChangeList out;
+  InitWithSyncDataTakeOutput(in, &out);
+  EXPECT_THAT(out, IsEmpty());
+  histogram_tester.ExpectBucketCount("Sync.Preferences.RemotePrefTypeMismatch",
+                                     true, 1);
+  EXPECT_THAT(prefs_.GetString(kStringPrefName), Eq(kExampleUrl0));
+}
+
 class TestPrefModelAssociatorClient : public PrefModelAssociatorClient {
  public:
   TestPrefModelAssociatorClient() {}
@@ -511,6 +535,134 @@
   EXPECT_TRUE(GetPreferenceValue(kDictPrefName).Equals(&expected_dict));
 }
 
+TEST_F(PrefServiceSyncableMergeTest, InitWithUnknownPrefsValue) {
+  base::HistogramTester histogram_tester;
+  const std::string pref_name1 = "testing.whitelisted_pref1";
+  const std::string pref_name2 = "testing.whitelisted_pref2";
+  pref_registry_->WhitelistLateRegistrationPrefForSync(pref_name1);
+  pref_registry_->WhitelistLateRegistrationPrefForSync(pref_name2);
+
+  syncer::SyncDataList in;
+  AddToRemoteDataList(pref_name1, base::Value("remote_value1"), &in);
+  AddToRemoteDataList(pref_name2, base::Value("remote_value2"), &in);
+  syncer::SyncChangeList out;
+  InitWithSyncDataTakeOutput(in, &out);
+  pref_registry_->RegisterStringPref(
+      pref_name1, "default_value",
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+  EXPECT_THAT(GetPreferenceValue(pref_name1).GetString(), Eq("remote_value1"));
+
+  histogram_tester.ExpectBucketCount("Sync.Preferences.SyncingUnknownPrefs", 2,
+                                     1);
+}
+
+TEST_F(PrefServiceSyncableMergeTest, ReceiveUnknownPrefsValue) {
+  base::HistogramTester histogram_tester;
+  const std::string pref_name = "testing.whitelisted_pref";
+  pref_registry_->WhitelistLateRegistrationPrefForSync(pref_name);
+
+  syncer::SyncChangeList out;
+  InitWithSyncDataTakeOutput(syncer::SyncDataList(), &out);
+
+  syncer::SyncChangeList remote_changes;
+  remote_changes.push_back(MakeRemoteChange(
+      1, pref_name, base::Value("remote_value"), SyncChange::ACTION_UPDATE));
+  pref_sync_service_->ProcessSyncChanges(FROM_HERE, remote_changes);
+  EXPECT_THAT(prefs_.IsPrefSynced(pref_name), Eq(true));
+
+  pref_registry_->RegisterStringPref(
+      pref_name, "default_value",
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+  EXPECT_THAT(GetPreferenceValue(pref_name).GetString(), Eq("remote_value"));
+}
+
+class ShouldNotBeNotifedObserver : public SyncedPrefObserver {
+ public:
+  ShouldNotBeNotifedObserver() {}
+  ~ShouldNotBeNotifedObserver() {}
+
+  void OnSyncedPrefChanged(const std::string& path, bool from_sync) override {
+    ADD_FAILURE() << "Unexpected notification about a pref change with path: '"
+                  << path << "' and from_sync: " << from_sync;
+  }
+};
+
+TEST_F(PrefServiceSyncableMergeTest, RegisterShouldClearTypeMismatchingData) {
+  base::HistogramTester histogram_tester;
+  const std::string pref_name = "testing.whitelisted_pref";
+  pref_registry_->WhitelistLateRegistrationPrefForSync(pref_name);
+  // Make sure no changes will be communicated to any synced pref listeners
+  // (those listeners are typically only used for metrics but we still don't
+  // want to inform them).
+  ShouldNotBeNotifedObserver observer;
+  prefs_.AddSyncedPrefObserver(pref_name, &observer);
+  syncer::SyncDataList in;
+  AddToRemoteDataList(pref_name, base::Value("remote_value"), &in);
+  syncer::SyncChangeList out;
+  InitWithSyncDataTakeOutput(in, &out);
+  ASSERT_THAT(out, IsEmpty());
+
+  EXPECT_TRUE(user_prefs_->GetValue(pref_name, nullptr));
+
+  pref_registry_->RegisterListPref(
+      pref_name, user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+  EXPECT_TRUE(GetPreferenceValue(pref_name).GetList().empty());
+  EXPECT_FALSE(user_prefs_->GetValue(pref_name, nullptr));
+  // Make sure the removal of the value was not communicated to sync via the
+  // SyncProcessor.
+  EXPECT_THAT(out, IsEmpty());
+
+  histogram_tester.ExpectBucketCount(
+      "Sync.Preferences.ClearedLocalPrefOnTypeMismatch", true, 1);
+  prefs_.RemoveSyncedPrefObserver(pref_name, &observer);
+}
+
+TEST_F(PrefServiceSyncableMergeTest, ShouldIgnoreUpdatesToNotSyncablePrefs) {
+  const std::string pref_name = "testing.not_syncable_pref";
+  pref_registry_->RegisterStringPref(pref_name, "default_value",
+                                     PrefRegistry::NO_REGISTRATION_FLAGS);
+  syncer::SyncDataList in;
+  AddToRemoteDataList(pref_name, base::Value("remote_value"), &in);
+  syncer::SyncChangeList out;
+  InitWithSyncDataTakeOutput(in, &out);
+  EXPECT_THAT(GetPreferenceValue(pref_name).GetString(), Eq("default_value"));
+
+  syncer::SyncChangeList remote_changes;
+  remote_changes.push_back(MakeRemoteChange(
+      1, pref_name, base::Value("remote_value2"), SyncChange::ACTION_UPDATE));
+  pref_sync_service_->ProcessSyncChanges(FROM_HERE, remote_changes);
+  EXPECT_THAT(prefs_.IsPrefSynced(pref_name), Eq(false));
+
+  EXPECT_THAT(GetPreferenceValue(pref_name).GetString(), Eq("default_value"));
+}
+
+TEST_F(PrefServiceSyncableMergeTest, GetAllSyncDataForLateRegisteredPrefs) {
+  const std::string pref_name = "testing.whitelisted_pref";
+  pref_registry_->WhitelistLateRegistrationPrefForSync(pref_name);
+
+  syncer::SyncDataList in;
+  AddToRemoteDataList(pref_name, base::Value("remote_value"), &in);
+  syncer::SyncChangeList out;
+  InitWithSyncDataTakeOutput(in, &out);
+
+  syncer::SyncDataList all_data =
+      prefs_.GetSyncableService(syncer::PREFERENCES)
+          ->GetAllSyncData(syncer::PREFERENCES);
+  EXPECT_THAT(all_data, IsEmpty());
+
+  // Make sure the preference appears in the result once it's registered.
+  pref_registry_->RegisterStringPref(
+      pref_name, "default_value",
+      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
+
+  all_data = prefs_.GetSyncableService(syncer::PREFERENCES)
+                 ->GetAllSyncData(syncer::PREFERENCES);
+  ASSERT_THAT(all_data, SizeIs(1));
+  EXPECT_THAT(all_data[0].GetSpecifics().preference().name(), Eq(pref_name));
+  EXPECT_THAT(all_data[0].GetSpecifics().preference().value(),
+              Eq("\"remote_value\""));
+}
+
 TEST_F(PrefServiceSyncableTest, FailModelAssociation) {
   syncer::SyncChangeList output;
   TestSyncProcessorStub* stub = new TestSyncProcessorStub(&output);
@@ -565,6 +717,24 @@
   EXPECT_TRUE(expected.Equals(&actual));
 }
 
+// Verifies that the implementation gracefully handles a remote update with the
+// wrong type. The local version should not get modified in these cases.
+TEST_F(PrefServiceSyncableTest, UpdatedSyncNodeActionUpdateTypeMismatch) {
+  base::HistogramTester histogram_tester;
+  GetPrefs()->SetString(kStringPrefName, kExampleUrl0);
+  InitWithNoSyncData();
+
+  base::Value remote_int_value(123);
+  syncer::SyncChangeList remote_changes;
+  remote_changes.push_back(MakeRemoteChange(
+      1, kStringPrefName, remote_int_value, SyncChange::ACTION_UPDATE));
+  pref_sync_service_->ProcessSyncChanges(FROM_HERE, remote_changes);
+
+  EXPECT_THAT(prefs_.GetString(kStringPrefName), Eq(kExampleUrl0));
+  histogram_tester.ExpectBucketCount("Sync.Preferences.RemotePrefTypeMismatch",
+                                     true, 1);
+}
+
 TEST_F(PrefServiceSyncableTest, UpdatedSyncNodeActionAdd) {
   InitWithNoSyncData();
 
diff --git a/components/sync_preferences/unknown_user_pref_accessor.cc b/components/sync_preferences/unknown_user_pref_accessor.cc
new file mode 100644
index 0000000..fac3a04f
--- /dev/null
+++ b/components/sync_preferences/unknown_user_pref_accessor.cc
@@ -0,0 +1,177 @@
+// Copyright 2018 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/sync_preferences/unknown_user_pref_accessor.h"
+
+#include <iterator>
+#include <memory>
+
+#include "base/json/json_string_value_serializer.h"
+#include "base/logging.h"
+#include "base/memory/ptr_util.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/values.h"
+#include "components/prefs/persistent_pref_store.h"
+#include "components/prefs/pref_service.h"
+#include "components/sync_preferences/pref_service_syncable.h"
+
+namespace sync_preferences {
+
+UnknownUserPrefAccessor::UnknownUserPrefAccessor(
+    PrefService* pref_service,
+    user_prefs::PrefRegistrySyncable* pref_registry,
+    PersistentPrefStore* user_prefs)
+    : pref_service_(pref_service),
+      pref_registry_(pref_registry),
+      user_prefs_(user_prefs) {}
+
+UnknownUserPrefAccessor::~UnknownUserPrefAccessor() {}
+
+UnknownUserPrefAccessor::PreferenceState
+UnknownUserPrefAccessor::GetPreferenceState(
+    const std::string& pref_name) const {
+  PreferenceState result;
+  result.registration_state = GetRegistrationState(pref_name);
+  switch (result.registration_state) {
+    case RegistrationState::kUnknown:
+    case RegistrationState::kUnknownWhitelisted:
+      if (!user_prefs_->GetValue(pref_name, &result.persisted_value)) {
+        result.persisted_value = nullptr;
+      }
+      break;
+    case RegistrationState::kSyncable:
+    case RegistrationState::kNotSyncable:
+      result.persisted_value = pref_service_->GetUserPrefValue(pref_name);
+      break;
+  }
+  return result;
+}
+
+void UnknownUserPrefAccessor::ClearPref(
+    const std::string& pref_name,
+    const PreferenceState& local_pref_state) {
+  switch (local_pref_state.registration_state) {
+    case RegistrationState::kUnknown:
+      NOTREACHED() << "Sync attempted to update an unknown pref which is not "
+                      "whitelisted: "
+                   << pref_name;
+      break;
+    case RegistrationState::kUnknownWhitelisted:
+      user_prefs_->RemoveValue(pref_name,
+                               WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+      break;
+    case RegistrationState::kSyncable:
+      pref_service_->ClearPref(pref_name);
+      break;
+    case RegistrationState::kNotSyncable:
+      // As this can happen if different clients disagree about which
+      // preferences should be synced, we only log a warning.
+      DLOG(WARNING)
+          << "Sync attempted to update a pref which is not registered as "
+             "syncable. Ignoring the remote change for pref: "
+          << pref_name;
+      break;
+  }
+}
+
+int UnknownUserPrefAccessor::GetNumberOfSyncingUnknownPrefs() const {
+  return synced_unknown_prefs_.size();
+}
+
+namespace {
+
+bool VerifyTypesBeforeSet(const std::string& pref_name,
+                          const base::Value* local_value,
+                          const base::Value& new_value) {
+  if (local_value == nullptr || local_value->type() == new_value.type()) {
+    return true;
+  }
+  UMA_HISTOGRAM_BOOLEAN("Sync.Preferences.RemotePrefTypeMismatch", true);
+  DLOG(WARNING) << "Unexpected type mis-match for pref. "
+                << "Synced value for " << pref_name << " is of type "
+                << new_value.type() << " which doesn't match the locally "
+                << "present pref type: " << local_value->type();
+  return false;
+}
+
+}  // namespace
+
+void UnknownUserPrefAccessor::SetPref(const std::string& pref_name,
+                                      const PreferenceState& local_pref_state,
+                                      const base::Value& value) {
+  // On type mis-match, we trust the local preference DB and ignore the remote
+  // change.
+  switch (local_pref_state.registration_state) {
+    case RegistrationState::kUnknown:
+      NOTREACHED() << "Sync attempted to update a unknown pref which is not "
+                      "whitelisted: "
+                   << pref_name;
+      break;
+    case RegistrationState::kUnknownWhitelisted:
+      if (VerifyTypesBeforeSet(pref_name, local_pref_state.persisted_value,
+                               value)) {
+        user_prefs_->SetValue(pref_name, value.CreateDeepCopy(),
+                              WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+      }
+      synced_unknown_prefs_.insert(pref_name);
+      break;
+    case RegistrationState::kSyncable:
+      if (VerifyTypesBeforeSet(pref_name, local_pref_state.persisted_value,
+                               value)) {
+        pref_service_->Set(pref_name, value);
+      }
+      break;
+    case RegistrationState::kNotSyncable:
+      // As this can happen if different clients disagree about which
+      // preferences should be synced, we only log a warning.
+      DLOG(WARNING)
+          << "Sync attempted to update a pref which is not registered as "
+             "syncable. Ignoring the remote change for pref: "
+          << pref_name;
+      break;
+  }
+}
+
+void UnknownUserPrefAccessor::EnforceRegisteredTypeInStore(
+    const std::string& pref_name) {
+  const base::Value* persisted_value = nullptr;
+  if (user_prefs_->GetValue(pref_name, &persisted_value)) {
+    // Get the registered type (typically from the default value).
+    const PrefService::Preference* pref =
+        pref_service_->FindPreference(pref_name);
+    DCHECK(pref);
+    if (pref->GetType() != persisted_value->type()) {
+      // We see conflicting type information and there's a chance the local
+      // type-conflicting data came in via sync. Remove it.
+      // TODO(tschumann): The value should get removed silently. Add a method
+      // RemoveValueSilently() to WriteablePrefStore. Note, that as of today
+      // that removal will only notify other pref stores but not sync -- that's
+      // done on a higher level.
+      user_prefs_->RemoveValue(pref_name,
+                               WriteablePrefStore::DEFAULT_PREF_WRITE_FLAGS);
+      UMA_HISTOGRAM_BOOLEAN("Sync.Preferences.ClearedLocalPrefOnTypeMismatch",
+                            true);
+    }
+  }
+  synced_unknown_prefs_.erase(pref_name);
+}
+
+UnknownUserPrefAccessor::RegistrationState
+UnknownUserPrefAccessor::GetRegistrationState(
+    const std::string& pref_name) const {
+  if (pref_registry_->defaults()->GetValue(pref_name, nullptr)) {
+    uint32_t flags = pref_registry_->GetRegistrationFlags(pref_name);
+    if ((flags & user_prefs::PrefRegistrySyncable::SYNCABLE_PREF) ||
+        (flags & user_prefs::PrefRegistrySyncable::SYNCABLE_PRIORITY_PREF)) {
+      return RegistrationState::kSyncable;
+    }
+    return RegistrationState::kNotSyncable;
+  }
+  if (pref_registry_->IsWhitelistedLateRegistrationPref(pref_name)) {
+    return RegistrationState::kUnknownWhitelisted;
+  }
+  return RegistrationState::kUnknown;
+}
+
+}  // namespace sync_preferences
diff --git a/components/sync_preferences/unknown_user_pref_accessor.h b/components/sync_preferences/unknown_user_pref_accessor.h
new file mode 100644
index 0000000..7c42ea2
--- /dev/null
+++ b/components/sync_preferences/unknown_user_pref_accessor.h
@@ -0,0 +1,101 @@
+// Copyright 2018 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_SYNC_PREFERENCES_UNKNOWN_USER_PREF_ACCESSOR_H_
+#define COMPONENTS_SYNC_PREFERENCES_UNKNOWN_USER_PREF_ACCESSOR_H_
+
+#include <memory>
+#include <set>
+#include <string>
+
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+
+class PersistentPrefStore;
+class PrefService;
+
+namespace base {
+class Value;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+namespace sync_preferences {
+
+// A class to access user prefs even before they were registered.
+// Currently, accessing not registered (unknown) prefs is limited to a
+// whitelist.
+class UnknownUserPrefAccessor {
+ public:
+  enum class RegistrationState {
+    kUnknown,  // Preference is not registered (on this Chrome instance).
+    kUnknownWhitelisted,  // Preference is not registered but whitelisted to be
+                          // synced without being registered.
+    kSyncable,            // Preference is registered as being synced.
+    kNotSyncable          // Preference is registered as not being synced.
+  };
+
+  struct PreferenceState {
+    // The registration state of a preference.
+    RegistrationState registration_state = RegistrationState::kUnknown;
+
+    // The actually stored value. nullptr if no value is persisted and the pref
+    // service serves a default value for this pref.
+    // Ownership lies with the underlying pref-store.
+    const base::Value* persisted_value = nullptr;
+  };
+
+  // |pref_service|, |pref_registry|, and |user_prefs| must not be null and must
+  // outlive the lifetime of the created instance. The caller keeps ownership
+  // over these objects.
+  UnknownUserPrefAccessor(PrefService* pref_service,
+                          user_prefs::PrefRegistrySyncable* pref_registry,
+                          PersistentPrefStore* user_prefs);
+  ~UnknownUserPrefAccessor();
+
+  // Computes the state of a preference with name |pref_name| which gives
+  // information about whether it's registered and the locally persisted value.
+  PreferenceState GetPreferenceState(const std::string& pref_name) const;
+
+  // Removes the value of the preference |pref_name| from the user prefstore.
+  // Must not be called for preferences having RegistrationState::kUnknown.
+  // When called for preferences registiered as not syncable
+  // (RegistrationState::kNotSyncable), no changes to the storage are made.
+  void ClearPref(const std::string& pref_name,
+                 const PreferenceState& local_pref_state);
+
+  // Changes the value of the preference |pref_name| on the user prefstore.
+  // Must not be called for preferences having RegistrationState::kUnknown.
+  // When called for preferences registiered as not syncable
+  // (RegistrationState::kNotSyncable), no changes to the storage are made.
+  void SetPref(const std::string& pref_name,
+               const PreferenceState& local_pref_state,
+               const base::Value& value);
+
+  // Verifies that the type which preference |pref_name| was registered with
+  // matches the type of any persisted value. On mismatch, the persisted value
+  // gets removed.
+  void EnforceRegisteredTypeInStore(const std::string& pref_name);
+
+  // Returns the number of synced preferences which have not been registered (so
+  // far).
+  int GetNumberOfSyncingUnknownPrefs() const;
+
+ private:
+  RegistrationState GetRegistrationState(const std::string& pref_name) const;
+
+  std::set<std::string> synced_unknown_prefs_;
+  PrefService* const pref_service_;
+  user_prefs::PrefRegistrySyncable* const pref_registry_;
+  PersistentPrefStore* const user_prefs_;
+
+  DISALLOW_COPY_AND_ASSIGN(UnknownUserPrefAccessor);
+};
+
+}  // namespace sync_preferences
+
+#endif  // COMPONENTS_SYNC_PREFERENCES_UNKNOWN_USER_PREF_ACCESSOR_H_